Skip to content

Commit e32bfcc

Browse files
support kwarg mav in addplot
1 parent dbc5b11 commit e32bfcc

File tree

7 files changed

+306
-50
lines changed

7 files changed

+306
-50
lines changed

examples/addplot.ipynb

Lines changed: 146 additions & 13 deletions
Large diffs are not rendered by default.

src/mplfinance/plotting.py

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,12 @@ def plot( data, **kwargs ):
253253

254254
style = config['style']
255255
if isinstance(style,str):
256-
style = _styles._get_mpfstyle(style)
256+
style = config['style'] = _styles._get_mpfstyle(style)
257257

258258
if isinstance(style,dict):
259259
_styles._apply_mpfstyle(style)
260+
else:
261+
raise TypeError('style should be a `dict`; why is it not?')
260262

261263
if config['figsize'] is None:
262264
w,h = config['figratio']
@@ -320,39 +322,17 @@ def plot( data, **kwargs ):
320322
for collection in collections:
321323
axA1.add_collection(collection)
322324

323-
mavgs = config['mav']
324-
if mavgs is not None:
325-
if isinstance(mavgs,int):
326-
mavgs = mavgs, # convert to tuple
327-
if len(mavgs) > 7:
328-
mavgs = mavgs[0:7] # take at most 7
329-
330-
if style['mavcolors'] is not None:
331-
mavc = cycle(style['mavcolors'])
332-
else:
333-
mavc = None
334-
335-
# Get rcParams['lines.linewidth'] and scale it
336-
# according to the deinsity of data??
337-
338-
for mav in mavgs:
339-
if ptype in VALID_PMOVE_TYPES:
340-
mavprices = pd.Series(brick_values).rolling(mav).mean().values
341-
else:
342-
mavprices = pd.Series(closes).rolling(mav).mean().values
343-
344-
lw = config['_width_config']['line_width']
345-
if mavc:
346-
axA1.plot(xdates, mavprices, linewidth=lw, color=next(mavc))
347-
else:
348-
axA1.plot(xdates, mavprices, linewidth=lw)
325+
if ptype in VALID_PMOVE_TYPES:
326+
mavprices = _plot_mav(axA1,config,xdates,brick_values)
327+
else:
328+
mavprices = _plot_mav(axA1,config,xdates,closes)
349329

350330
avg_dist_between_points = (xdates[-1] - xdates[0]) / float(len(xdates))
351331
if not config['tight_layout']:
352332
minx = xdates[0] - avg_dist_between_points
353333
maxx = xdates[-1] + avg_dist_between_points
354334
else:
355-
minx = xdates[0] - (0.45 * avg_dist_between_points)
335+
minx = xdates[0] - (0.45 * avg_dist_between_points)
356336
maxx = xdates[-1] + (0.45 * avg_dist_between_points)
357337

358338
if len(xdates) == 1: # kludge special case
@@ -387,10 +367,13 @@ def plot( data, **kwargs ):
387367
retdict[prekey+'_size'] = size
388368
if config['volume']:
389369
retdict[prekey+'_volumes'] = volumes
390-
if mavgs is not None:
391-
# This is WRONG! Returning the same mavprices for all mavgs ??!
392-
for i in range(0, len(mavgs)):
393-
retdict['mav' + str(mavgs[i])] = mavprices
370+
if config['mav'] is not None:
371+
mav = config['mav']
372+
if len(mav) != len(mavprices):
373+
warnings.warn('len(mav)='+str(len(mav))+' BUT len(mavprices)='+str(len(mavprices)))
374+
else:
375+
for jj in range(0,len(mav)):
376+
retdict['mav' + str(mav[jj])] = mavprices[jj]
394377
retdict['minx'] = minx
395378
retdict['maxx'] = maxx
396379
retdict['miny'] = miny
@@ -504,10 +487,13 @@ def plot( data, **kwargs ):
504487
ax = panels.at[panid,'axes'][0]
505488
for coll in collections:
506489
ax.add_collection(coll)
507-
datalim = (minx, min(l)), (maxx, max(h))
490+
if apdict['mav'] is not None:
491+
apmavprices = _plot_mav(ax,config,xdates,c,apdict['mav'])
492+
#datalim = (minx, min(l)), (maxx, max(h))
508493
#ax.update_datalim(datalim)
509-
ax.autoscale_view() # Is this really necessary??
510-
#ax.set_ylim(min(l),max(h))
494+
ax.autoscale_view()
495+
if (apdict["ylabel"] is not None):
496+
ax.set_ylabel(apdict["ylabel"])
511497
continue
512498

