Simplify calling backtesting by returning the proper result
This commit is contained in:
parent
e2e1d34828
commit
f2e182002d
@ -330,7 +330,7 @@ class Backtesting:
|
|||||||
def backtest(self, processed: Dict,
|
def backtest(self, processed: Dict,
|
||||||
start_date: datetime, end_date: datetime,
|
start_date: datetime, end_date: datetime,
|
||||||
max_open_trades: int = 0, position_stacking: bool = False,
|
max_open_trades: int = 0, position_stacking: bool = False,
|
||||||
enable_protections: bool = False) -> DataFrame:
|
enable_protections: bool = False) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Implement backtesting functionality
|
Implement backtesting functionality
|
||||||
|
|
||||||
@ -417,7 +417,13 @@ class Backtesting:
|
|||||||
trades += self.handle_left_open(open_trades, data=data)
|
trades += self.handle_left_open(open_trades, data=data)
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
|
|
||||||
return trade_list_to_dataframe(trades)
|
results = trade_list_to_dataframe(trades)
|
||||||
|
return {
|
||||||
|
'results': results,
|
||||||
|
'config': self.strategy.config,
|
||||||
|
'locks': PairLocks.get_all_locks(),
|
||||||
|
'final_balance': self.wallets.get_total(self.strategy.config['stake_currency']),
|
||||||
|
}
|
||||||
|
|
||||||
def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange):
|
def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange):
|
||||||
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
|
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
|
||||||
@ -457,14 +463,12 @@ class Backtesting:
|
|||||||
enable_protections=self.config.get('enable_protections', False),
|
enable_protections=self.config.get('enable_protections', False),
|
||||||
)
|
)
|
||||||
backtest_end_time = datetime.now(timezone.utc)
|
backtest_end_time = datetime.now(timezone.utc)
|
||||||
self.all_results[self.strategy.get_strategy_name()] = {
|
results.update({
|
||||||
'results': results,
|
|
||||||
'config': self.strategy.config,
|
|
||||||
'locks': PairLocks.get_all_locks(),
|
|
||||||
'final_balance': self.wallets.get_total(self.strategy.config['stake_currency']),
|
|
||||||
'backtest_start_time': int(backtest_start_time.timestamp()),
|
'backtest_start_time': int(backtest_start_time.timestamp()),
|
||||||
'backtest_end_time': int(backtest_end_time.timestamp()),
|
'backtest_end_time': int(backtest_end_time.timestamp()),
|
||||||
}
|
})
|
||||||
|
self.all_results[self.strategy.get_strategy_name()] = results
|
||||||
|
|
||||||
return min_date, max_date
|
return min_date, max_date
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
@ -31,7 +31,6 @@ from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F401
|
|||||||
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F401
|
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F401
|
||||||
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
||||||
from freqtrade.optimize.optimize_reports import generate_strategy_stats
|
from freqtrade.optimize.optimize_reports import generate_strategy_stats
|
||||||
from freqtrade.persistence.pairlock_middleware import PairLocks
|
|
||||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver, HyperOptResolver
|
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver, HyperOptResolver
|
||||||
from freqtrade.strategy import IStrategy
|
from freqtrade.strategy import IStrategy
|
||||||
|
|
||||||
@ -279,7 +278,7 @@ class Hyperopt:
|
|||||||
|
|
||||||
min_date, max_date = get_timerange(processed)
|
min_date, max_date = get_timerange(processed)
|
||||||
|
|
||||||
backtesting_results = self.backtesting.backtest(
|
bt_results = self.backtesting.backtest(
|
||||||
processed=processed,
|
processed=processed,
|
||||||
start_date=min_date.datetime,
|
start_date=min_date.datetime,
|
||||||
end_date=max_date.datetime,
|
end_date=max_date.datetime,
|
||||||
@ -288,17 +287,12 @@ class Hyperopt:
|
|||||||
enable_protections=self.config.get('enable_protections', False),
|
enable_protections=self.config.get('enable_protections', False),
|
||||||
)
|
)
|
||||||
backtest_end_time = datetime.now(timezone.utc)
|
backtest_end_time = datetime.now(timezone.utc)
|
||||||
|
bt_results.update({
|
||||||
bt_result = {
|
|
||||||
'results': backtesting_results,
|
|
||||||
'config': self.backtesting.strategy.config,
|
|
||||||
'locks': PairLocks.get_all_locks(),
|
|
||||||
'final_balance': self.backtesting.wallets.get_total(
|
|
||||||
self.backtesting.strategy.config['stake_currency']),
|
|
||||||
'backtest_start_time': int(backtest_start_time.timestamp()),
|
'backtest_start_time': int(backtest_start_time.timestamp()),
|
||||||
'backtest_end_time': int(backtest_end_time.timestamp()),
|
'backtest_end_time': int(backtest_end_time.timestamp()),
|
||||||
}
|
})
|
||||||
return self._get_results_dict(bt_result, min_date, max_date,
|
|
||||||
|
return self._get_results_dict(bt_results, min_date, max_date,
|
||||||
params_dict, params_details,
|
params_dict, params_details,
|
||||||
processed=processed)
|
processed=processed)
|
||||||
|
|
||||||
|
@ -274,7 +274,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
|
|||||||
"""
|
"""
|
||||||
results: Dict[str, DataFrame] = content['results']
|
results: Dict[str, DataFrame] = content['results']
|
||||||
if not isinstance(results, DataFrame):
|
if not isinstance(results, DataFrame):
|
||||||
return
|
return {}
|
||||||
config = content['config']
|
config = content['config']
|
||||||
max_open_trades = min(config['max_open_trades'], len(btdata.keys()))
|
max_open_trades = min(config['max_open_trades'], len(btdata.keys()))
|
||||||
starting_balance = config['dry_run_wallet']
|
starting_balance = config['dry_run_wallet']
|
||||||
|
@ -514,13 +514,14 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
|||||||
timerange=timerange)
|
timerange=timerange)
|
||||||
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
||||||
min_date, max_date = get_timerange(processed)
|
min_date, max_date = get_timerange(processed)
|
||||||
results = backtesting.backtest(
|
result = backtesting.backtest(
|
||||||
processed=processed,
|
processed=processed,
|
||||||
start_date=min_date,
|
start_date=min_date,
|
||||||
end_date=max_date,
|
end_date=max_date,
|
||||||
max_open_trades=10,
|
max_open_trades=10,
|
||||||
position_stacking=False,
|
position_stacking=False,
|
||||||
)
|
)
|
||||||
|
results = result['results']
|
||||||
assert not results.empty
|
assert not results.empty
|
||||||
assert len(results) == 2
|
assert len(results) == 2
|
||||||
|
|
||||||
@ -583,8 +584,8 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
|
|||||||
max_open_trades=1,
|
max_open_trades=1,
|
||||||
position_stacking=False,
|
position_stacking=False,
|
||||||
)
|
)
|
||||||
assert not results.empty
|
assert not results['results'].empty
|
||||||
assert len(results) == 1
|
assert len(results['results']) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_processed(default_conf, mocker, testdatadir) -> None:
|
def test_processed(default_conf, mocker, testdatadir) -> None:
|
||||||
@ -623,7 +624,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
|
|||||||
# While buy-signals are unrealistic, running backtesting
|
# While buy-signals are unrealistic, running backtesting
|
||||||
# over and over again should not cause different results
|
# over and over again should not cause different results
|
||||||
for [contour, numres] in tests:
|
for [contour, numres] in tests:
|
||||||
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)) == numres
|
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == numres
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('protections,contour,expected', [
|
@pytest.mark.parametrize('protections,contour,expected', [
|
||||||
@ -648,7 +649,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
# While buy-signals are unrealistic, running backtesting
|
# While buy-signals are unrealistic, running backtesting
|
||||||
# over and over again should not cause different results
|
# over and over again should not cause different results
|
||||||
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)) == expected
|
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
||||||
@ -662,8 +663,8 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
|||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting.strategy.advise_buy = fun # Override
|
backtesting.strategy.advise_buy = fun # Override
|
||||||
backtesting.strategy.advise_sell = fun # Override
|
backtesting.strategy.advise_sell = fun # Override
|
||||||
results = backtesting.backtest(**backtest_conf)
|
result = backtesting.backtest(**backtest_conf)
|
||||||
assert results.empty
|
assert result['results'].empty
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_only_sell(mocker, default_conf, testdatadir):
|
def test_backtest_only_sell(mocker, default_conf, testdatadir):
|
||||||
@ -677,8 +678,8 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir):
|
|||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting.strategy.advise_buy = fun # Override
|
backtesting.strategy.advise_buy = fun # Override
|
||||||
backtesting.strategy.advise_sell = fun # Override
|
backtesting.strategy.advise_sell = fun # Override
|
||||||
results = backtesting.backtest(**backtest_conf)
|
result = backtesting.backtest(**backtest_conf)
|
||||||
assert results.empty
|
assert result['results'].empty
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
||||||
@ -690,10 +691,11 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
|||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting.strategy.advise_buy = _trend_alternate # Override
|
backtesting.strategy.advise_buy = _trend_alternate # Override
|
||||||
backtesting.strategy.advise_sell = _trend_alternate # Override
|
backtesting.strategy.advise_sell = _trend_alternate # Override
|
||||||
results = backtesting.backtest(**backtest_conf)
|
result = backtesting.backtest(**backtest_conf)
|
||||||
# 200 candles in backtest data
|
# 200 candles in backtest data
|
||||||
# won't buy on first (shifted by 1)
|
# won't buy on first (shifted by 1)
|
||||||
# 100 buys signals
|
# 100 buys signals
|
||||||
|
results = result['results']
|
||||||
assert len(results) == 100
|
assert len(results) == 100
|
||||||
# One trade was force-closed at the end
|
# One trade was force-closed at the end
|
||||||
assert len(results.loc[results['is_open']]) == 0
|
assert len(results.loc[results['is_open']]) == 0
|
||||||
@ -745,9 +747,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
|||||||
results = backtesting.backtest(**backtest_conf)
|
results = backtesting.backtest(**backtest_conf)
|
||||||
|
|
||||||
# Make sure we have parallel trades
|
# Make sure we have parallel trades
|
||||||
assert len(evaluate_result_multi(results, '5m', 2)) > 0
|
assert len(evaluate_result_multi(results['results'], '5m', 2)) > 0
|
||||||
# make sure we don't have trades with more than configured max_open_trades
|
# make sure we don't have trades with more than configured max_open_trades
|
||||||
assert len(evaluate_result_multi(results, '5m', 3)) == 0
|
assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0
|
||||||
|
|
||||||
backtest_conf = {
|
backtest_conf = {
|
||||||
'processed': processed,
|
'processed': processed,
|
||||||
@ -757,7 +759,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
|||||||
'position_stacking': False,
|
'position_stacking': False,
|
||||||
}
|
}
|
||||||
results = backtesting.backtest(**backtest_conf)
|
results = backtesting.backtest(**backtest_conf)
|
||||||
assert len(evaluate_result_multi(results, '5m', 1)) == 0
|
assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||||
@ -803,7 +805,12 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
|||||||
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||||
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtestmock = MagicMock(return_value=pd.DataFrame(columns=BT_DATA_COLUMNS))
|
backtestmock = MagicMock(return_value={
|
||||||
|
'results': pd.DataFrame(columns=BT_DATA_COLUMNS),
|
||||||
|
'config': default_conf,
|
||||||
|
'locks': [],
|
||||||
|
'final_balance': 1000,
|
||||||
|
})
|
||||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
||||||
@ -867,39 +874,51 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
|||||||
def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys):
|
def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys):
|
||||||
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
|
||||||
|
'profit_ratio': [0.0, 0.0],
|
||||||
|
'profit_abs': [0.0, 0.0],
|
||||||
|
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
||||||
|
'2018-01-30 03:30:00', ], utc=True
|
||||||
|
),
|
||||||
|
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
||||||
|
'2018-01-30 05:35:00', ], utc=True),
|
||||||
|
'trade_duration': [235, 40],
|
||||||
|
'is_open': [False, False],
|
||||||
|
'stake_amount': [0.01, 0.01],
|
||||||
|
'open_rate': [0.104445, 0.10302485],
|
||||||
|
'close_rate': [0.104969, 0.103541],
|
||||||
|
'sell_reason': [SellType.ROI, SellType.ROI]
|
||||||
|
})
|
||||||
|
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
||||||
|
'profit_ratio': [0.03, 0.01, 0.1],
|
||||||
|
'profit_abs': [0.01, 0.02, 0.2],
|
||||||
|
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
||||||
|
'2018-01-30 03:30:00',
|
||||||
|
'2018-01-30 05:30:00'], utc=True
|
||||||
|
),
|
||||||
|
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
||||||
|
'2018-01-30 05:35:00',
|
||||||
|
'2018-01-30 08:30:00'], utc=True),
|
||||||
|
'trade_duration': [47, 40, 20],
|
||||||
|
'is_open': [False, False, False],
|
||||||
|
'stake_amount': [0.01, 0.01, 0.01],
|
||||||
|
'open_rate': [0.104445, 0.10302485, 0.122541],
|
||||||
|
'close_rate': [0.104969, 0.103541, 0.123541],
|
||||||
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||||
|
})
|
||||||
backtestmock = MagicMock(side_effect=[
|
backtestmock = MagicMock(side_effect=[
|
||||||
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
|
{
|
||||||
'profit_ratio': [0.0, 0.0],
|
'results': result1,
|
||||||
'profit_abs': [0.0, 0.0],
|
'config': default_conf,
|
||||||
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
'locks': [],
|
||||||
'2018-01-30 03:30:00', ], utc=True
|
'final_balance': 1000,
|
||||||
),
|
},
|
||||||
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
{
|
||||||
'2018-01-30 05:35:00', ], utc=True),
|
'results': result2,
|
||||||
'trade_duration': [235, 40],
|
'config': default_conf,
|
||||||
'is_open': [False, False],
|
'locks': [],
|
||||||
'stake_amount': [0.01, 0.01],
|
'final_balance': 1000,
|
||||||
'open_rate': [0.104445, 0.10302485],
|
}
|
||||||
'close_rate': [0.104969, 0.103541],
|
|
||||||
'sell_reason': [SellType.ROI, SellType.ROI]
|
|
||||||
}),
|
|
||||||
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
|
||||||
'profit_ratio': [0.03, 0.01, 0.1],
|
|
||||||
'profit_abs': [0.01, 0.02, 0.2],
|
|
||||||
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
|
||||||
'2018-01-30 03:30:00',
|
|
||||||
'2018-01-30 05:30:00'], utc=True
|
|
||||||
),
|
|
||||||
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
|
||||||
'2018-01-30 05:35:00',
|
|
||||||
'2018-01-30 08:30:00'], utc=True),
|
|
||||||
'trade_duration': [47, 40, 20],
|
|
||||||
'is_open': [False, False, False],
|
|
||||||
'stake_amount': [0.01, 0.01, 0.01],
|
|
||||||
'open_rate': [0.104445, 0.10302485, 0.122541],
|
|
||||||
'close_rate': [0.104969, 0.103541, 0.123541],
|
|
||||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
|
||||||
}),
|
|
||||||
])
|
])
|
||||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||||
|
Loading…
Reference in New Issue
Block a user