Merge pull request #4094 from MrKrautee/plot_area

Plot area between traces
This commit is contained in:
Matthias 2020-12-21 19:30:07 +01:00 committed by GitHub
commit 9d37ac9955
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 151 additions and 41 deletions

View File

@ -168,6 +168,7 @@ Additional features when using plot_config include:
* Specify colors per indicator
* Specify additional subplots
* Specify indicator pairs to fill area in between
The sample plot configuration below specifies fixed colors for the indicators. Otherwise consecutive plots may produce different colorschemes each time, making comparisons difficult.
It also allows multiple subplots to display both MACD and RSI at the same time.
@ -183,23 +184,33 @@ Sample configuration with inline comments explaining the process:
'ema50': {'color': '#CCCCCC'},
# By omitting color, a random color is selected.
'sar': {},
# fill area between senkou_a and senkou_b
'senkou_a': {
'color': 'green', #optional
'fill_to': 'senkou_b',
'fill_label': 'Ichimoku Cloud' #optional,
'fill_color': 'rgba(255,76,46,0.2)', #optional
},
# plot senkou_b, too. Not only the area to it.
'senkou_b': {}
},
'subplots': {
# Create subplot MACD
"MACD": {
'macd': {'color': 'blue'},
'macdsignal': {'color': 'orange'},
'macd': {'color': 'blue', 'fill_to': 'macdhist'},
'macdsignal': {'color': 'orange'}
},
# Additional subplot RSI
"RSI": {
'rsi': {'color': 'red'},
'rsi': {'color': 'red'}
}
}
}
```
```
!!! Note
The above configuration assumes that `ema10`, `ema50`, `macd`, `macdsignal` and `rsi` are columns in the DataFrame created by the strategy.
The above configuration assumes that `ema10`, `ema50`, `senkou_a`, `senkou_b`,
`macd`, `macdsignal`, `macdhist` and `rsi` are columns in the DataFrame created by the strategy.
## Plot profit

View File