513499
if isinstance(apdata,list) and not isinstance(apdata[0],(float,int)):
@@ -563,7 +549,8 @@ def plot( data, **kwargs ):
563549
ax.plot(xdates, ydata, linestyle=ls, color=color)
564550
else:
565551
raise ValueError('addplot type "'+str(aptype)+'" NOT yet supported.')
566-
552+
if apdict['mav'] is not None:
553+
apmavprices = _plot_mav(ax,config,xdates,ydata,apdict['mav'])
567554

568555
if config['fill_between'] is not None:
569556
fb = config['fill_between']
@@ -692,6 +679,34 @@ def plot( data, **kwargs ):
692679
# print('rcpdfhead(3)=',rcpdf.head(3))
693680
# return # rcpdf
694681

682+
def _plot_mav(ax,config,xdates,prices,apmav=None):
683+
style = config['style']
684+
if apmav is not None:
685+
mavgs = apmav
686+
else:
687+
mavgs = config['mav']
688+
mavp_list = []
689+
if mavgs is not None:
690+
if isinstance(mavgs,int):
691+
mavgs = mavgs, # convert to tuple
692+
if len(mavgs) > 7:
693+
mavgs = mavgs[0:7] # take at most 7
694+
695+
if style['mavcolors'] is not None:
696+
mavc = cycle(style['mavcolors'])
697+
else:
698+
mavc = None
699+
700+
for mav in mavgs:
701+
mavprices = pd.Series(prices).rolling(mav).mean().values
702+
lw = config['_width_config']['line_width']
703+
if mavc:
704+
ax.plot(xdates, mavprices, linewidth=lw, color=next(mavc))
705+
else:
706+
ax.plot(xdates, mavprices, linewidth=lw)
707+
mavp_list.append(mavprices)
708+
return mavp_list
709+
695710
def _auto_secondary_y( panels, panid, ylo, yhi ):
696711
# If mag(nitude) for this panel is not yet set, then set it
697712
# here, as this is the first ydata to be plotted on this panel:
@@ -711,7 +726,6 @@ def _auto_secondary_y( panels, panid, ylo, yhi ):
711726
def _valid_addplot_kwargs():
712727

713728
valid_linestyles = ('-','solid','--','dashed','-.','dashdot','.','dotted',None,' ','')
714-
#valid_types = ('line','scatter','bar','ohlc','candle')
715729
valid_types = ('line','scatter','bar', 'ohlc', 'candle')
716730

