From edcfa940937d0141f74b1bdd4f8161f76746fb83 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Sat, 15 May 2021 11:26:22 +0300 Subject: [PATCH] Include zero duration trades in backtesting report. --- docs/backtesting.md | 1 + freqtrade/optimize/optimize_reports.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index ee9926f32..8b3aa8cc1 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -413,6 +413,7 @@ It contains some useful key metrics about performance of your strategy on backte - `Best day` / `Worst day`: Best and worst day based on daily profit. - `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. +- `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. - `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 high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost. diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 2be3c3e62..b509f216f 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -208,6 +208,8 @@ def generate_trading_stats(results: DataFrame) -> Dict[str, Any]: winning_trades = results.loc[results['profit_ratio'] > 0] draw_trades = results.loc[results['profit_ratio'] == 0] losing_trades = results.loc[results['profit_ratio'] < 0] + zero_duration_trades = len(results.loc[(results['trade_duration'] == 0) & + (results['sell_reason'] == 'trailing_stop_loss')]) return { 'wins': len(winning_trades), @@ -219,6 +221,7 @@ def generate_trading_stats(results: DataFrame) -> Dict[str, Any]: if not winning_trades.empty else timedelta()), 'loser_holding_avg': (timedelta(minutes=round(losing_trades['trade_duration'].mean())) if not losing_trades.empty else timedelta()), + 'zero_duration_trades': zero_duration_trades, } @@ -496,6 +499,8 @@ def text_table_add_metrics(strat_results: Dict) -> str: if len(strat_results['trades']) > 0: best_trade = max(strat_results['trades'], key=lambda x: x['profit_ratio']) worst_trade = min(strat_results['trades'], key=lambda x: x['profit_ratio']) + zero_duration_trades_percent =\ + 100.0 / strat_results['total_trades'] * strat_results['zero_duration_trades'] metrics = [ ('Backtesting from', strat_results['backtest_start']), ('Backtesting to', strat_results['backtest_end']), @@ -508,7 +513,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: strat_results['stake_currency'])), ('Absolute profit ', round_coin_value(strat_results['profit_total_abs'], strat_results['stake_currency'])), - ('Total profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"), + ('Total profit %', f"{round(strat_results['profit_total'] * 100, 2):}%"), ('Trades per day', strat_results['trades_per_day']), ('Avg. stake amount', round_coin_value(strat_results['avg_stake_amount'], strat_results['stake_currency'])), @@ -532,6 +537,8 @@ def text_table_add_metrics(strat_results: Dict) -> str: f"{strat_results['draw_days']} / {strat_results['losing_days']}"), ('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"), ('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"), + ('Zero Duration Trades', f"{zero_duration_trades_percent:.1f}% " + f"({strat_results['zero_duration_trades']})"), ('', ''), # Empty line to improve readability ('Min balance', round_coin_value(strat_results['csum_min'],