88import datetime
99
1010from 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
1213from mplfinance ._arg_validators import _process_kwargs , _validate_vkwargs_dict
1314
1415from six .moves import zip
@@ -102,29 +103,6 @@ def _calculate_atr(atr_length, highs, lows, closes):
102103 atr += tr
103104 return atr / atr_length
104105
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-
128106def _updown_colors (upcolor ,downcolor ,opens ,closes ,use_prev_close = False ):
129107 if upcolor == downcolor :
130108 return upcolor
@@ -138,10 +116,10 @@ def _updown_colors(upcolor,downcolor,opens,closes,use_prev_close=False):
138116
139117def _valid_renko_kwargs ():
140118 '''
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:
145123 "Default" - The default value for the kwarg if none is specified.
146124 "Validator" - A function that takes the caller specified value for the kwarg,
147125 and validates that it is the correct type, and (for kwargs with
@@ -159,6 +137,29 @@ def _valid_renko_kwargs():
159137
160138 return vkwargs
161139
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+
162163def _construct_ohlc_collections (dates , opens , highs , lows , closes , marketcolors = None ):
163164 """Represent the time, open, high, low, close as a vertical line
164165 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
340341 sequence of high values
341342 lows : sequence
342343 sequence of low values
343- renko_params : dictionary
344- type : type of renko chart
344+ config_renko_params : kwargs table (dictionary)
345345 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
347347 closes : sequence
348348 sequence of closing values
349349 marketcolors : dict of colors: up, down, edge, wick, alpha
@@ -379,22 +379,27 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
379379 dc = mcolors .to_rgba (marketcolors ['candle' ]['down' ], alpha )
380380 euc = mcolors .to_rgba (marketcolors ['edge' ][ 'up' ], 1.0 )
381381 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
384389
385390 bricks = [] # holds bricks, 1 for down bricks, -1 for up bricks
386391 new_dates = [] # holds the dates corresponding with the index
387392 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.
388393
389- prev_num = 0
394+
390395 start_price = closes [0 ]
391396
392397 volume_cache = 0 # holds the volumes for the dates that were skipped
393398
394399 last_diff_sign = 0 # direction the bricks were last going in -1 -> down, 1 -> up
395400 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
398403 if last_diff_sign != 0 and num_bricks > 0 and curr_diff_sign != last_diff_sign :
399404 num_bricks -= 1
400405 last_diff_sign = curr_diff_sign
@@ -418,6 +423,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
418423 colors = []
419424 edge_colors = []
420425 brick_values = []
426+ prev_num = - 1 if bricks [0 ] > 0 else 0
421427 for index , number in enumerate (bricks ):
422428 if number == 1 : # up brick
423429 colors .append (uc )
@@ -429,6 +435,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
429435 prev_num += number
430436 brick_y = start_price + (prev_num * brick_size )
431437 brick_values .append (brick_y )
438+
432439 x , y = index , brick_y
433440
434441 verts .append ((
@@ -440,14 +447,144 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
440447 useAA = 0 , # use tuple here
441448 lw = None
442449 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+ )
448455
449456 return (rectCollection , ), new_dates , new_volumes , brick_values , brick_size
450457
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+
451588from matplotlib .ticker import Formatter
452589class IntegerIndexDateTimeFormatter (Formatter ):
453590 """
0 commit comments