717731
vkwargs = {
@@ -721,6 +735,9 @@ def _valid_addplot_kwargs():
721735
'type' : { 'Default' : 'line',
722736
'Validator' : lambda value: value in valid_types },
723737

738+
'mav' : { 'Default' : None,
739+
'Validator' : _mav_validator },
740+
724741
'panel' : { 'Default' : 0,
725742
'Validator' : lambda value: _valid_panel_id(value) },
726743

tests/reference_images/addplot07.png

58.4 KB
Loading

tests/reference_images/addplot08.png

148 KB
Loading

tests/reference_images/addplot09.png

67 KB
Loading

tests/reference_images/addplot10.png

62.8 KB
Loading

tests/test_addplot.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,109 @@ def test_addplot06(bolldata):
223223
print('result=',result)
224224
assert result is None
225225

226+
def test_addplot07(bolldata):
227+
228+
df = bolldata
229+
230+
fname = base+'07.png'
231+
tname = os.path.join(tdir,fname)
232+
rname = os.path.join(refd,fname)
233+
234+
mpf.plot(df,volume=True,savefig=tname,mav=(20,40,60))
235+
236+
tsize = os.path.getsize(tname)
237+
print(glob.glob(tname),'[',tsize,'bytes',']')
238+
239+
rsize = os.path.getsize(rname)
240+
print(glob.glob(rname),'[',rsize,'bytes',']')
241+
242+
result = compare_images(rname,tname,tol=IMGCOMP_TOLERANCE)
243+
if result is not None:
244+
print('result=',result)
245+
assert result is None
246+
247+
def test_addplot08(bolldata):
248+
df = bolldata
249+
250+
fname = base+'08.png'
251+
tname = os.path.join(tdir,fname)
252+
rname = os.path.join(refd,fname)
253+
254+
tcdf = df[['LowerB','UpperB']] # DataFrame with two columns
255+
256+
low_signal = percentB_belowzero(df['PercentB'], df['Close'])
257+
high_signal = percentB_aboveone(df['PercentB'], df['Close'])
258+
259+
import math
260+
new_low_signal = [x*20.*math.sin(x) for x in low_signal]
261+
262+
apds = [ mpf.make_addplot(tcdf,linestyle='dashdot'),
263+
mpf.make_addplot(new_low_signal,scatter=True,markersize=200,marker='^'),
264+
mpf.make_addplot(high_signal,scatter=True,markersize=200,marker='v'),
265+
mpf.make_addplot((df['PercentB']),panel='lower',color='g',linestyle='dotted')
266+
]
267+
268+
mpf.plot(df,addplot=apds,figscale=1.5,volume=True,
269+
mav=(15,30,45),style='default',savefig=tname)
270+
271+
tsize = os.path.getsize(tname)
272+
print(glob.glob(tname),'[',tsize,'bytes',']')
273+
274+
rsize = os.path.getsize(rname)
275+
print(glob.glob(rname),'[',rsize,'bytes',']')
276+
277+
result = compare_images(rname,tname,tol=IMGCOMP_TOLERANCE)
278+
if result is not None:
279+
print('result=',result)
280+
assert result is None
281+
282+
def test_addplot09(bolldata):
283+
284+
sdf = bolldata[50:130]
285+
286+
fname = base+'09.png'
287+
tname = os.path.join(tdir,fname)
288+
rname = os.path.join(refd,fname)
289+
290+
ap = mpf.make_addplot((sdf['PercentB'])-0.45,panel=1,color='g',type='bar', width=0.75, mav=(7,10,15))
291+
mpf.plot(sdf,addplot=ap,panel_ratios=(1,1),figratio=(1,1),figscale=1.5,savefig=tname)
292+
293+
tsize = os.path.getsize(tname)
294+
print(glob.glob(tname),'[',tsize,'bytes',']')
295+
296+
rsize = os.path.getsize(rname)
297+
print(glob.glob(rname),'[',rsize,'bytes',']')
298+
299+
# Using 0.9*IMGCOMP_TOLERANCE here because discovered that if
300+
# the only difference is the presence or absence of mav lines,
301+
# then the default IMGCOMP_TOLERANCE is too linient:
302+
result = compare_images(rname,tname,tol=0.9*IMGCOMP_TOLERANCE)
303+
if result is not None:
304+
print('result=',result)
305+
assert result is None
306+
307+
def test_addplot10(bolldata):
308+
309+
sdf = bolldata[50:130]
310+
311+
fname = base+'10.png'
312+
tname = os.path.join(tdir,fname)
313+
rname = os.path.join(refd,fname)
314+
315+
ap = mpf.make_addplot(sdf,panel=1,type='candle',ylabel='Candle',mav=12)
316+
mpf.plot(sdf,mav=10,ylabel='OHLC',addplot=ap,panel_ratios=(1,1),figratio=(1,1),figscale=1.5,savefig=tname)
317+
318+
tsize = os.path.getsize(tname)
319+
print(glob.glob(tname),'[',tsize,'bytes',']')
320+
321+
rsize = os.path.getsize(rname)
322+
print(glob.glob(rname),'[',rsize,'bytes',']')
323+
324+
# Using 0.9*IMGCOMP_TOLERANCE here because discovered that if
325+
# the only difference is the presence or absence of mav lines,
326+
# then the default IMGCOMP_TOLERANCE is too linient:
327+
result = compare_images(rname,tname,tol=0.9*IMGCOMP_TOLERANCE)
328+
if result is not None:
329+
print('result=',result)
330+
assert result is None
331+

0 commit comments

Comments
 (0)