99
1010from matplotlib import colors as mcolors
1111from matplotlib .collections import LineCollection , PolyCollection
12+ from mplfinance ._arg_validators import _process_kwargs , _validate_vkwargs_dict
1213
1314from six .moves import zip
1415
@@ -81,6 +82,49 @@ def roundTime(dt=None, roundTo=60):
8182 rounding = (seconds + roundTo / 2 ) // roundTo * roundTo
8283 return dt + datetime .timedelta (0 ,rounding - seconds ,- dt .microsecond )
8384
85+ def _calculate_atr (atr_length , highs , lows , closes ):
86+ """Calculate the average true range
87+ atr_length : time period to calculate over
88+ all_highs : list of highs
89+ all_lows : list of lows
90+ all_closes : list of closes
91+ """
92+ if atr_length < 1 :
93+ raise ValueError ("Specified atr_length may not be less than 1" )
94+ elif atr_length >= len (closes ):
95+ raise ValueError ("Specified atr_length is larger than the length of the dataset: " + str (len (closes )))
96+ atr = 0
97+ for i in range (len (highs )- atr_length , len (highs )):
98+ high = highs [i ]
99+ low = lows [i ]
100+ close_prev = closes [i - 1 ]
101+ tr = max (abs (high - low ), abs (high - close_prev ), abs (low - close_prev ))
102+ atr += tr
103+ return atr / atr_length
104+
105+ def renko_reformat_ydata (ydata , dates , old_dates ):
106+ """Reformats ydata to work on renko charts, can lead to unexpected
107+ outputs for the user as the xaxis does not scale evenly with dates.
108+ Missing dates ydata is averaged into the next date and dates that appear
109+ more than once have the same ydata
110+ ydata : y data likely coming from addplot
111+ dates : x-axis dates for the renko chart
112+ old_dates : original dates in the data set
113+ """
114+ new_ydata = [] # stores new ydata
115+ prev_data = 0
116+ skipped_dates = 0
117+ for i in range (len (ydata )):
118+ if old_dates [i ] not in dates :
119+ prev_data += ydata [i ]
120+ skipped_dates += 1
121+ else :
122+ dup_dates = dates .count (old_dates [i ])
123+ new_ydata .extend ([(ydata [i ]+ prev_data )/ (skipped_dates + 1 )]* dup_dates )
124+ skipped_dates = 0
125+ prev_data = 0
126+ return new_ydata
127+
84128def _updown_colors (upcolor ,downcolor ,opens ,closes ,use_prev_close = False ):
85129 if upcolor == downcolor :
86130 return upcolor
@@ -92,6 +136,29 @@ def _updown_colors(upcolor,downcolor,opens,closes,use_prev_close=False):
92136 _list = [ cmap [pre < cls ] for cls ,pre in zip (closes [1 :], closes ) ]
93137 return [first ] + _list
94138
139+ def _valid_renko_kwargs ():
140+ '''
141+ Construct and return the "valid renko kwargs table" for the mplfinance.plot(type='renko') function.
142+ A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are the
143+ valid key-words for the function. The value for each key is a dict containing
144+ 2 specific keys: "Default", and "Validator" with the following values:
145+ "Default" - The default value for the kwarg if none is specified.
146+ "Validator" - A function that takes the caller specified value for the kwarg,
147+ and validates that it is the correct type, and (for kwargs with
148+ a limited set of allowed values) may also validate that the
149+ kwarg value is one of the allowed values.
150+ '''
151+ vkwargs = {
152+ 'brick_size' : { 'Default' : 'atr' ,
153+ 'Validator' : lambda value : isinstance (value ,(float ,int )) or value == 'atr' },
154+ 'atr_length' : { 'Default' : 14 ,
155+ 'Validator' : lambda value : isinstance (value ,int ) },
156+ }
157+
158+ _validate_vkwargs_dict (vkwargs )
159+
160+ return vkwargs
161+
95162def _construct_ohlc_collections (dates , opens , highs , lows , closes , marketcolors = None ):
96163 """Represent the time, open, high, low, close as a vertical line
97164 ranging from low to high. The left tick is the open and the right
@@ -262,6 +329,121 @@ def _construct_candlestick_collections(dates, opens, highs, lows, closes, market
262329
263330 return rangeCollection , barCollection
264331
332+ def _construct_renko_collections (dates , highs , lows , volumes , config_renko_params , closes , marketcolors = None ):
333+ """Represent the price change with bricks
334+
335+ Parameters
336+ ----------
337+ dates : sequence
338+ sequence of dates
339+ highs : sequence
340+ sequence of high values
341+ lows : sequence
342+ sequence of low values
343+ renko_params : dictionary
344+ type : type of renko chart
345+ brick_size : size of each brick
346+ atr_legnth : length of time used for calculating atr
347+ closes : sequence
348+ sequence of closing values
349+ marketcolors : dict of colors: up, down, edge, wick, alpha
350+
351+ Returns
352+ -------
353+ ret : tuple
354+ rectCollection
355+ """
356+ renko_params = _process_kwargs (config_renko_params , _valid_renko_kwargs ())
357+ if marketcolors is None :
358+ marketcolors = _get_mpfstyle ('classic' )['marketcolors' ]
359+ print ('default market colors:' ,marketcolors )
360+
361+ brick_size = renko_params ['brick_size' ]
362+ atr_length = renko_params ['atr_length' ]
363+
364+
365+ if brick_size == 'atr' :
366+ brick_size = _calculate_atr (atr_length , highs , lows , closes )
367+ 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
371+ 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 ))
373+ 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 ))
375+
376+ alpha = marketcolors ['alpha' ]
377+
378+ uc = mcolors .to_rgba (marketcolors ['candle' ][ 'up' ], alpha )
379+ 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 )
382+
383+ cdiff = [(closes [i + 1 ] - closes [i ])/ brick_size for i in range (len (closes )- 1 )] # fill cdiff with close price change
384+
385+ bricks = [] # holds bricks, 1 for down bricks, -1 for up bricks
386+ new_dates = [] # holds the dates corresponding with the index
387+ 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.
388+
389+ prev_num = 0
390+ start_price = closes [0 ]
391+
392+ volume_cache = 0 # holds the volumes for the dates that were skipped
393+
394+
395+ for i in range (len (cdiff )):
396+ num_bricks = abs (int (round (cdiff [i ], 0 )))
397+
398+ if num_bricks != 0 :
399+ new_dates .extend ([dates [i ]]* num_bricks )
400+
401+ if volumes is not None : # only adds volumes if there are volume values when volume=True
402+ if num_bricks != 0 :
403+ new_volumes .extend ([volumes [i ] + volume_cache ]* num_bricks )
404+ volume_cache = 0
405+ else :
406+ volume_cache += volumes [i ]
407+
408+ if cdiff [i ] > 0 :
409+ bricks .extend ([1 ]* num_bricks )
410+ else :
411+ bricks .extend ([- 1 ]* num_bricks )
412+
413+ verts = []
414+ colors = []
415+ edge_colors = []
416+ brick_values = []
417+ for index , number in enumerate (bricks ):
418+ if number == 1 : # up brick
419+ colors .append (uc )
420+ edge_colors .append (euc )
421+ else : # down brick
422+ colors .append (dc )
423+ edge_colors .append (edc )
424+
425+ prev_num += number
426+ brick_y = start_price + (prev_num * brick_size )
427+ brick_values .append (brick_y )
428+ x , y = index , brick_y
429+
430+ verts .append ((
431+ (x , y ),
432+ (x , y + brick_size ),
433+ (x + 1 , y + brick_size ),
434+ (x + 1 , y )))
435+
436+ useAA = 0 , # use tuple here
437+ lw = None
438+ rectCollection = PolyCollection (verts ,
439+ facecolors = colors ,
440+ antialiaseds = useAA ,
441+ edgecolors = edge_colors ,
442+ linewidths = lw
443+ )
444+
445+ return (rectCollection , ), new_dates , new_volumes , brick_values
446+
265447from matplotlib .ticker import Formatter
266448class IntegerIndexDateTimeFormatter (Formatter ):
267449 """
0 commit comments