Backtesting: Offline runs with test data, use argument '--backtesting'
This commit is contained in:
206
freqtrade/tests/test_backtesting_internal.py
Normal file
206
freqtrade/tests/test_backtesting_internal.py
Normal file
@@ -0,0 +1,206 @@
|
||||
# pragma pylint: disable=missing-docstring,protected-access
|
||||
import os
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
from jsonschema import validate
|
||||
|
||||
from freqtrade.exchange import backtesting
|
||||
from freqtrade.exchange.backtesting import Backtesting
|
||||
from freqtrade.main import create_trade, init
|
||||
from freqtrade.misc import CONF_SCHEMA
|
||||
from freqtrade.persistence import Trade
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def conf():
|
||||
configuration = {
|
||||
'max_open_trades': 3,
|
||||
'stake_currency': 'BTC',
|
||||
'stake_amount': 0.05,
|
||||
'dry_run': True,
|
||||
'minimal_roi': {
|
||||
'60': 0.0,
|
||||
'40': 0.01,
|
||||
'20': 0.02,
|
||||
'0': 0.03
|
||||
},
|
||||
'stoploss': -0.40,
|
||||
'bid_strategy': {
|
||||
'ask_last_balance': 0.0
|
||||
},
|
||||
'exchange': {
|
||||
'name': 'bittrex',
|
||||
'key': 'key',
|
||||
'secret': 'secret',
|
||||
'pair_whitelist': [
|
||||
'BTC_RLC'
|
||||
]
|
||||
},
|
||||
'telegram': {
|
||||
'enabled': False,
|
||||
'token': 'token',
|
||||
'chat_id': 'chat_id'
|
||||
}
|
||||
}
|
||||
validate(configuration, CONF_SCHEMA)
|
||||
return configuration
|
||||
|
||||
|
||||
FILES = [
|
||||
os.path.join('freqtrade', 'tests', 'testdata', 'btc-edg.json'),
|
||||
os.path.join('freqtrade', 'tests', 'testdata', 'btc-etc.json')
|
||||
]
|
||||
PAIRS = ['BTC_EDG', 'BTC_ETC']
|
||||
TESTDATA = {
|
||||
PAIRS[0]: {
|
||||
'success': True,
|
||||
'message': '',
|
||||
'result': [
|
||||
{'O': 0.00014469, 'H': 0.00014469, 'L': 0.00014469, 'C': 0.00014469, 'V': 10.66173857, 'T': '2017-09-05T18:55:00', 'BV': 0.00154264},
|
||||
{'O': 0.00014469, 'H': 0.00014477, 'L': 0.00014469, 'C': 0.00014477, 'V': 410.54795113, 'T': '2017-09-05T19:00:00', 'BV': 0.05942728},
|
||||
{'O': 0.00014477, 'H': 0.00014477, 'L': 0.00014477, 'C': 0.00014477, 'V': 69.10850034, 'T': '2017-09-05T19:05:00', 'BV': 0.01000482},
|
||||
{'O': 0.00014470, 'H': 0.00014474, 'L': 0.00014400, 'C': 0.00014473, 'V': 7612.36224582, 'T': '2017-09-05T19:10:00', 'BV': 1.09730748},
|
||||
]
|
||||
},
|
||||
PAIRS[1]: {
|
||||
'success': True,
|
||||
'message': '',
|
||||
'result': [
|
||||
{'O': 0.00391500, 'H': 0.00392700, 'L': 0.00391500, 'C': 0.00392000, 'V': 29.90264260, 'T': '2017-09-05T18:55:00', 'BV': 0.11712504},
|
||||
{'O': 0.00392680, 'H': 0.00392749, 'L': 0.00391500, 'C': 0.00391500, 'V': 329.35043009, 'T': '2017-09-05T19:00:00', 'BV': 1.29065913},
|
||||
{'O': 0.00391500, 'H': 0.00392733, 'L': 0.00391500, 'C': 0.00392300, 'V': 186.96019741, 'T': '2017-09-05T19:05:00', 'BV': 0.73332203},
|
||||
{'O': 0.00391500, 'H': 0.00391500, 'L': 0.00390007, 'C': 0.00390007, 'V': 298.06457786, 'T': '2017-09-05T19:10:00', 'BV': 1.16560055},
|
||||
{'O': 0.00391490, 'H': 0.00391490, 'L': 0.00389126, 'C': 0.00389126, 'V': 1007.91208513, 'T': '2017-09-05T19:15:00', 'BV': 3.92491826}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def len_rows():
|
||||
ticker_history = TESTDATA[PAIRS[0]]
|
||||
return len(ticker_history['result'])
|
||||
|
||||
|
||||
def _json_load(file):
|
||||
pair = os.path.splitext(os.path.basename(file.name))[0].replace('-', '_').upper()
|
||||
return TESTDATA[pair]
|
||||
|
||||
|
||||
def test_init(conf, mocker):
|
||||
mocker.patch('json.load', side_effect=_json_load)
|
||||
mocker.patch('glob.glob', return_value=FILES)
|
||||
mocker.patch.dict('freqtrade.main._CONF', conf)
|
||||
mocker.patch('freqtrade.main.get_args', backtesting=True)
|
||||
mocker.patch('freqtrade.exchange', init=MagicMock())
|
||||
mocker.patch('freqtrade.main.backtesting.TICKER_HISTORY_INTERVAL_H', 1/6)
|
||||
init(conf)
|
||||
assert backtesting._TESTDATA == TESTDATA
|
||||
assert backtesting._LEN_ROWS == 4
|
||||
# 10 minutes (1/6 hours) should correspond to 2 rows
|
||||
assert backtesting._ROW_INDEX == 2
|
||||
assert backtesting._ROW_INTERVAL == 2
|
||||
|
||||
# Available test data should not cover the interval
|
||||
mocker.patch('freqtrade.main.backtesting.TICKER_HISTORY_INTERVAL_H', 24)
|
||||
with pytest.raises(RuntimeError):
|
||||
assert init(conf)
|
||||
|
||||
|
||||
def test_time_step(len_rows, mocker):
|
||||
mocker.patch('freqtrade.exchange.backtesting._LEN_ROWS', len_rows)
|
||||
for index in range(4):
|
||||
assert backtesting._ROW_INDEX == index
|
||||
assert backtesting.time_step() is True
|
||||
assert backtesting.time_step() is False
|
||||
|
||||
|
||||
def test_returns_minimum_date(mocker):
|
||||
mocker.patch('freqtrade.exchange.backtesting._TESTDATA', TESTDATA)
|
||||
mocker.patch('freqtrade.exchange.backtesting._ROW_INDEX', 3)
|
||||
mocker.patch('freqtrade.exchange.backtesting._ROW_INTERVAL', 2)
|
||||
assert backtesting.get_minimum_date(PAIRS[0]) == '2017-09-05T19:00:00'
|
||||
|
||||
|
||||
def test_returns_ticker(conf, mocker):
|
||||
mocker.patch('freqtrade.main.get_args', backtesting=True)
|
||||
mocker.patch('freqtrade.exchange.backtesting._get_testdata', return_value=(TESTDATA, 4))
|
||||
mocker.patch('freqtrade.exchange.backtesting.TICKER_HISTORY_INTERVAL_H', 1/6)
|
||||
first_pair_close = 0.00014477
|
||||
backtesting = Backtesting(conf)
|
||||
mocker.patch('freqtrade.exchange.backtesting._ROW_INDEX', 2)
|
||||
assert backtesting.get_ticker(PAIRS[0]) == \
|
||||
{'bid': first_pair_close, 'ask': first_pair_close, 'last': first_pair_close}
|
||||
|
||||
|
||||
def test_returns_ticker_history(conf, mocker):
|
||||
mocker.patch('freqtrade.exchange.backtesting._get_testdata', return_value=(TESTDATA, 4))
|
||||
mocker.patch('freqtrade.main.backtesting.TICKER_HISTORY_INTERVAL_H', 1/6)
|
||||
backtesting = Backtesting(conf)
|
||||
mocker.patch('freqtrade.exchange.backtesting._ROW_INDEX', 3)
|
||||
mocker.patch('freqtrade.exchange.backtesting._ROW_INTERVAL', 2)
|
||||
assert backtesting.get_ticker_history(PAIRS[0]) == \
|
||||
{'success': True,
|
||||
'message': '',
|
||||
'result': [
|
||||
{'O': 0.00014469, 'H': 0.00014477, 'L': 0.00014469, 'C': 0.00014477, 'V': 410.54795113, 'T': '2017-09-05T19:00:00', 'BV': 0.05942728},
|
||||
{'O': 0.00014477, 'H': 0.00014477, 'L': 0.00014477, 'C': 0.00014477, 'V': 69.10850034, 'T': '2017-09-05T19:05:00', 'BV': 0.01000482}
|
||||
]}
|
||||
|
||||
|
||||
def test_returns_current_time(mocker):
|
||||
mocker.patch('freqtrade.exchange.backtesting._TESTDATA', TESTDATA)
|
||||
mocker.patch('freqtrade.exchange.backtesting._ROW_INDEX', 1)
|
||||
assert backtesting.current_time(PAIRS[0]) == \
|
||||
arrow.get('2017-09-05T19:00:00').datetime.replace(tzinfo=None)
|
||||
|
||||
|
||||
def test_results(conf, mocker):
|
||||
current_time = datetime.utcnow()
|
||||
logger_mock = MagicMock()
|
||||
mocker.patch.dict('freqtrade.main._CONF', conf)
|
||||
mocker.patch('freqtrade.main.get_args', backtesting=True)
|
||||
mocker.patch('freqtrade.main.backtesting', init=MagicMock())
|
||||
mocker.patch('freqtrade.main.telegram', init=MagicMock())
|
||||
mocker.patch('freqtrade.main.backtesting.current_time', return_value=current_time)
|
||||
mocker.patch('freqtrade.main.get_buy_signal', return_value=True)
|
||||
mocker.patch('freqtrade.exchange.backtesting.logger', info=logger_mock)
|
||||
mocker.patch.multiple('freqtrade.main.exchange',
|
||||
get_ticker=MagicMock(return_value={
|
||||
'bid': 0.07256061,
|
||||
'ask': 0.072661,
|
||||
'last': 0.07256061
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_order_id'))
|
||||
init(conf)
|
||||
|
||||
# Create some test data
|
||||
trade = create_trade(15.0)
|
||||
assert trade
|
||||
trade.close_rate = 0.07256061
|
||||
trade.close_profit = 100.00
|
||||
trade.close_date = current_time
|
||||
trade.open_order_id = None
|
||||
trade.is_open = False
|
||||
Trade.session.add(trade)
|
||||
Trade.session.flush()
|
||||
|
||||
backtesting.print_results()
|
||||
assert logger_mock.call_count == 1
|
||||
assert '(100.00%)' in logger_mock.call_args_list[-1][0][0]
|
||||
|
||||
# Trade should not be closed yet
|
||||
Trade.session.delete(trade)
|
||||
trade.close_rate = None
|
||||
trade.close_profit = None
|
||||
trade.close_date = None
|
||||
trade.is_open = True
|
||||
Trade.session.add(trade)
|
||||
Trade.session.flush()
|
||||
|
||||
backtesting.print_results()
|
||||
assert logger_mock.call_count == 2
|
||||
assert 'No closed trade' in logger_mock.call_args_list[-1][0][0]
|
Reference in New Issue
Block a user