2019-06-29 18:30:31 +00:00
|
|
|
from copy import deepcopy
|
2019-07-21 13:49:52 +00:00
|
|
|
from pathlib import Path
|
2019-06-10 18:17:23 +00:00
|
|
|
from unittest.mock import MagicMock
|
|
|
|
|
2020-03-03 19:18:38 +00:00
|
|
|
import pandas as pd
|
2019-07-22 18:39:38 +00:00
|
|
|
import plotly.graph_objects as go
|
2019-09-03 05:05:48 +00:00
|
|
|
import pytest
|
2019-07-22 18:39:38 +00:00
|
|
|
from plotly.subplots import make_subplots
|
2019-06-10 18:17:23 +00:00
|
|
|
|
2020-03-03 19:18:38 +00:00
|
|
|
from freqtrade.commands import start_plot_dataframe, start_plot_profit
|
2019-08-14 08:07:32 +00:00
|
|
|
from freqtrade.configuration import TimeRange
|
2019-06-10 18:17:23 +00:00
|
|
|
from freqtrade.data import history
|
2019-06-30 08:25:48 +00:00
|
|
|
from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data
|
2019-12-30 14:02:17 +00:00
|
|
|
from freqtrade.exceptions import OperationalException
|
2020-12-19 19:50:15 +00:00
|
|
|
from freqtrade.plot.plotting import (add_areas, add_indicators, add_profit, create_plotconfig,
|
2020-09-28 17:43:15 +00:00
|
|
|
generate_candlestick_graph, generate_plot_filename,
|
|
|
|
generate_profit_graph, init_plotscript, load_and_plot_trades,
|
2020-12-19 19:50:15 +00:00
|
|
|
plot_profit, plot_trades, store_plot_file)
|
2020-02-11 22:39:15 +00:00
|
|
|
from freqtrade.resolvers import StrategyResolver
|
2020-07-22 13:15:50 +00:00
|
|
|
from tests.conftest import get_args, log_has, log_has_re, patch_exchange
|
2019-06-10 18:17:23 +00:00
|
|
|
|
2019-06-16 08:32:12 +00:00
|
|
|
|
2019-06-10 18:17:23 +00:00
|
|
|
def fig_generating_mock(fig, *args, **kwargs):
|
2019-06-30 07:44:50 +00:00
|
|
|
""" Return Fig - used to mock add_indicators and plot_trades"""
|
2019-06-10 18:17:23 +00:00
|
|
|
return fig
|
|
|
|
|
|
|
|
|
2019-06-11 04:45:36 +00:00
|
|
|
def find_trace_in_fig_data(data, search_string: str):
|
2019-07-14 18:14:35 +00:00
|
|
|
matches = (d for d in data if d.name == search_string)
|
2019-06-11 04:45:36 +00:00
|
|
|
return next(matches)
|
|
|
|
|
|
|
|
|
2019-09-05 20:00:16 +00:00
|
|
|
def generate_empty_figure():
|
2019-07-22 18:39:38 +00:00
|
|
|
return make_subplots(
|
2019-06-16 08:32:12 +00:00
|
|
|
rows=3,
|
|
|
|
cols=1,
|
|
|
|
shared_xaxes=True,
|
|
|
|
row_width=[1, 1, 4],
|
|
|
|
vertical_spacing=0.0001,
|
|
|
|
)
|
|
|
|
|
2019-06-16 12:03:55 +00:00
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_init_plotscript(default_conf, mocker, testdatadir):
|
2019-06-30 11:01:12 +00:00
|
|
|
default_conf['timerange'] = "20180110-20180112"
|
|
|
|
default_conf['trade_source'] = "file"
|
2020-06-01 18:47:27 +00:00
|
|
|
default_conf['timeframe'] = "5m"
|
2019-09-07 18:56:03 +00:00
|
|
|
default_conf["datadir"] = testdatadir
|
2022-01-06 18:28:04 +00:00
|
|
|
default_conf['exportfilename'] = testdatadir / "backtest-result_new.json"
|
2021-01-12 00:13:58 +00:00
|
|
|
supported_markets = ["TRX/BTC", "ADA/BTC"]
|
|
|
|
ret = init_plotscript(default_conf, supported_markets)
|
2020-03-08 10:35:31 +00:00
|
|
|
assert "ohlcv" in ret
|
2019-06-30 11:01:12 +00:00
|
|
|
assert "trades" in ret
|
|
|
|
assert "pairs" in ret
|
2020-11-14 08:28:00 +00:00
|
|
|
assert 'timerange' in ret
|
2019-06-30 11:01:12 +00:00
|
|
|
|
2019-10-30 08:20:56 +00:00
|
|
|
default_conf['pairs'] = ["TRX/BTC", "ADA/BTC"]
|
2021-01-12 00:13:58 +00:00
|
|
|
ret = init_plotscript(default_conf, supported_markets, 20)
|
2020-03-08 10:35:31 +00:00
|
|
|
assert "ohlcv" in ret
|
|
|
|
assert "TRX/BTC" in ret["ohlcv"]
|
|
|
|
assert "ADA/BTC" in ret["ohlcv"]
|
2022-04-25 23:46:40 +00:00
|
|
|
assert default_conf['strategy'].bot_started is True
|
2019-06-30 11:01:12 +00:00
|
|
|
|
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_add_indicators(default_conf, testdatadir, caplog):
|
2019-06-16 08:32:12 +00:00
|
|
|
pair = "UNITTEST/BTC"
|
|
|
|
timerange = TimeRange(None, 'line', 0, -1000)
|
|
|
|
|
2019-11-02 19:19:13 +00:00
|
|
|
data = history.load_pair_history(pair=pair, timeframe='1m',
|
2019-09-07 18:56:03 +00:00
|
|
|
datadir=testdatadir, timerange=timerange)
|
2020-01-04 10:18:51 +00:00
|
|
|
indicators1 = {"ema10": {}}
|
|
|
|
indicators2 = {"macd": {"color": "red"}}
|
2019-06-16 08:32:12 +00:00
|
|
|
|
2020-02-11 22:39:15 +00:00
|
|
|
strategy = StrategyResolver.load_strategy(default_conf)
|
|
|
|
|
2019-06-16 08:32:12 +00:00
|
|
|
# Generate buy/sell signals and indicators
|
2020-02-11 22:39:15 +00:00
|
|
|
data = strategy.analyze_ticker(data, {'pair': pair})
|
2019-09-05 20:00:16 +00:00
|
|
|
fig = generate_empty_figure()
|
2019-06-16 08:32:12 +00:00
|
|
|
|
|
|
|
# Row 1
|
2019-06-30 07:44:50 +00:00
|
|
|
fig1 = add_indicators(fig=deepcopy(fig), row=1, indicators=indicators1, data=data)
|
2019-06-16 08:32:12 +00:00
|
|
|
figure = fig1.layout.figure
|
|
|
|
ema10 = find_trace_in_fig_data(figure.data, "ema10")
|
|
|
|
assert isinstance(ema10, go.Scatter)
|
|
|
|
assert ema10.yaxis == "y"
|
|
|
|
|
2019-06-30 07:44:50 +00:00
|
|
|
fig2 = add_indicators(fig=deepcopy(fig), row=3, indicators=indicators2, data=data)
|
2019-06-16 08:32:12 +00:00
|
|
|
figure = fig2.layout.figure
|
|
|
|
macd = find_trace_in_fig_data(figure.data, "macd")
|
|
|
|
assert isinstance(macd, go.Scatter)
|
|
|
|
assert macd.yaxis == "y3"
|
2020-01-04 10:18:51 +00:00
|
|
|
assert macd.line.color == "red"
|
2019-06-16 08:32:12 +00:00
|
|
|
|
|
|
|
# No indicator found
|
2020-01-04 10:18:51 +00:00
|
|
|
fig3 = add_indicators(fig=deepcopy(fig), row=3, indicators={'no_indicator': {}}, data=data)
|
2019-06-16 08:32:12 +00:00
|
|
|
assert fig == fig3
|
2019-08-11 18:17:39 +00:00
|
|
|
assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog)
|
2019-06-10 18:17:23 +00:00
|
|
|
|
|
|
|
|
2020-12-19 19:32:13 +00:00
|
|
|
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"}}
|
|
|
|
|
2020-12-19 20:47:11 +00:00
|
|
|
ind_no_label = {"macd": {"fill_color": "red",
|
|
|
|
"fill_to": "macdhist"}}
|
|
|
|
|
|
|
|
ind_plain = {"macd": {"fill_to": "macdhist"}}
|
2020-12-19 19:32:13 +00:00
|
|
|
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)
|
|
|
|
|
2020-12-19 20:47:11 +00:00
|
|
|
# everythin given in plot config, row 3
|
2020-12-19 19:32:13 +00:00
|
|
|
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"
|
|
|
|
|
2020-12-19 20:47:11 +00:00
|
|
|
# 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"
|
|
|
|
|
2020-12-19 19:32:13 +00:00
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_plot_trades(testdatadir, caplog):
|
2019-09-06 22:59:42 +00:00
|
|
|
fig1 = generate_empty_figure()
|
2019-06-16 08:32:12 +00:00
|
|
|
# nothing happens when no trades are available
|
|
|
|
fig = plot_trades(fig1, None)
|
|
|
|
assert fig == fig1
|
2019-08-11 18:17:39 +00:00
|
|
|
assert log_has("No trades found.", caplog)
|
2019-06-16 17:53:48 +00:00
|
|
|
pair = "ADA/BTC"
|
2022-04-11 18:32:02 +00:00
|
|
|
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
2019-06-16 17:53:48 +00:00
|
|
|
trades = load_backtest_data(filename)
|
|
|
|
trades = trades.loc[trades['pair'] == pair]
|
2019-06-16 08:32:12 +00:00
|
|
|
|
2019-06-16 17:53:48 +00:00
|
|
|
fig = plot_trades(fig, trades)
|
|
|
|
figure = fig1.layout.figure
|
|
|
|
|
|
|
|
# Check buys - color, should be in first graph, ...
|
2020-01-04 19:27:27 +00:00
|
|
|
trade_buy = find_trace_in_fig_data(figure.data, 'Trade buy')
|
2019-06-16 17:53:48 +00:00
|
|
|
assert isinstance(trade_buy, go.Scatter)
|
|
|
|
assert trade_buy.yaxis == 'y'
|
|
|
|
assert len(trades) == len(trade_buy.x)
|
2020-01-04 19:27:27 +00:00
|
|
|
assert trade_buy.marker.color == 'cyan'
|
|
|
|
assert trade_buy.marker.symbol == 'circle-open'
|
2022-01-20 18:37:17 +00:00
|
|
|
assert trade_buy.text[0] == '3.99%, buy_tag, roi, 15 min'
|
2019-06-16 17:53:48 +00:00
|
|
|
|
2020-01-04 19:27:27 +00:00
|
|
|
trade_sell = find_trace_in_fig_data(figure.data, 'Sell - Profit')
|
2019-06-16 17:53:48 +00:00
|
|
|
assert isinstance(trade_sell, go.Scatter)
|
|
|
|
assert trade_sell.yaxis == 'y'
|
2021-01-29 18:06:46 +00:00
|
|
|
assert len(trades.loc[trades['profit_ratio'] > 0]) == len(trade_sell.x)
|
2020-01-04 19:27:27 +00:00
|
|
|
assert trade_sell.marker.color == 'green'
|
|
|
|
assert trade_sell.marker.symbol == 'square-open'
|
2022-01-20 18:37:17 +00:00
|
|
|
assert trade_sell.text[0] == '3.99%, buy_tag, roi, 15 min'
|
2020-01-04 19:27:27 +00:00
|
|
|
|
|
|
|
trade_sell_loss = find_trace_in_fig_data(figure.data, 'Sell - Loss')
|
|
|
|
assert isinstance(trade_sell_loss, go.Scatter)
|
|
|
|
assert trade_sell_loss.yaxis == 'y'
|
2021-01-29 18:06:46 +00:00
|
|
|
assert len(trades.loc[trades['profit_ratio'] <= 0]) == len(trade_sell_loss.x)
|
2020-01-04 19:27:27 +00:00
|
|
|
assert trade_sell_loss.marker.color == 'red'
|
|
|
|
assert trade_sell_loss.marker.symbol == 'square-open'
|
2021-11-11 14:58:30 +00:00
|
|
|
assert trade_sell_loss.text[5] == '-10.45%, stop_loss, 720 min'
|
2019-06-10 18:17:23 +00:00
|
|
|
|
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, testdatadir, caplog):
|
2019-06-30 07:44:50 +00:00
|
|
|
row_mock = mocker.patch('freqtrade.plot.plotting.add_indicators',
|
2019-06-10 18:17:23 +00:00
|
|
|
MagicMock(side_effect=fig_generating_mock))
|
|
|
|
trades_mock = mocker.patch('freqtrade.plot.plotting.plot_trades',
|
|
|
|
MagicMock(side_effect=fig_generating_mock))
|
|
|
|
|
2019-06-11 04:45:36 +00:00
|
|
|
pair = "UNITTEST/BTC"
|
|
|
|
timerange = TimeRange(None, 'line', 0, -1000)
|
2019-11-02 19:19:13 +00:00
|
|
|
data = history.load_pair_history(pair=pair, timeframe='1m',
|
2019-09-07 18:56:03 +00:00
|
|
|
datadir=testdatadir, timerange=timerange)
|
2021-08-24 18:30:42 +00:00
|
|
|
data['enter_long'] = 0
|
|
|
|
data['exit_long'] = 0
|
2022-02-02 22:02:54 +00:00
|
|
|
data['enter_short'] = 0
|
|
|
|
data['exit_short'] = 0
|
2019-06-11 04:45:36 +00:00
|
|
|
|
|
|
|
indicators1 = []
|
|
|
|
indicators2 = []
|
2019-06-29 15:23:03 +00:00
|
|
|
fig = generate_candlestick_graph(pair=pair, data=data, trades=None,
|
|
|
|
indicators1=indicators1, indicators2=indicators2)
|
2019-06-11 04:45:36 +00:00
|
|
|
assert isinstance(fig, go.Figure)
|
|
|
|
assert fig.layout.title.text == pair
|
|
|
|
figure = fig.layout.figure
|
|
|
|
|
|
|
|
assert len(figure.data) == 2
|
|
|
|
# Candlesticks are plotted first
|
|
|
|
candles = find_trace_in_fig_data(figure.data, "Price")
|
|
|
|
assert isinstance(candles, go.Candlestick)
|
|
|
|
|
|
|
|
volume = find_trace_in_fig_data(figure.data, "Volume")
|
|
|
|
assert isinstance(volume, go.Bar)
|
|
|
|
|
|
|
|
assert row_mock.call_count == 2
|
|
|
|
assert trades_mock.call_count == 1
|
|
|
|
|
2022-02-02 22:02:54 +00:00
|
|
|
assert log_has("No enter_long-signals found.", caplog)
|
|
|
|
assert log_has("No exit_long-signals found.", caplog)
|
|
|
|
assert log_has("No enter_short-signals found.", caplog)
|
|
|
|
assert log_has("No exit_short-signals found.", caplog)
|
2019-06-11 04:45:36 +00:00
|
|
|
|
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir):
|
2019-06-30 07:44:50 +00:00
|
|
|
row_mock = mocker.patch('freqtrade.plot.plotting.add_indicators',
|
2019-06-11 04:45:36 +00:00
|
|
|
MagicMock(side_effect=fig_generating_mock))
|
|
|
|
trades_mock = mocker.patch('freqtrade.plot.plotting.plot_trades',
|
|
|
|
MagicMock(side_effect=fig_generating_mock))
|
|
|
|
pair = 'UNITTEST/BTC'
|
|
|
|
timerange = TimeRange(None, 'line', 0, -1000)
|
2019-11-02 19:19:13 +00:00
|
|
|
data = history.load_pair_history(pair=pair, timeframe='1m',
|
2019-09-07 18:56:03 +00:00
|
|
|
datadir=testdatadir, timerange=timerange)
|
2019-06-10 18:17:23 +00:00
|
|
|
|
2020-02-11 22:39:15 +00:00
|
|
|
strategy = StrategyResolver.load_strategy(default_conf)
|
|
|
|
|
2019-06-11 04:45:36 +00:00
|
|
|
# Generate buy/sell signals and indicators
|
2020-02-11 22:39:15 +00:00
|
|
|
data = strategy.analyze_ticker(data, {'pair': pair})
|
2019-06-11 04:45:36 +00:00
|
|
|
|
2019-06-10 18:17:23 +00:00
|
|
|
indicators1 = []
|
|
|
|
indicators2 = []
|
2019-06-29 15:23:03 +00:00
|
|
|
fig = generate_candlestick_graph(pair=pair, data=data, trades=None,
|
|
|
|
indicators1=indicators1, indicators2=indicators2)
|
2019-06-10 18:17:23 +00:00
|
|
|
assert isinstance(fig, go.Figure)
|
2019-06-11 04:45:36 +00:00
|
|
|
assert fig.layout.title.text == pair
|
2019-06-10 18:17:23 +00:00
|
|
|
figure = fig.layout.figure
|
2019-06-11 04:45:36 +00:00
|
|
|
|
2022-02-02 22:02:54 +00:00
|
|
|
assert len(figure.data) == 8
|
2019-06-10 18:17:23 +00:00
|
|
|
# Candlesticks are plotted first
|
2019-06-11 04:45:36 +00:00
|
|
|
candles = find_trace_in_fig_data(figure.data, "Price")
|
|
|
|
assert isinstance(candles, go.Candlestick)
|
|
|
|
|
|
|
|
volume = find_trace_in_fig_data(figure.data, "Volume")
|
|
|
|
assert isinstance(volume, go.Bar)
|
|
|
|
|
2022-02-02 22:02:54 +00:00
|
|
|
enter_long = find_trace_in_fig_data(figure.data, "enter_long")
|
|
|
|
assert isinstance(enter_long, go.Scatter)
|
|
|
|
# All buy-signals should be plotted
|
|
|
|
assert int(data['enter_long'].sum()) == len(enter_long.x)
|
2019-06-11 04:45:36 +00:00
|
|
|
|
2022-02-02 22:02:54 +00:00
|
|
|
exit_long = find_trace_in_fig_data(figure.data, "exit_long")
|
|
|
|
assert isinstance(exit_long, go.Scatter)
|
|
|
|
# All buy-signals should be plotted
|
|
|
|
assert int(data['exit_long'].sum()) == len(exit_long.x)
|
2019-06-10 18:17:23 +00:00
|
|
|
|
2019-10-05 08:32:42 +00:00
|
|
|
assert find_trace_in_fig_data(figure.data, "Bollinger Band")
|
2019-06-10 18:17:23 +00:00
|
|
|
|
|
|
|
assert row_mock.call_count == 2
|
|
|
|
assert trades_mock.call_count == 1
|
2019-06-16 12:03:55 +00:00
|
|
|
|
|
|
|
|
2019-06-29 18:30:31 +00:00
|
|
|
def test_generate_Plot_filename():
|
|
|
|
fn = generate_plot_filename("UNITTEST/BTC", "5m")
|
|
|
|
assert fn == "freqtrade-plot-UNITTEST_BTC-5m.html"
|
|
|
|
|
|
|
|
|
2019-06-16 12:03:55 +00:00
|
|
|
def test_generate_plot_file(mocker, caplog):
|
2019-09-05 20:00:16 +00:00
|
|
|
fig = generate_empty_figure()
|
2019-06-16 12:03:55 +00:00
|
|
|
plot_mock = mocker.patch("freqtrade.plot.plotting.plot", MagicMock())
|
2019-07-21 13:49:52 +00:00
|
|
|
store_plot_file(fig, filename="freqtrade-plot-UNITTEST_BTC-5m.html",
|
2019-11-23 13:52:44 +00:00
|
|
|
directory=Path("user_data/plot"))
|
2019-06-16 12:03:55 +00:00
|
|
|
|
2019-11-23 13:52:44 +00:00
|
|
|
expected_fn = str(Path("user_data/plot/freqtrade-plot-UNITTEST_BTC-5m.html"))
|
2019-06-16 12:03:55 +00:00
|
|
|
assert plot_mock.call_count == 1
|
|
|
|
assert plot_mock.call_args[0][0] == fig
|
|
|
|
assert (plot_mock.call_args_list[0][1]['filename']
|
2019-10-18 09:31:43 +00:00
|
|
|
== expected_fn)
|
|
|
|
assert log_has(f"Stored plot as {expected_fn}",
|
2019-08-11 18:17:39 +00:00
|
|
|
caplog)
|
2019-06-30 08:24:10 +00:00
|
|
|
|
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_add_profit(testdatadir):
|
2022-04-11 18:32:02 +00:00
|
|
|
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
2019-06-30 08:24:10 +00:00
|
|
|
bt_data = load_backtest_data(filename)
|
2019-08-14 08:07:32 +00:00
|
|
|
timerange = TimeRange.parse_timerange("20180110-20180112")
|
2019-06-30 08:24:10 +00:00
|
|
|
|
2019-11-02 19:19:13 +00:00
|
|
|
df = history.load_pair_history(pair="TRX/BTC", timeframe='5m',
|
2019-09-07 18:56:03 +00:00
|
|
|
datadir=testdatadir, timerange=timerange)
|
2019-09-06 22:59:42 +00:00
|
|
|
fig = generate_empty_figure()
|
2019-06-30 08:24:10 +00:00
|
|
|
|
|
|
|
cum_profits = create_cum_profit(df.set_index('date'),
|
2019-10-30 08:20:56 +00:00
|
|
|
bt_data[bt_data["pair"] == 'TRX/BTC'],
|
2019-10-28 13:24:12 +00:00
|
|
|
"cum_profits", timeframe="5m")
|
2019-06-30 08:24:10 +00:00
|
|
|
|
|
|
|
fig1 = add_profit(fig, row=2, data=cum_profits, column='cum_profits', name='Profits')
|
|
|
|
figure = fig1.layout.figure
|
|
|
|
profits = find_trace_in_fig_data(figure.data, "Profits")
|
2019-10-05 08:32:42 +00:00
|
|
|
assert isinstance(profits, go.Scatter)
|
2019-06-30 08:24:10 +00:00
|
|
|
assert profits.yaxis == "y2"
|
2019-06-30 08:47:55 +00:00
|
|
|
|
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_generate_profit_graph(testdatadir):
|
2022-04-11 18:32:02 +00:00
|
|
|
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
2019-06-30 08:47:55 +00:00
|
|
|
trades = load_backtest_data(filename)
|
2019-08-14 08:07:32 +00:00
|
|
|
timerange = TimeRange.parse_timerange("20180110-20180112")
|
2020-04-05 12:43:01 +00:00
|
|
|
pairs = ["TRX/BTC", "XLM/BTC"]
|
2020-06-26 07:21:28 +00:00
|
|
|
trades = trades[trades['close_date'] < pd.Timestamp('2018-01-12', tz='UTC')]
|
2019-06-30 08:47:55 +00:00
|
|
|
|
2020-03-08 10:35:31 +00:00
|
|
|
data = history.load_data(datadir=testdatadir,
|
|
|
|
pairs=pairs,
|
|
|
|
timeframe='5m',
|
|
|
|
timerange=timerange)
|
|
|
|
|
2019-06-30 08:47:55 +00:00
|
|
|
trades = trades[trades['pair'].isin(pairs)]
|
|
|
|
|
2021-04-25 08:10:09 +00:00
|
|
|
fig = generate_profit_graph(pairs, data, trades, timeframe="5m", stake_currency='BTC')
|
2019-06-30 08:47:55 +00:00
|
|
|
assert isinstance(fig, go.Figure)
|
|
|
|
|
2019-08-24 13:21:16 +00:00
|
|
|
assert fig.layout.title.text == "Freqtrade Profit plot"
|
2019-08-24 12:49:35 +00:00
|
|
|
assert fig.layout.yaxis.title.text == "Price"
|
2021-04-25 08:10:09 +00:00
|
|
|
assert fig.layout.yaxis2.title.text == "Profit BTC"
|
|
|
|
assert fig.layout.yaxis3.title.text == "Profit BTC"
|
2019-08-24 12:49:35 +00:00
|
|
|
|
2019-06-30 08:47:55 +00:00
|
|
|
figure = fig.layout.figure
|
2022-01-01 13:40:20 +00:00
|
|
|
assert len(figure.data) == 7
|
2019-06-30 08:47:55 +00:00
|
|
|
|
|
|
|
avgclose = find_trace_in_fig_data(figure.data, "Avg close price")
|
2019-10-05 08:32:42 +00:00
|
|
|
assert isinstance(avgclose, go.Scatter)
|
2019-06-30 08:47:55 +00:00
|
|
|
|
|
|
|
profit = find_trace_in_fig_data(figure.data, "Profit")
|
2019-10-05 08:32:42 +00:00
|
|
|
assert isinstance(profit, go.Scatter)
|
2022-01-04 15:16:08 +00:00
|
|
|
drawdown = find_trace_in_fig_data(figure.data, "Max drawdown 35.69%")
|
2022-01-01 13:06:15 +00:00
|
|
|
assert isinstance(drawdown, go.Scatter)
|
|
|
|
parallel = find_trace_in_fig_data(figure.data, "Parallel trades")
|
2022-01-01 15:40:18 +00:00
|
|
|
assert isinstance(parallel, go.Scatter)
|
2019-06-30 08:47:55 +00:00
|
|
|
|
2022-01-01 13:40:20 +00:00
|
|
|
underwater = find_trace_in_fig_data(figure.data, "Underwater Plot")
|
|
|
|
assert isinstance(underwater, go.Scatter)
|
2019-06-30 08:47:55 +00:00
|
|
|
|
|
|
|
for pair in pairs:
|
|
|
|
profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}")
|
2019-10-05 08:32:42 +00:00
|
|
|
assert isinstance(profit_pair, go.Scatter)
|
2019-08-22 14:21:48 +00:00
|
|
|
|
2020-12-30 07:30:41 +00:00
|
|
|
with pytest.raises(OperationalException, match=r"No trades found.*"):
|
|
|
|
# Pair cannot be empty - so it's an empty dataframe.
|
2021-04-25 08:10:09 +00:00
|
|
|
generate_profit_graph(pairs, data, trades.loc[trades['pair'].isnull()], timeframe="5m",
|
|
|
|
stake_currency='BTC')
|
2020-12-30 07:30:41 +00:00
|
|
|
|
2019-08-22 14:21:48 +00:00
|
|
|
|
|
|
|
def test_start_plot_dataframe(mocker):
|
2019-09-06 22:59:42 +00:00
|
|
|
aup = mocker.patch("freqtrade.plot.plotting.load_and_plot_trades", MagicMock())
|
2019-08-22 14:21:48 +00:00
|
|
|
args = [
|
|
|
|
"plot-dataframe",
|
2021-07-13 05:05:35 +00:00
|
|
|
"--config", "config_examples/config_bittrex.example.json",
|
2019-08-22 14:21:48 +00:00
|
|
|
"--pairs", "ETH/BTC"
|
|
|
|
]
|
|
|
|
start_plot_dataframe(get_args(args))
|
|
|
|
|
|
|
|
assert aup.call_count == 1
|
|
|
|
called_config = aup.call_args_list[0][0][0]
|
|
|
|
assert "pairs" in called_config
|
|
|
|
assert called_config['pairs'] == ["ETH/BTC"]
|
2019-08-22 14:43:28 +00:00
|
|
|
|
|
|
|
|
2019-09-06 22:59:42 +00:00
|
|
|
def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir):
|
2020-07-22 13:15:50 +00:00
|
|
|
patch_exchange(mocker)
|
|
|
|
|
2019-08-22 14:43:28 +00:00
|
|
|
default_conf['trade_source'] = 'file'
|
2019-09-07 18:56:03 +00:00
|
|
|
default_conf["datadir"] = testdatadir
|
2022-01-06 18:28:04 +00:00
|
|
|
default_conf['exportfilename'] = testdatadir / "backtest-result_new.json"
|
2019-08-22 15:09:58 +00:00
|
|
|
default_conf['indicators1'] = ["sma5", "ema10"]
|
|
|
|
default_conf['indicators2'] = ["macd"]
|
2019-08-22 14:43:28 +00:00
|
|
|
default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"]
|
|
|
|
|
|
|
|
candle_mock = MagicMock()
|
|
|
|
store_mock = MagicMock()
|
|
|
|
mocker.patch.multiple(
|
|
|
|
"freqtrade.plot.plotting",
|
|
|
|
generate_candlestick_graph=candle_mock,
|
|
|
|
store_plot_file=store_mock
|
2020-01-04 19:27:27 +00:00
|
|
|
)
|
2019-09-06 22:59:42 +00:00
|
|
|
load_and_plot_trades(default_conf)
|
2019-08-22 14:43:28 +00:00
|
|
|
|
|
|
|
# Both mocks should be called once per pair
|
|
|
|
assert candle_mock.call_count == 2
|
|
|
|
assert store_mock.call_count == 2
|
|
|
|
|
|
|
|
assert candle_mock.call_args_list[0][1]['indicators1'] == ['sma5', 'ema10']
|
|
|
|
assert candle_mock.call_args_list[0][1]['indicators2'] == ['macd']
|
|
|
|
|
|
|
|
assert log_has("End of plotting process. 2 plots generated", caplog)
|
2019-08-22 14:51:09 +00:00
|
|
|
|
|
|
|
|
2019-08-24 13:35:43 +00:00
|
|
|
def test_start_plot_profit(mocker):
|
2019-08-22 14:51:09 +00:00
|
|
|
aup = mocker.patch("freqtrade.plot.plotting.plot_profit", MagicMock())
|
|
|
|
args = [
|
|
|
|
"plot-profit",
|
2021-07-13 05:05:35 +00:00
|
|
|
"--config", "config_examples/config_bittrex.example.json",
|
2019-08-22 14:51:09 +00:00
|
|
|
"--pairs", "ETH/BTC"
|
|
|
|
]
|
|
|
|
start_plot_profit(get_args(args))
|
|
|
|
|
|
|
|
assert aup.call_count == 1
|
|
|
|
called_config = aup.call_args_list[0][0][0]
|
|
|
|
assert "pairs" in called_config
|
|
|
|
assert called_config['pairs'] == ["ETH/BTC"]
|
2019-08-22 15:02:22 +00:00
|
|
|
|
|
|
|
|
2019-09-03 05:05:48 +00:00
|
|
|
def test_start_plot_profit_error(mocker):
|
2019-09-08 17:38:16 +00:00
|
|
|
|
2019-09-03 05:05:48 +00:00
|
|
|
args = [
|
2020-08-26 18:52:09 +00:00
|
|
|
'plot-profit',
|
|
|
|
'--pairs', 'ETH/BTC'
|
2019-09-03 05:05:48 +00:00
|
|
|
]
|
2019-09-08 17:38:16 +00:00
|
|
|
argsp = get_args(args)
|
|
|
|
# Make sure we use no config. Details: #2241
|
|
|
|
# not resetting config causes random failures if config.json exists
|
2020-08-26 18:52:09 +00:00
|
|
|
argsp['config'] = []
|
2019-09-03 05:05:48 +00:00
|
|
|
with pytest.raises(OperationalException):
|
2019-09-08 17:38:16 +00:00
|
|
|
start_plot_profit(argsp)
|
2019-09-03 05:05:48 +00:00
|
|
|
|
|
|
|
|
2021-02-01 11:58:18 +00:00
|
|
|
def test_plot_profit(default_conf, mocker, testdatadir):
|
|
|
|
patch_exchange(mocker)
|
2019-08-22 15:02:22 +00:00
|
|
|
default_conf['trade_source'] = 'file'
|
2020-08-26 18:52:09 +00:00
|
|
|
default_conf['datadir'] = testdatadir
|
|
|
|
default_conf['exportfilename'] = testdatadir / 'backtest-result_test_nofile.json'
|
|
|
|
default_conf['pairs'] = ['ETH/BTC', 'LTC/BTC']
|
2019-08-22 15:02:22 +00:00
|
|
|
|
|
|
|
profit_mock = MagicMock()
|
|
|
|
store_mock = MagicMock()
|
|
|
|
mocker.patch.multiple(
|
|
|
|
"freqtrade.plot.plotting",
|
|
|
|
generate_profit_graph=profit_mock,
|
|
|
|
store_plot_file=store_mock
|
|
|
|
)
|
2020-05-21 05:13:08 +00:00
|
|
|
with pytest.raises(OperationalException,
|
|
|
|
match=r"No trades found, cannot generate Profit-plot.*"):
|
|
|
|
plot_profit(default_conf)
|
|
|
|
|
2022-04-11 18:32:02 +00:00
|
|
|
default_conf['exportfilename'] = testdatadir / "backtest_results/backtest-result_new.json"
|
2020-05-21 05:13:08 +00:00
|
|
|
|
2019-08-22 15:02:22 +00:00
|
|
|
plot_profit(default_conf)
|
|
|
|
|
|
|
|
# Plot-profit generates one combined plot
|
|
|
|
assert profit_mock.call_count == 1
|
|
|
|
assert store_mock.call_count == 1
|
|
|
|
|
|
|
|
assert profit_mock.call_args_list[0][0][0] == default_conf['pairs']
|
2021-05-30 14:11:24 +00:00
|
|
|
assert store_mock.call_args_list[0][1]['auto_open'] is False
|
2020-01-04 10:30:21 +00:00
|
|
|
|
2021-06-12 07:03:55 +00:00
|
|
|
del default_conf['timeframe']
|
|
|
|
with pytest.raises(OperationalException, match=r"Timeframe must be set.*--timeframe.*"):
|
|
|
|
plot_profit(default_conf)
|
|
|
|
|
2020-01-04 10:30:21 +00:00
|
|
|
|
|
|
|
@pytest.mark.parametrize("ind1,ind2,plot_conf,exp", [
|
2020-01-05 18:50:38 +00:00
|
|
|
# No indicators, use plot_conf
|
2020-01-04 10:30:21 +00:00
|
|
|
([], [], {},
|
|
|
|
{'main_plot': {'sma': {}, 'ema3': {}, 'ema5': {}},
|
|
|
|
'subplots': {'Other': {'macd': {}, 'macdsignal': {}}}}),
|
2020-01-05 18:50:38 +00:00
|
|
|
# use indicators
|
2020-01-04 10:30:21 +00:00
|
|
|
(['sma', 'ema3'], ['macd'], {},
|
2020-01-05 18:50:38 +00:00
|
|
|
{'main_plot': {'sma': {}, 'ema3': {}}, 'subplots': {'Other': {'macd': {}}}}),
|
|
|
|
# only main_plot - adds empty subplots
|
2020-01-04 10:30:21 +00:00
|
|
|
([], [], {'main_plot': {'sma': {}}},
|
|
|
|
{'main_plot': {'sma': {}}, 'subplots': {}}),
|
2020-01-05 18:50:38 +00:00
|
|
|
# Main and subplots
|
|
|
|
([], [], {'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}},
|
|
|
|
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}),
|
|
|
|
# no main_plot, adds empty main_plot
|
2020-01-04 10:30:21 +00:00
|
|
|
([], [], {'subplots': {'RSI': {'rsi': {'color': 'red'}}}},
|
|
|
|
{'main_plot': {}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}),
|
2020-01-05 18:50:38 +00:00
|
|
|
# indicator 1 / 2 should have prevelance
|
|
|
|
(['sma', 'ema3'], ['macd'],
|
|
|
|
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}},
|
|
|
|
{'main_plot': {'sma': {}, 'ema3': {}}, 'subplots': {'Other': {'macd': {}}}}
|
|
|
|
),
|
|
|
|
# indicator 1 - overrides plot_config main_plot
|
|
|
|
(['sma', 'ema3'], [],
|
|
|
|
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}},
|
|
|
|
{'main_plot': {'sma': {}, 'ema3': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}
|
|
|
|
),
|
|
|
|
# indicator 2 - overrides plot_config subplots
|
|
|
|
([], ['macd', 'macd_signal'],
|
|
|
|
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}},
|
|
|
|
{'main_plot': {'sma': {}}, 'subplots': {'Other': {'macd': {}, 'macd_signal': {}}}}
|
|
|
|
),
|
2020-01-04 10:30:21 +00:00
|
|
|
])
|
|
|
|
def test_create_plotconfig(ind1, ind2, plot_conf, exp):
|
|
|
|
|
|
|
|
res = create_plotconfig(ind1, ind2, plot_conf)
|
|
|
|
assert 'main_plot' in res
|
|
|
|
assert 'subplots' in res
|
|
|
|
assert isinstance(res['main_plot'], dict)
|
|
|
|
assert isinstance(res['subplots'], dict)
|
|
|
|
|
|
|
|
assert res == exp
|