Backtesting does not need to convert to BacktestResult object
This commit is contained in:
parent
3b51545d23
commit
48977493bb
@ -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())
|
||||||
|
@ -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'],
|
||||||
|
@ -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]
|
||||||
|
@ -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]
|
||||||
}),
|
}),
|
||||||
|
Loading…
Reference in New Issue
Block a user