Keep trade state in LocalTrade

This commit is contained in:
Matthias 2022-10-16 13:34:58 +02:00
parent 49426a924d
commit 0e8cf366f5
3 changed files with 22 additions and 17 deletions

View File

@ -924,7 +924,7 @@ class Backtesting:
Handling of left open trades at the end of backtesting Handling of left open trades at the end of backtesting
""" """
for pair in open_trades.keys(): 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: if trade.open_order_id and trade.nr_of_successful_entries == 0:
# Ignore trade if entry-order did not fill yet # Ignore trade if entry-order did not fill yet
continue continue
@ -1098,15 +1098,12 @@ class Backtesting:
indexes: Dict = defaultdict(int) indexes: Dict = defaultdict(int)
current_time = start_date + timedelta(minutes=self.timeframe_min) 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( self.progress.init_step(BacktestState.BACKTEST, int(
(end_date - start_date) / timedelta(minutes=self.timeframe_min))) (end_date - start_date) / timedelta(minutes=self.timeframe_min)))
# Loop timerange and get candle for each pair at that point in time # Loop timerange and get candle for each pair at that point in time
while current_time <= end_date: 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() self.check_abort()
for i, pair in enumerate(data): for i, pair in enumerate(data):
row_index = indexes[pair] row_index = indexes[pair]
@ -1118,13 +1115,11 @@ class Backtesting:
indexes[pair] = row_index indexes[pair] = row_index
self.dataprovider._set_dataframe_max_index(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 # 1. Manage currently open orders of active trades
if self.manage_open_orders(t, current_time, row): if self.manage_open_orders(t, current_time, row):
# Close trade # Close trade
open_trade_count -= 1
open_trade_count_start -= 1 open_trade_count_start -= 1
open_trades[pair].remove(t)
LocalTrade.remove_bt_trade(t) LocalTrade.remove_bt_trade(t)
self.wallets.update() self.wallets.update()
@ -1134,7 +1129,7 @@ class Backtesting:
# don't open on the last row # don't open on the last row
trade_dir = self.check_for_trade_entry(row) trade_dir = self.check_for_trade_entry(row)
if ( 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 self.trade_slot_available(max_open_trades, open_trade_count_start)
and current_time != end_date and current_time != end_date
and trade_dir is not None and trade_dir is not None
@ -1146,13 +1141,11 @@ class Backtesting:
# This emulates previous behavior - not sure if this is correct # This emulates previous behavior - not sure if this is correct
# Prevents entering if the trade-slot was freed in this candle # Prevents entering if the trade-slot was freed in this candle
open_trade_count_start += 1 open_trade_count_start += 1
open_trade_count += 1
# logger.debug(f"{pair} - Emulate creation of new trade: {trade}.") # logger.debug(f"{pair} - Emulate creation of new trade: {trade}.")
open_trades[pair].append(trade)
LocalTrade.add_bt_trade(trade) LocalTrade.add_bt_trade(trade)
self.wallets.update() self.wallets.update()
for trade in list(open_trades[pair]): for trade in list(LocalTrade.bt_trades_open_pp[pair]):
# 3. Process entry orders. # 3. Process entry orders.
order = trade.select_order(trade.entry_side, is_open=True) order = trade.select_order(trade.entry_side, is_open=True)
if order and self._get_order_filled(order.price, row): if order and self._get_order_filled(order.price, row):
@ -1178,8 +1171,6 @@ class Backtesting:
trade.close(order.price, show_msg=False) trade.close(order.price, show_msg=False)
# logger.debug(f"{pair} - Backtesting exit {trade}") # logger.debug(f"{pair} - Backtesting exit {trade}")
open_trade_count -= 1
open_trades[pair].remove(trade)
LocalTrade.close_bt_trade(trade) LocalTrade.close_bt_trade(trade)
self.wallets.update() self.wallets.update()
self.run_protections( self.run_protections(
@ -1189,7 +1180,7 @@ class Backtesting:
self.progress.increment() self.progress.increment()
current_time += timedelta(minutes=self.timeframe_min) 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() self.wallets.update()
results = trade_list_to_dataframe(LocalTrade.trades) results = trade_list_to_dataframe(LocalTrade.trades)

View File

@ -2,6 +2,7 @@
This module contains the class to persist trades into SQLite This module contains the class to persist trades into SQLite
""" """
import logging import logging
from collections import defaultdict
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from math import isclose from math import isclose
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
@ -255,6 +256,9 @@ class LocalTrade():
# Trades container for backtesting # Trades container for backtesting
trades: List['LocalTrade'] = [] trades: List['LocalTrade'] = []
trades_open: 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 total_profit: float = 0
realized_profit: float = 0 realized_profit: float = 0
@ -538,6 +542,8 @@ class LocalTrade():
""" """
LocalTrade.trades = [] LocalTrade.trades = []
LocalTrade.trades_open = [] LocalTrade.trades_open = []
LocalTrade.bt_trades_open_pp = defaultdict(list)
LocalTrade.bt_open_open_trade_count = 0
LocalTrade.total_profit = 0 LocalTrade.total_profit = 0
def adjust_min_max_rates(self, current_price: float, current_price_low: float) -> None: def adjust_min_max_rates(self, current_price: float, current_price_low: float) -> None:
@ -1067,6 +1073,8 @@ class LocalTrade():
@staticmethod @staticmethod
def close_bt_trade(trade): def close_bt_trade(trade):
LocalTrade.trades_open.remove(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.trades.append(trade)
LocalTrade.total_profit += trade.close_profit_abs LocalTrade.total_profit += trade.close_profit_abs
@ -1074,12 +1082,16 @@ class LocalTrade():
def add_bt_trade(trade): def add_bt_trade(trade):
if trade.is_open: if trade.is_open:
LocalTrade.trades_open.append(trade) LocalTrade.trades_open.append(trade)
LocalTrade.bt_trades_open_pp[trade.pair].append(trade)
LocalTrade.bt_open_open_trade_count += 1
else: else:
LocalTrade.trades.append(trade) LocalTrade.trades.append(trade)
@staticmethod @staticmethod
def remove_bt_trade(trade): def remove_bt_trade(trade):
LocalTrade.trades_open.remove(trade) LocalTrade.trades_open.remove(trade)
LocalTrade.bt_trades_open_pp[trade.pair].remove(trade)
LocalTrade.bt_open_open_trade_count -= 1
@staticmethod @staticmethod
def get_open_trades() -> List[Any]: def get_open_trades() -> List[Any]:
@ -1096,7 +1108,7 @@ class LocalTrade():
if Trade.use_db: if Trade.use_db:
return Trade.query.filter(Trade.is_open.is_(True)).count() return Trade.query.filter(Trade.is_open.is_(True)).count()
else: else:
return len(LocalTrade.trades_open) return LocalTrade.bt_open_open_trade_count
@staticmethod @staticmethod
def stoploss_reinitialization(desired_stoploss): def stoploss_reinitialization(desired_stoploss):

View File

@ -2406,6 +2406,8 @@ def test_Trade_object_idem():
'get_trading_volume', '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 # Parent (LocalTrade) should have the same attributes
for item in trade: for item in trade:
@ -2416,7 +2418,7 @@ def test_Trade_object_idem():
# Fails if only a column is added without corresponding parent field # Fails if only a column is added without corresponding parent field
for item in localtrade: for item in localtrade:
if (not item.startswith('__') 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)): and type(getattr(LocalTrade, item)) not in (property, FunctionType)):
assert item in trade assert item in trade