Skip to content

Commit

Permalink
addplot external axes
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielGoldfarb committed Aug 2, 2020
1 parent 57a3d89 commit fd56a6a
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 260 deletions.
271 changes: 68 additions & 203 deletions examples/external_axes.ipynb

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions examples/mpf_animation_demo1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import pandas as pd
import mplfinance as mpf
import matplotlib.animation as animation

idf = pd.read_csv('data/SPY_20110701_20120630_Bollinger.csv',index_col=0,parse_dates=True)
idf.shape
idf.head(3)
idf.tail(3)
df = idf.loc['2011-07-01':'2011-12-30',:]

fig = mpf.figure(style='charles',figsize=(7,8))
ax1 = fig.add_subplot(2,1,1)
ax2 = fig.add_subplot(3,1,3)

def animate(ival):
if (20+ival) > len(df):
print('no more data to plot')
ani.event_source.interval *= 3
if ani.event_source.interval > 12000:
exit()
return
data = df.iloc[0:(20+ival)]
ax1.clear()
ax2.clear()
mpf.plot(data,ax=ax1,volume=ax2,type='candle')

ani = animation.FuncAnimation(fig, animate, interval=250)

mpf.show()
18 changes: 3 additions & 15 deletions examples/mpf_animation.py → examples/mpf_animation_demo2.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
import pandas as pd
import mplfinance as mpf
import matplotlib.pyplot as plt
import matplotlib.animation as animation

idf = pd.read_csv('data/SPY_20110701_20120630_Bollinger.csv',index_col=0,parse_dates=True)
idf.shape
idf.head(3)
idf.tail(3)
df = idf.loc['2011-07-01':'2011-12-30',:]

print('len(df)=',len(df))

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

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

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

ani = animation.FuncAnimation(fig, animate, interval=250)
ani = animation.FuncAnimation(fig, animate, interval=200)

plt.show()
mpf.show()
74 changes: 74 additions & 0 deletions examples/mpf_animation_macd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import pandas as pd
import mplfinance as mpf
import matplotlib.animation as animation

mpf.__version__

idf = pd.read_csv('data/SPY_20110701_20120630_Bollinger.csv',index_col=0,parse_dates=True)
idf.shape
idf.head(3)
idf.tail(3)
df = idf.loc['2011-07-01':'2011-12-30',:]


# =======
# MACD:

df = df.iloc[0:30]

exp12 = df['Close'].ewm(span=12, adjust=False).mean()
exp26 = df['Close'].ewm(span=26, adjust=False).mean()
macd = exp12 - exp26
signal = macd.ewm(span=9, adjust=False).mean()
histogram = macd - signal

apds = [mpf.make_addplot(exp12,color='lime'),
mpf.make_addplot(exp26,color='c'),
mpf.make_addplot(histogram,type='bar',width=0.7,panel=1,
color='dimgray',alpha=1,secondary_y=False),
mpf.make_addplot(macd,panel=1,color='fuchsia',secondary_y=True),
mpf.make_addplot(signal,panel=1,color='b',secondary_y=True),
]

s = mpf.make_mpf_style(base_mpf_style='classic',rc={'figure.facecolor':'lightgray'})

fig, axes = mpf.plot(df,type='candle',addplot=apds,figscale=1.5,figratio=(7,5),title='\n\nMACD',
style=s,volume=True,volume_panel=2,panel_ratios=(6,3,2),returnfig=True)

ax_main = axes[0]
ax_emav = ax_main
ax_hisg = axes[2]
ax_macd = axes[3]
ax_sign = ax_macd
ax_volu = axes[4]

df = idf.loc['2011-07-01':'2011-12-30',:]

def animate(ival):
if (20+ival) > len(df):
print('no more data to plot')
ani.event_source.interval *= 3
if ani.event_source.interval > 12000:
exit()
return
data = df.iloc[0:(30+ival)]
exp12 = data['Close'].ewm(span=12, adjust=False).mean()
exp26 = data['Close'].ewm(span=26, adjust=False).mean()
macd = exp12 - exp26
signal = macd.ewm(span=9, adjust=False).mean()
histogram = macd - signal
apds = [mpf.make_addplot(exp12,color='lime',ax=ax_emav),
mpf.make_addplot(exp26,color='c',ax=ax_emav),
mpf.make_addplot(histogram,type='bar',width=0.7,
color='dimgray',alpha=1,ax=ax_hisg),
mpf.make_addplot(macd,color='fuchsia',ax=ax_macd),
mpf.make_addplot(signal,color='b',ax=ax_sign),
]

for ax in axes:
ax.clear()
mpf.plot(data,type='candle',addplot=apds,ax=ax_main,volume=ax_volu)

ani = animation.FuncAnimation(fig,animate,interval=100)

