Backtesting does not need to convert to BacktestResult object

This commit is contained in:
Matthias 2021-01-23 12:50:20 +01:00
parent 3b51545d23
commit 48977493bb
4 changed files with 25 additions and 42 deletions

View File

@ -9,7 +9,7 @@ from copy import deepcopy
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, NamedTuple, Optional, Tuple from typing import Any, Dict, List, NamedTuple, Optional, Tuple
from pandas import DataFrame from pandas import DataFrame, to_datetime
from freqtrade.configuration import TimeRange, remove_credentials, validate_config_consistency from freqtrade.configuration import TimeRange, remove_credentials, validate_config_consistency
from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.constants import DATETIME_PRINT_FORMAT
@ -264,7 +264,7 @@ class Backtesting:
else: else:
return sell_row[OPEN_IDX] return sell_row[OPEN_IDX]
def _get_sell_trade_entry(self, trade: Trade, sell_row: Tuple) -> Optional[BacktestResult]: def _get_sell_trade_entry(self, trade: Trade, sell_row: Tuple) -> Optional[Trade]:
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], sell_row[DATE_IDX], sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], sell_row[DATE_IDX],
sell_row[BUY_IDX], sell_row[SELL_IDX], sell_row[BUY_IDX], sell_row[SELL_IDX],
@ -276,25 +276,12 @@ class Backtesting:
trade.close_date = sell_row[DATE_IDX] trade.close_date = sell_row[DATE_IDX]
trade.sell_reason = sell.sell_type trade.sell_reason = sell.sell_type
trade.close(closerate, show_msg=False) trade.close(closerate, show_msg=False)
return trade
return BacktestResult(pair=trade.pair,
profit_percent=trade.calc_profit_ratio(rate=closerate),
profit_abs=trade.calc_profit(rate=closerate),
open_date=trade.open_date,
open_rate=trade.open_rate,
open_fee=self.fee,
close_date=sell_row[DATE_IDX],
close_rate=closerate,
close_fee=self.fee,
amount=trade.amount,
trade_duration=trade_dur,
open_at_end=False,
sell_reason=sell.sell_type
)
return None return None
def handle_left_open(self, open_trades: Dict[str, List[Trade]], def handle_left_open(self, open_trades: Dict[str, List[Trade]],
data: Dict[str, List[Tuple]]) -> List[BacktestResult]: data: Dict[str, List[Tuple]]) -> List[Trade]:
""" """
Handling of left open trades at the end of backtesting Handling of left open trades at the end of backtesting
""" """
@ -304,24 +291,11 @@ class Backtesting:
for trade in open_trades[pair]: for trade in open_trades[pair]:
sell_row = data[pair][-1] sell_row = data[pair][-1]
trade_entry = BacktestResult(pair=trade.pair, trade.close_date = sell_row[DATE_IDX]
profit_percent=trade.calc_profit_ratio( trade.sell_reason = SellType.FORCE_SELL
rate=sell_row[OPEN_IDX]), trade.close(sell_row[OPEN_IDX], show_msg=False)
profit_abs=trade.calc_profit(sell_row[OPEN_IDX]), trade.is_open = True
open_date=trade.open_date, trades.append(trade)
open_rate=trade.open_rate,
open_fee=self.fee,
close_date=sell_row[DATE_IDX],
close_rate=sell_row[OPEN_IDX],
close_fee=self.fee,
amount=trade.amount,
trade_duration=int((
sell_row[DATE_IDX] - trade.open_date
).total_seconds() // 60),
open_at_end=True,
sell_reason=SellType.FORCE_SELL
)
trades.append(trade_entry)
return trades return trades
def backtest(self, processed: Dict, stake_amount: float, def backtest(self, processed: Dict, stake_amount: float,
@ -348,7 +322,7 @@ class Backtesting:
f"start_date: {start_date}, end_date: {end_date}, " f"start_date: {start_date}, end_date: {end_date}, "
f"max_open_trades: {max_open_trades}, position_stacking: {position_stacking}" f"max_open_trades: {max_open_trades}, position_stacking: {position_stacking}"
) )
trades = [] trades: List[Trade] = []
self.prepare_backtest(enable_protections) self.prepare_backtest(enable_protections)
# Use dict of lists with data for performance # Use dict of lists with data for performance
@ -429,7 +403,16 @@ class Backtesting:
trades += self.handle_left_open(open_trades, data=data) trades += self.handle_left_open(open_trades, data=data)
return DataFrame.from_records(trades, columns=BacktestResult._fields) cols = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'open_fee', 'close_fee', 'trade_duration',
'profit_ratio', 'profit_percent', 'profit_abs', 'sell_reason',
'initial_stop_loss_abs', 'initial_stop_loss_ratio' 'stop_loss', 'stop_loss_ratio',
'min_rate', 'max_rate', 'is_open', ]
df = DataFrame.from_records([t.to_json() for t in trades], columns=cols)
if len(df) > 0:
df.loc[:, 'close_date'] = to_datetime(df['close_date'], utc=True)
df.loc[:, 'open_date'] = to_datetime(df['open_date'], utc=True)
return df
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())

View File

@ -253,7 +253,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
results=results) results=results)
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
max_open_trades=max_open_trades, max_open_trades=max_open_trades,
results=results.loc[results['open_at_end']], results=results.loc[results['is_open']],
skip_nan=True) skip_nan=True)
daily_stats = generate_daily_stats(results) daily_stats = generate_daily_stats(results)
best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'], best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'],

View File

@ -629,7 +629,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
# 100 buys signals # 100 buys signals
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.open_at_end]) == 0 assert len(results.loc[results['is_open']]) == 0
@pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC']) @pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC'])
@ -811,7 +811,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'close_date': pd.to_datetime(['2018-01-29 20:45:00', 'close_date': pd.to_datetime(['2018-01-29 20:45:00',
'2018-01-30 05:35:00', ], utc=True), '2018-01-30 05:35:00', ], utc=True),
'trade_duration': [235, 40], 'trade_duration': [235, 40],
'open_at_end': [False, False], 'is_open': [False, False],
'open_rate': [0.104445, 0.10302485], 'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541], 'close_rate': [0.104969, 0.103541],
'sell_reason': [SellType.ROI, SellType.ROI] 'sell_reason': [SellType.ROI, SellType.ROI]
@ -827,7 +827,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'2018-01-30 05:35:00', '2018-01-30 05:35:00',
'2018-01-30 08:30:00'], utc=True), '2018-01-30 08:30:00'], utc=True),
'trade_duration': [47, 40, 20], 'trade_duration': [47, 40, 20],
'open_at_end': [False, False, False], 'is_open': [False, False, False],
'open_rate': [0.104445, 0.10302485, 0.122541], 'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541], 'close_rate': [0.104969, 0.103541, 0.123541],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]

View File

@ -72,7 +72,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214], "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14], "trade_duration": [123, 34, 31, 14],
"open_at_end": [False, False, False, True], "is_open": [False, False, False, True],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS, "sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL] SellType.ROI, SellType.FORCE_SELL]
}), }),