@@ -103,6 +103,51 @@ def _calculate_atr(atr_length, highs, lows, closes):
103103 atr += tr
104104 return atr / atr_length
105105
106+ def combine_adjacent (arr ):
107+ """Sum like signed adjacent elements
108+ arr : starting array
109+
110+ Returns
111+ -------
112+ output: new summed array
113+ indexes: indexes indicating the first
114+ element summed for each group in arr
115+ """
116+ output , indexes = [], []
117+ curr_i = 0
118+ while len (arr ) > 0 :
119+ curr_sign = arr [0 ]/ abs (arr [0 ])
120+ index = 0
121+ while index < len (arr ) and arr [index ]/ abs (arr [index ]) == curr_sign :
122+ index += 1
123+ output .append (sum (arr [:index ]))
124+ indexes .append (curr_i )
125+ curr_i += index
126+
127+ for _ in range (index ):
128+ arr .pop (0 )
129+ return output , indexes
130+
131+ def coalesce_volume_dates (in_volumes , in_dates , indexes ):
132+ """Sums volumes between the indexes and ouputs
133+ dates at the indexes
134+ in_volumes : original volume list
135+ in_dates : original dates list
136+ indexes : list of indexes
137+
138+ Returns
139+ -------
140+ volumes: new volume array
141+ dates: new dates array
142+ """
143+ volumes , dates = [], []
144+ for i in range (len (indexes )):
145+ dates .append (in_dates [indexes [i ]])
146+ to_sum_to = indexes [i + 1 ] if i + 1 < len (indexes ) else len (in_volumes )
147+ volumes .append (sum (in_volumes [indexes [i ]:to_sum_to ]))
148+ return volumes , dates
149+
150+
106151def _updown_colors (upcolor ,downcolor ,opens ,closes ,use_prev_close = False ):
107152 if upcolor == downcolor :
108153 return upcolor
@@ -130,7 +175,7 @@ def _valid_renko_kwargs():
130175 'brick_size' : { 'Default' : 'atr' ,
131176 'Validator' : lambda value : isinstance (value ,(float ,int )) or value == 'atr' },
132177 'atr_length' : { 'Default' : 14 ,
133- 'Validator' : lambda value : isinstance (value ,int ) },
178+ 'Validator' : lambda value : isinstance (value ,int ) or value == 'total' },
134179 }
135180
136181 _validate_vkwargs_dict (vkwargs )
@@ -153,7 +198,7 @@ def _valid_pointnfig_kwargs():
153198 'box_size' : { 'Default' : 'atr' ,
154199 'Validator' : lambda value : isinstance (value ,(float ,int )) or value == 'atr' },
155200 'atr_length' : { 'Default' : 14 ,
156- 'Validator' : lambda value : isinstance (value ,int ) },
201+ 'Validator' : lambda value : isinstance (value ,int ) or value == 'total' },
157202 }
158203
159204 _validate_vkwargs_dict (vkwargs )
@@ -363,67 +408,76 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
363408
364409
365410 if brick_size == 'atr' :
366- brick_size = _calculate_atr (atr_length , highs , lows , closes )
411+ if atr_length == 'total' :
412+ brick_size = _calculate_atr (len (closes )- 1 , highs , lows , closes )
413+ else :
414+ brick_size = _calculate_atr (atr_length , highs , lows , closes )
367415 else : # is an integer or float
368- total_atr = _calculate_atr (len (closes )- 1 , highs , lows , closes )
369- upper_limit = 1.5 * total_atr
370- lower_limit = 0.01 * total_atr
416+ upper_limit = (max (closes ) - min (closes )) / 5
417+ lower_limit = (max (closes ) - min (closes )) / 32
371418 if brick_size > upper_limit :
372- raise ValueError ("Specified brick_size may not be larger than (1.5* the Average True Value of the dataset) which has value: " + str (upper_limit ))
419+ raise ValueError ("Specified brick_size may not be larger than (20% of the close price range of the dataset) which has value: " + str (upper_limit ))
373420 elif brick_size < lower_limit :
374- raise ValueError ("Specified brick_size may not be smaller than (0.01* the Average True Value of the dataset) which has value: " + str (lower_limit ))
421+ raise ValueError ("Specified brick_size may not be smaller than (3.125% of the close price range of the dataset) which has value: " + str (lower_limit ))
375422
376423 alpha = marketcolors ['alpha' ]
377424
378425 uc = mcolors .to_rgba (marketcolors ['candle' ][ 'up' ], alpha )
379426 dc = mcolors .to_rgba (marketcolors ['candle' ]['down' ], alpha )
380- euc = mcolors .to_rgba (marketcolors ['edge' ][ 'up' ], 1.0 )
381- edc = mcolors .to_rgba (marketcolors ['edge' ]['down' ], 1.0 )
427+ euc = mcolors .to_rgba (marketcolors ['edge' ][ 'up' ], 1.0 )
428+ edc = mcolors .to_rgba (marketcolors ['edge' ]['down' ], 1.0 )
382429
383430 cdiff = []
384431 prev_close_brick = closes [0 ]
432+ volume_cache = 0 # holds the volumes for the dates that were skipped
433+ new_dates = [] # holds the dates corresponding with the index
434+ new_volumes = [] # holds the volumes corresponding with the index. If more than one index for the same day then they all have the same volume.
435+
385436 for i in range (len (closes )- 1 ):
386437 brick_diff = int ((closes [i + 1 ] - prev_close_brick ) / brick_size )
387- cdiff .append (brick_diff )
388- prev_close_brick += brick_diff * brick_size
438+ if brick_diff == 0 :
439+ if volumes is not None :
440+ volume_cache += volumes [i ]
441+ continue
389442
390- bricks = [] # holds bricks, 1 for down bricks, -1 for up bricks
391- new_dates = [] # holds the dates corresponding with the index
392- new_volumes = [] # holds the volumes corresponding with the index. If more than one index for the same day then they all have the same volume.
443+ cdiff .extend ([int (brick_diff / abs (brick_diff ))] * abs (brick_diff ))
444+ if volumes is not None :
445+ new_volumes .extend ([volumes [i ] + volume_cache ] * abs (brick_diff ))
446+ volume_cache = 0
447+ new_dates .extend ([dates [i ]] * abs (brick_diff ))
448+ prev_close_brick += brick_diff * brick_size
393449
394-
395- start_price = closes [0 ]
450+ bricks = [] # holds bricks, 1 for down bricks, -1 for up bricks
451+ curr_price = closes [0 ]
396452
397- volume_cache = 0 # holds the volumes for the dates that were skipped
398-
399453 last_diff_sign = 0 # direction the bricks were last going in -1 -> down, 1 -> up
400- for i in range (len (cdiff )):
401- num_bricks = abs (cdiff [i ])
402- curr_diff_sign = cdiff [i ]/ abs (cdiff [i ]) if cdiff [i ] != 0 else 0
403- if last_diff_sign != 0 and num_bricks > 0 and curr_diff_sign != last_diff_sign :
404- num_bricks -= 1
454+ dates_volumes_index = 0 # keeps track of the index of the current date/volume
455+ for diff in cdiff :
456+
457+ curr_diff_sign = diff / abs (diff )
458+ if last_diff_sign != 0 and curr_diff_sign != last_diff_sign :
459+ last_diff_sign = curr_diff_sign
460+ new_dates .pop (dates_volumes_index )
461+ if volumes is not None :
462+ if dates_volumes_index == len (new_volumes )- 1 :
463+ new_volumes [dates_volumes_index - 1 ] += new_volumes [dates_volumes_index ]
464+ else :
465+ new_volumes [dates_volumes_index + 1 ] += new_volumes [dates_volumes_index ]
466+ new_volumes .pop (dates_volumes_index )
467+ continue
405468 last_diff_sign = curr_diff_sign
406-
407- if num_bricks != 0 :
408- new_dates .extend ([dates [i ]]* num_bricks )
409-
410- if volumes is not None : # only adds volumes if there are volume values when volume=True
411- if num_bricks != 0 :
412- new_volumes .extend ([volumes [i ] + volume_cache ]* num_bricks )
413- volume_cache = 0
414- else :
415- volume_cache += volumes [i ]
416-
417- if cdiff [i ] > 0 :
418- bricks .extend ([1 ]* num_bricks )
469+
470+ if diff > 0 :
471+ bricks .extend ([1 ]* abs (diff ))
419472 else :
420- bricks .extend ([- 1 ]* num_bricks )
473+ bricks .extend ([- 1 ]* abs (diff ))
474+ dates_volumes_index += 1
421475
422- verts = []
423- colors = []
424- edge_colors = []
425- brick_values = []
426- prev_num = - 1 if bricks [ 0 ] > 0 else 0
476+
477+ verts = [] # holds the brick vertices
478+ colors = [] # holds the facecolors for each brick
479+ edge_colors = [] # holds the edgecolors for each brick
480+ brick_values = [] # holds the brick values for each brick
427481 for index , number in enumerate (bricks ):
428482 if number == 1 : # up brick
429483 colors .append (uc )
@@ -432,11 +486,10 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
432486 colors .append (dc )
433487 edge_colors .append (edc )
434488
435- prev_num += number
436- brick_y = start_price + (prev_num * brick_size )
437- brick_values .append (brick_y )
489+ curr_price += (brick_size * number )
490+ brick_values .append (curr_price )
438491
439- x , y = index , brick_y
492+ x , y = index , curr_price
440493
441494 verts .append ((
442495 (x , y ),
@@ -488,61 +541,75 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
488541
489542
490543 if box_size == 'atr' :
491- box_size = _calculate_atr (atr_length , highs , lows , closes )
544+ if atr_length == 'total' :
545+ box_size = _calculate_atr (len (closes )- 1 , highs , lows , closes )
546+ else :
547+ box_size = _calculate_atr (atr_length , highs , lows , closes )
492548 else : # is an integer or float
493- total_atr = _calculate_atr (len (closes )- 1 , highs , lows , closes )
494- upper_limit = 5 * total_atr
495- lower_limit = 0.01 * total_atr
549+ upper_limit = (max (closes ) - min (closes )) / 5
550+ lower_limit = (max (closes ) - min (closes )) / 32
496551 if box_size > upper_limit :
497- raise ValueError ("Specified box_size may not be larger than (1.5* the Average True Value of the dataset) which has value: " + str (upper_limit ))
552+ raise ValueError ("Specified box_size may not be larger than (20% of the close price range of the dataset) which has value: " + str (upper_limit ))
498553 elif box_size < lower_limit :
499- 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 ))
554+ raise ValueError ("Specified box_size may not be smaller than (3.125% of the close price range of the dataset) which has value: " + str (lower_limit ))
500555
501556 alpha = marketcolors ['alpha' ]
502557
503558 uc = mcolors .to_rgba (marketcolors ['candle' ][ 'up' ], alpha )
504559 dc = mcolors .to_rgba (marketcolors ['candle' ]['down' ], alpha )
505560 tfc = mcolors .to_rgba (marketcolors ['edge' ]['down' ], 0 ) # transparent face color
506561
507- cdiff = []
508- prev_close_box = closes [0 ]
509- new_volumes = [] # holds the volumes corresponding with the index. If more than one index for the same day then they all have the same volume.
510- new_dates = [] # holds the dates corresponding with the index
562+ boxes = [] # each element in an integer representing the number of boxes to be drawn on that indexes column (negative numbers -> Os, positive numbers -> Xs)
563+ prev_close_box = closes [0 ] # represents the value of the last box in the previous column
511564 volume_cache = 0 # holds the volumes for the dates that were skipped
512- prev_sign = 0
513- current_cdiff_index = - 1
514-
565+ temp_volumes , temp_dates = [], [] # holds the temp adjusted volumes and dates respectively
566+
515567 for i in range (len (closes )- 1 ):
516568 box_diff = int ((closes [i + 1 ] - prev_close_box ) / box_size )
517569 if box_diff == 0 :
518570 if volumes is not None :
519571 volume_cache += volumes [i ]
520572 continue
521- sign = box_diff / abs (box_diff )
522- if sign == prev_sign :
523- cdiff [current_cdiff_index ] += box_diff
524- if volumes is not None :
525- new_volumes [current_cdiff_index ] += volumes [i ] + volume_cache
526- volume_cache = 0
527- else :
528- cdiff .append (box_diff )
529- if volumes is not None :
530- new_volumes .append (volumes [i ] + volume_cache )
531- volume_cache = 0
532- new_dates .append (dates [i ])
533- prev_sign = sign
534- current_cdiff_index += 1
535-
573+
574+ boxes .append (box_diff )
575+ if volumes is not None :
576+ temp_volumes .append (volumes [i ] + volume_cache )
577+ volume_cache = 0
578+ temp_dates .append (dates [i ])
536579 prev_close_box += box_diff * box_size
537580
581+ # combine adjacent similarly signed differences
582+ boxes , indexes = combine_adjacent (boxes )
583+ new_volumes , new_dates = coalesce_volume_dates (temp_volumes , temp_dates , indexes )
584+
585+ #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
586+ first_elem = boxes [0 ]
587+ boxes = [boxes [i ]- int ((boxes [i ]/ abs (boxes [i ]))) for i in range (1 , len (boxes ))]
588+ boxes .insert (0 , first_elem )
589+
590+ # adjust volume and dates to make sure volume is combined into non 0 box indexes and only use dates from non 0 box indexes
591+ temp_volumes , temp_dates = [], []
592+ for i in range (len (boxes )):
593+ if boxes [i ] == 0 :
594+ volume_cache += new_volumes [i ]
595+ else :
596+ temp_volumes .append (new_volumes [i ] + volume_cache )
597+ volume_cache = 0
598+ temp_dates .append (new_dates [i ])
599+
600+ #remove 0s from boxes
601+ boxes = list (filter (lambda diff : diff != 0 , boxes ))
602+
603+ # combine adjacent similarly signed differences again after 0s removed
604+ boxes , indexes = combine_adjacent (boxes )
605+ new_volumes , new_dates = coalesce_volume_dates (temp_volumes , temp_dates , indexes )
538606
539607 curr_price = closes [0 ]
540-
541608 box_values = [] # y values for the boxes
542609 circle_patches = [] # list of circle patches to be used to create the cirCollection
543610 line_seg = [] # line segments that make up the Xs
544611
545- for index , difference in enumerate (cdiff ):
612+ for index , difference in enumerate (boxes ):
546613 diff = abs (difference )
547614
548615 sign = (difference / abs (difference )) # -1 or 1
@@ -551,13 +618,12 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
551618 x = [index ] * (diff )
552619 y = [curr_price + (i * box_size * sign ) for i in range (start_iteration , diff + start_iteration )]
553620
554-
555621 curr_price += (box_size * sign * (diff ))
556622 box_values .append (sum (y ) / len (y ))
557623
558624 for i in range (len (x )): # x and y have the same length
559625 height = box_size * 0.85
560- width = ( 50 / box_size ) / len ( new_dates )
626+ width = 0.6
561627 if height < 0.5 :
562628 width = height
563629
0 commit comments