Split semi summary into option target, perp lock, and sizing lines.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+76
-139
@@ -1079,7 +1079,7 @@ export default function PlanPage() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mono meta plan-semi-summary">
|
||||
<div className="mono plan-semi-lines">
|
||||
{(() => {
|
||||
const shown = filterSemiLadderRows(
|
||||
ladder?.rows || [],
|
||||
@@ -1095,34 +1095,51 @@ export default function PlanPage() {
|
||||
? Number(snap.index_px)
|
||||
: null;
|
||||
const strike =
|
||||
pick?.strike != null
|
||||
? Number(pick.strike)
|
||||
: ladder?.atm_strike != null
|
||||
? Number(ladder.atm_strike)
|
||||
: null;
|
||||
open && pos?.strike != null && Number.isFinite(Number(pos.strike))
|
||||
? Number(pos.strike)
|
||||
: pick?.strike != null
|
||||
? Number(pick.strike)
|
||||
: ladder?.atm_strike != null
|
||||
? Number(ladder.atm_strike)
|
||||
: null;
|
||||
const ask = pick?.ask != null ? Number(pick.ask) : null;
|
||||
const fee = Number(plan.fee_rate ?? 0.0005);
|
||||
const feeOk = Number.isFinite(fee) && fee >= 0 ? fee : 0.0005;
|
||||
const moveN = Number(semiMove) || 50;
|
||||
|
||||
let optQty = Number(semiOptU) || 4;
|
||||
let perpQty = Number(semiPerpU) || 1;
|
||||
if (riskLocked) {
|
||||
let kShow: number | null = riskLocked
|
||||
? plan.risk_last_k != null
|
||||
? Number(plan.risk_last_k)
|
||||
: null
|
||||
: null;
|
||||
let budgetShow: number | null = null;
|
||||
let sizingLine = "";
|
||||
|
||||
if (!riskBased) {
|
||||
sizingLine = `以损定仓 · 未开启(手动 期权 ${fmt(semiOptU, 2)} / 永续 ${fmt(semiPerpU, 2)} ETH)`;
|
||||
} else if (riskLocked) {
|
||||
optQty = Number(
|
||||
plan.option_qty_eth ?? pos?.option_qty_eth ?? optQty,
|
||||
);
|
||||
perpQty = Number(
|
||||
plan.perp_qty_eth ?? pos?.perp_qty_eth ?? perpQty,
|
||||
);
|
||||
} else if (riskBased) {
|
||||
sizingLine = `以损定仓(锁) · 期权 ${fmt(optQty, 2)} ETH / 永续 ${fmt(perpQty, 2)} ETH${
|
||||
kShow != null ? ` · k=${fmt(kShow, 1)}` : ""
|
||||
}`;
|
||||
} else {
|
||||
const budget = Number(plan.risk_sizing_preview?.budget);
|
||||
budgetShow = Number.isFinite(budget) ? budget : null;
|
||||
if (
|
||||
ask != null &&
|
||||
idx != null &&
|
||||
Number.isFinite(budget) &&
|
||||
budget > 0
|
||||
budgetShow != null &&
|
||||
budgetShow > 0
|
||||
) {
|
||||
const est = estimateSemiRiskQty({
|
||||
budget,
|
||||
budget: budgetShow,
|
||||
indexPx: idx,
|
||||
optionAsk: ask,
|
||||
feeRate: feeOk,
|
||||
@@ -1133,12 +1150,33 @@ export default function PlanPage() {
|
||||
if (est.ok && est.optionQty != null && est.perpQty != null) {
|
||||
optQty = est.optionQty;
|
||||
perpQty = est.perpQty;
|
||||
kShow = est.k ?? null;
|
||||
sizingLine = `以损定仓 · 期权 ${fmt(optQty, 2)} ETH / 永续 ${fmt(perpQty, 2)} ETH · k=${fmt(kShow, 1)} · 预算${fmt(budgetShow, 2)}U · 卖一${fmtExPx("option", ask)}${
|
||||
pick != null ? ` · K${Math.round(pick.strike)}` : ""
|
||||
}`;
|
||||
} else {
|
||||
sizingLine = `以损定仓 · ${est.detail}`;
|
||||
}
|
||||
} else {
|
||||
const prev = plan.risk_sizing_preview;
|
||||
if (prev?.ok && prev.option_qty_eth != null) {
|
||||
optQty = Number(prev.option_qty_eth);
|
||||
perpQty = Number(prev.perp_qty_eth ?? perpQty);
|
||||
kShow =
|
||||
prev.k != null ? Number(prev.k) : kShow;
|
||||
sizingLine = `以损定仓 · 期权 ${fmt(optQty, 2)} ETH / 永续 ${fmt(perpQty, 2)} ETH${
|
||||
kShow != null ? ` · k=${fmt(kShow, 1)}` : ""
|
||||
}${
|
||||
prev.budget != null
|
||||
? ` · 预算${fmt(Number(prev.budget), 2)}U`
|
||||
: ""
|
||||
} · 待本单卖一`;
|
||||
} else {
|
||||
sizingLine = `以损定仓 · ${
|
||||
prev?.ok === false
|
||||
? String(prev.detail || "预览失败")
|
||||
: "待卖一/预算推算开仓量"
|
||||
}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1148,146 +1186,45 @@ export default function PlanPage() {
|
||||
(riskBased
|
||||
? Number(semiExitU) *
|
||||
Number(
|
||||
plan.risk_last_k ??
|
||||
kShow ??
|
||||
plan.risk_last_k ??
|
||||
plan.risk_sizing_preview?.k ??
|
||||
1,
|
||||
)
|
||||
: semiExitU),
|
||||
);
|
||||
|
||||
let line1 = "";
|
||||
if (strike == null || !Number.isFinite(strike)) {
|
||||
return `期权目标 行权价${semiView === "short" ? "−" : "+"}${fmt(Number(semiMove) || 50, 0)} · 永续锁定≥${fmt(reverseTgt, 2)}U`;
|
||||
}
|
||||
if (idx == null || !Number.isFinite(idx)) {
|
||||
line1 = `期权目标 · 行权价${semiView === "short" ? "−" : "+"}${fmt(moveN, 0)}(待选约)`;
|
||||
} else if (idx == null || !Number.isFinite(idx)) {
|
||||
const tgt =
|
||||
semiView === "long"
|
||||
? strike + Number(semiMove)
|
||||
: strike - Number(semiMove);
|
||||
return `期权目标 K${Math.round(strike)}→${fmtExPx("index", tgt)} · 永续锁定≥${fmt(reverseTgt, 2)}U`;
|
||||
semiView === "long" ? strike + moveN : strike - moveN;
|
||||
line1 = `期权目标 · K${Math.round(strike)}→${fmtExPx("index", tgt)}`;
|
||||
} else {
|
||||
const prof = estimateSemiMoveProfit({
|
||||
view: semiView,
|
||||
indexPx: idx,
|
||||
strike,
|
||||
movePoints: moveN,
|
||||
optionQty: optQty,
|
||||
perpQty: perpQty,
|
||||
feeRate: 0,
|
||||
});
|
||||
if (!prof.ok) {
|
||||
line1 = `期权目标 · ${prof.detail}`;
|
||||
} else {
|
||||
line1 = `期权目标 · K${Math.round(strike)}→${fmtExPx("index", prof.targetPx!)} · 现价${fmtExPx("index", idx)} · 到点预估≈${fmt(prof.netEst, 2)}U(期权${fmt(prof.optionPnl, 1)}/永续${fmt(prof.perpPnl, 1)})`;
|
||||
}
|
||||
}
|
||||
|
||||
const prof = estimateSemiMoveProfit({
|
||||
view: semiView,
|
||||
indexPx: idx,
|
||||
strike,
|
||||
movePoints: Number(semiMove) || 50,
|
||||
optionQty: optQty,
|
||||
perpQty: perpQty,
|
||||
feeRate: 0,
|
||||
});
|
||||
if (!prof.ok) {
|
||||
return prof.detail;
|
||||
}
|
||||
const line2 = `永续目标 · 净利锁定≥${fmt(reverseTgt, 2)}U(基数${fmt(semiExitU, 1)}×k)`;
|
||||
|
||||
return (
|
||||
<>
|
||||
期权目标 K{Math.round(strike)}→
|
||||
{fmtExPx("index", prof.targetPx!)} · 现价
|
||||
{fmtExPx("index", idx)} · 到点预估≈{fmt(prof.netEst, 2)}U
|
||||
<span className="meta">
|
||||
{" "}
|
||||
(期权{fmt(prof.optionPnl, 1)}/永续
|
||||
{fmt(prof.perpPnl, 1)})
|
||||
</span>
|
||||
{" · "}
|
||||
永续锁定≥{fmt(reverseTgt, 2)}U
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="mono meta plan-semi-sizing">
|
||||
{(() => {
|
||||
const shown = filterSemiLadderRows(
|
||||
ladder?.rows || [],
|
||||
semiMny,
|
||||
Number(semiOtmOff) || 25,
|
||||
semiView,
|
||||
);
|
||||
const pick = pickSizingAskRow(shown);
|
||||
const ask = pick?.ask != null ? Number(pick.ask) : null;
|
||||
const idx =
|
||||
ladder?.index_px != null
|
||||
? Number(ladder.index_px)
|
||||
: snap?.index_px != null
|
||||
? Number(snap.index_px)
|
||||
: null;
|
||||
if (!riskBased) {
|
||||
return (
|
||||
<>
|
||||
手动仓 · 开仓 期权 {fmt(semiOptU, 2)} ETH / 永续{" "}
|
||||
{fmt(semiPerpU, 2)} ETH
|
||||
<span className="meta">
|
||||
{" "}
|
||||
(系统设置可开以损定仓)
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (riskLocked) {
|
||||
return (
|
||||
<>
|
||||
以损定仓(锁) · 期权{" "}
|
||||
{fmt(plan.option_qty_eth ?? pos?.option_qty_eth, 2)} ETH /
|
||||
永续 {fmt(plan.perp_qty_eth ?? pos?.perp_qty_eth, 2)} ETH
|
||||
{plan.risk_last_k != null
|
||||
? ` · k=${fmt(plan.risk_last_k, 1)}`
|
||||
: ""}
|
||||
</>
|
||||
);
|
||||
}
|
||||
const budget = Number(plan.risk_sizing_preview?.budget);
|
||||
const fee = Number(plan.fee_rate ?? 0.0005);
|
||||
if (
|
||||
ask == null ||
|
||||
idx == null ||
|
||||
!Number.isFinite(budget) ||
|
||||
budget <= 0
|
||||
) {
|
||||
const prev = plan.risk_sizing_preview;
|
||||
if (prev?.ok && prev.option_qty_eth != null) {
|
||||
return (
|
||||
<>
|
||||
以损定仓 · 期权 {fmt(Number(prev.option_qty_eth), 2)} ETH
|
||||
/ 永续 {fmt(Number(prev.perp_qty_eth), 2)} ETH
|
||||
{prev.k != null ? ` · k=${fmt(Number(prev.k), 1)}` : ""}
|
||||
{prev.budget != null
|
||||
? ` · 预算${fmt(Number(prev.budget), 2)}U`
|
||||
: ""}
|
||||
<span className="meta"> · 待本单卖一</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
以损定仓 ·{" "}
|
||||
{prev?.ok === false
|
||||
? String(prev.detail || "预览失败")
|
||||
: "待卖一/预算推算开仓量"}
|
||||
</>
|
||||
);
|
||||
}
|
||||
const est = estimateSemiRiskQty({
|
||||
budget,
|
||||
indexPx: idx,
|
||||
optionAsk: ask,
|
||||
feeRate: Number.isFinite(fee) && fee >= 0 ? fee : 0.0005,
|
||||
perpUnit: Number(semiPerpU) || 1,
|
||||
optionUnit: Number(semiOptU) || 4,
|
||||
exitUnit: Number(semiExitU) || 5,
|
||||
});
|
||||
if (!est.ok) {
|
||||
return <>以损定仓 · {est.detail}</>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
以损定仓 · 期权 {fmt(est.optionQty, 2)} ETH / 永续{" "}
|
||||
{fmt(est.perpQty, 2)} ETH · k={fmt(est.k, 1)} · 预算
|
||||
{fmt(budget, 2)}U · 卖一{fmtExPx("option", ask)}
|
||||
{pick != null
|
||||
? ` · K${Math.round(pick.strike)}`
|
||||
: ""}
|
||||
{est.netTarget != null
|
||||
? ` · 逆向目标≈${fmt(est.netTarget, 2)}U`
|
||||
: ""}
|
||||
<div className="plan-semi-line">{line1}</div>
|
||||
<div className="plan-semi-line">{line2}</div>
|
||||
<div className="plan-semi-line">{sizingLine}</div>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
|
||||
@@ -572,15 +572,19 @@ input {
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.plan-semi-summary {
|
||||
margin-top: 6px !important;
|
||||
.plan-semi-lines {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.plan-semi-sizing {
|
||||
margin-top: 2px !important;
|
||||
font-size: 12px;
|
||||
opacity: 0.95;
|
||||
.plan-semi-line {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.plan-semi-btns {
|
||||
|
||||
Reference in New Issue
Block a user