Skip to content

Commit fd56a6a

Browse files
addplot external axes
1 parent 57a3d89 commit fd56a6a

File tree

7 files changed

+239
-260
lines changed

7 files changed

+239
-260
lines changed

examples/external_axes.ipynb

Lines changed: 68 additions & 203 deletions
Large diffs are not rendered by default.

examples/mpf_animation_demo1.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import pandas as pd
2+
import mplfinance as mpf
3+
import matplotlib.animation as animation
4+
5+
idf = pd.read_csv('data/SPY_20110701_20120630_Bollinger.csv',index_col=0,parse_dates=True)
6+
idf.shape
7+
idf.head(3)
8+
idf.tail(3)
9+
df = idf.loc['2011-07-01':'2011-12-30',:]
10+
11+
fig = mpf.figure(style='charles',figsize=(7,8))
12+
ax1 = fig.add_subplot(2,1,1)
13+
ax2 = fig.add_subplot(3,1,3)
14+
15+
def animate(ival):
16+
if (20+ival) > len(df):
17+
print('no more data to plot')
18+
ani.event_source.interval *= 3
19+
if ani.event_source.interval > 12000:
20+
exit()
21+
return
22+
data = df.iloc[0:(20+ival)]
23+
ax1.clear()
24+
ax2.clear()
25+
mpf.plot(data,ax=ax1,volume=ax2,type='candle')
26+
27+
ani = animation.FuncAnimation(fig, animate, interval=250)
28+
29+
mpf.show()
Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
import pandas as pd
22
import mplfinance as mpf
3-
import matplotlib.pyplot as plt
43
import matplotlib.animation as animation
54

65
idf = pd.read_csv('data/SPY_20110701_20120630_Bollinger.csv',index_col=0,parse_dates=True)
7-
idf.shape
8-
idf.head(3)
9-
idf.tail(3)
10-
df = idf.loc['2011-07-01':'2011-12-30',:]
11-
12-
print('len(df)=',len(df))
136

14-
# fig = mpf.figure(style='default',figsize=(7,8))
15-
# ax1 = fig.add_subplot(2,1,1)
16-
# ax2 = fig.add_subplot(3,1,3)
7+
df = idf.loc['2011-07-01':'2011-12-30',:]
178

189
pkwargs=dict(type='candle',mav=(10,20))
1910

@@ -24,9 +15,6 @@
2415
def animate(ival):
2516
if (20+ival) > len(df):
2617
print('no more data to plot')
27-
#answer = input('Exit (y/n)?')
28-
#if answer.lower()[0:1] == 'y':
29-
# exit()
3018
ani.event_source.interval *= 3
3119
if ani.event_source.interval > 12000:
3220
exit()
@@ -36,6 +24,6 @@ def animate(ival):
3624
ax2.clear()
3725
mpf.plot(data,ax=ax1,volume=ax2,**pkwargs)
3826

39-
ani = animation.FuncAnimation(fig, animate, interval=250)
27+
ani = animation.FuncAnimation(fig, animate, interval=200)
4028

41-
plt.show()
29+
mpf.show()