@ -263,6 +263,65 @@ def create_plotconfig(indicators1: List[str], indicators2: List[str],
return plot_config
def plot_area(fig, row: int, data: pd.DataFrame, indicator_a: str,
indicator_b: str, label: str = "",
fill_color: str = "rgba(0,176,246,0.2)") -> make_subplots:
""" Creates a plot for the area between two traces and adds it to fig.
:param fig: Plot figure to append to
:param row: row number for this plot
:param data: candlestick DataFrame
:param indicator_a: indicator name as populated in stragetie
:param indicator_b: indicator name as populated in stragetie
:param label: label for the filled area
:param fill_color: color to be used for the filled area
:return: fig with added filled_traces plot
"""
if indicator_a in data and indicator_b in data:
# make lines invisible to get the area plotted, only.
line = {'color': 'rgba(255,255,255,0)'}
# TODO: Figure out why scattergl causes problems plotly/plotly.js#2284
trace_a = go.Scatter(x=data.date, y=data[indicator_a],
showlegend=False,
line=line)
trace_b = go.Scatter(x=data.date, y=data[indicator_b], name=label,
fill="tonexty", fillcolor=fill_color,
line=line)
fig.add_trace(trace_a, row, 1)
fig.add_trace(trace_b, row, 1)
return fig
def add_areas(fig, row: int, data: pd.DataFrame, indicators) -> make_subplots:
""" Adds all area plots (specified in plot_config) to fig.
:param fig: Plot figure to append to
:param row: row number for this plot
:param data: candlestick DataFrame
:param indicators: dict with indicators. ie.: plot_config['main_plot'] or
plot_config['subplots'][subplot_label]
:return: fig with added filled_traces plot
"""
for indicator, ind_conf in indicators.items():
if 'fill_to' in ind_conf:
indicator_b = ind_conf['fill_to']
if indicator in data and indicator_b in data:
label = ind_conf.get('fill_label',
f'{indicator}<>{indicator_b}')
fill_color = ind_conf.get('fill_color', 'rgba(0,176,246,0.2)')
fig = plot_area(fig, row, data, indicator, indicator_b,
label=label, fill_color=fill_color)
elif indicator not in data:
logger.info(
'Indicator "%s" ignored. Reason: This indicator is not '
'found in your strategy.', indicator
)
elif indicator_b not in data:
logger.info(
'fill_to: "%s" ignored. Reason: This indicator is not '
'in your strategy.', indicator_b
)
return fig
def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFrame = None, *,
indicators1: List[str] = [],
indicators2: List[str] = [],
@ -280,7 +339,6 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
:return: Plotly figure
"""
plot_config = create_plotconfig(indicators1, indicators2, plot_config)
rows = 2 + len(plot_config['subplots'])
row_widths = [1 for _ in plot_config['subplots']]
# Define the graph
@ -346,36 +404,20 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
fig.add_trace(sells, 1, 1)
else:
logger.warning("No sell-signals found.")
# TODO: Figure out why scattergl causes problems plotly/plotly.js#2284
if 'bb_lowerband' in data and 'bb_upperband' in data:
bb_lower = go.Scatter(
x=data.date,
y=data.bb_lowerband,
showlegend=False,
line={'color': 'rgba(255,255,255,0)'},
)
bb_upper = go.Scatter(
x=data.date,
y=data.bb_upperband,
name='Bollinger Band',
fill="tonexty",
fillcolor="rgba(0,176,246,0.2)",
line={'color': 'rgba(255,255,255,0)'},
)
fig.add_trace(bb_lower, 1, 1)
fig.add_trace(bb_upper, 1, 1)
if ('bb_upperband' in plot_config['main_plot']
and 'bb_lowerband' in plot_config['main_plot']):
del plot_config['main_plot']['bb_upperband']
del plot_config['main_plot']['bb_lowerband']
# Add indicators to main plot
# Add Bollinger Bands
fig = plot_area(fig, 1, data, 'bb_lowerband', 'bb_upperband',
label="Bollinger Band")
# prevent bb_lower and bb_upper from plotting
try:
del plot_config['main_plot']['bb_lowerband']
del plot_config['main_plot']['bb_upperband']
except KeyError:
pass
# main plot goes to row 1
fig = add_indicators(fig=fig, row=1, indicators=plot_config['main_plot'], data=data)
fig = add_areas(fig, 1, data, plot_config['main_plot'])
fig = plot_trades(fig, trades)
# Volume goes to row 2
# sub plot: Volume goes to row 2
volume = go.Bar(
x=data['date'],
y=data['volume'],
@ -384,13 +426,14 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
marker_line_color='DarkSlateGrey'
)
fig.add_trace(volume, 2, 1)
# Add indicators to separate row
for i, name in enumerate(plot_config['subplots']):
fig = add_indicators(fig=fig, row=3 + i,
indicators=plot_config['subplots'][name],
# add each sub plot to a separate row
for i, label in enumerate(plot_config['subplots']):
sub_config = plot_config['subplots'][label]
row = 3 + i
fig = add_indicators(fig=fig, row=row, indicators=sub_config,
data=data)
# fill area between indicators ( 'fill_to': 'other_indicator')
fig = add_areas(fig, row, data, sub_config)
return fig

View File

@ -13,7 +13,7 @@ from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data
from freqtrade.exceptions import OperationalException
from freqtrade.plot.plotting import (add_indicators, add_profit, create_plotconfig,
from freqtrade.plot.plotting import (add_areas, add_indicators, add_profit, create_plotconfig,
generate_candlestick_graph, generate_plot_filename,
generate_profit_graph, init_plotscript, load_and_plot_trades,
plot_profit, plot_trades, store_plot_file)
@ -96,6 +96,62 @@ def test_add_indicators(default_conf, testdatadir, caplog):
assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog)
def test_add_areas(default_conf, testdatadir, caplog):
pair = "UNITTEST/BTC"
timerange = TimeRange(None, 'line', 0, -1000)
data = history.load_pair_history(pair=pair, timeframe='1m',
datadir=testdatadir, timerange=timerange)
indicators = {"macd": {"color": "red",
"fill_color": "black",
"fill_to": "macdhist",
"fill_label": "MACD Fill"}}
ind_no_label = {"macd": {"fill_color": "red",
"fill_to": "macdhist"}}
ind_plain = {"macd": {"fill_to": "macdhist"}}
default_conf.update({'strategy': 'DefaultStrategy'})
strategy = StrategyResolver.load_strategy(default_conf)
# Generate buy/sell signals and indicators
data = strategy.analyze_ticker(data, {'pair': pair})
fig = generate_empty_figure()
# indicator mentioned in fill_to does not exist
fig1 = add_areas(fig, 1, data, {'ema10': {'fill_to': 'no_fill_indicator'}})
assert fig == fig1
assert log_has_re(r'fill_to: "no_fill_indicator" ignored\..*', caplog)
# indicator does not exist
fig2 = add_areas(fig, 1, data, {'no_indicator': {'fill_to': 'ema10'}})
assert fig == fig2
assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog)
# everythin given in plot config, row 3
fig3 = add_areas(fig, 3, data, indicators)
figure = fig3.layout.figure
fill_macd = find_trace_in_fig_data(figure.data, "MACD Fill")
assert isinstance(fill_macd, go.Scatter)
assert fill_macd.yaxis == "y3"
assert fill_macd.fillcolor == "black"
# label missing, row 1
fig4 = add_areas(fig, 1, data, ind_no_label)
figure = fig4.layout.figure
fill_macd = find_trace_in_fig_data(figure.data, "macd<>macdhist")
assert isinstance(fill_macd, go.Scatter)
assert fill_macd.yaxis == "y"
assert fill_macd.fillcolor == "red"
# fit_to only
fig5 = add_areas(fig, 1, data, ind_plain)
figure = fig5.layout.figure
fill_macd = find_trace_in_fig_data(figure.data, "macd<>macdhist")
assert isinstance(fill_macd, go.Scatter)
assert fill_macd.yaxis == "y"
def test_plot_trades(testdatadir, caplog):
fig1 = generate_empty_figure()
# nothing happens when no trades are available