@@ -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' : 2 ,
394+ 'Validator' : lambda value : isinstance (value ,int ) }
393395 }
394396
395397 _validate_vkwargs_dict (vkwargs )
@@ -882,10 +884,11 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
882884 first to ensure every time there is a trend change (ex. previous box is
883885 an X, current brick is a O) we draw one less box to account for the price
884886 having to move the previous box's amount before creating a box in the
885- opposite direction. Next we adjust volume and dates to combine volume into
886- non 0 box indexes and to only use dates from non 0 box indexes. We then
887- remove all 0s from the boxes array and once again combine adjacent similarly
888- signed differences in boxes.
887+ opposite direction. During this same step we also combine like signed elements
888+ and associated volume/date data ignoring any zero values that are created by
889+ subtracting 1 from the box value. Next we recreate the box array utilizing a
890+ rolling_change and volume_cache to store and sum the changes that don't break
891+ the reversal threshold.
889892
890893 Lastly, we enumerate through the boxes to populate the line_seg and circle_patches
891894 arrays. line_seg holds the / and \ line segments that make up an X and
@@ -929,20 +932,28 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
929932
930933 box_size = pointnfig_params ['box_size' ]
931934 atr_length = pointnfig_params ['atr_length' ]
935+ reversal = pointnfig_params ['reversal' ]
936+
937+ # box_size upper limit (also used for calculating upper limit of reversal)
938+ upper_limit = (max (closes ) - min (closes )) / 2
932939
933940 if box_size == 'atr' :
934941 if atr_length == 'total' :
935942 box_size = _calculate_atr (len (closes )- 1 , highs , lows , closes )
936943 else :
937944 box_size = _calculate_atr (atr_length , highs , lows , closes )
938945 else : # is an integer or float
939- upper_limit = (max (closes ) - min (closes )) / 2
940946 lower_limit = 0.01 * _calculate_atr (len (closes )- 1 , highs , lows , closes )
941947 if box_size > upper_limit :
942948 raise ValueError ("Specified box_size may not be larger than (50% of the close price range of the dataset) which has value: " + str (upper_limit ))
943949 elif box_size < lower_limit :
944950 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 ))
945951
952+ if reversal < 2 :
953+ raise ValueError ("Specified reversal may not be smaller than 2" )
954+ elif reversal * box_size > upper_limit * 0.6 :
955+ raise ValueError ("Product of specified box_size and reversal which has value: " + str (reversal * box_size ) + " may not exceed (30% of the close price range of the dataset) which has value: " + str (upper_limit * 0.6 ))
956+
946957 alpha = marketcolors ['alpha' ]
947958
948959 uc = mcolors .to_rgba (marketcolors ['ohlc' ][ 'up' ], alpha )
@@ -972,27 +983,70 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
972983 boxes , indexes = combine_adjacent (boxes )
973984 new_volumes , new_dates = coalesce_volume_dates (temp_volumes , temp_dates , indexes )
974985
975- #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
976- first_elem = boxes [0 ]
977- boxes = [boxes [i ]- int ((boxes [i ]/ abs (boxes [i ]))) for i in range (1 , len (boxes ))]
978- boxes .insert (0 , first_elem )
979-
980- # adjust volume and dates to make sure volume is combined into non 0 box indexes and only use dates from non 0 box indexes
981- temp_volumes , temp_dates = [], []
982- for i in range (len (boxes )):
983- if boxes [i ] == 0 :
984- volume_cache += new_volumes [i ]
985- else :
986+ adjusted_boxes = [boxes [0 ]]
987+ temp_volumes , temp_dates = [new_volumes [0 ]], [new_dates [0 ]]
988+ volume_cache = 0
989+
990+ # Clean data to subtract 1 from all box # not including the first boxes element and combine like signed adjacent values (after ignoring zeros)
991+ for i in range (1 , len (boxes )):
992+ adjusted_value = boxes [i ]- int ((boxes [i ]/ abs (boxes [i ])))
993+
994+ # not equal to 0 and different signs
995+ if adjusted_value != 0 and adjusted_boxes [- 1 ]* adjusted_value < 0 :
996+
997+ # Append adjusted_value, volumes, and date to associated lists
998+ adjusted_boxes .append (adjusted_value )
986999 temp_volumes .append (new_volumes [i ] + volume_cache )
987- volume_cache = 0
9881000 temp_dates .append (new_dates [i ])
989-
990- #remove 0s from boxes
991- boxes = list (filter (lambda diff : diff != 0 , boxes ))
9921001
993- # combine adjacent similarly signed differences again after 0s removed
994- boxes , indexes = combine_adjacent (boxes )
995- new_volumes , new_dates = coalesce_volume_dates (temp_volumes , temp_dates , indexes )
1002+ # reset volume_cache once we use it
1003+ volume_cache = 0
1004+
1005+ # not equal to 0 and same signs
1006+ elif adjusted_value != 0 and adjusted_boxes [- 1 ]* adjusted_value > 0 :
1007+
1008+ # Add adjusted_value and volume values to last added elements
1009+ adjusted_boxes [- 1 ] += adjusted_value
1010+ temp_volumes [- 1 ] += new_volumes [i ] + volume_cache
1011+
1012+ # reset volume_cache once we use it
1013+ volume_cache = 0
1014+
1015+ else : # adjusted_value == 0
1016+ volume_cache += new_volumes [i ]
1017+
1018+ boxes = [adjusted_boxes [0 ]]
1019+ new_volumes = [temp_volumes [0 ]]
1020+ new_dates = [temp_dates [0 ]]
1021+
1022+ rolling_change = 0
1023+ volume_cache = 0
1024+
1025+ #Clean data to account for reversal size (added to allow overriding the default reversal of 2)
1026+ for i in range (1 , len (adjusted_boxes )):
1027+
1028+ # Add to rolling_change and volume_cache which stores the box and volume values
1029+ rolling_change += adjusted_boxes [i ]
1030+ volume_cache += temp_volumes [i ]
1031+
1032+ # Add to new list if the rolling change is >= the reversal - 1
1033+ # The -1 is because we have already subtracted 1 from the box values in the previous loop
1034+ if abs (rolling_change ) >= reversal - 1 :
1035+
1036+ # if rolling_change is the same sign as the previous # of boxes then combine
1037+ if rolling_change * boxes [- 1 ] > 0 :
1038+ boxes [- 1 ] += rolling_change
1039+ new_volumes [- 1 ] += volume_cache
1040+
1041+ # otherwise add new box
1042+ else : # < 0 (== 0 can't happen since neither rolling_change or boxes[-1] can be 0)
1043+ boxes .append (rolling_change )
1044+ new_volumes .append (volume_cache )
1045+ new_dates .append (temp_dates [i ])
1046+
1047+ # reset rolling_change and volume_cache once we've used them
1048+ rolling_change = 0
1049+ volume_cache = 0
9961050
9971051 curr_price = closes [0 ]
9981052 box_values = [] # y values for the boxes
0 commit comments