From 8d3c181246c9d7fda480e1d8d6dd6247aba06270 Mon Sep 17 00:00:00 2001
From: dekun
Date: Fri, 7 Aug 2026 16:18:40 +0800
Subject: [PATCH] Hide perp signal direction in option-option mode.
Plan panel shows Call+Put structure and OO selection/exit labels instead.
Co-authored-by: Cursor
---
backend/app/strategy/engine.py | 30 +++++++
frontend/src/api/client.ts | 5 ++
frontend/src/pages/Plan.tsx | 158 ++++++++++++++++++++++-----------
3 files changed, 142 insertions(+), 51 deletions(-)
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 ? (