Merge pull request #11758 from mrpabloyeah/add-max-trade-duration-to-backtest-results
Add min/max trade duration to backtest results
This commit is contained in:
@@ -370,8 +370,18 @@ def text_table_add_metrics(strat_results: dict) -> None:
|
|||||||
f"{strat_results['winning_days']} / "
|
f"{strat_results['winning_days']} / "
|
||||||
f"{strat_results['draw_days']} / {strat_results['losing_days']}",
|
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']}"),
|
"Min/Max/Avg. Duration Winners",
|
||||||
|
f"{strat_results.get('winner_holding_min', 'N/A')} / "
|
||||||
|
f"{strat_results.get('winner_holding_max', 'N/A')} / "
|
||||||
|
f"{strat_results.get('winner_holding_avg', 'N/A')}",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Min/Max/Avg. Duration Losers",
|
||||||
|
f"{strat_results.get('loser_holding_min', 'N/A')} / "
|
||||||
|
f"{strat_results.get('loser_holding_max', 'N/A')} / "
|
||||||
|
f"{strat_results.get('loser_holding_avg', 'N/A')}",
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"Max Consecutive Wins / Loss",
|
"Max Consecutive Wins / Loss",
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from freqtrade.ft_types import (
|
|||||||
BacktestResultType,
|
BacktestResultType,
|
||||||
get_BacktestResultType_default,
|
get_BacktestResultType_default,
|
||||||
)
|
)
|
||||||
from freqtrade.util import decimals_per_coin, fmt_coin, get_dry_run_wallet
|
from freqtrade.util import decimals_per_coin, fmt_coin, format_duration, get_dry_run_wallet
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -336,22 +336,44 @@ def generate_trading_stats(results: DataFrame) -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
winning_trades = results.loc[results["profit_ratio"] > 0]
|
winning_trades = results.loc[results["profit_ratio"] > 0]
|
||||||
|
winning_duration = winning_trades["trade_duration"]
|
||||||
draw_trades = results.loc[results["profit_ratio"] == 0]
|
draw_trades = results.loc[results["profit_ratio"] == 0]
|
||||||
losing_trades = results.loc[results["profit_ratio"] < 0]
|
losing_trades = results.loc[results["profit_ratio"] < 0]
|
||||||
|
losing_duration = losing_trades["trade_duration"]
|
||||||
|
|
||||||
holding_avg = (
|
holding_avg = (
|
||||||
timedelta(minutes=round(results["trade_duration"].mean()))
|
timedelta(minutes=round(results["trade_duration"].mean()))
|
||||||
if not results.empty
|
if not results.empty
|
||||||
else timedelta()
|
else timedelta()
|
||||||
)
|
)
|
||||||
|
winner_holding_min = (
|
||||||
|
timedelta(minutes=round(winning_duration[winning_duration > 0].min()))
|
||||||
|
if not winning_duration.empty
|
||||||
|
else timedelta()
|
||||||
|
)
|
||||||
|
winner_holding_max = (
|
||||||
|
timedelta(minutes=round(winning_duration.max()))
|
||||||
|
if not winning_duration.empty
|
||||||
|
else timedelta()
|
||||||
|
)
|
||||||
winner_holding_avg = (
|
winner_holding_avg = (
|
||||||
timedelta(minutes=round(winning_trades["trade_duration"].mean()))
|
timedelta(minutes=round(winning_duration.mean()))
|
||||||
if not winning_trades.empty
|
if not winning_duration.empty
|
||||||
|
else timedelta()
|
||||||
|
)
|
||||||
|
loser_holding_min = (
|
||||||
|
timedelta(minutes=round(losing_duration[losing_duration > 0].min()))
|
||||||
|
if not losing_duration.empty
|
||||||
|
else timedelta()
|
||||||
|
)
|
||||||
|
loser_holding_max = (
|
||||||
|
timedelta(minutes=round(losing_duration.max()))
|
||||||
|
if not losing_duration.empty
|
||||||
else timedelta()
|
else timedelta()
|
||||||
)
|
)
|
||||||
loser_holding_avg = (
|
loser_holding_avg = (
|
||||||
timedelta(minutes=round(losing_trades["trade_duration"].mean()))
|
timedelta(minutes=round(losing_duration.mean()))
|
||||||
if not losing_trades.empty
|
if not losing_duration.empty
|
||||||
else timedelta()
|
else timedelta()
|
||||||
)
|
)
|
||||||
winstreak, loss_streak = calc_streak(results)
|
winstreak, loss_streak = calc_streak(results)
|
||||||
@@ -363,9 +385,17 @@ def generate_trading_stats(results: DataFrame) -> dict[str, Any]:
|
|||||||
"winrate": len(winning_trades) / len(results) if len(results) else 0.0,
|
"winrate": len(winning_trades) / len(results) if len(results) else 0.0,
|
||||||
"holding_avg": holding_avg,
|
"holding_avg": holding_avg,
|
||||||
"holding_avg_s": holding_avg.total_seconds(),
|
"holding_avg_s": holding_avg.total_seconds(),
|
||||||
"winner_holding_avg": winner_holding_avg,
|
"winner_holding_min": format_duration(winner_holding_min),
|
||||||
|
"winner_holding_min_s": winner_holding_min.total_seconds(),
|
||||||
|
"winner_holding_max": format_duration(winner_holding_max),
|
||||||
|
"winner_holding_max_s": winner_holding_max.total_seconds(),
|
||||||
|
"winner_holding_avg": format_duration(winner_holding_avg),
|
||||||
"winner_holding_avg_s": winner_holding_avg.total_seconds(),
|
"winner_holding_avg_s": winner_holding_avg.total_seconds(),
|
||||||
"loser_holding_avg": loser_holding_avg,
|
"loser_holding_min": format_duration(loser_holding_min),
|
||||||
|
"loser_holding_min_s": loser_holding_min.total_seconds(),
|
||||||
|
"loser_holding_max": format_duration(loser_holding_max),
|
||||||
|
"loser_holding_max_s": loser_holding_max.total_seconds(),
|
||||||
|
"loser_holding_avg": format_duration(loser_holding_avg),
|
||||||
"loser_holding_avg_s": loser_holding_avg.total_seconds(),
|
"loser_holding_avg_s": loser_holding_avg.total_seconds(),
|
||||||
"max_consecutive_wins": winstreak,
|
"max_consecutive_wins": winstreak,
|
||||||
"max_consecutive_losses": loss_streak,
|
"max_consecutive_losses": loss_streak,
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ from freqtrade.util.datetime_helpers import (
|
|||||||
shorten_date,
|
shorten_date,
|
||||||
)
|
)
|
||||||
from freqtrade.util.dry_run_wallet import get_dry_run_wallet
|
from freqtrade.util.dry_run_wallet import get_dry_run_wallet
|
||||||
from freqtrade.util.formatters import decimals_per_coin, fmt_coin, fmt_coin2, round_value
|
from freqtrade.util.formatters import (
|
||||||
|
decimals_per_coin,
|
||||||
|
fmt_coin,
|
||||||
|
fmt_coin2,
|
||||||
|
format_duration,
|
||||||
|
round_value,
|
||||||
|
)
|
||||||
from freqtrade.util.ft_precise import FtPrecise
|
from freqtrade.util.ft_precise import FtPrecise
|
||||||
from freqtrade.util.measure_time import MeasureTime
|
from freqtrade.util.measure_time import MeasureTime
|
||||||
from freqtrade.util.periodic_cache import PeriodicCache
|
from freqtrade.util.periodic_cache import PeriodicCache
|
||||||
@@ -44,6 +50,7 @@ __all__ = [
|
|||||||
"shorten_date",
|
"shorten_date",
|
||||||
"decimals_per_coin",
|
"decimals_per_coin",
|
||||||
"round_value",
|
"round_value",
|
||||||
|
"format_duration",
|
||||||
"fmt_coin",
|
"fmt_coin",
|
||||||
"fmt_coin2",
|
"fmt_coin2",
|
||||||
"MeasureTime",
|
"MeasureTime",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from freqtrade.constants import DECIMAL_PER_COIN_FALLBACK, DECIMALS_PER_COIN
|
from freqtrade.constants import DECIMAL_PER_COIN_FALLBACK, DECIMALS_PER_COIN
|
||||||
|
|
||||||
|
|
||||||
@@ -66,3 +68,15 @@ def fmt_coin2(
|
|||||||
val = f"{val} {coin}"
|
val = f"{val} {coin}"
|
||||||
|
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def format_duration(td: timedelta) -> str:
|
||||||
|
"""
|
||||||
|
Format a timedelta object to "XXd HH:MM" format
|
||||||
|
:param td: Timedelta object to format
|
||||||
|
:return: Formatted time string
|
||||||
|
"""
|
||||||
|
d = td.days
|
||||||
|
h, r = divmod(td.seconds, 3600)
|
||||||
|
m, s = divmod(r, 60)
|
||||||
|
return f"{d}d {h:02d}:{m:02d}"
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ from freqtrade.optimize.optimize_reports.optimize_reports import (
|
|||||||
generate_tag_metrics,
|
generate_tag_metrics,
|
||||||
)
|
)
|
||||||
from freqtrade.resolvers.strategy_resolver import StrategyResolver
|
from freqtrade.resolvers.strategy_resolver import StrategyResolver
|
||||||
from freqtrade.util import dt_ts
|
from freqtrade.util import dt_ts, format_duration
|
||||||
from freqtrade.util.datetime_helpers import dt_from_ts, dt_utc
|
from freqtrade.util.datetime_helpers import dt_from_ts, dt_utc
|
||||||
from tests.conftest import CURRENT_TEST_STRATEGY, log_has_re
|
from tests.conftest import CURRENT_TEST_STRATEGY, log_has_re
|
||||||
from tests.data.test_history import _clean_test_file
|
from tests.data.test_history import _clean_test_file
|
||||||
@@ -482,8 +482,8 @@ def test_generate_trading_stats(testdatadir):
|
|||||||
bt_data = load_backtest_data(filename)
|
bt_data = load_backtest_data(filename)
|
||||||
res = generate_trading_stats(bt_data)
|
res = generate_trading_stats(bt_data)
|
||||||
assert isinstance(res, dict)
|
assert isinstance(res, dict)
|
||||||
assert res["winner_holding_avg"] == timedelta(seconds=1440)
|
assert res["winner_holding_avg"] == format_duration(timedelta(seconds=1440))
|
||||||
assert res["loser_holding_avg"] == timedelta(days=1, seconds=21420)
|
assert res["loser_holding_avg"] == format_duration(timedelta(days=1, seconds=21420))
|
||||||
assert "wins" in res
|
assert "wins" in res
|
||||||
assert "losses" in res
|
assert "losses" in res
|
||||||
assert "draws" in res
|
assert "draws" in res
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from freqtrade.util import decimals_per_coin, fmt_coin, round_value
|
from datetime import timedelta
|
||||||
from freqtrade.util.formatters import fmt_coin2
|
|
||||||
|
from freqtrade.util import decimals_per_coin, fmt_coin, fmt_coin2, format_duration, round_value
|
||||||
|
|
||||||
|
|
||||||
def test_decimals_per_coin():
|
def test_decimals_per_coin():
|
||||||
@@ -45,3 +46,12 @@ def test_round_value():
|
|||||||
assert round_value(0.1274512123, 5) == "0.12745"
|
assert round_value(0.1274512123, 5) == "0.12745"
|
||||||
assert round_value(222.2, 3, True) == "222.200"
|
assert round_value(222.2, 3, True) == "222.200"
|
||||||
assert round_value(222.2, 0, True) == "222"
|
assert round_value(222.2, 0, True) == "222"
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_duration():
|
||||||
|
assert format_duration(timedelta(minutes=5)) == "0d 00:05"
|
||||||
|
assert format_duration(timedelta(minutes=75)) == "0d 01:15"
|
||||||
|
assert format_duration(timedelta(minutes=1440)) == "1d 00:00"
|
||||||
|
assert format_duration(timedelta(minutes=1445)) == "1d 00:05"
|
||||||
|
assert format_duration(timedelta(minutes=11445)) == "7d 22:45"
|
||||||
|
assert format_duration(timedelta(minutes=101445)) == "70d 10:45"
|
||||||
|
|||||||
Reference in New Issue
Block a user