From 0e8cf366f5f0e48dd26d50c1dd7c6f4937c9190e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Oct 2022 13:34:58 +0200 Subject: [PATCH] Keep trade state in LocalTrade --- freqtrade/optimize/backtesting.py | 21 ++++++--------------- freqtrade/persistence/trade_model.py | 14 +++++++++++++- tests/test_persistence.py | 4 +++- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 68e45fae0..1908a261a 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -924,7 +924,7 @@ class Backtesting: Handling of left open trades at the end of backtesting """ for pair in open_trades.keys(): - for trade in open_trades[pair]: + for trade in list(open_trades[pair]): if trade.open_order_id and trade.nr_of_successful_entries == 0: # Ignore trade if entry-order did not fill yet continue @@ -1098,15 +1098,12 @@ class Backtesting: indexes: Dict = defaultdict(int) current_time = start_date + timedelta(minutes=self.timeframe_min) - open_trades: Dict[str, List[LocalTrade]] = defaultdict(list) - open_trade_count = 0 - self.progress.init_step(BacktestState.BACKTEST, int( (end_date - start_date) / timedelta(minutes=self.timeframe_min))) # Loop timerange and get candle for each pair at that point in time while current_time <= end_date: - open_trade_count_start = open_trade_count + open_trade_count_start = LocalTrade.bt_open_open_trade_count self.check_abort() for i, pair in enumerate(data): row_index = indexes[pair] @@ -1118,13 +1115,11 @@ class Backtesting: indexes[pair] = row_index self.dataprovider._set_dataframe_max_index(row_index) - for t in list(open_trades[pair]): + for t in list(LocalTrade.bt_trades_open_pp[pair]): # 1. Manage currently open orders of active trades if self.manage_open_orders(t, current_time, row): # Close trade - open_trade_count -= 1 open_trade_count_start -= 1 - open_trades[pair].remove(t) LocalTrade.remove_bt_trade(t) self.wallets.update() @@ -1134,7 +1129,7 @@ class Backtesting: # don't open on the last row trade_dir = self.check_for_trade_entry(row) if ( - (position_stacking or len(open_trades[pair]) == 0) + (position_stacking or len(LocalTrade.bt_trades_open_pp[pair]) == 0) and self.trade_slot_available(max_open_trades, open_trade_count_start) and current_time != end_date and trade_dir is not None @@ -1146,13 +1141,11 @@ class Backtesting: # This emulates previous behavior - not sure if this is correct # Prevents entering if the trade-slot was freed in this candle open_trade_count_start += 1 - open_trade_count += 1 # logger.debug(f"{pair} - Emulate creation of new trade: {trade}.") - open_trades[pair].append(trade) LocalTrade.add_bt_trade(trade) self.wallets.update() - for trade in list(open_trades[pair]): + for trade in list(LocalTrade.bt_trades_open_pp[pair]): # 3. Process entry orders. order = trade.select_order(trade.entry_side, is_open=True) if order and self._get_order_filled(order.price, row): @@ -1178,8 +1171,6 @@ class Backtesting: trade.close(order.price, show_msg=False) # logger.debug(f"{pair} - Backtesting exit {trade}") - open_trade_count -= 1 - open_trades[pair].remove(trade) LocalTrade.close_bt_trade(trade) self.wallets.update() self.run_protections( @@ -1189,7 +1180,7 @@ class Backtesting: self.progress.increment() current_time += timedelta(minutes=self.timeframe_min) - self.handle_left_open(open_trades, data=data) + self.handle_left_open(LocalTrade.bt_trades_open_pp, data=data) self.wallets.update() results = trade_list_to_dataframe(LocalTrade.trades) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index c632aa817..73e067480 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -2,6 +2,7 @@ This module contains the class to persist trades into SQLite """ import logging +from collections import defaultdict from datetime import datetime, timedelta, timezone from math import isclose from typing import Any, Dict, List, Optional @@ -255,6 +256,9 @@ class LocalTrade(): # Trades container for backtesting trades: List['LocalTrade'] = [] trades_open: List['LocalTrade'] = [] + # Copy of trades_open - but indexed by pair + bt_trades_open_pp: Dict[str, List['LocalTrade']] = defaultdict(list) + bt_open_open_trade_count: int = 0 total_profit: float = 0 realized_profit: float = 0 @@ -538,6 +542,8 @@ class LocalTrade(): """ LocalTrade.trades = [] LocalTrade.trades_open = [] + LocalTrade.bt_trades_open_pp = defaultdict(list) + LocalTrade.bt_open_open_trade_count = 0 LocalTrade.total_profit = 0 def adjust_min_max_rates(self, current_price: float, current_price_low: float) -> None: @@ -1067,6 +1073,8 @@ class LocalTrade(): @staticmethod def close_bt_trade(trade): LocalTrade.trades_open.remove(trade) + LocalTrade.bt_trades_open_pp[trade.pair].remove(trade) + LocalTrade.bt_open_open_trade_count -= 1 LocalTrade.trades.append(trade) LocalTrade.total_profit += trade.close_profit_abs @@ -1074,12 +1082,16 @@ class LocalTrade(): def add_bt_trade(trade): if trade.is_open: LocalTrade.trades_open.append(trade) + LocalTrade.bt_trades_open_pp[trade.pair].append(trade) + LocalTrade.bt_open_open_trade_count += 1 else: LocalTrade.trades.append(trade) @staticmethod def remove_bt_trade(trade): LocalTrade.trades_open.remove(trade) + LocalTrade.bt_trades_open_pp[trade.pair].remove(trade) + LocalTrade.bt_open_open_trade_count -= 1 @staticmethod def get_open_trades() -> List[Any]: @@ -1096,7 +1108,7 @@ class LocalTrade(): if Trade.use_db: return Trade.query.filter(Trade.is_open.is_(True)).count() else: - return len(LocalTrade.trades_open) + return LocalTrade.bt_open_open_trade_count @staticmethod def stoploss_reinitialization(desired_stoploss): diff --git a/tests/test_persistence.py b/tests/test_persistence.py index e7f218c02..ae2672830 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -2406,6 +2406,8 @@ def test_Trade_object_idem(): 'get_trading_volume', ) + EXCLUDES2 = ('trades', 'trades_open', 'bt_trades_open_pp', 'bt_open_open_trade_count', + 'total_profit') # Parent (LocalTrade) should have the same attributes for item in trade: @@ -2416,7 +2418,7 @@ def test_Trade_object_idem(): # Fails if only a column is added without corresponding parent field for item in localtrade: if (not item.startswith('__') - and item not in ('trades', 'trades_open', 'total_profit') + and item not in EXCLUDES2 and type(getattr(LocalTrade, item)) not in (property, FunctionType)): assert item in trade