Color 期期 preview PnL and show RR vs full premium loss.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,6 +4,25 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-19 · 期期情景测算:盈亏配色 + 盈亏比(亏=全额保费)
|
||||
|
||||
### 修改原因
|
||||
|
||||
情景弹窗合计无红绿区分;需要一眼看盈亏,并按「最大亏损=权利金全亏」给出上破/下破盈亏比。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `hedge_plan.js` | 合计/腿盈亏用 `hp-pnl-pos/neg`;摘要显示盈亏比 |
|
||||
| `hedge_plan_calc_lib.py` | summary 增加 `rr_at_up` / `rr_at_down` / `rr_risk_premium` |
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
正数为绿、负数为红;摘要可见「盈亏比 上破 x:1 / 下破 y:1(亏=全额保费)」。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-19 · 修复期期「按张数」拆分:用同张数总张数 2n
|
||||
|
||||
### 修改原因
|
||||
|
||||
+2
-1
@@ -149,7 +149,8 @@
|
||||
### 3.5 情景测算弹窗
|
||||
|
||||
测算不再占页面下方大块区域:点 **计算** 弹出结果,底部 **启动计划 / 取消**。
|
||||
取消只关窗;启动按当前参数真实下单(仍受门禁约束)。
|
||||
取消只关窗;启动按当前参数真实下单(仍受门禁约束)。
|
||||
期期弹窗:合计盈亏绿/红配色;摘要显示盈亏比(盈利÷全额保费,亏损按权利金全亏计)。
|
||||
|
||||
### 3.6 进行中 / 历史 / 统计
|
||||
|
||||
|
||||
@@ -121,6 +121,24 @@
|
||||
return '<span class="opt-moneyness opt-moneyness-' + m + '">' + label + "</span>";
|
||||
}
|
||||
|
||||
function pnlClass(v) {
|
||||
if (v == null || v === "") return "";
|
||||
const n = Number(v);
|
||||
if (Number.isNaN(n)) return "";
|
||||
return n >= 0 ? "hp-pnl-pos" : "hp-pnl-neg";
|
||||
}
|
||||
|
||||
function fmtPnlHtml(v, digits) {
|
||||
if (v == null || v === "" || Number.isNaN(Number(v))) return "—";
|
||||
const cls = pnlClass(v);
|
||||
return '<strong class="' + cls + '">' + fmt(v, digits) + "</strong>";
|
||||
}
|
||||
|
||||
function fmtRr(v) {
|
||||
if (v == null || Number.isNaN(Number(v))) return "—";
|
||||
return fmt(v, 2) + ":1";
|
||||
}
|
||||
|
||||
function matchesMoneyFilter(c) {
|
||||
const f = state.moneyFilter || "all";
|
||||
if (f === "all") return true;
|
||||
@@ -997,21 +1015,35 @@
|
||||
if (summary) {
|
||||
if (d.plan_type === "perp_options") {
|
||||
summary.innerHTML =
|
||||
"止盈合计 <strong>" +
|
||||
fmt(s.tp_total) +
|
||||
"</strong> · 止损合计 <strong>" +
|
||||
fmt(s.sl_total) +
|
||||
"</strong> · 保费 " +
|
||||
"止盈合计 " +
|
||||
fmtPnlHtml(s.tp_total) +
|
||||
" · 止损合计 " +
|
||||
fmtPnlHtml(s.sl_total) +
|
||||
" · 保费 " +
|
||||
fmt(s.premium_paid) +
|
||||
(s.hedge_ratio_at_sl != null ? " · 止损对冲率 " + fmt(s.hedge_ratio_at_sl) + "%" : "");
|
||||
} else {
|
||||
const upTot = s.at_target_up_total != null ? s.at_target_up_total : s.at_target_total;
|
||||
const dnTot = s.at_target_down_total;
|
||||
let rrLine = "";
|
||||
if (s.rr_at_up != null || s.rr_at_down != null) {
|
||||
rrLine =
|
||||
" · 盈亏比 上破 " +
|
||||
fmtRr(s.rr_at_up) +
|
||||
(dnTot != null ? " / 下破 " + fmtRr(s.rr_at_down) : "") +
|
||||
'<span class="muted">(亏=全额保费 ' +
|
||||
fmt(s.rr_risk_premium != null ? s.rr_risk_premium : s.premium_paid) +
|
||||
")</span>";
|
||||
}
|
||||
summary.innerHTML =
|
||||
"目标价合计 <strong>" +
|
||||
fmt(s.at_target_total) +
|
||||
"</strong> · 到期现价 <strong>" +
|
||||
fmt(s.expiry_flat_total) +
|
||||
"</strong> · 保费 " +
|
||||
"上破 " +
|
||||
fmtPnlHtml(upTot) +
|
||||
(dnTot != null ? " · 下破 " + fmtPnlHtml(dnTot) : "") +
|
||||
" · 到期现价 " +
|
||||
fmtPnlHtml(s.expiry_flat_total) +
|
||||
" · 保费 " +
|
||||
fmt(s.premium_paid) +
|
||||
rrLine +
|
||||
(s.expiry_is_loss ? " · 到期无盈利(记总亏损)" : "");
|
||||
}
|
||||
}
|
||||
@@ -1021,11 +1053,11 @@
|
||||
const tr = document.createElement("tr");
|
||||
let mid;
|
||||
if (sc.perp_pnl != null) {
|
||||
mid = "永续 " + fmt(sc.perp_pnl);
|
||||
mid = "永续 " + fmtPnlHtml(sc.perp_pnl);
|
||||
} else {
|
||||
mid = "A " + fmt(sc.leg_a_pnl) + " / B " + fmt(sc.leg_b_pnl);
|
||||
mid = "A " + fmtPnlHtml(sc.leg_a_pnl) + " / B " + fmtPnlHtml(sc.leg_b_pnl);
|
||||
}
|
||||
const optCol = sc.options_pnl != null ? fmt(sc.options_pnl) : "—";
|
||||
const optCol = sc.options_pnl != null ? fmtPnlHtml(sc.options_pnl) : "—";
|
||||
tr.innerHTML =
|
||||
"<td>" +
|
||||
(sc.label || sc.id) +
|
||||
@@ -1035,9 +1067,9 @@
|
||||
mid +
|
||||
"</td><td>" +
|
||||
optCol +
|
||||
"</td><td><strong>" +
|
||||
fmt(sc.total) +
|
||||
"</strong></td><td class=\"muted\">" +
|
||||
"</td><td>" +
|
||||
fmtPnlHtml(sc.total) +
|
||||
"</td><td class=\"muted\">" +
|
||||
(sc.note || "") +
|
||||
"</td>";
|
||||
tbody.appendChild(tr);
|
||||
|
||||
@@ -541,6 +541,10 @@ def build_options_options_preview(
|
||||
"expiry_flat_total": round(expiry_loss, 4),
|
||||
"premium_paid": round(prem, 6),
|
||||
"expiry_is_loss": flat_total <= 0,
|
||||
# 盈亏比:盈利/全亏保费(风险=权利金全损)
|
||||
"rr_risk_premium": round(prem, 6),
|
||||
"rr_at_up": round(at_up / prem, 4) if prem > 0 else None,
|
||||
"rr_at_down": round(at_dn / prem, 4) if prem > 0 else None,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -301,4 +301,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/hedge_plan.js?v=24"></script>
|
||||
<script src="/static/hedge_plan.js?v=25"></script>
|
||||
|
||||
@@ -110,6 +110,9 @@ class TestHedgePlanCalc(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(p["summary"]["premium_paid"], 10)
|
||||
self.assertTrue(p["summary"]["expiry_is_loss"])
|
||||
self.assertEqual(p["summary"]["rr_risk_premium"], 10)
|
||||
self.assertIsNotNone(p["summary"]["rr_at_up"])
|
||||
self.assertAlmostEqual(p["summary"]["rr_at_up"], p["summary"]["at_target_up_total"] / 10, places=4)
|
||||
self.assertEqual(len(p["scenarios"]), 4)
|
||||
self.assertEqual(p["scenarios"][0]["id"], "target_up")
|
||||
self.assertEqual(p["scenarios"][1]["id"], "target_down")
|
||||
|
||||
Reference in New Issue
Block a user