Skip to content
436 changes: 436 additions & 0 deletions examples/Inbuilt_Indicator.ipynb

Large diffs are not rendered by default.

246 changes: 246 additions & 0 deletions src/mplfinance/_indicators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict
from mplfinance._arg_validators import _mav_validator
import numpy as np
import pandas as pd
import matplotlib.colors as mcolors
from mplfinance._helpers import _list_of_dict

def _valid_indicator_kwargs():
valid_indicators = ('sma','ema','ichimoku','bband')

vikwargs = {
'kind' : { 'Default' : 'sma',
'Description' : 'Name of Studies: "sma","ema","ichimoku","bband"',
'Validator' : lambda value: value in valid_indicators },

'period' : { 'Default' : None,
'Description' : 'Indicator window size(s); (int or tuple of ints)',
'Validator' : _mav_validator },

'color' : { 'Default' : None,
'Description' : 'color (or sequence of colors) of line(s), scatter marker(s), or bar(s).',
'Validator' : lambda value: mcolors.is_color_like(value) or
(isinstance(value,(list,tuple,np.ndarray)) and all([mcolors.is_color_like(v) for v in value])) },

'legend_label' : { 'Default' : None,
'Description' : 'Figure Title (see also `axtitle`)',
'Validator' : lambda value: isinstance(value,(str,list)) },
}

_validate_vkwargs_dict(vikwargs)

return vikwargs


def _validate_indicator_para(inlist):
valid_study = _process_kwargs(inlist, _valid_indicator_kwargs())

return valid_study

def _make_indicator_plot(astudy,colss,axA1,closes,highs,lows,xdates,script_title):
if astudy.get("kind") == "ema":
if astudy.get("period") is not None:
ema_period = astudy['period']
else:
ema_period = 7

if astudy.get("color") is not None:
colss = astudy['color']

else:
colss = colss

yticks = [*axA1.get_yticks(),]
yticklabels = [*axA1.get_yticklabels(),]
colors = {}

if isinstance(ema_period,tuple):
for i_id,ilength in enumerate(ema_period):
iema = pd.Series(closes).ewm(span=ilength,adjust=False).mean()
iema_values = iema.values
yticks.append(iema_values[-1].round(2))
yticklabels.append(float(iema_values[-1].round(2)))
colors.update({str(iema_values[-1].round(2)):colss[i_id]})
if 'legend_label' in astudy:
label = astudy['legend_label']
axA1.plot(xdates,iema_values,color=colss[i_id],label=label[0])
axA1.legend(title=script_title, title_fontsize='large',loc='upper left')
else:
axA1.plot(xdates,iema_values,color=colss[i_id])
else:
iema = pd.Series(closes).ewm(span=ema_period,adjust=False).mean()
iema_values = iema.values
yticks.append(iema_values[-1].round(2))
yticklabels.append(iema_values[-1].round(2))
colors.update({str(iema_values[-1].round(2)):colss[0]})
if 'legend_label' in astudy:
label = astudy['legend_label']
axA1.plot(xdates,iema_values,color=colss[0],label=label[0])
axA1.legend(title=script_title, title_fontsize='large',loc='upper left')
else:
axA1.plot(xdates,iema_values,color=colss[0])
axA1.set_yticks(yticks, labels=yticklabels)
for xtic in axA1.get_yticklabels():
if xtic.get_text() in colors.keys():
xtic.set_color(colors[xtic.get_text()])

if astudy.get("kind") == "sma":
if astudy.get("period") is not None:
sma_period = astudy['period']
else:
sma_period = 7

if astudy.get("color") is not None:
colss = astudy['color']

else:
colss == colss

yticks = [*axA1.get_yticks(),]
yticklabels = [*axA1.get_yticklabels(),]
colors = {}

if isinstance(sma_period,tuple):
for i_id,ilength in enumerate(sma_period):
isma = pd.Series(closes).rolling(ilength).mean()
isma_values = isma.values
yticks.append(isma_values[-1].round(2))
yticklabels.append(float(isma_values[-1].round(2)))
colors.update({str(isma_values[-1].round(2)):colss[i_id]})
if 'legend_label' in astudy:
label = astudy['legend_label']
axA1.plot(xdates,isma_values,color=colss[i_id],label=label[0])
axA1.legend(title=script_title, title_fontsize='large',loc='upper left')
else:
axA1.plot(xdates,isma_values,color=colss[i_id])
else:
isma = pd.Series(closes).rolling(sma_period).mean()
isma_values = isma.values
yticks.append(isma_values[-1].round(2))
yticklabels.append(isma_values[-1].round(2))
colors.update({str(isma_values[-1].round(2)):colss[0]})
if 'legend_label' in astudy:
label = astudy['legend_label']
axA1.plot(xdates,isma_values,color=colss[0],label=label[0])
axA1.legend(title=script_title, title_fontsize='large',loc='upper left')
else:
axA1.plot(xdates,isma_values,color=colss[0])
axA1.set_yticks(yticks, labels=yticklabels)
for xtic in axA1.get_yticklabels():
if xtic.get_text() in colors.keys():
xtic.set_color(colors[xtic.get_text()])