examples/mpf_animation_macd.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import pandas as pd
2+
import mplfinance as mpf
3+
import matplotlib.animation as animation
4+
5+
mpf.__version__
6+
7+
idf = pd.read_csv('data/SPY_20110701_20120630_Bollinger.csv',index_col=0,parse_dates=True)
8+
idf.shape
9+
idf.head(3)
10+
idf.tail(3)
11+
df = idf.loc['2011-07-01':'2011-12-30',:]
12+
13+
14+
# =======
15+
# MACD:
16+
17+
df = df.iloc[0:30]
18+
19+
exp12 = df['Close'].ewm(span=12, adjust=False).mean()
20+
exp26 = df['Close'].ewm(span=26, adjust=False).mean()
21+
macd = exp12 - exp26
22+
signal = macd.ewm(span=9, adjust=False).mean()
23+
histogram = macd - signal
24+
25+
apds = [mpf.make_addplot(exp12,color='lime'),
26+
mpf.make_addplot(exp26,color='c'),
27+
mpf.make_addplot(histogram,type='bar',width=0.7,panel=1,
28+
color='dimgray',alpha=1,secondary_y=False),
29+
mpf.make_addplot(macd,panel=1,color='fuchsia',secondary_y=True),
30+
mpf.make_addplot(signal,panel=1,color='b',secondary_y=True),
31+
]
32+
33+
s = mpf.make_mpf_style(base_mpf_style='classic',rc={'figure.facecolor':'lightgray'})
34+
35+
fig, axes = mpf.plot(df,type='candle',addplot=apds,figscale=1.5,figratio=(7,5),title='\n\nMACD',
36+
style=s,volume=True,volume_panel=2,panel_ratios=(6,3,2),returnfig=True)
37+
38+
ax_main = axes[0]
39+
ax_emav = ax_main
40+
ax_hisg = axes[2]
41+
ax_macd = axes[3]
42+
ax_sign = ax_macd
43+
ax_volu = axes[4]
44+
45+
df = idf.loc['2011-07-01':'2011-12-30',:]
46+
47+
def animate(ival):
48+
if (20+ival) > len(df):
49+
print('no more data to plot')
50+
ani.event_source.interval *= 3
51+
if ani.event_source.interval > 12000:
52+
exit()
53+
return
54+
data = df.iloc[0:(30+ival)]
55+
exp12 = data['Close'].ewm(span=12, adjust=False).mean()
56+
exp26 = data['Close'].ewm(span=26, adjust=False).mean()
57+
macd = exp12 - exp26
58+
signal = macd.ewm(span=9, adjust=False).mean()
59+
histogram = macd - signal
60+
apds = [mpf.make_addplot(exp12,color='lime',ax=ax_emav),
61+
mpf.make_addplot(exp26,color='c',ax=ax_emav),
62+
mpf.make_addplot(histogram,type='bar',width=0.7,
63+
color='dimgray',alpha=1,ax=ax_hisg),
64+
mpf.make_addplot(macd,color='fuchsia',ax=ax_macd),
65+
mpf.make_addplot(signal,color='b',ax=ax_sign),
66+
]
67+
68+
for ax in axes:
69+
ax.clear()
70+
mpf.plot(data,type='candle',addplot=apds,ax=ax_main,volume=ax_volu)
71+
72+
ani = animation.FuncAnimation(fig,animate,interval=100)
73+
74+
mpf.show()

src/mplfinance/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
from mplfinance.plotting import plot, make_addplot
33
from mplfinance._styles import make_mpf_style, make_marketcolors, available_styles
44
from mplfinance._version import __version__
5-
from mplfinance._mplwraps import figure
5+
from mplfinance._mplwraps import figure, show