mpf.show()
2 changes: 1 addition & 1 deletion src/mplfinance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from mplfinance.plotting import plot, make_addplot
from mplfinance._styles import make_mpf_style, make_marketcolors, available_styles
from mplfinance._version import __version__
from mplfinance._mplwraps import figure
from mplfinance._mplwraps import figure, show
4 changes: 4 additions & 0 deletions src/mplfinance/_mplwraps.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@
- Figure.add_axes()
- Figure.subplot() (this is analogous to pyplot.subplot() which calls Figure.add_subplot())
- Figure.subplots()
(3) A wrapper to matplot.pyplot.show(), because it happens often enough, when using mplfinance,
that sometimes one has to import matplotlib.pyplot *ONLY* for the purpose of calling .show()
"""

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

def _check_for_and_apply_style(kwargs):

Expand Down
101 changes: 60 additions & 41 deletions src/mplfinance/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,21 +497,27 @@ def plot( data, **kwargs ):

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

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

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

if config['volume']:
lo = math.log(max(math.fabs(np.nanmin(volumes)),1e-7),10) - 0.5
hi = math.log(max(math.fabs(np.nanmax(volumes)),1e-7),10) + 0.5
panels.at[config['volume_panel'],'mag'] = {'lo':lo,'hi':hi}
panels['mag'] = [None]*len(panels) # create 'mag'nitude column

panels.at[config['main_panel'],'mag'] = {'lo':lo,'hi':hi} # update main panel magnitude range

if config['volume']:
lo = math.log(max(math.fabs(np.nanmin(volumes)),1e-7),10) - 0.5
hi = math.log(max(math.fabs(np.nanmax(volumes)),1e-7),10) + 0.5
panels.at[config['volume_panel'],'mag'] = {'lo':lo,'hi':hi}

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

for apdict in addplot:

panid = apdict['panel']
if panid == 'main' : panid = 0 # for backwards compatibility
elif panid == 'lower': panid = 1 # for backwards compatibility
if apdict['y_on_right'] is not None:
panels.at[panid,'y_on_right'] = apdict['y_on_right']
panid = apdict['panel']
if not external_axes_mode:
if panid == 'main' : panid = 0 # for backwards compatibility
elif panid == 'lower': panid = 1 # for backwards compatibility
if apdict['y_on_right'] is not None:
panels.at[panid,'y_on_right'] = apdict['y_on_right']

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

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

apdata = apdict['data']
aptype = apdict['type']
external_axes_mode = apdict['ax'] is not None

#--------------------------------------------------------------#
# Note: _auto_secondary_y() sets the 'magnitude' column in the
Expand All @@ -716,16 +726,21 @@ def _addplot_collections(panid,panels,apdict,xdates,config):
raise TypeError('addplot type "'+aptype+'" MUST be accompanied by addplot data of type `pd.DataFrame`')
d,o,h,l,c,v = _check_and_prepare_data(apdata,config)
collections = _construct_mpf_collections(aptype,d,xdates,o,h,l,c,v,config,config['style'])
lo = math.log(max(math.fabs(np.nanmin(l)),1e-7),10) - 0.5
hi = math.log(max(math.fabs(np.nanmax(h)),1e-7),10) + 0.5
secondary_y = _auto_secondary_y( panels, panid, lo, hi )
if 'auto' != apdict['secondary_y']:
secondary_y = apdict['secondary_y']
if secondary_y:
ax = panels.at[panid,'axes'][1]
panels.at[panid,'used2nd'] = True
else:
ax = panels.at[panid,'axes'][0]

if not external_axes_mode:
lo = math.log(max(math.fabs(np.nanmin(l)),1e-7),10) - 0.5
hi = math.log(max(math.fabs(np.nanmax(h)),1e-7),10) + 0.5
secondary_y = _auto_secondary_y( panels, panid, lo, hi )
if 'auto' != apdict['secondary_y']:
secondary_y = apdict['secondary_y']
if secondary_y:
ax = panels.at[panid,'axes'][1]
panels.at[panid,'used2nd'] = True
else:
ax = panels.at[panid,'axes'][0]
else:
ax = apdict['ax']

for coll in collections:
ax.add_collection(coll)
if apdict['mav'] is not None:
Expand All @@ -734,21 +749,25 @@ def _addplot_collections(panid,panels,apdict,xdates,config):
return ax

def _addplot_columns(panid,panels,ydata,apdict,xdates,config):
secondary_y = False
if apdict['secondary_y'] == 'auto':
yd = [y for y in ydata if not math.isnan(y)]
ymhi = math.log(max(math.fabs(np.nanmax(yd)),1e-7),10)
ymlo = math.log(max(math.fabs(np.nanmin(yd)),1e-7),10)
secondary_y = _auto_secondary_y( panels, panid, ymlo, ymhi )
external_axes_mode = apdict['ax'] is not None
if not external_axes_mode:
secondary_y = False
if apdict['secondary_y'] == 'auto':
yd = [y for y in ydata if not math.isnan(y)]
ymhi = math.log(max(math.fabs(np.nanmax(yd)),1e-7),10)
ymlo = math.log(max(math.fabs(np.nanmin(yd)),1e-7),10)
secondary_y = _auto_secondary_y( panels, panid, ymlo, ymhi )
else:
secondary_y = apdict['secondary_y']
#print("apdict['secondary_y'] says secondary_y is",secondary_y)

if secondary_y:
ax = panels.at[panid,'axes'][1]
panels.at[panid,'used2nd'] = True
else:
ax = panels.at[panid,'axes'][0]
else:
secondary_y = apdict['secondary_y']
#print("apdict['secondary_y'] says secondary_y is",secondary_y)

if secondary_y:
ax = panels.at[panid,'axes'][1]
panels.at[panid,'used2nd'] = True
else:
ax = panels.at[panid,'axes'][0]
ax = apdict['ax']

aptype = apdict['type']
if aptype == 'scatter':
Expand Down

0 comments on commit fd56a6a

Please sign in to comment.