if astudy.get("kind") == "BBand":
if astudy.get("period") is not None:
BBand_period = astudy['period']
else:
BBand_period = 21

if astudy.get("color") is not None:
colss = astudy['color']
else:
colss = colss
rolling_mean = pd.Series(closes).rolling(window=BBand_period).mean()
rolling_std = pd.Series(closes).rolling(window=BBand_period).std()
middle_band = pd.Series(closes).rolling(window=BBand_period).mean()
upper_band = rolling_mean + (rolling_std * 3)
lower_band = rolling_mean - (rolling_std * 3)


if 'legend_label' in astudy:
label = astudy['legend_label']
axA1.plot(xdates,upper_band.values,color=colss[0],label=label[0])
axA1.plot(xdates,middle_band.values,color=colss[1],label=label[1])
axA1.plot(xdates,lower_band.values,color=colss[2],label=label[2])
axA1.legend(title=script_title, title_fontsize='large',loc='upper left')
else:
axA1.plot(xdates,upper_band.values,color=colss[0])
axA1.plot(xdates,middle_band.values,color=colss[1])
axA1.plot(xdates,lower_band.values,color=colss[2])


upper = upper_band.values[-1].round(2)
middle = middle_band.values[-1].round(2)
lower = lower_band.values[-1].round(2)

yticks = [*axA1.get_yticks(), upper,middle,lower]
yticklabels = [*axA1.get_yticklabels(), float(upper),float(middle),float(lower)]
colors = {str(upper):colss[0],str(middle):colss[1],str(lower):colss[2]}

axA1.set_yticks(yticks, labels=yticklabels)
for xtic in axA1.get_yticklabels():
if xtic.get_text() in colors.keys():
xtic.set_color(colors[xtic.get_text()])

if astudy['kind'] == 'ichimoku':
if astudy.get("period") is not None:
short_period = astudy['period'][0]
long_period = astudy['period'][1]
window_period = astudy['period'][2]
else:
short_period = 9
long_period = 26
window_period = 52

if astudy.get("color") is not None:
colss = astudy['color']

else:
colss = colss

yticks = [*axA1.get_yticks(),]
yticklabels = [*axA1.get_yticklabels(),]
colors = {}


Tenkan_sen = (pd.Series(closes).rolling(window=short_period).max() + pd.Series(closes).rolling(window=short_period).min()) / 2
Kijun_sen = (pd.Series(closes).rolling(window=long_period).max() + pd.Series(closes).rolling(window=long_period).min()) / 2
Senkou_Span_A = (Tenkan_sen + Kijun_sen) / 2
Senkou_Span_B = (pd.Series(highs).rolling(window=window_period).max() + pd.Series(lows).rolling(window=window_period).min()) / 2
Chikou_Span = pd.Series(closes).shift(periods=-long_period)
Tenkan_sen_values = Tenkan_sen.values
yticks.append(Tenkan_sen_values[-1].round(2))
yticklabels.append(float(Tenkan_sen_values[-1].round(2)))
colors.update({str(Tenkan_sen_values[-1].round(2)):colss[0]})

Kijun_sen_values = Kijun_sen.values
yticks.append(Kijun_sen_values[-1].round(2))
yticklabels.append(float(Kijun_sen_values[-1].round(2)))
colors.update({str(Kijun_sen_values[-1].round(2)):colss[1]})

Senkou_Span_A_values = Senkou_Span_A.values
yticks.append(Senkou_Span_A_values[-1].round(2))
yticklabels.append(float(Senkou_Span_A_values[-1].round(2)))
colors.update({str(Senkou_Span_A_values[-1].round(2)):colss[2]})

Senkou_Span_B_values = Senkou_Span_B.values
yticks.append(Senkou_Span_B_values[-1].round(2))
yticklabels.append(float(Senkou_Span_B_values[-1].round(2)))
colors.update({str(Senkou_Span_B_values[-1].round(2)):colss[3]})

Chikou_Span_values = Chikou_Span.values
yticks.append(Chikou_Span_values[-(long_period + 1)].round(2))
yticklabels.append(float(Chikou_Span_values[-(long_period + 1)].round(2)))
colors.update({str(Chikou_Span_values[-1].round(2)):colss[4]})

