diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 106d0f200..176ed34c5 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -9,7 +9,7 @@ from copy import deepcopy from datetime import datetime, timedelta, timezone 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.constants import DATETIME_PRINT_FORMAT @@ -264,7 +264,7 @@ class Backtesting: else: 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_row[BUY_IDX], sell_row[SELL_IDX], @@ -276,25 +276,12 @@ class Backtesting: trade.close_date = sell_row[DATE_IDX] trade.sell_reason = sell.sell_type 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 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 """ @@ -304,24 +291,11 @@ class Backtesting: for trade in open_trades[pair]: sell_row = data[pair][-1] - trade_entry = BacktestResult(pair=trade.pair, - profit_percent=trade.calc_profit_ratio( - rate=sell_row[OPEN_IDX]), - profit_abs=trade.calc_profit(sell_row[OPEN_IDX]), - open_date=trade.open_date, - 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) + trade.close_date = sell_row[DATE_IDX] + trade.sell_reason = SellType.FORCE_SELL + trade.close(sell_row[OPEN_IDX], show_msg=False) + trade.is_open = True + trades.append(trade) return trades def backtest(self, processed: Dict, stake_amount: float, @@ -348,7 +322,7 @@ class Backtesting: f"start_date: {start_date}, end_date: {end_date}, " f"max_open_trades: {max_open_trades}, position_stacking: {position_stacking}" ) - trades = [] + trades: List[Trade] = [] self.prepare_backtest(enable_protections) # Use dict of lists with data for performance @@ -429,7 +403,16 @@ class Backtesting: 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): logger.info("Running backtesting for Strategy %s", strat.get_strategy_name()) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 96ddb91a0..1a4c2f269 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -253,7 +253,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame], results=results) left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, max_open_trades=max_open_trades, - results=results.loc[results['open_at_end']], + results=results.loc[results['is_open']], skip_nan=True) daily_stats = generate_daily_stats(results) best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'], diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index e55e166d9..fcac05a26 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -629,7 +629,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): # 100 buys signals assert len(results) == 100 # 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']) @@ -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', '2018-01-30 05:35:00', ], utc=True), 'trade_duration': [235, 40], - 'open_at_end': [False, False], + 'is_open': [False, False], 'open_rate': [0.104445, 0.10302485], 'close_rate': [0.104969, 0.103541], '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 08:30:00'], utc=True), 'trade_duration': [47, 40, 20], - 'open_at_end': [False, False, False], + 'is_open': [False, False, False], '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] diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index f184cb125..30dda1152 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -72,7 +72,7 @@ def test_generate_backtest_stats(default_conf, testdatadir): "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "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, SellType.ROI, SellType.FORCE_SELL] }),