diff --git a/backend/app/strategy/engine.py b/backend/app/strategy/engine.py index 8e10985..ef60579 100644 --- a/backend/app/strategy/engine.py +++ b/backend/app/strategy/engine.py @@ -261,6 +261,36 @@ class StrategyEngine: in ("perp_option", "option_option") else "perp_option" ), + "oo_amplitude_pct": float( + self.ledger.get_setting_float( + "oo_amplitude_pct", s.oo_amplitude_pct + ) + or s.oo_amplitude_pct + ), + "oo_amplitude_hours": float( + self.ledger.get_setting_float( + "oo_amplitude_hours", s.oo_amplitude_hours + ) + or s.oo_amplitude_hours + ), + "oo_min_option_hours": float( + self.ledger.get_setting_float( + "oo_min_option_hours", s.oo_min_option_hours + ) + or s.oo_min_option_hours + ), + "oo_min_leverage": float( + self.ledger.get_setting_float( + "oo_min_leverage", s.oo_min_leverage + ) + or s.oo_min_leverage + ), + "oo_reward_ratio": float( + self.ledger.get_setting_float( + "oo_reward_ratio", s.oo_reward_ratio + ) + or s.oo_reward_ratio + ), "risk_perp_unit": risk_perp_unit, "risk_option_unit": risk_option_unit, "risk_exit_unit": risk_exit_unit, diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index a74e1a0..e2b6600 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -348,6 +348,11 @@ export type PlanState = { risk_effective_loss_pct?: number; risk_loss_pct?: number; hedge_mode?: "perp_option" | "option_option"; + oo_amplitude_pct?: number; + oo_amplitude_hours?: number; + oo_min_option_hours?: number; + oo_min_leverage?: number; + oo_reward_ratio?: number; sizing_mode?: "manual" | "risk_based"; risk_based?: boolean; ledger: { equity: number; available: number; reserved: number }; diff --git a/frontend/src/pages/Plan.tsx b/frontend/src/pages/Plan.tsx index 99b941f..2f543c9 100644 --- a/frontend/src/pages/Plan.tsx +++ b/frontend/src/pages/Plan.tsx @@ -185,6 +185,10 @@ export default function PlanPage() { ); const pos = plan?.position; + const isOo = + plan?.hedge_mode === "option_option" || + pos?.hedge_mode === "option_option" || + !!pos?.option2_inst_id; const open = !!pos?.has_position; const exitMode = plan?.exit_mode ?? "fixed_usdt"; const riskBased = @@ -202,20 +206,28 @@ export default function PlanPage() { : null; const netPnl = pos?.net_pnl ?? 0; const movePct = pos?.move_pct ?? 0; - const exitRuleLabel = - exitMode === "premium_multiple" + const ooRatio = Number(plan?.oo_reward_ratio ?? 2); + const exitRuleLabel = isOo + ? exitTarget != null + ? `${riskLocked ? "锁定 " : ""}净盈≥${fmt(exitTarget)} U(预算×${fmt(ooRatio, 1)})` + : `净盈≥预算×${fmt(ooRatio, 1)}(达标只平盈利腿)` + : exitMode === "premium_multiple" ? `权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}` : riskBased ? exitTarget != null ? `${riskLocked ? "锁定 " : "固定 "}${fmt(exitTarget)} U(基数${fmt(plan?.risk_exit_unit ?? 15)})` : `待估算(基数${fmt(plan?.risk_exit_unit ?? 15)})` : `固定 ${fmt(plan?.net_profit_target ?? 15)} U`; - const riskRatioLabel = `比例${Number(plan?.risk_perp_unit ?? 1)}:${Number(plan?.risk_option_unit ?? 2)}`; + const riskRatioLabel = isOo + ? "Call:Put 1:1" + : `比例${Number(plan?.risk_perp_unit ?? 1)}:${Number(plan?.risk_option_unit ?? 2)}`; const sizingModeLabel = riskBased ? [ - plan?.hedge_mode === "option_option" ? "期期对冲" : "以损定仓", + isOo ? "期期对冲" : "以损定仓", riskRatioLabel, - plan?.risk_last_k != null ? `k=${fmt(plan.risk_last_k, 1)}` : null, + !isOo && plan?.risk_last_k != null + ? `k=${fmt(plan.risk_last_k, 1)}` + : null, riskLocked ? exitTarget != null ? `目标锁定${fmt(exitTarget, 2)}U` @@ -236,7 +248,7 @@ export default function PlanPage() { ] .filter(Boolean) .join(" ") - : plan?.hedge_mode === "option_option" + : isOo ? "期期对冲" : "手动仓位"; const phaseLabel = PHASE_ZH[plan?.phase || ""] || plan?.phase || "—"; @@ -247,8 +259,18 @@ export default function PlanPage() { : plan?.atm_open_offset_enabled ? `ATM偏差≤${fmt(plan?.max_atm_open_offset ?? 3, 0)}` : "ATM偏差关"; - const exitDetail = - exitMode === "premium_multiple" + const ooSelectLabel = [ + `振幅≥${fmt(plan?.oo_amplitude_pct ?? 1.5, 1)}%/${fmt(plan?.oo_amplitude_hours ?? 12, 0)}h`, + "虚值Call@高·Put@低", + `剩余≥${fmt(plan?.oo_min_option_hours ?? 24, 0)}h`, + `杠杆≥${fmt(plan?.oo_min_leverage ?? 200, 0)}x`, + displayOptQty != null ? `单腿${fmt(displayOptQty, 1)}ETH` : null, + ] + .filter(Boolean) + .join(" · "); + const exitDetail = isOo + ? `净盈利≥预算×${fmt(ooRatio, 1)} → 只平盈利腿,亏损腿残留` + : exitMode === "premium_multiple" ? `净盈利≥初始权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}` : `净盈利≥${fmt(plan?.net_profit_target ?? 15)} U`; const expiryMs = @@ -328,26 +350,40 @@ export default function PlanPage() {

开仓 - 永续{" "} - {displayPerpQty != null - ? `${fmt(displayPerpQty, 2)} ETH` - : "—"}{" "} - / {fmt(plan?.leverage, 0)}x - · 期权{" "} - {displayOptQty != null - ? `${fmt(displayOptQty, 2)} ETH` - : "—"}{" "} - 名义 · 剩余≥ - {fmt(plan?.min_option_hours, 0)}h · 期权杠杆≥ - {fmt(plan?.min_option_leverage, 0)}x · {atmRule} - {plan?.skip_weekends ? " · 周末跳过开仓" : ""} - {plan?.one_expiry_per_day !== false - ? " · 每个到期只开一次" - : ""} + {isOo ? ( + <> + {ooSelectLabel} + {plan?.skip_weekends ? " · 周末跳过开仓" : ""} + {plan?.one_expiry_per_day !== false + ? " · 每个到期只开一次" + : ""} + + ) : ( + <> + 永续{" "} + {displayPerpQty != null + ? `${fmt(displayPerpQty, 2)} ETH` + : "—"}{" "} + / {fmt(plan?.leverage, 0)}x + · 期权{" "} + {displayOptQty != null + ? `${fmt(displayOptQty, 2)} ETH` + : "—"}{" "} + 名义 · 剩余≥ + {fmt(plan?.min_option_hours, 0)}h · 期权杠杆≥ + {fmt(plan?.min_option_leverage, 0)}x · {atmRule} + {plan?.skip_weekends ? " · 周末跳过开仓" : ""} + {plan?.one_expiry_per_day !== false + ? " · 每个到期只开一次" + : ""} + + )}

平仓 - {exitDetail} → 双腿全平;期权远虚则只平永续、期权到期结算;未达标拖到期 + {isOo + ? `${exitDetail};否则到期结算` + : `${exitDetail} → 双腿全平;期权远虚则只平永续、期权到期结算;未达标拖到期`}

节奏 @@ -460,24 +496,30 @@ export default function PlanPage() {

选约条件 - 永续 - {displayPerpQty != null - ? `${fmt(displayPerpQty, 2)}ETH` - : "—"} - /期权 - {displayOptQty != null - ? `${fmt(displayOptQty, 2)}ETH` - : "—"}{" "} - · 剩余≥ - {fmt(plan?.min_option_hours, 0)}h · 期权杠杆≥ - {fmt(plan?.min_option_leverage, 0)}x - {plan?.fixed_direction_enabled - ? plan?.fixed_perp_side === "short" - ? " · 固定空+Call(实/平)" - : " · 固定多+Put(实/平)" - : plan?.atm_open_offset_enabled - ? ` · ATM偏差≤${fmt(plan?.max_atm_open_offset ?? 3, 0)}` - : " · ATM偏差关"} + {isOo ? ( + ooSelectLabel + ) : ( + <> + 永续 + {displayPerpQty != null + ? `${fmt(displayPerpQty, 2)}ETH` + : "—"} + /期权 + {displayOptQty != null + ? `${fmt(displayOptQty, 2)}ETH` + : "—"}{" "} + · 剩余≥ + {fmt(plan?.min_option_hours, 0)}h · 期权杠杆≥ + {fmt(plan?.min_option_leverage, 0)}x + {plan?.fixed_direction_enabled + ? plan?.fixed_perp_side === "short" + ? " · 固定空+Call(实/平)" + : " · 固定多+Put(实/平)" + : plan?.atm_open_offset_enabled + ? ` · ATM偏差≤${fmt(plan?.max_atm_open_offset ?? 3, 0)}` + : " · ATM偏差关"} + + )}
@@ -525,10 +567,20 @@ export default function PlanPage() {
-
- 信号方向 - {biasTag} -
+ {isOo ? ( +
+ 开仓结构 + + 买 Call + 买 Put + · 不按永期方向信号 + +
+ ) : ( +
+ 信号方向 + {biasTag} +
+ )}
出场规则 {exitRuleLabel} @@ -540,7 +592,7 @@ export default function PlanPage() { {open ? fmt(netPnl) : "—"} {" / "} - {open || exitMode === "fixed_usdt" + {open || exitMode === "fixed_usdt" || isOo ? exitTarget != null ? fmt(exitTarget) : "—" @@ -548,11 +600,15 @@ export default function PlanPage() {
- 永续/期权浮盈 + {isOo ? "Call/Put 浮盈" : "永续/期权浮盈"} - {fmt(pos?.perp_upl)} + + {fmt(isOo ? pos?.option_upl : pos?.perp_upl)} + {" / "} - {fmt(pos?.option_upl)} + + {fmt(isOo ? pos?.option2_upl : pos?.option_upl)} +
{plan?.mode === "LIVE" && open ? (