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
"""
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)

View File

@ -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):

View File

@ -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