diff --git a/frontend/src/pages/Plan.tsx b/frontend/src/pages/Plan.tsx
index 729046b..52ddcdd 100644
--- a/frontend/src/pages/Plan.tsx
+++ b/frontend/src/pages/Plan.tsx
@@ -1079,7 +1079,7 @@ export default function PlanPage() {
/>
-
+
{(() => {
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
-
- {" "}
- (期权{fmt(prof.optionPnl, 1)}/永续
- {fmt(prof.perpPnl, 1)})
-
- {" · "}
- 永续锁定≥{fmt(reverseTgt, 2)}U
- >
- );
- })()}
-
-
- {(() => {
- 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
-
- {" "}
- (系统设置可开以损定仓)
-
- >
- );
- }
- 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`
- : ""}
-
· 待本单卖一
- >
- );
- }
- 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`
- : ""}
+
{line1}
+
{line2}
+
{sizingLine}
>
);
})()}
diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css
index ee5784a..2ae5190 100644
--- a/frontend/src/styles/app.css
+++ b/frontend/src/styles/app.css
@@ -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 {