Merge pull request #4094 from MrKrautee/plot_area
Plot area between traces
This commit is contained in:
commit
9d37ac9955
@ -168,6 +168,7 @@ Additional features when using plot_config include:
|
|||||||
|
|
||||||
* Specify colors per indicator
|
* Specify colors per indicator
|
||||||
* Specify additional subplots
|
* 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.
|
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.
|
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'},
|
'ema50': {'color': '#CCCCCC'},
|
||||||
# By omitting color, a random color is selected.
|
# By omitting color, a random color is selected.
|
||||||
'sar': {},
|
'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': {
|
'subplots': {
|
||||||
# Create subplot MACD
|
# Create subplot MACD
|
||||||
"MACD": {
|
"MACD": {
|
||||||
'macd': {'color': 'blue'},
|
'macd': {'color': 'blue', 'fill_to': 'macdhist'},
|
||||||
'macdsignal': {'color': 'orange'},
|
'macdsignal': {'color': 'orange'}
|
||||||
},
|
},
|
||||||
# Additional subplot RSI
|
# Additional subplot RSI
|
||||||
"RSI": {
|
"RSI": {
|
||||||
'rsi': {'color': 'red'},
|
'rsi': {'color': 'red'}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
|
```
|
||||||
!!! Note
|
!!! 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
|
## Plot profit
|
||||||
|
|
||||||
|
@ -263,6 +263,65 @@ def create_plotconfig(indicators1: List[str], indicators2: List[str],
|
|||||||
return plot_config
|
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, *,
|
def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFrame = None, *,
|
||||||
indicators1: List[str] = [],
|
indicators1: List[str] = [],
|
||||||
indicators2: List[str] = [],
|
indicators2: List[str] = [],
|
||||||
@ -280,7 +339,6 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
|
|||||||
:return: Plotly figure
|
:return: Plotly figure
|
||||||
"""
|
"""
|
||||||
plot_config = create_plotconfig(indicators1, indicators2, plot_config)
|
plot_config = create_plotconfig(indicators1, indicators2, plot_config)
|
||||||
|
|
||||||
rows = 2 + len(plot_config['subplots'])
|
rows = 2 + len(plot_config['subplots'])
|
||||||
row_widths = [1 for _ in plot_config['subplots']]
|
row_widths = [1 for _ in plot_config['subplots']]
|
||||||
# Define the graph
|
# 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)
|
fig.add_trace(sells, 1, 1)
|
||||||
else:
|
else:
|
||||||
logger.warning("No sell-signals found.")
|
logger.warning("No sell-signals found.")
|
||||||
|
# Add Bollinger Bands
|
||||||
# TODO: Figure out why scattergl causes problems plotly/plotly.js#2284
|
fig = plot_area(fig, 1, data, 'bb_lowerband', 'bb_upperband',
|
||||||
if 'bb_lowerband' in data and 'bb_upperband' in data:
|
label="Bollinger Band")
|
||||||
bb_lower = go.Scatter(
|
# prevent bb_lower and bb_upper from plotting
|
||||||
x=data.date,
|
try:
|
||||||
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']
|
del plot_config['main_plot']['bb_lowerband']
|
||||||
|
del plot_config['main_plot']['bb_upperband']
|
||||||
# Add indicators to main plot
|
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_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)
|
fig = plot_trades(fig, trades)
|
||||||
|
# sub plot: Volume goes to row 2
|
||||||
# Volume goes to row 2
|
|
||||||
volume = go.Bar(
|
volume = go.Bar(
|
||||||
x=data['date'],
|
x=data['date'],
|
||||||
y=data['volume'],
|
y=data['volume'],
|
||||||
@ -384,13 +426,14 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
|
|||||||
marker_line_color='DarkSlateGrey'
|
marker_line_color='DarkSlateGrey'
|
||||||
)
|
)
|
||||||
fig.add_trace(volume, 2, 1)
|
fig.add_trace(volume, 2, 1)
|
||||||
|
# add each sub plot to a separate row
|
||||||
# Add indicators to separate row
|
for i, label in enumerate(plot_config['subplots']):
|
||||||
for i, name in enumerate(plot_config['subplots']):
|
sub_config = plot_config['subplots'][label]
|
||||||
fig = add_indicators(fig=fig, row=3 + i,
|
row = 3 + i
|
||||||
indicators=plot_config['subplots'][name],
|
fig = add_indicators(fig=fig, row=row, indicators=sub_config,
|
||||||
data=data)
|
data=data)
|
||||||
|
# fill area between indicators ( 'fill_to': 'other_indicator')
|
||||||
|
fig = add_areas(fig, row, data, sub_config)
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ from freqtrade.configuration import TimeRange
|
|||||||
from freqtrade.data import history
|
from freqtrade.data import history
|
||||||
from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data
|
from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data
|
||||||
from freqtrade.exceptions import OperationalException
|
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_candlestick_graph, generate_plot_filename,
|
||||||
generate_profit_graph, init_plotscript, load_and_plot_trades,
|
generate_profit_graph, init_plotscript, load_and_plot_trades,
|
||||||
plot_profit, plot_trades, store_plot_file)
|
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)
|
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):
|
def test_plot_trades(testdatadir, caplog):
|
||||||
fig1 = generate_empty_figure()
|
fig1 = generate_empty_figure()
|
||||||
# nothing happens when no trades are available
|
# nothing happens when no trades are available
|
||||||
|
Loading…
Reference in New Issue
Block a user