9
9
10
10
from matplotlib import colors as mcolors
11
11
from matplotlib .collections import LineCollection , PolyCollection
12
+ from mplfinance ._arg_validators import _process_kwargs , _validate_vkwargs_dict
12
13
13
14
from six .moves import zip
14
15
@@ -81,6 +82,49 @@ def roundTime(dt=None, roundTo=60):
81
82
rounding = (seconds + roundTo / 2 ) // roundTo * roundTo
82
83
return dt + datetime .timedelta (0 ,rounding - seconds ,- dt .microsecond )
83
84
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
+
84
128
def _updown_colors (upcolor ,downcolor ,opens ,closes ,use_prev_close = False ):
85
129
if upcolor == downcolor :
86
130
return upcolor
@@ -92,6 +136,29 @@ def _updown_colors(upcolor,downcolor,opens,closes,use_prev_close=False):
92
136
_list = [ cmap [pre < cls ] for cls ,pre in zip (closes [1 :], closes ) ]
93
137
return [first ] + _list
94
138
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
+
95
162
def _construct_ohlc_collections (dates , opens , highs , lows , closes , marketcolors = None ):
96
163
"""Represent the time, open, high, low, close as a vertical line
97
164
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
262
329
263
330
return rangeCollection , barCollection
264
331
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
+
265
447
from matplotlib .ticker import Formatter
266
448
class IntegerIndexDateTimeFormatter (Formatter ):
267
449
"""
0 commit comments