Estimate semi forward PnL from strike intrinsic on move points.
Replace misleading exit-unit label with option/perp point-move preview vs selected K. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
-1
@@ -64,7 +64,8 @@
|
|||||||
- 流动性不足:进入 `liquidity_wait`;回落未达标则取消挂起。
|
- 流动性不足:进入 `liquidity_wait`;回落未达标则取消挂起。
|
||||||
- 半自动平仓**禁止**远虚「只平永续、期权归档」;顺序仍为**先期权后永续**。
|
- 半自动平仓**禁止**远虚「只平永续、期权归档」;顺序仍为**先期权后永续**。
|
||||||
|
|
||||||
首页预览「指数 → 到点」用**当前指数**示意;真实触发达标用**开仓指数**。
|
首页预览「指数 → 到点」用**当前指数**示意;真实触发达标用**开仓指数**。
|
||||||
|
「波动点」旁的**顺向预估**按选定行权价的内在价值变化 + 永续点位粗算(不含 IV/时间价值);与「净利基数×k」逆向兑现目标分开显示。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-08-08 — 半自动波动点顺向预估盈利
|
||||||
|
|
||||||
|
### 变更
|
||||||
|
|
||||||
|
1. 「波动点」下方按标的顺向 N 点 + 行权价内在价值变化粗估组合盈利(期权腿/永续腿分列)。
|
||||||
|
2. 与「净利基数×k」逆向兑现目标分开显示,不再把基数目标误标为顺向净利。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-08-08 — 半自动以损定仓预览开仓量
|
## 2026-08-08 — 半自动以损定仓预览开仓量
|
||||||
|
|
||||||
### 变更
|
### 变更
|
||||||
|
|||||||
+175
-12
@@ -225,6 +225,68 @@ function estimateSemiRiskQty(args: {
|
|||||||
return { ok: false, detail: "预算内无合规 k" };
|
return { ok: false, detail: "预算内无合规 k" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标的顺向波动 move 点后的组合盈亏粗估(期权用内在价值变化,永续按点位)。
|
||||||
|
* 多=Call+永续空;空=Put+永续多。不含权利金时间价值/IV。
|
||||||
|
*/
|
||||||
|
function estimateSemiMoveProfit(args: {
|
||||||
|
view: "long" | "short";
|
||||||
|
indexPx: number;
|
||||||
|
strike: number;
|
||||||
|
movePoints: number;
|
||||||
|
optionQty: number;
|
||||||
|
perpQty: number;
|
||||||
|
feeRate?: number;
|
||||||
|
}): {
|
||||||
|
ok: boolean;
|
||||||
|
detail: string;
|
||||||
|
targetPx?: number;
|
||||||
|
optionPnl?: number;
|
||||||
|
perpPnl?: number;
|
||||||
|
feesEst?: number;
|
||||||
|
netEst?: number;
|
||||||
|
} {
|
||||||
|
const {
|
||||||
|
view,
|
||||||
|
indexPx,
|
||||||
|
strike,
|
||||||
|
movePoints,
|
||||||
|
optionQty,
|
||||||
|
perpQty,
|
||||||
|
feeRate = 0,
|
||||||
|
} = args;
|
||||||
|
const S = Number(indexPx);
|
||||||
|
const K = Number(strike);
|
||||||
|
const move = Number(movePoints);
|
||||||
|
const oq = Number(optionQty);
|
||||||
|
const pq = Number(perpQty);
|
||||||
|
if (!(S > 0) || !(K > 0) || !(move > 0) || !(oq > 0) || !(pq > 0)) {
|
||||||
|
return { ok: false, detail: "缺指数/行权价/数量" };
|
||||||
|
}
|
||||||
|
const tgt = view === "long" ? S + move : S - move;
|
||||||
|
const callIntr = (px: number) => Math.max(0, px - K);
|
||||||
|
const putIntr = (px: number) => Math.max(0, K - px);
|
||||||
|
const dIntr =
|
||||||
|
view === "long"
|
||||||
|
? callIntr(tgt) - callIntr(S)
|
||||||
|
: putIntr(tgt) - putIntr(S);
|
||||||
|
// 多:空永续随涨亏;空:多永续随跌亏 → 均为 -move×qty
|
||||||
|
const optionPnl = dIntr * oq;
|
||||||
|
const perpPnl = -move * pq;
|
||||||
|
const fees =
|
||||||
|
feeRate > 0 ? S * feeRate * 3 * Math.max(oq, pq) /* 粗估开平 */ : 0;
|
||||||
|
const net = optionPnl + perpPnl - fees;
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
detail: "ok",
|
||||||
|
targetPx: tgt,
|
||||||
|
optionPnl: Number(optionPnl.toFixed(2)),
|
||||||
|
perpPnl: Number(perpPnl.toFixed(2)),
|
||||||
|
feesEst: Number(fees.toFixed(2)),
|
||||||
|
netEst: Number(net.toFixed(2)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default function PlanPage() {
|
export default function PlanPage() {
|
||||||
const [snap, setSnap] = useState<MarketSnapshot | null>(null);
|
const [snap, setSnap] = useState<MarketSnapshot | null>(null);
|
||||||
const [plan, setPlan] = useState<PlanState | null>(null);
|
const [plan, setPlan] = useState<PlanState | null>(null);
|
||||||
@@ -817,7 +879,8 @@ export default function PlanPage() {
|
|||||||
<li>人工定方向、行权类型、永续:期权配比与出场 →「授权开下一单」后机器盯选约/开/平。</li>
|
<li>人工定方向、行权类型、永续:期权配比与出场 →「授权开下一单」后机器盯选约/开/平。</li>
|
||||||
<li>平仓后停在等待授权,不自动连开;与全自动循环无关。</li>
|
<li>平仓后停在等待授权,不自动连开;与全自动循环无关。</li>
|
||||||
<li>虚值:偏离 ≤ 设定点数;杠杆门 ≥180(默认 200)。实值/平值用本单最低杠杆。</li>
|
<li>虚值:偏离 ≤ 设定点数;杠杆门 ≥180(默认 200)。实值/平值用本单最低杠杆。</li>
|
||||||
<li>顺向:开仓指数 ± 波动点数,且组合净利 > 0 → 双腿全平(先期权后永续)。</li>
|
<li>波动点:标的相对当前价顺向波动 N 点(多涨/空跌);下方按行权价内在价值变化 + 永续点位粗估盈利。</li>
|
||||||
|
<li>顺向出场:开仓指数 ± 波动点,且组合净利 > 0 → 双腿全平(先期权后永续)。</li>
|
||||||
<li>逆向兑现:组合净利 ≥ 净利基数 × k → 全平。</li>
|
<li>逆向兑现:组合净利 ≥ 净利基数 × k → 全平。</li>
|
||||||
<li>以损定仓时配比为单位再乘 k;手动仓按配比直接开。</li>
|
<li>以损定仓时配比为单位再乘 k;手动仓按配比直接开。</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -910,7 +973,7 @@ export default function PlanPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label htmlFor="semiMove">波动点</label>
|
<label htmlFor="semiMove">波动点(预估)</label>
|
||||||
<input
|
<input
|
||||||
id="semiMove"
|
id="semiMove"
|
||||||
className="mono"
|
className="mono"
|
||||||
@@ -976,16 +1039,116 @@ export default function PlanPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mono meta plan-semi-summary">
|
<div className="mono meta plan-semi-summary">
|
||||||
{(() => {
|
{(() => {
|
||||||
const idx = snap?.index_px ?? ladder?.index_px;
|
const shown = filterSemiLadderRows(
|
||||||
if (idx == null || !Number.isFinite(Number(idx))) {
|
ladder?.rows || [],
|
||||||
return `净利目标≈${fmt(plan.semi_net_exit_target ?? semiExitU, 2)}U`;
|
semiMny,
|
||||||
|
Number(semiOtmOff) || 25,
|
||||||
|
semiView,
|
||||||
|
);
|
||||||
|
const pick = pickSizingAskRow(shown);
|
||||||
|
const idx =
|
||||||
|
ladder?.index_px != null
|
||||||
|
? Number(ladder.index_px)
|
||||||
|
: snap?.index_px != null
|
||||||
|
? Number(snap.index_px)
|
||||||
|
: null;
|
||||||
|
const 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;
|
||||||
|
|
||||||
|
let optQty = Number(semiOptU) || 4;
|
||||||
|
let perpQty = Number(semiPerpU) || 1;
|
||||||
|
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) {
|
||||||
|
const budget = Number(plan.risk_sizing_preview?.budget);
|
||||||
|
if (
|
||||||
|
ask != null &&
|
||||||
|
idx != null &&
|
||||||
|
Number.isFinite(budget) &&
|
||||||
|
budget > 0
|
||||||
|
) {
|
||||||
|
const est = estimateSemiRiskQty({
|
||||||
|
budget,
|
||||||
|
indexPx: idx,
|
||||||
|
optionAsk: ask,
|
||||||
|
feeRate: feeOk,
|
||||||
|
perpUnit: Number(semiPerpU) || 1,
|
||||||
|
optionUnit: Number(semiOptU) || 4,
|
||||||
|
exitUnit: Number(semiExitU) || 5,
|
||||||
|
});
|
||||||
|
if (est.ok && est.optionQty != null && est.perpQty != null) {
|
||||||
|
optQty = est.optionQty;
|
||||||
|
perpQty = est.perpQty;
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const n = Number(idx);
|
|
||||||
const tgt =
|
const reverseTgt = Number(
|
||||||
semiView === "long"
|
plan.semi_net_exit_target ??
|
||||||
? n + Number(semiMove)
|
(riskBased
|
||||||
: n - Number(semiMove);
|
? Number(semiExitU) *
|
||||||
return `${fmtExPx("index", n)}→${fmtExPx("index", tgt)} · 净利≈${fmt(plan.semi_net_exit_target ?? semiExitU, 2)}U`;
|
Number(
|
||||||
|
plan.risk_last_k ??
|
||||||
|
plan.risk_sizing_preview?.k ??
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
: semiExitU),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (idx == null || !Number.isFinite(idx)) {
|
||||||
|
return `逆向兑现≥${fmt(reverseTgt, 2)}U`;
|
||||||
|
}
|
||||||
|
if (strike == null || !Number.isFinite(strike)) {
|
||||||
|
const tgt =
|
||||||
|
semiView === "long"
|
||||||
|
? idx + Number(semiMove)
|
||||||
|
: idx - Number(semiMove);
|
||||||
|
return `${fmtExPx("index", idx)}→${fmtExPx("index", tgt)} · 待行权价估盈利 · 逆向≥${fmt(reverseTgt, 2)}U`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prof = estimateSemiMoveProfit({
|
||||||
|
view: semiView,
|
||||||
|
indexPx: idx,
|
||||||
|
strike,
|
||||||
|
movePoints: Number(semiMove) || 50,
|
||||||
|
optionQty: optQty,
|
||||||
|
perpQty: perpQty,
|
||||||
|
feeRate: 0, // 预估展示用内在+永续点位;手续费另计
|
||||||
|
});
|
||||||
|
if (!prof.ok) {
|
||||||
|
return prof.detail;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{fmtExPx("index", idx)}→{fmtExPx("index", prof.targetPx!)} ·
|
||||||
|
K{Math.round(strike)} · 顺向预估≈{fmt(prof.netEst, 2)}U
|
||||||
|
<span className="meta">
|
||||||
|
{" "}
|
||||||
|
(期权{fmt(prof.optionPnl, 1)}/永续
|
||||||
|
{fmt(prof.perpPnl, 1)} · 内在粗估)
|
||||||
|
</span>
|
||||||
|
{" · "}
|
||||||
|
逆向≥{fmt(reverseTgt, 2)}U
|
||||||
|
</>
|
||||||
|
);
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
<div className="mono meta plan-semi-sizing">
|
<div className="mono meta plan-semi-sizing">
|
||||||
@@ -1080,7 +1243,7 @@ export default function PlanPage() {
|
|||||||
? ` · K${Math.round(pick.strike)}`
|
? ` · K${Math.round(pick.strike)}`
|
||||||
: ""}
|
: ""}
|
||||||
{est.netTarget != null
|
{est.netTarget != null
|
||||||
? ` · 净利目标≈${fmt(est.netTarget, 2)}U`
|
? ` · 逆向目标≈${fmt(est.netTarget, 2)}U`
|
||||||
: ""}
|
: ""}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user