8
8
import datetime
9
9
10
10
from matplotlib import colors as mcolors
11
- from matplotlib .collections import LineCollection , PolyCollection
11
+ from matplotlib .patches import Ellipse
12
+ from matplotlib .collections import LineCollection , PolyCollection , PatchCollection
12
13
from mplfinance ._arg_validators import _process_kwargs , _validate_vkwargs_dict
13
14
14
15
from six .moves import zip
@@ -102,29 +103,6 @@ def _calculate_atr(atr_length, highs, lows, closes):
102
103
atr += tr
103
104
return atr / atr_length
104
105
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
-
128
106
def _updown_colors (upcolor ,downcolor ,opens ,closes ,use_prev_close = False ):
129
107
if upcolor == downcolor :
130
108
return upcolor
@@ -138,10 +116,10 @@ def _updown_colors(upcolor,downcolor,opens,closes,use_prev_close=False):
138
116
139
117
def _valid_renko_kwargs ():
140
118
'''
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:
119
+ Construct and return the "valid renko kwargs table" for the mplfinance.plot(type='renko')
120
+ function. A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are
121
+ the valid key-words for the function. The value for each key is a dict containing 2
122
+ specific keys: "Default", and "Validator" with the following values:
145
123
"Default" - The default value for the kwarg if none is specified.
146
124
"Validator" - A function that takes the caller specified value for the kwarg,
147
125
and validates that it is the correct type, and (for kwargs with
@@ -159,6 +137,29 @@ def _valid_renko_kwargs():
159
137
160
138
return vkwargs
161
139
140
+ def _valid_pointnfig_kwargs ():
141
+ '''
142
+ Construct and return the "valid pointnfig kwargs table" for the mplfinance.plot(type='pnf')
143
+ function. A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are
144
+ the valid key-words for the function. The value for each key is a dict containing 2
145
+ specific keys: "Default", and "Validator" with the following values:
146
+ "Default" - The default value for the kwarg if none is specified.
147
+ "Validator" - A function that takes the caller specified value for the kwarg,
148
+ and validates that it is the correct type, and (for kwargs with
149
+ a limited set of allowed values) may also validate that the
150
+ kwarg value is one of the allowed values.
151
+ '''
152
+ vkwargs = {
153
+ 'box_size' : { 'Default' : 'atr' ,
154
+ 'Validator' : lambda value : isinstance (value ,(float ,int )) or value == 'atr' },
155
+ 'atr_length' : { 'Default' : 14 ,
156
+ 'Validator' : lambda value : isinstance (value ,int ) },
157
+ }
158
+
159
+ _validate_vkwargs_dict (vkwargs )
160
+
161
+ return vkwargs
162
+
162
163
def _construct_ohlc_collections (dates , opens , highs , lows , closes , marketcolors = None ):
163
164
"""Represent the time, open, high, low, close as a vertical line
164
165
ranging from low to high. The left tick is the open and the right
@@ -340,10 +341,9 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
340
341
sequence of high values
341
342
lows : sequence
342
343
sequence of low values
343
- renko_params : dictionary
344
- type : type of renko chart
344
+ config_renko_params : kwargs table (dictionary)
345
345
brick_size : size of each brick
346
- atr_legnth : length of time used for calculating atr
346
+ atr_length : length of time used for calculating atr
347
347
closes : sequence
348
348
sequence of closing values
349
349
marketcolors : dict of colors: up, down, edge, wick, alpha
@@ -379,22 +379,27 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
379
379
dc = mcolors .to_rgba (marketcolors ['candle' ]['down' ], alpha )
380
380
euc = mcolors .to_rgba (marketcolors ['edge' ][ 'up' ], 1.0 )
381
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
382
+
383
+ cdiff = []
384
+ prev_close_brick = closes [0 ]
385
+ for i in range (len (closes )- 1 ):
386
+ brick_diff = int ((closes [i + 1 ] - prev_close_brick ) / brick_size )
387
+ cdiff .append (brick_diff )
388
+ prev_close_brick += brick_diff * brick_size
384
389
385
390
bricks = [] # holds bricks, 1 for down bricks, -1 for up bricks
386
391
new_dates = [] # holds the dates corresponding with the index
387
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.
388
393
389
- prev_num = 0
394
+
390
395
start_price = closes [0 ]
391
396
392
397
volume_cache = 0 # holds the volumes for the dates that were skipped
393
398
394
399
last_diff_sign = 0 # direction the bricks were last going in -1 -> down, 1 -> up
395
400
for i in range (len (cdiff )):
396
- num_bricks = abs (int ( cdiff [i ]) )
397
- curr_diff_sign = cdiff [i ]/ abs (cdiff [i ])
401
+ num_bricks = abs (cdiff [i ])
402
+ curr_diff_sign = cdiff [i ]/ abs (cdiff [i ]) if cdiff [ i ] != 0 else 0
398
403
if last_diff_sign != 0 and num_bricks > 0 and curr_diff_sign != last_diff_sign :
399
404
num_bricks -= 1
400
405
last_diff_sign = curr_diff_sign
@@ -418,6 +423,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
418
423
colors = []
419
424
edge_colors = []
420
425
brick_values = []
426
+ prev_num = - 1 if bricks [0 ] > 0 else 0
421
427
for index , number in enumerate (bricks ):
422
428
if number == 1 : # up brick
423
429
colors .append (uc )
@@ -429,6 +435,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
429
435
prev_num += number
430
436
brick_y = start_price + (prev_num * brick_size )
431
437
brick_values .append (brick_y )
438
+
432
439
x , y = index , brick_y
433
440
434
441
verts .append ((
@@ -440,14 +447,144 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
440
447
useAA = 0 , # use tuple here
441
448
lw = None
442
449
rectCollection = PolyCollection (verts ,
443
- facecolors = colors ,
444
- antialiaseds = useAA ,
445
- edgecolors = edge_colors ,
446
- linewidths = lw
447
- )
450
+ facecolors = colors ,
451
+ antialiaseds = useAA ,
452
+ edgecolors = edge_colors ,
453
+ linewidths = lw
454
+ )
448
455
449
456
return (rectCollection , ), new_dates , new_volumes , brick_values , brick_size
450
457
458
+ def _construct_pointnfig_collections (dates , highs , lows , volumes , config_pointnfig_params , closes , marketcolors = None ):
459
+ """Represent the price change with Xs and Os
460
+
461
+ Parameters
462
+ ----------
463
+ dates : sequence
464
+ sequence of dates
465
+ highs : sequence
466
+ sequence of high values
467
+ lows : sequence
468
+ sequence of low values
469
+ config_pointnfig_params : kwargs table (dictionary)
470
+ box_size : size of each box
471
+ atr_length : length of time used for calculating atr
472
+ closes : sequence
473
+ sequence of closing values
474
+ marketcolors : dict of colors: up, down, edge, wick, alpha
475
+
476
+ Returns
477
+ -------
478
+ ret : tuple
479
+ rectCollection
480
+ """
481
+ pointnfig_params = _process_kwargs (config_pointnfig_params , _valid_pointnfig_kwargs ())
482
+ if marketcolors is None :
483
+ marketcolors = _get_mpfstyle ('classic' )['marketcolors' ]
484
+ print ('default market colors:' ,marketcolors )
485
+
486
+ box_size = pointnfig_params ['box_size' ]
487
+ atr_length = pointnfig_params ['atr_length' ]
488
+
489
+
490
+ if box_size == 'atr' :
491
+ box_size = _calculate_atr (atr_length , highs , lows , closes )
492
+ 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
496
+ 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 ))
498
+ 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 ))
500
+
501
+ alpha = marketcolors ['alpha' ]
502
+
503
+ uc = mcolors .to_rgba (marketcolors ['candle' ][ 'up' ], alpha )
504
+ dc = mcolors .to_rgba (marketcolors ['candle' ]['down' ], alpha )
505
+ tfc = mcolors .to_rgba (marketcolors ['edge' ]['down' ], 0 ) # transparent face color
506
+
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
511
+ volume_cache = 0 # holds the volumes for the dates that were skipped
512
+ prev_sign = 0
513
+ current_cdiff_index = - 1
514
+
515
+ for i in range (len (closes )- 1 ):
516
+ box_diff = int ((closes [i + 1 ] - prev_close_box ) / box_size )
517
+ if box_diff == 0 :
518
+ if volumes is not None :
519
+ volume_cache += volumes [i ]
520
+ 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
+
536
+ prev_close_box += box_diff * box_size
537
+
538
+
539
+ curr_price = closes [0 ]
540
+
541
+ box_values = [] # y values for the boxes
542
+ circle_patches = [] # list of circle patches to be used to create the cirCollection
543
+ line_seg = [] # line segments that make up the Xs
544
+
545
+ for index , difference in enumerate (cdiff ):
546
+ diff = abs (difference )
547
+
548
+ sign = (difference / abs (difference )) # -1 or 1
549
+ start_iteration = 0 if sign > 0 else 1
550
+
551
+ x = [index ] * (diff )
552
+ y = [curr_price + (i * box_size * sign ) for i in range (start_iteration , diff + start_iteration )]
553
+
554
+
555
+ curr_price += (box_size * sign * (diff ))
556
+ box_values .append (sum (y ) / len (y ))
557
+
558
+ for i in range (len (x )): # x and y have the same length
559
+ height = box_size * 0.85
560
+ width = (50 / box_size )/ len (new_dates )
561
+ if height < 0.5 :
562
+ width = height
563
+
564
+ padding = (box_size * 0.075 )
565
+ if sign == 1 : # X
566
+ line_seg .append ([(x [i ]- width / 2 , y [i ] + padding ), (x [i ]+ width / 2 , y [i ]+ height + padding )]) # create / part of the X
567
+ line_seg .append ([(x [i ]- width / 2 , y [i ]+ height + padding ), (x [i ]+ width / 2 , y [i ]+ padding )]) # create \ part of the X
568
+ else : # O
569
+ circle_patches .append (Ellipse ((x [i ], y [i ]- (height / 2 ) - padding ), width , height ))
570
+
571
+ useAA = 0 , # use tuple here
572
+ lw = 0.5
573
+
574
+ cirCollection = PatchCollection (circle_patches )
575
+ cirCollection .set_facecolor ([tfc ] * len (circle_patches ))
576
+ cirCollection .set_edgecolor ([dc ] * len (circle_patches ))
577
+
578
+ xCollection = LineCollection (line_seg ,
579
+ colors = [uc ] * len (line_seg ),
580
+ linewidths = lw ,
581
+ antialiaseds = useAA
582
+ )
583
+
584
+ return (cirCollection , xCollection ), new_dates , new_volumes , box_values , box_size
585
+
586
+
587
+
451
588
from matplotlib .ticker import Formatter
452
589
class IntegerIndexDateTimeFormatter (Formatter ):
453
590
"""
0 commit comments