Merge pull request #1937 from xmatthias/feat/plot_module
move parts of scripts/plot_dataframe.py to main bot code
This commit is contained in:
@@ -151,6 +151,11 @@ def patch_coinmarketcap(mocker) -> None:
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_persistence(default_conf):
|
||||
persistence.init(default_conf['db_url'], default_conf['dry_run'])
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def default_conf():
|
||||
""" Returns validated configuration suitable for most tests """
|
||||
|
@@ -1,8 +1,15 @@
|
||||
import pytest
|
||||
from pandas import DataFrame
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freqtrade.data.btanalysis import BT_DATA_COLUMNS, load_backtest_data
|
||||
from freqtrade.data.history import make_testdata_path
|
||||
from arrow import Arrow
|
||||
import pytest
|
||||
from pandas import DataFrame, to_datetime
|
||||
|
||||
from freqtrade.arguments import TimeRange
|
||||
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS,
|
||||
extract_trades_of_period,
|
||||
load_backtest_data, load_trades)
|
||||
from freqtrade.data.history import load_pair_history, make_testdata_path
|
||||
from freqtrade.tests.test_persistence import create_mock_trades
|
||||
|
||||
|
||||
def test_load_backtest_data():
|
||||
@@ -19,3 +26,59 @@ def test_load_backtest_data():
|
||||
|
||||
with pytest.raises(ValueError, match=r"File .* does not exist\."):
|
||||
load_backtest_data(str("filename") + "nofile")
|
||||
|
||||
|
||||
def test_load_trades_file(default_conf, fee, mocker):
|
||||
# Real testing of load_backtest_data is done in test_load_backtest_data
|
||||
lbt = mocker.patch("freqtrade.data.btanalysis.load_backtest_data", MagicMock())
|
||||
filename = make_testdata_path(None) / "backtest-result_test.json"
|
||||
load_trades(db_url=None, exportfilename=filename)
|
||||
assert lbt.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_load_trades_db(default_conf, fee, mocker):
|
||||
|
||||
create_mock_trades(fee)
|
||||
# remove init so it does not init again
|
||||
init_mock = mocker.patch('freqtrade.persistence.init', MagicMock())
|
||||
|
||||
trades = load_trades(db_url=default_conf['db_url'], exportfilename=None)
|
||||
assert init_mock.call_count == 1
|
||||
assert len(trades) == 3
|
||||
assert isinstance(trades, DataFrame)
|
||||
assert "pair" in trades.columns
|
||||
assert "open_time" in trades.columns
|
||||
|
||||
|
||||
def test_extract_trades_of_period():
|
||||
pair = "UNITTEST/BTC"
|
||||
timerange = TimeRange(None, 'line', 0, -1000)
|
||||
|
||||
data = load_pair_history(pair=pair, ticker_interval='1m',
|
||||
datadir=None, timerange=timerange)
|
||||
|
||||
# timerange = 2017-11-14 06:07 - 2017-11-14 22:58:00
|
||||
trades = DataFrame(
|
||||
{'pair': [pair, pair, pair, pair],
|
||||
'profit_percent': [0.0, 0.1, -0.2, -0.5],
|
||||
'profit_abs': [0.0, 1, -2, -5],
|
||||
'open_time': to_datetime([Arrow(2017, 11, 13, 15, 40, 0).datetime,
|
||||
Arrow(2017, 11, 14, 9, 41, 0).datetime,
|
||||
Arrow(2017, 11, 14, 14, 20, 0).datetime,
|
||||
Arrow(2017, 11, 15, 3, 40, 0).datetime,
|
||||
], utc=True
|
||||
),
|
||||
'close_time': to_datetime([Arrow(2017, 11, 13, 16, 40, 0).datetime,
|
||||
Arrow(2017, 11, 14, 10, 41, 0).datetime,
|
||||
Arrow(2017, 11, 14, 15, 25, 0).datetime,
|
||||
Arrow(2017, 11, 15, 3, 55, 0).datetime,
|
||||
], utc=True)
|
||||
})
|
||||
trades1 = extract_trades_of_period(data, trades)
|
||||
# First and last trade are dropped as they are out of range
|
||||
assert len(trades1) == 2
|
||||
assert trades1.iloc[0].open_time == Arrow(2017, 11, 14, 9, 41, 0).datetime
|
||||
assert trades1.iloc[0].close_time == Arrow(2017, 11, 14, 10, 41, 0).datetime
|
||||
assert trades1.iloc[-1].open_time == Arrow(2017, 11, 14, 14, 20, 0).datetime
|
||||
assert trades1.iloc[-1].close_time == Arrow(2017, 11, 14, 15, 25, 0).datetime
|
||||
|
@@ -187,6 +187,23 @@ def test_download_data_options() -> None:
|
||||
assert args.exchange == 'binance'
|
||||
|
||||
|
||||
def test_plot_dataframe_options() -> None:
|
||||
args = [
|
||||
'--indicators1', 'sma10,sma100',
|
||||
'--indicators2', 'macd,fastd,fastk',
|
||||
'--plot-limit', '30',
|
||||
'-p', 'UNITTEST/BTC',
|
||||
]
|
||||
arguments = Arguments(args, '')
|
||||
arguments.common_scripts_options()
|
||||
arguments.plot_dataframe_options()
|
||||
pargs = arguments.parse_args(True)
|
||||
assert pargs.indicators1 == "sma10,sma100"
|
||||
assert pargs.indicators2 == "macd,fastd,fastk"
|
||||
assert pargs.plot_limit == 30
|
||||
assert pargs.pairs == "UNITTEST/BTC"
|
||||
|
||||
|
||||
def test_check_int_positive() -> None:
|
||||
|
||||
assert Arguments.check_int_positive("3") == 3
|
||||
|
@@ -11,9 +11,48 @@ from freqtrade.persistence import Trade, clean_dry_run_db, init
|
||||
from freqtrade.tests.conftest import log_has
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_persistence(default_conf):
|
||||
init(default_conf['db_url'], default_conf['dry_run'])
|
||||
def create_mock_trades(fee):
|
||||
"""
|
||||
Create some fake trades ...
|
||||
"""
|
||||
# Simulate dry_run entries
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
open_order_id='dry_run_buy_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
|
||||
trade = Trade(
|
||||
pair='ETC/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
is_open=False,
|
||||
open_order_id='dry_run_sell_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
|
||||
# Simulate prod entry
|
||||
trade = Trade(
|
||||
pair='ETC/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
open_order_id='prod_buy_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
|
||||
|
||||
def test_init_create_session(default_conf):
|
||||
@@ -671,45 +710,7 @@ def test_adjust_min_max_rates(fee):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_get_open(default_conf, fee):
|
||||
|
||||
# Simulate dry_run entries
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
open_order_id='dry_run_buy_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
|
||||
trade = Trade(
|
||||
pair='ETC/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
is_open=False,
|
||||
open_order_id='dry_run_sell_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
|
||||
# Simulate prod entry
|
||||
trade = Trade(
|
||||
pair='ETC/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
open_order_id='prod_buy_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
|
||||
create_mock_trades(fee)
|
||||
assert len(Trade.get_open_trades()) == 2
|
||||
|
||||
|
||||
|
188
freqtrade/tests/test_plotting.py
Normal file
188
freqtrade/tests/test_plotting.py
Normal file
@@ -0,0 +1,188 @@
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from plotly import tools
|
||||
import plotly.graph_objs as go
|
||||
from copy import deepcopy
|
||||
|
||||
from freqtrade.arguments import TimeRange
|
||||
from freqtrade.data import history
|
||||
from freqtrade.data.btanalysis import load_backtest_data
|
||||
from freqtrade.plot.plotting import (generate_graph, generate_plot_file,
|
||||
generate_row, plot_trades)
|
||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||
from freqtrade.tests.conftest import log_has, log_has_re
|
||||
|
||||
|
||||
def fig_generating_mock(fig, *args, **kwargs):
|
||||
""" Return Fig - used to mock generate_row and plot_trades"""
|
||||
return fig
|
||||
|
||||
|
||||
def find_trace_in_fig_data(data, search_string: str):
|
||||
matches = filter(lambda x: x.name == search_string, data)
|
||||
return next(matches)
|
||||
|
||||
|
||||
def generage_empty_figure():
|
||||
return tools.make_subplots(
|
||||
rows=3,
|
||||
cols=1,
|
||||
shared_xaxes=True,
|
||||
row_width=[1, 1, 4],
|
||||
vertical_spacing=0.0001,
|
||||
)
|
||||
|
||||
|
||||
def test_generate_row(default_conf, caplog):
|
||||
pair = "UNITTEST/BTC"
|
||||
timerange = TimeRange(None, 'line', 0, -1000)
|
||||
|
||||
data = history.load_pair_history(pair=pair, ticker_interval='1m',
|
||||
datadir=None, timerange=timerange)
|
||||
indicators1 = ["ema10"]
|
||||
indicators2 = ["macd"]
|
||||
|
||||
# Generate buy/sell signals and indicators
|
||||
strat = DefaultStrategy(default_conf)
|
||||
data = strat.analyze_ticker(data, {'pair': pair})
|
||||
fig = generage_empty_figure()
|
||||
|
||||
# Row 1
|
||||
fig1 = generate_row(fig=deepcopy(fig), row=1, indicators=indicators1, data=data)
|
||||
figure = fig1.layout.figure
|
||||
ema10 = find_trace_in_fig_data(figure.data, "ema10")
|
||||
assert isinstance(ema10, go.Scatter)
|
||||
assert ema10.yaxis == "y"
|
||||
|
||||
fig2 = generate_row(fig=deepcopy(fig), row=3, indicators=indicators2, data=data)
|
||||
figure = fig2.layout.figure
|
||||
macd = find_trace_in_fig_data(figure.data, "macd")
|
||||
assert isinstance(macd, go.Scatter)
|
||||
assert macd.yaxis == "y3"
|
||||
|
||||
# No indicator found
|
||||
fig3 = generate_row(fig=deepcopy(fig), row=3, indicators=['no_indicator'], data=data)
|
||||
assert fig == fig3
|
||||
assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_plot_trades():
|
||||
fig1 = generage_empty_figure()
|
||||
# nothing happens when no trades are available
|
||||
fig = plot_trades(fig1, None)
|
||||
assert fig == fig1
|
||||
pair = "ADA/BTC"
|
||||
filename = history.make_testdata_path(None) / "backtest-result_test.json"
|
||||
trades = load_backtest_data(filename)
|
||||
trades = trades.loc[trades['pair'] == pair]
|
||||
|
||||
fig = plot_trades(fig, trades)
|
||||
figure = fig1.layout.figure
|
||||
|
||||
# Check buys - color, should be in first graph, ...
|
||||
trade_buy = find_trace_in_fig_data(figure.data, "trade_buy")
|
||||
assert isinstance(trade_buy, go.Scatter)
|
||||
assert trade_buy.yaxis == 'y'
|
||||
assert len(trades) == len(trade_buy.x)
|
||||
assert trade_buy.marker.color == 'green'
|
||||
|
||||
trade_sell = find_trace_in_fig_data(figure.data, "trade_sell")
|
||||
assert isinstance(trade_sell, go.Scatter)
|
||||
assert trade_sell.yaxis == 'y'
|
||||
assert len(trades) == len(trade_sell.x)
|
||||
assert trade_sell.marker.color == 'red'
|
||||
|
||||
|
||||
def test_generate_graph_no_signals_no_trades(default_conf, mocker, caplog):
|
||||
row_mock = mocker.patch('freqtrade.plot.plotting.generate_row',
|
||||
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)
|
||||
data = history.load_pair_history(pair=pair, ticker_interval='1m',
|
||||
datadir=None, timerange=timerange)
|
||||
data['buy'] = 0
|
||||
data['sell'] = 0
|
||||
|
||||
indicators1 = []
|
||||
indicators2 = []
|
||||
fig = generate_graph(pair=pair, data=data, trades=None,
|
||||
indicators1=indicators1, indicators2=indicators2)
|
||||
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
|
||||
|
||||
assert log_has("No buy-signals found.", caplog.record_tuples)
|
||||
assert log_has("No sell-signals found.", caplog.record_tuples)
|
||||
|
||||
|
||||
def test_generate_graph_no_trades(default_conf, mocker):
|
||||
row_mock = mocker.patch('freqtrade.plot.plotting.generate_row',
|
||||
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)
|
||||
data = history.load_pair_history(pair=pair, ticker_interval='1m',
|
||||
datadir=None, timerange=timerange)
|
||||
|
||||
# Generate buy/sell signals and indicators
|
||||
strat = DefaultStrategy(default_conf)
|
||||
data = strat.analyze_ticker(data, {'pair': pair})
|
||||
|
||||
indicators1 = []
|
||||
indicators2 = []
|
||||
fig = generate_graph(pair=pair, data=data, trades=None,
|
||||
indicators1=indicators1, indicators2=indicators2)
|
||||
assert isinstance(fig, go.Figure)
|
||||
assert fig.layout.title.text == pair
|
||||
figure = fig.layout.figure
|
||||
|
||||
assert len(figure.data) == 6
|
||||
# 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)
|
||||
|
||||
buy = find_trace_in_fig_data(figure.data, "buy")
|
||||
assert isinstance(buy, go.Scatter)
|
||||
# All buy-signals should be plotted
|
||||
assert int(data.buy.sum()) == len(buy.x)
|
||||
|
||||
sell = find_trace_in_fig_data(figure.data, "sell")
|
||||
assert isinstance(sell, go.Scatter)
|
||||
# All buy-signals should be plotted
|
||||
assert int(data.sell.sum()) == len(sell.x)
|
||||
|
||||
assert find_trace_in_fig_data(figure.data, "BB lower")
|
||||
assert find_trace_in_fig_data(figure.data, "BB upper")
|
||||
|
||||
assert row_mock.call_count == 2
|
||||
assert trades_mock.call_count == 1
|
||||
|
||||
|
||||
def test_generate_plot_file(mocker, caplog):
|
||||
fig = generage_empty_figure()
|
||||
plot_mock = mocker.patch("freqtrade.plot.plotting.plot", MagicMock())
|
||||
generate_plot_file(fig, "UNITTEST/BTC", "5m")
|
||||
|
||||
assert plot_mock.call_count == 1
|
||||
assert plot_mock.call_args[0][0] == fig
|
||||
assert (plot_mock.call_args_list[0][1]['filename']
|
||||
== "user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html")
|
Reference in New Issue
Block a user