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
|
## 2026-07-19 · 修复期期「按张数」拆分:用同张数总张数 2n
|
||||||
|
|
||||||
### 修改原因
|
### 修改原因
|
||||||
|
|||||||
@@ -150,6 +150,7 @@
|
|||||||
|
|
||||||
测算不再占页面下方大块区域:点 **计算** 弹出结果,底部 **启动计划 / 取消**。
|
测算不再占页面下方大块区域:点 **计算** 弹出结果,底部 **启动计划 / 取消**。
|
||||||
取消只关窗;启动按当前参数真实下单(仍受门禁约束)。
|
取消只关窗;启动按当前参数真实下单(仍受门禁约束)。
|
||||||
|
期期弹窗:合计盈亏绿/红配色;摘要显示盈亏比(盈利÷全额保费,亏损按权利金全亏计)。
|
||||||
|
|
||||||
### 3.6 进行中 / 历史 / 统计
|
### 3.6 进行中 / 历史 / 统计
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,24 @@
|
|||||||
return '<span class="opt-moneyness opt-moneyness-' + m + '">' + label + "</span>";
|
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) {
|
function matchesMoneyFilter(c) {
|
||||||
const f = state.moneyFilter || "all";
|
const f = state.moneyFilter || "all";
|
||||||
if (f === "all") return true;
|
if (f === "all") return true;
|
||||||
@@ -997,21 +1015,35 @@
|
|||||||
if (summary) {
|
if (summary) {
|
||||||
if (d.plan_type === "perp_options") {
|
if (d.plan_type === "perp_options") {
|
||||||
summary.innerHTML =
|
summary.innerHTML =
|
||||||
"止盈合计 <strong>" +
|
"止盈合计 " +
|
||||||
fmt(s.tp_total) +
|
fmtPnlHtml(s.tp_total) +
|
||||||
"</strong> · 止损合计 <strong>" +
|
" · 止损合计 " +
|
||||||
fmt(s.sl_total) +
|
fmtPnlHtml(s.sl_total) +
|
||||||
"</strong> · 保费 " +
|
" · 保费 " +
|
||||||
fmt(s.premium_paid) +
|
fmt(s.premium_paid) +
|
||||||
(s.hedge_ratio_at_sl != null ? " · 止损对冲率 " + fmt(s.hedge_ratio_at_sl) + "%" : "");
|
(s.hedge_ratio_at_sl != null ? " · 止损对冲率 " + fmt(s.hedge_ratio_at_sl) + "%" : "");
|
||||||
} else {
|
} 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 =
|
summary.innerHTML =
|
||||||
"目标价合计 <strong>" +
|
"上破 " +
|
||||||
fmt(s.at_target_total) +
|
fmtPnlHtml(upTot) +
|
||||||
"</strong> · 到期现价 <strong>" +
|
(dnTot != null ? " · 下破 " + fmtPnlHtml(dnTot) : "") +
|
||||||
fmt(s.expiry_flat_total) +
|
" · 到期现价 " +
|
||||||
"</strong> · 保费 " +
|
fmtPnlHtml(s.expiry_flat_total) +
|
||||||
|
" · 保费 " +
|
||||||
fmt(s.premium_paid) +
|
fmt(s.premium_paid) +
|
||||||
|
rrLine +
|
||||||
(s.expiry_is_loss ? " · 到期无盈利(记总亏损)" : "");
|
(s.expiry_is_loss ? " · 到期无盈利(记总亏损)" : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1021,11 +1053,11 @@
|
|||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
let mid;
|
let mid;
|
||||||
if (sc.perp_pnl != null) {
|
if (sc.perp_pnl != null) {
|
||||||
mid = "永续 " + fmt(sc.perp_pnl);
|
mid = "永续 " + fmtPnlHtml(sc.perp_pnl);
|
||||||
} else {
|
} 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 =
|
tr.innerHTML =
|
||||||
"<td>" +
|
"<td>" +
|
||||||
(sc.label || sc.id) +
|
(sc.label || sc.id) +
|
||||||
@@ -1035,9 +1067,9 @@
|
|||||||
mid +
|
mid +
|
||||||
"</td><td>" +
|
"</td><td>" +
|
||||||
optCol +
|
optCol +
|
||||||
"</td><td><strong>" +
|
"</td><td>" +
|
||||||
fmt(sc.total) +
|
fmtPnlHtml(sc.total) +
|
||||||
"</strong></td><td class=\"muted\">" +
|
"</td><td class=\"muted\">" +
|
||||||
(sc.note || "") +
|
(sc.note || "") +
|
||||||
"</td>";
|
"</td>";
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
|
|||||||
@@ -541,6 +541,10 @@ def build_options_options_preview(
|
|||||||
"expiry_flat_total": round(expiry_loss, 4),
|
"expiry_flat_total": round(expiry_loss, 4),
|
||||||
"premium_paid": round(prem, 6),
|
"premium_paid": round(prem, 6),
|
||||||
"expiry_is_loss": flat_total <= 0,
|
"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>
|
</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.assertEqual(p["summary"]["premium_paid"], 10)
|
||||||
self.assertTrue(p["summary"]["expiry_is_loss"])
|
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(len(p["scenarios"]), 4)
|
||||||
self.assertEqual(p["scenarios"][0]["id"], "target_up")
|
self.assertEqual(p["scenarios"][0]["id"], "target_up")
|
||||||
self.assertEqual(p["scenarios"][1]["id"], "target_down")
|
self.assertEqual(p["scenarios"][1]["id"], "target_down")
|
||||||
|
|||||||
Reference in New Issue
Block a user