207 lines
7.9 KiB
Python
207 lines
7.9 KiB
Python
![]() |
# 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]
|