if 'legend_label' in astudy:
label = astudy['legend_label']
axA1.plot(xdates,Tenkan_sen_values,color=colss[0],label=label[0])
axA1.plot(xdates,Kijun_sen_values,color=colss[1],label=label[1])
axA1.plot(xdates,Senkou_Span_A_values,color=colss[2],label=label[2])
axA1.plot(xdates,Senkou_Span_B_values,color=colss[3],label=label[3])
axA1.plot(xdates,Chikou_Span_values,color=colss[4],label=label[4])
axA1.legend(title=script_title, title_fontsize='large',loc='upper left')
else:
axA1.plot(xdates,Tenkan_sen_values,color=colss[0])
axA1.plot(xdates,Kijun_sen_values,color=colss[1])
axA1.plot(xdates,Senkou_Span_A_values,color=colss[2])
axA1.plot(xdates,Senkou_Span_B_values,color=colss[3])
axA1.plot(xdates,Chikou_Span_values,color=colss[4])

axA1.fill_between(xdates,Senkou_Span_A_values,Senkou_Span_B_values,where = Senkou_Span_A_values>= Senkou_Span_B_values, color='green',alpha=0.1)
axA1.fill_between(xdates,Senkou_Span_A_values,Senkou_Span_B_values,where = Senkou_Span_A_values<= Senkou_Span_B_values, color='red',alpha=0.1)
axA1.set_yticks(yticks, labels=yticklabels)
for xtic in axA1.get_yticklabels():
if xtic.get_text() in colors.keys():
xtic.set_color(colors[xtic.get_text()])
1 change: 1 addition & 0 deletions src/mplfinance/_kwarg_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def kwarg_help( func_name=None, kwarg_names=None, sort=False ):
'lines' : mpf._utils._valid_lines_kwargs,
'scale_width_adjustment': mpf._widths._valid_scale_width_kwargs,
'update_width_config': mpf._widths._valid_update_width_kwargs,
'indicators' : mpf._indicators._valid_indicator_kwargs,
}

func_kwarg_aliases = {
Expand Down
2 changes: 1 addition & 1 deletion src/mplfinance/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version_info = (0, 12, 9, 'beta', 8)
version_info = (0, 12, 9, 'beta', 9)

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

Expand Down
37 changes: 35 additions & 2 deletions src/mplfinance/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
from mplfinance._helpers import _list_of_dict
from mplfinance._helpers import _num_or_seq_of_num
from mplfinance._helpers import _adjust_color_brightness
from mplfinance._indicators import _validate_indicator_para
from mplfinance._indicators import _make_indicator_plot

VALID_PMOVE_TYPES = ['renko', 'pnf']

Expand Down Expand Up @@ -121,6 +123,10 @@ def _valid_plot_kwargs():
'Description' : 'Moving Average window size(s); (int or tuple of ints)',
'Validator' : _mav_validator },

'indicators' : { 'Default' : None,
'Description' : 'Moving Average window size(s); (int or tuple of ints)',
'Validator' : lambda value: isinstance(value,dict) or (isinstance(value,list) and all([isinstance(d,dict) for d in value])) },

'ema' : { 'Default' : None,
'Description' : 'Exponential Moving Average window size(s); (int or tuple of ints)',
'Validator' : _mav_validator },
Expand Down Expand Up @@ -840,6 +846,30 @@ def plot( data, **kwargs ):

axA1.set_ylabel(config['ylabel'])

indicator_list = config['indicators']

prop_cycle = plt.rcParams['axes.prop_cycle']
colss = prop_cycle.by_key()['color']

if indicator_list is not None:
if isinstance(indicator_list,dict):
indicator_list = [indicator_list,]

elif not _list_of_dict(indicator_list):
raise TypeError('indicator must be `dict`, or `list of dict`, NOT '+str(type(indicator_list)))
else:
indicator_list = []

for indict in indicator_list:
astudy = _validate_indicator_para(indict)
_make_indicator_plot(astudy,colss,axA1,closes,highs,lows,xdates,script_title)

if config['title']:
script_title = config['title']
else:
script_title = None


if config['volume']:
if external_axes_mode:
volumeAxes.tick_params(axis='x',rotation=xrotation)
Expand Down Expand Up @@ -910,7 +940,10 @@ def plot( data, **kwargs ):
title_kwargs.update(title_dict) # allows override default values set by mplfinance above
else:
title = config['title'] # config['title'] is a string
fig.suptitle(title,**title_kwargs)
if config['indicators']:
pass
else:
fig.suptitle(title,**title_kwargs)


if config['axtitle'] is not None:
Expand Down Expand Up @@ -1395,4 +1428,4 @@ def make_addplot(data, **kwargs):
if config['scatter'] == True and config['type'] == 'line':
config['type'] = 'scatter'

return dict( data=data, **config)
return dict( data=data, **config)