Add option-option hedge mode with SIM/LIVE parity.

Mutual hedge_mode, amplitude OTM selection, 1:1 risk sizing, win-leg/full close, dual audits and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-07 16:01:16 +08:00
parent 15fe2f72dc
commit ec244c63c6
22 changed files with 2640 additions and 83 deletions
+76
View File
@@ -64,6 +64,13 @@ KEYS = (
"martingale_enabled",
"martingale_start_after_loss_days",
"martingale_max_doubles",
"hedge_mode",
"oo_amplitude_pct",
"oo_amplitude_hours",
"oo_min_option_hours",
"oo_min_leverage",
"oo_reward_ratio",
"oo_budget_cushion",
)
@@ -110,6 +117,15 @@ class StrategySettingsBody(BaseModel):
martingale_enabled: bool | None = None
martingale_start_after_loss_days: int | None = Field(default=None, ge=1, le=30)
martingale_max_doubles: int | None = Field(default=None, ge=1, le=10)
hedge_mode: str | None = Field(
default=None, pattern="^(perp_option|option_option)$"
)
oo_amplitude_pct: float | None = Field(default=None, ge=0.1, le=50)
oo_amplitude_hours: float | None = Field(default=None, ge=1, le=168)
oo_min_option_hours: float | None = Field(default=None, ge=1, le=720)
oo_min_leverage: float | None = Field(default=None, ge=1, le=10000)
oo_reward_ratio: float | None = Field(default=None, ge=0.5, le=20)
oo_budget_cushion: float | None = Field(default=None, ge=0.5, le=1.0)
def _as_bool(raw: str | None, default: bool) -> bool:
@@ -327,6 +343,42 @@ def _read_settings() -> dict:
or s.martingale_max_doubles
)
),
"hedge_mode": (
hm
if (
hm := str(
db.get_setting("hedge_mode", s.hedge_mode) or s.hedge_mode
)
.strip()
.lower()
)
in ("perp_option", "option_option")
else "perp_option"
),
"oo_amplitude_pct": float(
db.get_setting("oo_amplitude_pct", str(s.oo_amplitude_pct))
or s.oo_amplitude_pct
),
"oo_amplitude_hours": float(
db.get_setting("oo_amplitude_hours", str(s.oo_amplitude_hours))
or s.oo_amplitude_hours
),
"oo_min_option_hours": float(
db.get_setting("oo_min_option_hours", str(s.oo_min_option_hours))
or s.oo_min_option_hours
),
"oo_min_leverage": float(
db.get_setting("oo_min_leverage", str(s.oo_min_leverage))
or s.oo_min_leverage
),
"oo_reward_ratio": float(
db.get_setting("oo_reward_ratio", str(s.oo_reward_ratio))
or s.oo_reward_ratio
),
"oo_budget_cushion": float(
db.get_setting("oo_budget_cushion", str(s.oo_budget_cushion))
or s.oo_budget_cushion
),
"risk_sizing_preview": _risk_preview_safe(),
"exchange": rt.exchange,
"perp_inst_id": rt.perp_inst_id,
@@ -410,6 +462,13 @@ async def put_strategy_settings(
"martingale_enabled",
"martingale_start_after_loss_days",
"martingale_max_doubles",
"hedge_mode",
"oo_amplitude_pct",
"oo_amplitude_hours",
"oo_min_option_hours",
"oo_min_leverage",
"oo_reward_ratio",
"oo_budget_cushion",
)
hit = [k for k in locked_keys if k in data]
if hit:
@@ -419,6 +478,23 @@ async def put_strategy_settings(
)
# 以损定仓 ↔ 手动仓位互斥;开启以损定仓时强制 fixed_usdt,并忽略手填名义/出场
hedge_mode = str(
data.get(
"hedge_mode",
db.get_setting("hedge_mode", s.hedge_mode) or s.hedge_mode,
)
).strip().lower()
if hedge_mode not in ("perp_option", "option_option"):
hedge_mode = "perp_option"
data["hedge_mode"] = hedge_mode
if hedge_mode == "option_option":
# 期期:强制以损定仓 + 亏损幅度%
data["sizing_mode"] = "risk_based"
data["risk_loss_mode"] = "percent"
data["exit_mode"] = "fixed_usdt"
data.pop("perp_qty_eth", None)
data["fixed_direction_enabled"] = False
sizing_mode = str(
data.get(
"sizing_mode",