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 <cursoragent@cursor.com>
This commit is contained in:
@@ -800,12 +800,12 @@
|
|||||||
}
|
}
|
||||||
if (closedEl) closedEl.textContent = String(d.total_closed || 0);
|
if (closedEl) closedEl.textContent = String(d.total_closed || 0);
|
||||||
if (profitEl) {
|
if (profitEl) {
|
||||||
profitEl.textContent = d.total_profit != null && d.total_profit > 0
|
profitEl.textContent = d.avg_win != null && d.avg_win > 0
|
||||||
? fmt(d.total_profit, 2) + " USDC" : (d.total_closed ? "0 USDC" : "—");
|
? fmt(d.avg_win, 2) + " USDC" : (d.win_count ? "0 USDC" : "—");
|
||||||
}
|
}
|
||||||
if (lossEl) {
|
if (lossEl) {
|
||||||
lossEl.textContent = d.total_loss != null && d.total_loss > 0
|
lossEl.textContent = d.avg_loss != null && d.avg_loss > 0
|
||||||
? fmt(d.total_loss, 2) + " USDC" : (d.total_closed ? "0 USDC" : "—");
|
? fmt(d.avg_loss, 2) + " USDC" : (d.loss_count ? "0 USDC" : "—");
|
||||||
}
|
}
|
||||||
if (avgHoldEl) avgHoldEl.textContent = fmtDuration(d.avg_hold_sec);
|
if (avgHoldEl) avgHoldEl.textContent = fmtDuration(d.avg_hold_sec);
|
||||||
if (winHoldEl) winHoldEl.textContent = fmtDuration(d.avg_win_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 (ring) ring.style.setProperty("--win-pct", String(winRate));
|
||||||
if (ringLabel) ringLabel.textContent = d.total_closed ? winRate.toFixed(0) + "%" : "0%";
|
if (ringLabel) ringLabel.textContent = d.total_closed ? winRate.toFixed(0) + "%" : "0%";
|
||||||
|
|
||||||
const profit = Math.max(0, Number(d.total_profit) || 0);
|
const profit = Math.max(0, Number(d.avg_win) || 0);
|
||||||
const loss = Math.max(0, Number(d.total_loss) || 0);
|
const loss = Math.max(0, Number(d.avg_loss) || 0);
|
||||||
const pnlTotal = profit + loss;
|
const pnlTotal = profit + loss;
|
||||||
if (pnlTotal > 0) {
|
if (pnlTotal > 0) {
|
||||||
setBarFill(profitBar, (profit / pnlTotal) * 100);
|
setBarFill(profitBar, (profit / pnlTotal) * 100);
|
||||||
@@ -879,8 +879,8 @@
|
|||||||
} else {
|
} else {
|
||||||
setBarFill(profitBar, 0);
|
setBarFill(profitBar, 0);
|
||||||
setBarFill(lossBar, 0);
|
setBarFill(lossBar, 0);
|
||||||
if (profitBarLabel) profitBarLabel.textContent = d.total_closed ? "0 USDC" : "—";
|
if (profitBarLabel) profitBarLabel.textContent = d.win_count ? "0 USDC" : "—";
|
||||||
if (lossBarLabel) lossBarLabel.textContent = d.total_closed ? "0 USDC" : "—";
|
if (lossBarLabel) lossBarLabel.textContent = d.loss_count ? "0 USDC" : "—";
|
||||||
}
|
}
|
||||||
|
|
||||||
const winHold = Number(d.avg_win_hold_sec) || 0;
|
const winHold = Number(d.avg_win_hold_sec) || 0;
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ def compute_options_stats(get_db) -> dict[str, Any]:
|
|||||||
"loss_count": len(losses),
|
"loss_count": len(losses),
|
||||||
"win_rate": win_rate,
|
"win_rate": win_rate,
|
||||||
"profit_loss_ratio": profit_loss_ratio_from_averages(avg_win, avg_loss),
|
"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_profit": round(sum(wins), 4) if wins else 0.0,
|
||||||
"total_loss": round(abs(sum(losses)), 4) if losses else 0.0,
|
"total_loss": round(abs(sum(losses)), 4) if losses else 0.0,
|
||||||
"avg_hold_sec": _avg_seconds(all_holds),
|
"avg_hold_sec": _avg_seconds(all_holds),
|
||||||
|
|||||||
@@ -115,14 +115,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="opt-stats-chart opt-stats-chart--pnl">
|
<div class="opt-stats-chart opt-stats-chart--pnl">
|
||||||
<div class="opt-stats-bar-row">
|
<div class="opt-stats-bar-row">
|
||||||
<span class="k">盈利</span>
|
<span class="k">平均盈利</span>
|
||||||
<div class="opt-stats-bar-track">
|
<div class="opt-stats-bar-track">
|
||||||
<div class="opt-stats-bar-fill opt-stats-bar-fill--profit" id="opt-stats-bar-profit"></div>
|
<div class="opt-stats-bar-fill opt-stats-bar-fill--profit" id="opt-stats-bar-profit"></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="v pos-pnl-profit" id="opt-stats-bar-profit-label">—</span>
|
<span class="v pos-pnl-profit" id="opt-stats-bar-profit-label">—</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="opt-stats-bar-row">
|
<div class="opt-stats-bar-row">
|
||||||
<span class="k">亏损</span>
|
<span class="k">平均亏损</span>
|
||||||
<div class="opt-stats-bar-track">
|
<div class="opt-stats-bar-track">
|
||||||
<div class="opt-stats-bar-fill opt-stats-bar-fill--loss" id="opt-stats-bar-loss"></div>
|
<div class="opt-stats-bar-fill opt-stats-bar-fill--loss" id="opt-stats-bar-loss"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,11 +161,11 @@
|
|||||||
<span class="v" id="opt-stats-closed">—</span>
|
<span class="v" id="opt-stats-closed">—</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="options-stat-item">
|
<div class="options-stat-item">
|
||||||
<span class="k">盈利</span>
|
<span class="k">平均盈利</span>
|
||||||
<span class="v pos-pnl-profit" id="opt-stats-profit">—</span>
|
<span class="v pos-pnl-profit" id="opt-stats-profit">—</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="options-stat-item">
|
<div class="options-stat-item">
|
||||||
<span class="k">亏损</span>
|
<span class="k">平均亏损</span>
|
||||||
<span class="v pos-pnl-loss" id="opt-stats-loss">—</span>
|
<span class="v pos-pnl-loss" id="opt-stats-loss">—</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="options-stat-item">
|
<div class="options-stat-item">
|
||||||
@@ -212,4 +212,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
||||||
<script src="/static/options_panel.js?v=25"></script>
|
<script src="/static/options_panel.js?v=26"></script>
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ class OptionsStatsLibTests(TestCase):
|
|||||||
self.assertEqual(out["win_count"], 1)
|
self.assertEqual(out["win_count"], 1)
|
||||||
self.assertEqual(out["loss_count"], 1)
|
self.assertEqual(out["loss_count"], 1)
|
||||||
self.assertEqual(out["win_rate"], 50.0)
|
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_win_hold_sec"], 3600.0, delta=5.0)
|
||||||
self.assertAlmostEqual(out["avg_loss_hold_sec"], 3 * 3600.0, delta=5.0)
|
self.assertAlmostEqual(out["avg_loss_hold_sec"], 3 * 3600.0, delta=5.0)
|
||||||
self.assertEqual(out["open_count"], 1)
|
self.assertEqual(out["open_count"], 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user