Skip to content

Commit 5114322

Browse files
Merge pull request #529 from DanielGoldfarb/fill_between
fill_between support for addplot, and for list of fill_between.
2 parents f52adac + 3f063b4 commit 5114322

15 files changed

+1156
-40
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pip install --upgrade mplfinance
2323
- **[Customizing the Appearance of Plots](https://github.com/matplotlib/mplfinance/blob/master/markdown/customization_and_styles.md)**
2424
- **[Adding Your Own Technical Studies to Plots](https://github.com/matplotlib/mplfinance/blob/master/examples/addplot.ipynb)**
2525
- **[Subplots: Multiple Plots on a Single Figure](https://github.com/matplotlib/mplfinance/blob/master/markdown/subplots.md)**
26+
- **[Fill Between: Filling Plots with Color](https://github.com/matplotlib/mplfinance/blob/master/examples/fill_between.ipynb)**
2627
- **[Price-Movement Plots (Renko, P&F, etc)](https://github.com/matplotlib/mplfinance/blob/master/examples/price-movement_plots.ipynb)**
2728
- **[Trends, Support, Resistance, and Trading Lines](https://github.com/matplotlib/mplfinance/blob/master/examples/using_lines.ipynb)**
2829
- **[Coloring Individual Candlesticks](https://github.com/matplotlib/mplfinance/blob/master/examples/marketcolor_overrides.ipynb)** (New: December 2021)

examples/fill_between.ipynb

+807
Large diffs are not rendered by default.

examples/macd.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import pandas as pd
2+
import mplfinance as mpf
3+
4+
import matplotlib.dates as mdates
5+
6+
idf = pd.read_csv('data/SPY_20110701_20120630_Bollinger.csv',index_col=0,parse_dates=True)
7+
df = idf.loc['2011-07-01':'2011-12-30',:]
8+
9+
10+
# =======
11+
# MACD:
12+
13+
# df = df.iloc[0:30]
14+
15+
exp12 = df['Close'].ewm(span=12, adjust=False).mean()
16+
exp26 = df['Close'].ewm(span=26, adjust=False).mean()
17+
macd = exp12 - exp26
18+
signal = macd.ewm(span=9, adjust=False).mean()
19+
histogram = macd - signal
20+
21+
fb_green = dict(y1=macd.values,y2=signal.values,where=signal<macd,color="#93c47d",alpha=0.6,interpolate=True)
22+
fb_red = dict(y1=macd.values,y2=signal.values,where=signal>macd,color="#e06666",alpha=0.6,interpolate=True)
23+
fb_green['panel'] = 1
24+
fb_red['panel'] = 1
25+
fb = [fb_green,fb_red]
26+
27+
apds = [mpf.make_addplot(exp12,color='lime'),
28+
mpf.make_addplot(exp26,color='c'),
29+
mpf.make_addplot(histogram,type='bar',width=0.7,panel=1,
30+
color='dimgray',alpha=1,secondary_y=True),
31+
mpf.make_addplot(macd,panel=1,color='fuchsia',secondary_y=False),
32+
mpf.make_addplot(signal,panel=1,color='b',secondary_y=False)#,fill_between=fb),
33+
]
34+
35+
s = mpf.make_mpf_style(base_mpf_style='classic',rc={'figure.facecolor':'lightgray'})
36+
37+
mpf.plot(df,type='candle',addplot=apds,figscale=1.6,figratio=(6,5),title='\n\nMACD',
38+
style=s,volume=True,volume_panel=2,panel_ratios=(3,4,1),fill_between=fb)#,show_nontrading=True)
39+

examples/plot_customizations.ipynb

+22-22
Large diffs are not rendered by default.

src/mplfinance/_arg_validators.py

+13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import numpy as np
44
import datetime
55
from mplfinance._helpers import _list_of_dict, _mpf_is_color_like
6+
from mplfinance._helpers import _num_or_seq_of_num
67
import matplotlib as mpl
78
import warnings
89

@@ -430,3 +431,15 @@ def _check_for_external_axes(config):
430431

431432
external_axes_mode = True if isinstance(config['ax'],mpl.axes.Axes) else False
432433
return external_axes_mode
434+
435+
def _valid_fb_dict(value):
436+
return (isinstance(value,dict) and
437+
'y1' in value and
438+
_num_or_seq_of_num(value['y1']))
439+
440+
def _fill_between_validator(value):
441+
if _num_or_seq_of_num(value): return True
442+
if _valid_fb_dict(value): return True
443+
if _list_of_dict(value):
444+
return all([_valid_fb_dict(v) for v in value])
445+
return False

src/mplfinance/_helpers.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ def _list_of_dict(x):
7171
return isinstance(x,list) and all([isinstance(item,dict) for item in x])
7272

7373
def _num_or_seq_of_num(value):
74-
return ( isinstance(value,(int,float)) or
74+
return ( isinstance(value,(int,float,np.integer,np.floating)) or
7575
(isinstance(value,(list,tuple,np.ndarray)) and
76-
all([isinstance(v,(int,float)) for v in value]))
76+
all([isinstance(v,(int,float,np.integer,np.floating)) for v in value]))
7777
)
7878

7979
def roundTime(dt=None, roundTo=60):

src/mplfinance/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version_info = (0, 12, 8, 'beta', 11)
1+
version_info = (0, 12, 9, 'beta', 0)
22

33
_specifier_ = {'alpha': 'a','beta': 'b','candidate': 'rc','final': ''}
44

src/mplfinance/plotting.py

+35-15
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from mplfinance import _styles
3333

3434
from mplfinance._arg_validators import _check_and_prepare_data, _mav_validator
35-
from mplfinance._arg_validators import _get_valid_plot_types
35+
from mplfinance._arg_validators import _get_valid_plot_types, _fill_between_validator
3636
from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict
3737
from mplfinance._arg_validators import _kwarg_not_implemented, _bypass_kwarg_validation
3838
from mplfinance._arg_validators import _hlines_validator, _vlines_validator
@@ -304,9 +304,7 @@ def _valid_plot_kwargs():
304304
'Description' : 'fill between specification as y-value, or sequence of'+
305305
' y-values, or dict containing key "y1" plus any additional'+
306306
' kwargs for `fill_between()`',
307-
'Validator' : lambda value: _num_or_seq_of_num(value) or
308-
(isinstance(value,dict) and 'y1' in value and
309-
_num_or_seq_of_num(value['y1'])) },
307+
'Validator' : _fill_between_validator },
310308

311309
'tight_layout' : { 'Default' : False,
312310
'Description' : 'True|False implement tight layout (minimal padding around Figure)'+
@@ -687,7 +685,7 @@ def plot( data, **kwargs ):
687685
aptype = apdict['type']
688686
if aptype == 'ohlc' or aptype == 'candle':
689687
ax = _addplot_collections(panid,panels,apdict,xdates,config)
690-
_addplot_apply_supplements(ax,apdict)
688+
_addplot_apply_supplements(ax,apdict,xdates)
691689
else:
692690
apdata = apdict['data']
693691
if isinstance(apdata,list) and not isinstance(apdata[0],(float,int)):
@@ -700,24 +698,28 @@ def plot( data, **kwargs ):
700698
for column in apdata:
701699
ydata = apdata.loc[:,column] if havedf else column
702700
ax = _addplot_columns(panid,panels,ydata,apdict,xdates,config)
703-
_addplot_apply_supplements(ax,apdict)
701+
_addplot_apply_supplements(ax,apdict,xdates)
704702

705703
# fill_between is NOT supported for external_axes_mode
706704
# (caller can easily call ax.fill_between() themselves).
707705
if config['fill_between'] is not None and not external_axes_mode:
708-
fb = config['fill_between']
709-
panid = config['main_panel']
710-
if isinstance(fb,dict):
706+
fblist = copy.deepcopy(config['fill_between'])
707+
if _num_or_seq_of_num(fblist):
708+
fblist = [dict(y1=fblist),]
709+
elif isinstance(fblist,dict):
710+
fblist = [fblist,]
711+
if not _list_of_dict(fblist):
712+
raise TypeError('Bad type for `fill_between` specifier.')
713+
for fb in fblist:
711714
if 'x' in fb:
712715
raise ValueError('fill_between dict may not contain `x`')
716+
panid = config['main_panel']
713717
if 'panel' in fb:
714718
panid = fb['panel']
715719
del fb['panel']
716-
else:
717-
fb = dict(y1=fb)
718-
fb['x'] = xdates
719-
ax = panels.at[panid,'axes'][0]
720-
ax.fill_between(**fb)
720+
fb['x'] = xdates # add 'x' to caller's fb dict
721+
ax = panels.at[panid,'axes'][0]
722+
ax.fill_between(**fb)
721723

722724
# put the primary axis on one side,
723725
# and the twinx() on the "other" side:
@@ -1045,7 +1047,7 @@ def _addplot_columns(panid,panels,ydata,apdict,xdates,config):
10451047

10461048
return ax
10471049

1048-
def _addplot_apply_supplements(ax,apdict):
1050+
def _addplot_apply_supplements(ax,apdict,xdates):
10491051
if (apdict['ylabel'] is not None):
10501052
ax.set_ylabel(apdict['ylabel'])
10511053
if apdict['ylim'] is not None:
@@ -1059,6 +1061,20 @@ def _addplot_apply_supplements(ax,apdict):
10591061
ax.set_yscale(yscale,**ysd)
10601062
elif isinstance(ysd,str):
10611063
ax.set_yscale(ysd)
1064+
# added by Wen
1065+
if "fill_between" in apdict and apdict['fill_between'] is not None:
1066+
# deep copy because mplfinance code sometimes modifies the fill_between dict
1067+
fblist = copy.deepcopy(apdict['fill_between'])
1068+
if isinstance(fblist,dict):
1069+
fblist = [fblist,]
1070+
if _list_of_dict(fblist):
1071+
for fb in fblist:
1072+
if 'x' in fb:
1073+
raise ValueError('fill_between dict may not contain `x`')
1074+
fb['x'] = xdates # add 'x' to caller's fb dict
1075+
ax.fill_between(**fb)
1076+
else:
1077+
raise ValueError('Invalid addplot fill_between: must be `dict` or `list of dict`')
10621078

10631079
def _set_ylabels_side(ax_pri,ax_sec,primary_on_right):
10641080
# put the primary axis on one side,
@@ -1234,6 +1250,10 @@ def _valid_addplot_kwargs():
12341250
" style\'s marketcolors). For addplot `type='ohlc'`"+
12351251
" and type='candle'",
12361252
'Validator' : lambda value: _is_marketcolor_object(value) },
1253+
'fill_between': { 'Default' : None, # added by Wen
1254+
'Description' : " fill region",
1255+
'Validator' : _fill_between_validator },
1256+
12371257
}
12381258

12391259
_validate_vkwargs_dict(vkwargs)
22.9 KB
Loading
23.2 KB
Loading
26.1 KB
Loading
28.3 KB
Loading
30.1 KB
Loading
70.9 KB
Loading

0 commit comments

Comments
 (0)