From 68733eb4f98454e512c892ed5982c307361e6115 Mon Sep 17 00:00:00 2001 From: dekun Date: Sat, 11 Jul 2026 09:29:26 +0800 Subject: [PATCH] Show average profit and loss in option stats panel. Expose avg_win and avg_loss from stats API and update chart labels and values to match the average-based P/L ratio. Co-authored-by: Cursor --- lib/common/static/options_panel.js | 16 ++++++++-------- lib/options/options_stats_lib.py | 2 ++ lib/options/templates/options_panel.html | 10 +++++----- tests/test_options_stats_lib.py | 2 ++ 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index c12b3ad..d163871 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -800,12 +800,12 @@ } if (closedEl) closedEl.textContent = String(d.total_closed || 0); if (profitEl) { - profitEl.textContent = d.total_profit != null && d.total_profit > 0 - ? fmt(d.total_profit, 2) + " USDC" : (d.total_closed ? "0 USDC" : "—"); + profitEl.textContent = d.avg_win != null && d.avg_win > 0 + ? fmt(d.avg_win, 2) + " USDC" : (d.win_count ? "0 USDC" : "—"); } if (lossEl) { - lossEl.textContent = d.total_loss != null && d.total_loss > 0 - ? fmt(d.total_loss, 2) + " USDC" : (d.total_closed ? "0 USDC" : "—"); + lossEl.textContent = d.avg_loss != null && d.avg_loss > 0 + ? fmt(d.avg_loss, 2) + " USDC" : (d.loss_count ? "0 USDC" : "—"); } if (avgHoldEl) avgHoldEl.textContent = fmtDuration(d.avg_hold_sec); if (winHoldEl) winHoldEl.textContent = fmtDuration(d.avg_win_hold_sec); @@ -868,8 +868,8 @@ if (ring) ring.style.setProperty("--win-pct", String(winRate)); if (ringLabel) ringLabel.textContent = d.total_closed ? winRate.toFixed(0) + "%" : "0%"; - const profit = Math.max(0, Number(d.total_profit) || 0); - const loss = Math.max(0, Number(d.total_loss) || 0); + const profit = Math.max(0, Number(d.avg_win) || 0); + const loss = Math.max(0, Number(d.avg_loss) || 0); const pnlTotal = profit + loss; if (pnlTotal > 0) { setBarFill(profitBar, (profit / pnlTotal) * 100); @@ -879,8 +879,8 @@ } else { setBarFill(profitBar, 0); setBarFill(lossBar, 0); - if (profitBarLabel) profitBarLabel.textContent = d.total_closed ? "0 USDC" : "—"; - if (lossBarLabel) lossBarLabel.textContent = d.total_closed ? "0 USDC" : "—"; + if (profitBarLabel) profitBarLabel.textContent = d.win_count ? "0 USDC" : "—"; + if (lossBarLabel) lossBarLabel.textContent = d.loss_count ? "0 USDC" : "—"; } const winHold = Number(d.avg_win_hold_sec) || 0; diff --git a/lib/options/options_stats_lib.py b/lib/options/options_stats_lib.py index fd81e9c..41e4bb3 100644 --- a/lib/options/options_stats_lib.py +++ b/lib/options/options_stats_lib.py @@ -93,6 +93,8 @@ def compute_options_stats(get_db) -> dict[str, Any]: "loss_count": len(losses), "win_rate": win_rate, "profit_loss_ratio": profit_loss_ratio_from_averages(avg_win, avg_loss), + "avg_win": round(avg_win, 4) if avg_win is not None else None, + "avg_loss": round(abs(avg_loss), 4) if avg_loss is not None else None, "total_profit": round(sum(wins), 4) if wins else 0.0, "total_loss": round(abs(sum(losses)), 4) if losses else 0.0, "avg_hold_sec": _avg_seconds(all_holds), diff --git a/lib/options/templates/options_panel.html b/lib/options/templates/options_panel.html index ff1d4f1..24a0d77 100644 --- a/lib/options/templates/options_panel.html +++ b/lib/options/templates/options_panel.html @@ -115,14 +115,14 @@
- 盈利 + 平均盈利
- 亏损 + 平均亏损
@@ -161,11 +161,11 @@
- 盈利 + 平均盈利
- 亏损 + 平均亏损
@@ -212,4 +212,4 @@
- + diff --git a/tests/test_options_stats_lib.py b/tests/test_options_stats_lib.py index 81e9119..af254cf 100644 --- a/tests/test_options_stats_lib.py +++ b/tests/test_options_stats_lib.py @@ -61,6 +61,8 @@ class OptionsStatsLibTests(TestCase): self.assertEqual(out["win_count"], 1) self.assertEqual(out["loss_count"], 1) self.assertEqual(out["win_rate"], 50.0) + self.assertAlmostEqual(out["avg_win"], 1.2, places=4) + self.assertAlmostEqual(out["avg_loss"], 1.0, places=4) self.assertAlmostEqual(out["avg_win_hold_sec"], 3600.0, delta=5.0) self.assertAlmostEqual(out["avg_loss_hold_sec"], 3 * 3600.0, delta=5.0) self.assertEqual(out["open_count"], 1)