@@ -389,7 +389,9 @@ def _valid_pnf_kwargs():
389
389
'box_size' : { 'Default' : 'atr' ,
390
390
'Validator' : lambda value : isinstance (value ,(float ,int )) or value == 'atr' },
391
391
'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 ) }
393
395
}
394
396
395
397
_validate_vkwargs_dict (vkwargs )
@@ -855,7 +857,9 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
855
857
edgecolors = edge_colors ,
856
858
linewidths = lw
857
859
)
858
- return [rectCollection ,], new_dates , new_volumes , brick_values , brick_size
860
+ calculated_values = dict (dates = new_dates ,volumes = new_volumes ,
861
+ values = brick_values ,size = brick_size )
862
+ return [rectCollection ,], calculated_values
859
863
860
864
861
865
def _construct_pointnfig_collections (dates , highs , lows , volumes , config_pointnfig_params , closes , marketcolors = None ):
@@ -882,10 +886,11 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
882
886
first to ensure every time there is a trend change (ex. previous box is
883
887
an X, current brick is a O) we draw one less box to account for the price
884
888
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.
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.
889
894
890
895
Lastly, we enumerate through the boxes to populate the line_seg and circle_patches
891
896
arrays. line_seg holds the / and \ line segments that make up an X and
@@ -929,6 +934,7 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
929
934
930
935
box_size = pointnfig_params ['box_size' ]
931
936
atr_length = pointnfig_params ['atr_length' ]
937
+ reversal = pointnfig_params ['reversal' ]
932
938
933
939
if box_size == 'atr' :
934
940
if atr_length == 'total' :
@@ -943,6 +949,9 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
943
949
elif box_size < lower_limit :
944
950
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 ))
945
951
952
+ if reversal < 1 or reversal > 9 :
953
+ raise ValueError ("Specified reversal must be an integer in the range [1,9]" )
954
+
946
955
alpha = marketcolors ['alpha' ]
947
956
948
957
uc = mcolors .to_rgba (marketcolors ['ohlc' ][ 'up' ], alpha )
@@ -972,33 +981,88 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
972
981
boxes , indexes = combine_adjacent (boxes )
973
982
new_volumes , new_dates = coalesce_volume_dates (temp_volumes , temp_dates , indexes )
974
983
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 :
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 )
986
997
temp_volumes .append (new_volumes [i ] + volume_cache )
987
- volume_cache = 0
988
998
temp_dates .append (new_dates [i ])
989
-
990
- #remove 0s from boxes
991
- boxes = list (filter (lambda diff : diff != 0 , boxes ))
992
999
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 )
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
996
1060
997
1061
curr_price = closes [0 ]
998
1062
box_values = [] # y values for the boxes
999
1063
circle_patches = [] # list of circle patches to be used to create the cirCollection
1000
1064
line_seg = [] # line segments that make up the Xs
1001
-
1065
+
1002
1066
for index , difference in enumerate (boxes ):
1003
1067
diff = abs (difference )
1004
1068
@@ -1007,9 +1071,9 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
1007
1071
1008
1072
x = [index ] * (diff )
1009
1073
y = [curr_price + (i * box_size * sign ) for i in range (start_iteration , diff + start_iteration )]
1010
-
1074
+
1011
1075
curr_price += (box_size * sign * (diff ))
1012
- box_values .append (sum ( y ) / len ( y ) )
1076
+ box_values .append ( y )
1013
1077
1014
1078
for i in range (len (x )): # x and y have the same length
1015
1079
height = box_size * 0.85
@@ -1036,7 +1100,9 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
1036
1100
linewidths = lw ,
1037
1101
antialiaseds = useAA
1038
1102
)
1039
- return [cirCollection , xCollection ], new_dates , new_volumes , box_values , box_size
1103
+ calculated_values = dict (dates = new_dates ,counts = boxes ,values = box_values ,
1104
+ volumes = new_volumes ,size = box_size )
1105
+ return [cirCollection , xCollection ], calculated_values
1040
1106
1041
1107
1042
1108
def _construct_aline_collections (alines , dtix = None ):
0 commit comments