@@ -389,7 +389,9 @@ def _valid_pnf_kwargs():
389389 'box_size' : { 'Default' : 'atr' ,
390390 'Validator' : lambda value : isinstance (value ,(float ,int )) or value == 'atr' },
391391 'atr_length' : { 'Default' : 14 ,
392- 'Validator' : lambda value : isinstance (value ,int ) or value == 'total' },
392+ 'Validator' : lambda value : isinstance (value ,int ) or value == 'total' },
393+ 'reversal' : { 'Default' : 1 ,
394+ 'Validator' : lambda value : isinstance (value ,int ) }
393395 }
394396
395397 _validate_vkwargs_dict (vkwargs )
@@ -884,10 +886,11 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
884886 first to ensure every time there is a trend change (ex. previous box is
885887 an X, current brick is a O) we draw one less box to account for the price
886888 having to move the previous box's amount before creating a box in the
887- opposite direction. Next we adjust volume and dates to combine volume into
888- non 0 box indexes and to only use dates from non 0 box indexes. We then
889- remove all 0s from the boxes array and once again combine adjacent similarly
890- signed differences in boxes.
889+ opposite direction. During this same step we also combine like signed elements
890+ and associated volume/date data ignoring any zero values that are created by
891+ subtracting 1 from the box value. Next we recreate the box array utilizing a
892+ rolling_change and volume_cache to store and sum the changes that don't break
893+ the reversal threshold.
891894
892895 Lastly, we enumerate through the boxes to populate the line_seg and circle_patches
893896 arrays. line_seg holds the / and \ line segments that make up an X and
@@ -931,6 +934,7 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
931934
932935 box_size = pointnfig_params ['box_size' ]
933936 atr_length = pointnfig_params ['atr_length' ]
937+ reversal = pointnfig_params ['reversal' ]
934938
935939 if box_size == 'atr' :
936940 if atr_length == 'total' :
@@ -945,6 +949,9 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
945949 elif box_size < lower_limit :
946950 raise ValueError ("Specified box_size may not be smaller than (0.01* the Average True Value of the dataset) which has value: " + str (lower_limit ))
947951
952+ if reversal < 1 or reversal > 9 :
953+ raise ValueError ("Specified reversal must be an integer in the range [1,9]" )
954+
948955 alpha = marketcolors ['alpha' ]
949956
950957 uc = mcolors .to_rgba (marketcolors ['ohlc' ][ 'up' ], alpha )
@@ -974,27 +981,82 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
974981 boxes , indexes = combine_adjacent (boxes )
975982 new_volumes , new_dates = coalesce_volume_dates (temp_volumes , temp_dates , indexes )
976983
977- #subtract 1 from the abs of each diff except the first to account for the first box using the last box in the opposite direction
978- first_elem = boxes [0 ]
979- boxes = [boxes [i ]- int ((boxes [i ]/ abs (boxes [i ]))) for i in range (1 , len (boxes ))]
980- boxes .insert (0 , first_elem )
981-
982- # adjust volume and dates to make sure volume is combined into non 0 box indexes and only use dates from non 0 box indexes
983- temp_volumes , temp_dates = [], []
984- for i in range (len (boxes )):
985- if boxes [i ] == 0 :
986- volume_cache += new_volumes [i ]
987- else :
984+ adjusted_boxes = [boxes [0 ]]
985+ temp_volumes , temp_dates = [new_volumes [0 ]], [new_dates [0 ]]
986+ volume_cache = 0
987+
988+ # Clean data to subtract 1 from all box # not including the first boxes element and combine like signed adjacent values (after ignoring zeros)
989+ for i in range (1 , len (boxes )):
990+ adjusted_value = boxes [i ]- int ((boxes [i ]/ abs (boxes [i ])))
991+
992+ # not equal to 0 and different signs
993+ if adjusted_value != 0 and adjusted_boxes [- 1 ]* adjusted_value < 0 :
994+
995+ # Append adjusted_value, volumes, and date to associated lists
996+ adjusted_boxes .append (adjusted_value )
988997 temp_volumes .append (new_volumes [i ] + volume_cache )
989- volume_cache = 0
990998 temp_dates .append (new_dates [i ])
991-
992- #remove 0s from boxes
993- boxes = list (filter (lambda diff : diff != 0 , boxes ))
994999
995- # combine adjacent similarly signed differences again after 0s removed
996- boxes , indexes = combine_adjacent (boxes )
997- new_volumes , new_dates = coalesce_volume_dates (temp_volumes , temp_dates , indexes )
1000+ # reset volume_cache once we use it
1001+ volume_cache = 0
1002+
1003+ # not equal to 0 and same signs
1004+ elif adjusted_value != 0 and adjusted_boxes [- 1 ]* adjusted_value > 0 :
1005+
1006+ # Add adjusted_value and volume values to last added elements
1007+ adjusted_boxes [- 1 ] += adjusted_value
1008+ temp_volumes [- 1 ] += new_volumes [i ] + volume_cache
1009+
1010+ # reset volume_cache once we use it
1011+ volume_cache = 0
1012+
1013+ else : # adjusted_value == 0
1014+ volume_cache += new_volumes [i ]
1015+
1016+ boxes = [adjusted_boxes [0 ]]
1017+ new_volumes = [temp_volumes [0 ]]
1018+ new_dates = [temp_dates [0 ]]
1019+
1020+ rolling_change = 0
1021+ volume_cache = 0
1022+ biggest_difference = 0 # only used for the last column
1023+
1024+ #Clean data to account for reversal size (added to allow overriding the default reversal of 1)
1025+ for i in range (1 , len (adjusted_boxes )):
1026+
1027+ # Add to rolling_change and volume_cache which stores the box and volume values
1028+ rolling_change += adjusted_boxes [i ]
1029+ volume_cache += temp_volumes [i ]
1030+
1031+ # if rolling_change is the same sign as the previous box and the abs value is bigger than the
1032+ # abs value of biggest_difference then we should replace biggest_difference with rolling_change
1033+ if rolling_change * boxes [- 1 ] > 0 and abs (rolling_change ) > abs (biggest_difference ):
1034+ biggest_difference = rolling_change
1035+
1036+ # Add to new list if the rolling change is >= the reversal
1037+ if abs (rolling_change ) >= reversal :
1038+
1039+ # if rolling_change is the same sign as the previous # of boxes then combine
1040+ if rolling_change * boxes [- 1 ] > 0 :
1041+ boxes [- 1 ] += rolling_change
1042+ new_volumes [- 1 ] += volume_cache
1043+
1044+ # otherwise add new box
1045+ else : # < 0 (== 0 can't happen since neither rolling_change or boxes[-1] can be 0)
1046+ boxes .append (rolling_change )
1047+ new_volumes .append (volume_cache )
1048+ new_dates .append (temp_dates [i ])
1049+
1050+ # reset rolling_change and volume_cache once we've used them
1051+ rolling_change = 0
1052+ volume_cache = 0
1053+
1054+ # reset biggest_difference as we start from the beginning every time there is a reversal
1055+ biggest_difference = 0
1056+
1057+ # Adjust the last box column if the left over rolling_change is the same sign as the column
1058+ boxes [- 1 ] += biggest_difference
1059+ new_volumes [- 1 ] += volume_cache
9981060
9991061 curr_price = closes [0 ]
10001062 box_values = [] # y values for the boxes
0 commit comments