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>
|
</div>
|
||||||
<div className="mono meta plan-semi-summary">
|
<div className="mono plan-semi-lines">
|
||||||
{(() => {
|
{(() => {
|
||||||
const shown = filterSemiLadderRows(
|
const shown = filterSemiLadderRows(
|
||||||
ladder?.rows || [],
|
ladder?.rows || [],
|
||||||
@@ -1095,34 +1095,51 @@ export default function PlanPage() {
|
|||||||
? Number(snap.index_px)
|
? Number(snap.index_px)
|
||||||
: null;
|
: null;
|
||||||
const strike =
|
const strike =
|
||||||
pick?.strike != null
|
open && pos?.strike != null && Number.isFinite(Number(pos.strike))
|
||||||
? Number(pick.strike)
|
? Number(pos.strike)
|
||||||
: ladder?.atm_strike != null
|
: pick?.strike != null
|
||||||
? Number(ladder.atm_strike)
|
? Number(pick.strike)
|
||||||
: null;
|
: ladder?.atm_strike != null
|
||||||
|
? Number(ladder.atm_strike)
|
||||||
|
: null;
|
||||||
const ask = pick?.ask != null ? Number(pick.ask) : null;
|
const ask = pick?.ask != null ? Number(pick.ask) : null;
|
||||||
const fee = Number(plan.fee_rate ?? 0.0005);
|
const fee = Number(plan.fee_rate ?? 0.0005);
|
||||||
const feeOk = Number.isFinite(fee) && fee >= 0 ? fee : 0.0005;
|
const feeOk = Number.isFinite(fee) && fee >= 0 ? fee : 0.0005;
|
||||||
|
const moveN = Number(semiMove) || 50;
|
||||||
|
|
||||||
let optQty = Number(semiOptU) || 4;
|
let optQty = Number(semiOptU) || 4;
|
||||||
let perpQty = Number(semiPerpU) || 1;
|
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(
|
optQty = Number(
|
||||||
plan.option_qty_eth ?? pos?.option_qty_eth ?? optQty,
|
plan.option_qty_eth ?? pos?.option_qty_eth ?? optQty,
|
||||||
);
|
);
|
||||||
perpQty = Number(
|
perpQty = Number(
|
||||||
plan.perp_qty_eth ?? pos?.perp_qty_eth ?? perpQty,
|
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);
|
const budget = Number(plan.risk_sizing_preview?.budget);
|
||||||
|
budgetShow = Number.isFinite(budget) ? budget : null;
|
||||||
if (
|
if (
|
||||||
ask != null &&
|
ask != null &&
|
||||||
idx != null &&
|
idx != null &&
|
||||||
Number.isFinite(budget) &&
|
budgetShow != null &&
|
||||||
budget > 0
|
budgetShow > 0
|
||||||
) {
|
) {
|
||||||
const est = estimateSemiRiskQty({
|
const est = estimateSemiRiskQty({
|
||||||
budget,
|
budget: budgetShow,
|
||||||
indexPx: idx,
|
indexPx: idx,
|
||||||
optionAsk: ask,
|
optionAsk: ask,
|
||||||
feeRate: feeOk,
|
feeRate: feeOk,
|
||||||
@@ -1133,12 +1150,33 @@ export default function PlanPage() {
|
|||||||
if (est.ok && est.optionQty != null && est.perpQty != null) {
|
if (est.ok && est.optionQty != null && est.perpQty != null) {
|
||||||
optQty = est.optionQty;
|
optQty = est.optionQty;
|
||||||
perpQty = est.perpQty;
|
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 {
|
} else {
|
||||||
const prev = plan.risk_sizing_preview;
|
const prev = plan.risk_sizing_preview;
|
||||||
if (prev?.ok && prev.option_qty_eth != null) {
|
if (prev?.ok && prev.option_qty_eth != null) {
|
||||||
optQty = Number(prev.option_qty_eth);
|
optQty = Number(prev.option_qty_eth);
|
||||||
perpQty = Number(prev.perp_qty_eth ?? perpQty);
|
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
|
(riskBased
|
||||||
? Number(semiExitU) *
|
? Number(semiExitU) *
|
||||||
Number(
|
Number(
|
||||||
plan.risk_last_k ??
|
kShow ??
|
||||||
|
plan.risk_last_k ??
|
||||||
plan.risk_sizing_preview?.k ??
|
plan.risk_sizing_preview?.k ??
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
: semiExitU),
|
: semiExitU),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let line1 = "";
|
||||||
if (strike == null || !Number.isFinite(strike)) {
|
if (strike == null || !Number.isFinite(strike)) {
|
||||||
return `期权目标 行权价${semiView === "short" ? "−" : "+"}${fmt(Number(semiMove) || 50, 0)} · 永续锁定≥${fmt(reverseTgt, 2)}U`;
|
line1 = `期权目标 · 行权价${semiView === "short" ? "−" : "+"}${fmt(moveN, 0)}(待选约)`;
|
||||||
}
|
} else if (idx == null || !Number.isFinite(idx)) {
|
||||||
if (idx == null || !Number.isFinite(idx)) {
|
|
||||||
const tgt =
|
const tgt =
|
||||||
semiView === "long"
|
semiView === "long" ? strike + moveN : strike - moveN;
|
||||||
? strike + Number(semiMove)
|
line1 = `期权目标 · K${Math.round(strike)}→${fmtExPx("index", tgt)}`;
|
||||||
: strike - Number(semiMove);
|
} else {
|
||||||
return `期权目标 K${Math.round(strike)}→${fmtExPx("index", tgt)} · 永续锁定≥${fmt(reverseTgt, 2)}U`;
|
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({
|
const line2 = `永续目标 · 净利锁定≥${fmt(reverseTgt, 2)}U(基数${fmt(semiExitU, 1)}×k)`;
|
||||||
view: semiView,
|
|
||||||
indexPx: idx,
|
|
||||||
strike,
|
|
||||||
movePoints: Number(semiMove) || 50,
|
|
||||||
optionQty: optQty,
|
|
||||||
perpQty: perpQty,
|
|
||||||
feeRate: 0,
|
|
||||||
});
|
|
||||||
if (!prof.ok) {
|
|
||||||
return prof.detail;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
期权目标 K{Math.round(strike)}→
|
<div className="plan-semi-line">{line1}</div>
|
||||||
{fmtExPx("index", prof.targetPx!)} · 现价
|
<div className="plan-semi-line">{line2}</div>
|
||||||
{fmtExPx("index", idx)} · 到点预估≈{fmt(prof.netEst, 2)}U
|
<div className="plan-semi-line">{sizingLine}</div>
|
||||||
<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`
|
|
||||||
: ""}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|||||||
@@ -572,15 +572,19 @@ input {
|
|||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-semi-summary {
|
.plan-semi-lines {
|
||||||
margin-top: 6px !important;
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
opacity: 0.95;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-semi-sizing {
|
.plan-semi-line {
|
||||||
margin-top: 2px !important;
|
white-space: normal;
|
||||||
font-size: 12px;
|
word-break: break-word;
|
||||||
opacity: 0.95;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-semi-btns {
|
.plan-semi-btns {
|
||||||
|
|||||||
Reference in New Issue
Block a user