Skip to content

Commit 4532941

Browse files
committed
Working pf plots
1 parent 3e1cc72 commit 4532941

File tree

2 files changed

+133
-23
lines changed

2 files changed

+133
-23
lines changed

src/mplfinance/_utils.py

Lines changed: 116 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import datetime
99

1010
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, CircleCollection, PatchCollection
1213
from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict
1314

1415
from six.moves import zip
@@ -341,9 +342,8 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
341342
lows : sequence
342343
sequence of low values
343344
renko_params : dictionary
344-
type : type of renko chart
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
@@ -436,14 +436,122 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
436436
useAA = 0, # use tuple here
437437
lw = None
438438
rectCollection = PolyCollection(verts,
439-
facecolors=colors,
440-
antialiaseds=useAA,
441-
edgecolors=edge_colors,
442-
linewidths=lw
443-
)
439+
facecolors=colors,
440+
antialiaseds=useAA,
441+
edgecolors=edge_colors,
442+
linewidths=lw
443+
)
444444

445445
return (rectCollection, ), new_dates, new_volumes, brick_values
446446

447+
def _construct_pf_collections(dates, highs, lows, volumes, config_renko_params, closes, marketcolors=None):
448+
"""Represent the price change with Xs and Os
449+
450+
Parameters
451+
----------
452+
dates : sequence
453+
sequence of dates
454+
highs : sequence
455+
sequence of high values
456+
lows : sequence
457+
sequence of low values
458+
renko_params : dictionary
459+
brick_size : size of each brick/box
460+
atr_length : length of time used for calculating atr
461+
closes : sequence
462+
sequence of closing values
463+
marketcolors : dict of colors: up, down, edge, wick, alpha
464+
465+
Returns
466+
-------
467+
ret : tuple
468+
rectCollection
469+
"""
470+
renko_params = _process_kwargs(config_renko_params, _valid_renko_kwargs())
471+
if marketcolors is None:
472+
marketcolors = _get_mpfstyle('classic')['marketcolors']
473+
print('default market colors:',marketcolors)
474+
475+
box_size = renko_params['brick_size']
476+
atr_length = renko_params['atr_length']
477+
478+
479+
if box_size == 'atr':
480+
box_size = _calculate_atr(atr_length, highs, lows, closes)
481+
else: # is an integer or float
482+
total_atr = _calculate_atr(len(closes)-1, highs, lows, closes)
483+
upper_limit = 1.5*total_atr
484+
lower_limit = 0.01*total_atr
485+
if box_size > upper_limit:
486+
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))
487+
elif box_size < lower_limit:
488+
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))
489+
490+
alpha = marketcolors['alpha']
491+
492+
uc = mcolors.to_rgba(marketcolors['candle'][ 'up' ], alpha)
493+
dc = mcolors.to_rgba(marketcolors['candle']['down'], alpha)
494+
tfc = mcolors.to_rgba(marketcolors['edge']['down'], 0) # transparent face color
495+
496+
cdiff = [(closes[i+1] - closes[i])/box_size for i in range(len(closes)-1)] # fill cdiff with close price change
497+
curr_price = closes[0]
498+
new_dates = [] # holds the dates corresponding with the index
499+
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.
500+
box_values = [] # y values for the boxes
501+
circle_patches = []
502+
line_seg = [] # line segments that make up the Xs
503+
504+
volume_cache = 0 # holds the volumes for the dates that were skipped
505+
index = -1
506+
last_trend_positive = True
507+
for diff_index, difference in enumerate(cdiff):
508+
diff = abs(int(round(difference, 0)))
509+
if volumes is not None: # only adds volumes if there are volume values when volume=True
510+
if diff != 0:
511+
new_volumes.extend([volumes[diff_index] + volume_cache]*diff)
512+
volume_cache = 0
513+
else:
514+
volume_cache += volumes[diff_index]
515+
516+
if diff != 0:
517+
index += 1
518+
new_dates.append(dates[diff_index])
519+
else:
520+
continue
521+
522+
523+
sign = (difference / abs(difference)) # -1 or 1
524+
x = [index] * (diff)
525+
start_iteration = 0 if sign > 0 else 1
526+
start_iteration += 0 if (last_trend_positive and sign > 0) or (not last_trend_positive and sign < 0) else 1
527+
528+
y = [(curr_price + (i * box_size * sign)) for i in range(start_iteration, diff+start_iteration)]
529+
box_values.extend(y)
530+
531+
spacing = 0.1 * box_size
532+
curr_price += (box_size * sign * (diff)) + spacing
533+
for i in range(len(x)): # x and y have the same length
534+
if sign == 1: # X
535+
line_seg.append([(x[i]-0.25, y[i]-(box_size/2)), (x[i]+0.25, y[i]+(box_size/2))]) # create / part of the X
536+
line_seg.append([(x[i]-0.25, y[i]+(box_size/2)), (x[i]+0.25, y[i]-(box_size/2))]) # create \ part of the X
537+
else: # O
538+
circle_patches.append(Ellipse((x[i], y[i]), 0.5, box_size/0.9))
539+
useAA = 0, # use tuple here
540+
lw = 0.5
541+
542+
cirCollection = PatchCollection(circle_patches)
543+
cirCollection.set_facecolor([tfc] * len(circle_patches))
544+
cirCollection.set_edgecolor([dc] * len(circle_patches))
545+
xCollection = LineCollection(line_seg,
546+
colors=[uc] * len(line_seg),
547+
linewidths=lw,
548+
antialiaseds=useAA
549+
)
550+
551+
return (cirCollection, xCollection), new_dates, new_volumes, box_values
552+
553+
554+
447555
from matplotlib.ticker import Formatter
448556
class IntegerIndexDateTimeFormatter(Formatter):
449557
"""

src/mplfinance/plotting.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from mplfinance._utils import _construct_ohlc_collections
1616
from mplfinance._utils import _construct_candlestick_collections
1717
from mplfinance._utils import _construct_renko_collections
18+
from mplfinance._utils import _construct_pf_collections
1819

1920
from mplfinance._utils import renko_reformat_ydata
2021
from mplfinance._utils import _updown_colors
@@ -65,7 +66,7 @@ def _valid_plot_kwargs():
6566

6667
vkwargs = {
6768
'type' : { 'Default' : 'ohlc',
68-
'Validator' : lambda value: value in ['candle','candlestick','ohlc','bars','ohlc_bars','line', 'renko'] },
69+
'Validator' : lambda value: value in ['candle','candlestick','ohlc','bars','ohlc_bars','line', 'renko', 'pf', 'p&f'] },
6970

7071
'style' : { 'Default' : 'default',
7172
'Validator' : lambda value: value in _styles.available_styles() or isinstance(value,dict) },
@@ -168,8 +169,8 @@ def plot( data, **kwargs ):
168169

169170
config = _process_kwargs(kwargs, _valid_plot_kwargs())
170171

171-
if config['type'] == 'renko' and config['addplot'] is not None:
172-
err = "`addplot` is not supported for `type='renko'`"
172+
if config['type'] in ['renko', 'pf', 'p&f'] and config['addplot'] is not None:
173+
err = "`addplot` is not supported for `type=" + config['type'] +"'`"
173174
raise ValueError(err)
174175

175176
style = config['style']
@@ -246,7 +247,7 @@ def plot( data, **kwargs ):
246247

247248
ptype = config['type']
248249

249-
if ptype is not 'renko':
250+
if ptype not in ['renko', 'pf', 'p&f']:
250251
if config['show_nontrading']:
251252
formatter = mdates.DateFormatter(fmtstring)
252253
xdates = dates
@@ -266,16 +267,20 @@ def plot( data, **kwargs ):
266267
elif ptype == 'renko':
267268
collections, new_dates, volumes, brick_values = _construct_renko_collections(dates, highs, lows, volumes, config['renko_params'], closes,
268269
marketcolors=style['marketcolors'] )
269-
270-
formatter = IntegerIndexDateTimeFormatter(new_dates, fmtstring)
271-
xdates = np.arange(len(new_dates))
272-
273-
ax1.xaxis.set_major_formatter(formatter)
270+
elif ptype == 'pf' or ptype == 'p&f':
271+
collections, new_dates, volumes, brick_values = _construct_pf_collections(dates, highs, lows, volumes, config['renko_params'], closes,
272+
marketcolors=style['marketcolors'] )
274273
elif ptype == 'line':
275274
ax1.plot(xdates, closes, color=config['linecolor'])
276275
else:
277276
raise ValueError('Unrecognized plot type = "'+ ptype + '"')
278277

278+
if ptype in ['renko', 'pf', 'p&f']:
279+
formatter = IntegerIndexDateTimeFormatter(new_dates, fmtstring)
280+
xdates = np.arange(len(new_dates))
281+
282+
ax1.xaxis.set_major_formatter(formatter)
283+
279284
if collections is not None:
280285
for collection in collections:
281286
ax1.add_collection(collection)
@@ -293,7 +298,7 @@ def plot( data, **kwargs ):
293298
mavc = None
294299

295300
for mav in mavgs:
296-
if ptype == 'renko':
301+
if ptype in ['renko', 'pf', 'p&f']:
297302
mavprices = pd.Series(brick_values).rolling(mav).mean().values
298303
else:
299304
mavprices = data['Close'].rolling(mav).mean().values
@@ -312,7 +317,7 @@ def plot( data, **kwargs ):
312317
if mavgs is not None:
313318
for i in range(0, len(mavgs)):
314319
retdict['mav' + str(mavgs[i])] = mavprices
315-
320+
316321
avg_dist_between_points = (xdates[-1] - xdates[0]) / float(len(xdates))
317322
minx = xdates[0] - avg_dist_between_points
318323
maxx = xdates[-1] + avg_dist_between_points
@@ -337,7 +342,7 @@ def plot( data, **kwargs ):
337342
used_ax3 = False
338343
used_ax4 = False
339344
addplot = config['addplot']
340-
if addplot is not None and ptype is not 'renko':
345+
if addplot is not None and ptype not in ['renko', 'pf', 'p&f']:
341346
# Calculate the Order of Magnitude Range
342347
# If addplot['secondary_y'] == 'auto', then: If the addplot['data']
343348
# is out of the Order of Magnitude Range, then use secondary_y.
@@ -406,9 +411,6 @@ def plot( data, **kwargs ):
406411
if ax == ax4:
407412
used_ax4 = True
408413

409-
if ptype == 'renko':
410-
ydata = renko_reformat_ydata(ydata, new_dates, dates)
411-
412414
if apdict['scatter']:
413415
size = apdict['markersize']
414416
mark = apdict['marker']

0 commit comments

Comments
 (0)