Merge pull request #5002 from freqtrade/track_rejected_trades

Track rejected trades
This commit is contained in:
Matthias 2021-05-23 14:56:50 +01:00 committed by GitHub
commit 971d5b2ecc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 30 additions and 6 deletions

View File

@ -298,6 +298,7 @@ A backtesting result will look like that:
| Avg. Duration Winners | 4:23:00 | | Avg. Duration Winners | 4:23:00 |
| Avg. Duration Loser | 6:55:00 | | Avg. Duration Loser | 6:55:00 |
| Zero Duration Trades | 4.6% (20) | | Zero Duration Trades | 4.6% (20) |
| Rejected Buy signals | 3089 |
| | | | | |
| Min balance | 0.00945123 BTC | | Min balance | 0.00945123 BTC |
| Max balance | 0.01846651 BTC | | Max balance | 0.01846651 BTC |
@ -386,6 +387,7 @@ It contains some useful key metrics about performance of your strategy on backte
| Avg. Duration Winners | 4:23:00 | | Avg. Duration Winners | 4:23:00 |
| Avg. Duration Loser | 6:55:00 | | Avg. Duration Loser | 6:55:00 |
| Zero Duration Trades | 4.6% (20) | | Zero Duration Trades | 4.6% (20) |
| Rejected Buy signals | 3089 |
| | | | | |
| Min balance | 0.00945123 BTC | | Min balance | 0.00945123 BTC |
| Max balance | 0.01846651 BTC | | Max balance | 0.01846651 BTC |
@ -416,6 +418,7 @@ It contains some useful key metrics about performance of your strategy on backte
- `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade). - `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade).
- `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades.
- `Zero Duration Trades`: A number of trades that completed within same candle as they opened and had `trailing_stop_loss` sell reason. A significant amount of such trades may indicate that strategy is exploiting trailing stoploss behavior in backtesting and produces unrealistic results. - `Zero Duration Trades`: A number of trades that completed within same candle as they opened and had `trailing_stop_loss` sell reason. A significant amount of such trades may indicate that strategy is exploiting trailing stoploss behavior in backtesting and produces unrealistic results.
- `Rejected Buy signals`: Buy signals that could not be acted upon due to max_open_trades being reached.
- `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period. - `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period.
- `Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced). - `Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced).
- `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost. - `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost.

View File

@ -177,6 +177,7 @@ class Backtesting:
Trade.use_db = False Trade.use_db = False
PairLocks.reset_locks() PairLocks.reset_locks()
Trade.reset_trades() Trade.reset_trades()
self.rejected_trades = 0
self.dataprovider.clear_cache() self.dataprovider.clear_cache()
def _get_ohlcv_as_lists(self, processed: Dict[str, DataFrame]) -> Dict[str, Tuple]: def _get_ohlcv_as_lists(self, processed: Dict[str, DataFrame]) -> Dict[str, Tuple]:
@ -336,6 +337,14 @@ class Backtesting:
trades.append(trade1) trades.append(trade1)
return trades return trades
def trade_slot_available(self, max_open_trades: int, open_trade_count: int) -> bool:
# Always allow trades when max_open_trades is enabled.
if max_open_trades <= 0 or open_trade_count < max_open_trades:
return True
# Rejected trade
self.rejected_trades += 1
return False
def backtest(self, processed: Dict, def backtest(self, processed: Dict,
start_date: datetime, end_date: datetime, start_date: datetime, end_date: datetime,
max_open_trades: int = 0, position_stacking: bool = False, max_open_trades: int = 0, position_stacking: bool = False,
@ -397,11 +406,14 @@ class Backtesting:
# without positionstacking, we can only have one open trade per pair. # without positionstacking, we can only have one open trade per pair.
# max_open_trades must be respected # max_open_trades must be respected
# don't open on the last row # don't open on the last row
if ((position_stacking or len(open_trades[pair]) == 0) if (
and (max_open_trades <= 0 or open_trade_count_start < max_open_trades) (position_stacking or len(open_trades[pair]) == 0)
and tmp != end_date and self.trade_slot_available(max_open_trades, open_trade_count_start)
and row[BUY_IDX] == 1 and row[SELL_IDX] != 1 and tmp != end_date
and not PairLocks.is_pair_locked(pair, row[DATE_IDX])): and row[BUY_IDX] == 1
and row[SELL_IDX] != 1
and not PairLocks.is_pair_locked(pair, row[DATE_IDX])
):
trade = self._enter_trade(pair, row) trade = self._enter_trade(pair, row)
if trade: if trade:
# TODO: hacky workaround to avoid opening > max_open_trades # TODO: hacky workaround to avoid opening > max_open_trades
@ -439,6 +451,7 @@ class Backtesting:
'results': results, 'results': results,
'config': self.strategy.config, 'config': self.strategy.config,
'locks': PairLocks.get_all_locks(), 'locks': PairLocks.get_all_locks(),
'rejected_signals': self.rejected_trades,
'final_balance': self.wallets.get_total(self.strategy.config['stake_currency']), 'final_balance': self.wallets.get_total(self.strategy.config['stake_currency']),
} }

View File

@ -355,6 +355,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'starting_balance': starting_balance, 'starting_balance': starting_balance,
'dry_run_wallet': starting_balance, 'dry_run_wallet': starting_balance,
'final_balance': content['final_balance'], 'final_balance': content['final_balance'],
'rejected_signals': content['rejected_signals'],
'max_open_trades': max_open_trades, 'max_open_trades': max_open_trades,
'max_open_trades_setting': (config['max_open_trades'] 'max_open_trades_setting': (config['max_open_trades']
if config['max_open_trades'] != float('inf') else -1), if config['max_open_trades'] != float('inf') else -1),
@ -561,7 +562,6 @@ def text_table_add_metrics(strat_results: Dict) -> str:
strat_results['stake_currency'])), strat_results['stake_currency'])),
('Total trade volume', round_coin_value(strat_results['total_volume'], ('Total trade volume', round_coin_value(strat_results['total_volume'],
strat_results['stake_currency'])), strat_results['stake_currency'])),
('', ''), # Empty line to improve readability ('', ''), # Empty line to improve readability
('Best Pair', f"{strat_results['best_pair']['key']} " ('Best Pair', f"{strat_results['best_pair']['key']} "
f"{round(strat_results['best_pair']['profit_sum_pct'], 2)}%"), f"{round(strat_results['best_pair']['profit_sum_pct'], 2)}%"),
@ -580,6 +580,7 @@ def text_table_add_metrics(strat_results: Dict) -> str:
('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"), ('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"),
('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"), ('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"),
('Zero Duration Trades', zero_duration_trades), ('Zero Duration Trades', zero_duration_trades),
('Rejected Buy signals', strat_results.get('rejected_signals', 'N/A')),
('', ''), # Empty line to improve readability ('', ''), # Empty line to improve readability
('Min balance', round_coin_value(strat_results['csum_min'], ('Min balance', round_coin_value(strat_results['csum_min'],

View File

@ -831,6 +831,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'results': pd.DataFrame(columns=BT_DATA_COLUMNS), 'results': pd.DataFrame(columns=BT_DATA_COLUMNS),
'config': default_conf, 'config': default_conf,
'locks': [], 'locks': [],
'rejected_signals': 20,
'final_balance': 1000, 'final_balance': 1000,
}) })
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
@ -938,12 +939,14 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'results': result1, 'results': result1,
'config': default_conf, 'config': default_conf,
'locks': [], 'locks': [],
'rejected_signals': 20,
'final_balance': 1000, 'final_balance': 1000,
}, },
{ {
'results': result2, 'results': result2,
'config': default_conf, 'config': default_conf,
'locks': [], 'locks': [],
'rejected_signals': 20,
'final_balance': 1000, 'final_balance': 1000,
} }
]) ])

View File

@ -441,6 +441,7 @@ def test_hyperopt_format_results(hyperopt):
'config': hyperopt.config, 'config': hyperopt.config,
'locks': [], 'locks': [],
'final_balance': 0.02, 'final_balance': 0.02,
'rejected_signals': 2,
'backtest_start_time': 1619718665, 'backtest_start_time': 1619718665,
'backtest_end_time': 1619718665, 'backtest_end_time': 1619718665,
} }
@ -593,6 +594,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
}), }),
'config': hyperopt_conf, 'config': hyperopt_conf,
'locks': [], 'locks': [],
'rejected_signals': 20,
'final_balance': 1000, 'final_balance': 1000,
} }

View File

@ -79,6 +79,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
'config': default_conf, 'config': default_conf,
'locks': [], 'locks': [],
'final_balance': 1000.02, 'final_balance': 1000.02,
'rejected_signals': 20,
'backtest_start_time': Arrow.utcnow().int_timestamp, 'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp, 'backtest_end_time': Arrow.utcnow().int_timestamp,
} }
@ -126,6 +127,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
'config': default_conf, 'config': default_conf,
'locks': [], 'locks': [],
'final_balance': 1000.02, 'final_balance': 1000.02,
'rejected_signals': 20,
'backtest_start_time': Arrow.utcnow().int_timestamp, 'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp, 'backtest_end_time': Arrow.utcnow().int_timestamp,
} }