feat(backtester): extend compute_metrics with alpha/beta/winners/best
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was canceled

In-place extension (no fork). Existing tests still pass; new fields are
optional and None when no benchmark is supplied.
This commit is contained in:
Viktor Barzin 2026-05-24 00:57:42 +00:00
parent 23ce45a4f2
commit cd75c4ab7e
2 changed files with 121 additions and 3 deletions

View file

@ -185,9 +185,7 @@ def _populate_dict_trade_aggregates(
if not trade_log:
return
closed = [
t for t in trade_log if t.get("pnl_pct") is not None
]
closed = [t for t in trade_log if t.get("pnl_pct") is not None]
if not closed:
return
@ -203,6 +201,27 @@ def _populate_dict_trade_aggregates(
elif avg_win > 0:
result.avg_win_loss_ratio = float("inf")
# Kevin extensions: winners / losers / best / worst (use Decimal pnl_pct if present)
winners_d = [t for t in closed if Decimal(str(t["pnl_pct"])) > 0]
losers_d = [t for t in closed if Decimal(str(t["pnl_pct"])) <= 0]
if winners_d:
total = sum(Decimal(str(t["pnl_pct"])) for t in winners_d)
result.avg_winner_pct = total / Decimal(len(winners_d))
if losers_d:
total_l = sum(Decimal(str(t["pnl_pct"])) for t in losers_d)
result.avg_loser_pct = total_l / Decimal(len(losers_d))
if closed:
best = max(closed, key=lambda t: Decimal(str(t["pnl_pct"])))
worst = min(closed, key=lambda t: Decimal(str(t["pnl_pct"])))
result.best_trade = {
"symbol": best["symbol"],
"pnl_pct": Decimal(str(best["pnl_pct"])),
}
result.worst_trade = {
"symbol": worst["symbol"],
"pnl_pct": Decimal(str(worst["pnl_pct"])),
}
def _populate_benchmark_metrics(
result: BacktestResult,