src/mplfinance/_mplwraps.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@
2222
- Figure.add_axes()
2323
- Figure.subplot() (this is analogous to pyplot.subplot() which calls Figure.add_subplot())
2424
- Figure.subplots()
25+
26+
(3) A wrapper to matplot.pyplot.show(), because it happens often enough, when using mplfinance,
27+
that sometimes one has to import matplotlib.pyplot *ONLY* for the purpose of calling .show()
2528
"""
2629

30+
show = plt.show # Not a true wrapper, rather an assignment.
2731

2832
def _check_for_and_apply_style(kwargs):
2933

src/mplfinance/plotting.py

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -497,21 +497,27 @@ def plot( data, **kwargs ):
497497

498498
addplot = config['addplot']
499499
if addplot is not None and ptype not in VALID_PMOVE_TYPES:
500-
# Calculate the Order of Magnitude Range ('mag')
501-
# If addplot['secondary_y'] == 'auto', then: If the addplot['data']
502-
# is out of the Order of Magnitude Range, then use secondary_y.
503-
# Calculate omrange for Main panel, and for Lower (volume) panel:
504-
lo = math.log(max(math.fabs(np.nanmin(lows)),1e-7),10) - 0.5
505-
hi = math.log(max(math.fabs(np.nanmax(highs)),1e-7),10) + 0.5
500+
# NOTE: If in external_axes_mode, then all code relating
501+
# to panels and secondary_y becomes irrrelevant.
502+
# If the user wants something on a secondary_y then user should
503+
# determine that externally, and pass in the appropriate axes.
506504

507-
panels['mag'] = [None]*len(panels) # create 'mag'nitude column
505+
if not external_axes_mode:
506+
# Calculate the Order of Magnitude Range ('mag')
507+
# If addplot['secondary_y'] == 'auto', then: If the addplot['data']
508+
# is out of the Order of Magnitude Range, then use secondary_y.
508509

509-
panels.at[config['main_panel'],'mag'] = {'lo':lo,'hi':hi} # update main panel magnitude range
510+
lo = math.log(max(math.fabs(np.nanmin(lows)),1e-7),10) - 0.5
511+
hi = math.log(max(math.fabs(np.nanmax(highs)),1e-7),10) + 0.5
510512

511-
if config['volume']:
512-
lo = math.log(max(math.fabs(np.nanmin(volumes)),1e-7),10) - 0.5
513-
hi = math.log(max(math.fabs(np.nanmax(volumes)),1e-7),10) + 0.5
514-
panels.at[config['volume_panel'],'mag'] = {'lo':lo,'hi':hi}
513+
panels['mag'] = [None]*len(panels) # create 'mag'nitude column
514+
515+
panels.at[config['main_panel'],'mag'] = {'lo':lo,'hi':hi} # update main panel magnitude range
516+
517+
if config['volume']:
518+
lo = math.log(max(math.fabs(np.nanmin(volumes)),1e-7),10) - 0.5
519+
hi = math.log(max(math.fabs(np.nanmax(volumes)),1e-7),10) + 0.5
520+
panels.at[config['volume_panel'],'mag'] = {'lo':lo,'hi':hi}
515521

516522
if isinstance(addplot,dict):
517523
addplot = [addplot,] # make list of dict to be consistent
@@ -521,11 +527,12 @@ def plot( data, **kwargs ):
521527

522528
for apdict in addplot:
523529

524-
panid = apdict['panel']
525-
if panid == 'main' : panid = 0 # for backwards compatibility
526-
elif panid == 'lower': panid = 1 # for backwards compatibility
527-
if apdict['y_on_right'] is not None:
528-
panels.at[panid,'y_on_right'] = apdict['y_on_right']
530+
panid = apdict['panel']
531+
if not external_axes_mode:
532+
if panid == 'main' : panid = 0 # for backwards compatibility
533+
elif panid == 'lower': panid = 1 # for backwards compatibility
534+
if apdict['y_on_right'] is not None:
535+
panels.at[panid,'y_on_right'] = apdict['y_on_right']
529536

530537
aptype = apdict['type']
531538
if aptype == 'ohlc' or aptype == 'candle':
@@ -545,6 +552,8 @@ def plot( data, **kwargs ):
545552
ax = _addplot_columns(panid,panels,ydata,apdict,xdates,config)
546553
_addplot_apply_supplements(ax,apdict)
547554

555+
# fill_between is NOT supported for external_axes_mode
556+
# (caller can easily call ax.fill_between() themselves).
548557
if config['fill_between'] is not None and not external_axes_mode:
549558
fb = config['fill_between']
550559
panid = config['main_panel']
@@ -696,6 +705,7 @@ def _addplot_collections(panid,panels,apdict,xdates,config):
696705

697706
apdata = apdict['data']
698707
aptype = apdict['type']
708+
external_axes_mode = apdict['ax'] is not None
699709

700710
#--------------------------------------------------------------#
701711
# Note: _auto_secondary_y() sets the 'magnitude' column in the
@@ -716,16 +726,21 @@ def _addplot_collections(panid,panels,apdict,xdates,config):
716726
raise TypeError('addplot type "'+aptype+'" MUST be accompanied by addplot data of type `pd.DataFrame`')
717727
d,o,h,l,c,v = _check_and_prepare_data(apdata,config)
718728
collections = _construct_mpf_collections(aptype,d,xdates,o,h,l,c,v,config,config['style'])
719-
lo = math.log(max(math.fabs(np.nanmin(l)),1e-7),10) - 0.5
720-
hi = math.log(max(math.fabs(np.nanmax(h)),1e-7),10) + 0.5
721-
secondary_y = _auto_secondary_y( panels, panid, lo, hi )
722-
if 'auto' != apdict['secondary_y']:
723-
secondary_y = apdict['secondary_y']
724-
if secondary_y:
725-
ax = panels.at[panid,'axes'][1]
726-
panels.at[panid,'used2nd'] = True
727-
else:
728-
ax = panels.at[panid,'axes'][0]
729+
730+
if not external_axes_mode:
731+
lo = math.log(max(math.fabs(np.nanmin(l)),1e-7),10) - 0.5
732+
hi = math.log(max(math.fabs(np.nanmax(h)),1e-7),10) + 0.5
733+
secondary_y = _auto_secondary_y( panels, panid, lo, hi )
734+
if 'auto' != apdict['secondary_y']:
735+
secondary_y = apdict['secondary_y']
736+
if secondary_y:
737+
ax = panels.at[panid,'axes'][1]
738+
panels.at[panid,'used2nd'] = True
739+
else:
740+
ax = panels.at[panid,'axes'][0]
741+
else:
742+
ax = apdict['ax']
743+
729744
for coll in collections:
730745
ax.add_collection(coll)
731746
if apdict['mav'] is not None:
@@ -734,21 +749,25 @@ def _addplot_collections(panid,panels,apdict,xdates,config):
734749
return ax
735750

736751
def _addplot_columns(panid,panels,ydata,apdict,xdates,config):
737-
secondary_y = False
738-
if apdict['secondary_y'] == 'auto':
739-
yd = [y for y in ydata if not math.isnan(y)]
740-
ymhi = math.log(max(math.fabs(np.nanmax(yd)),1e-7),10)
741-
ymlo = math.log(max(math.fabs(np.nanmin(yd)),1e-7),10)
742-
secondary_y = _auto_secondary_y( panels, panid, ymlo, ymhi )
752+
external_axes_mode = apdict['ax'] is not None
753+
if not external_axes_mode:
754+
secondary_y = False
755+
if apdict['secondary_y'] == 'auto':
756+
yd = [y for y in ydata if not math.isnan(y)]
757+
ymhi = math.log(max(math.fabs(np.nanmax(yd)),1e-7),10)
758+
ymlo = math.log(max(math.fabs(np.nanmin(yd)),1e-7),10)
759+
secondary_y = _auto_secondary_y( panels, panid, ymlo, ymhi )
760+
else:
761+
secondary_y = apdict['secondary_y']
762+
#print("apdict['secondary_y'] says secondary_y is",secondary_y)
763+
764+
if secondary_y:
765+
ax = panels.at[panid,'axes'][1]
766+
panels.at[panid,'used2nd'] = True
767+
else:
768+
ax = panels.at[panid,'axes'][0]
743769
else:
744-
secondary_y = apdict['secondary_y']
745-
#print("apdict['secondary_y'] says secondary_y is",secondary_y)
746-
747-
if secondary_y:
748-
ax = panels.at[panid,'axes'][1]
749-
panels.at[panid,'used2nd'] = True
750-
else:
751-
ax = panels.at[panid,'axes'][0]
770+
ax = apdict['ax']
752771

753772
aptype = apdict['type']
754773
if aptype == 'scatter':

0 commit comments

Comments
 (0)