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
+66 -12
View File
@@ -104,17 +104,24 @@ def assess_open_capacity(
option_ask: float | None = None,
option_qty_eth: float | None = None,
perp_qty_eth: float | None = None,
call_ask: float | None = None,
put_ask: float | None = None,
) -> dict[str, Any]:
"""
返回永续/期权是否有足够交易账户资金开新仓。
- 永续:交易账户 USDT >= 名义/杠杆
- 期权:交易账户 USDC >= 卖一×名义×(1+费率)
可选覆盖 ask/名义(选约后应用选中腿卖一,避免与 max(call,put) 打架)。
- 期期:期权需 (call_ask+put_ask)×qty×(1+fee);永续视为不需要
"""
global _notified_while_short
db = db or get_db()
s = get_settings()
ledger = Ledger(db)
hedge = str(
ledger.get_setting_str("hedge_mode", s.hedge_mode) or s.hedge_mode
).strip().lower()
if hedge not in ("perp_option", "option_option"):
hedge = "perp_option"
lev = float(ledger.get_setting_float("leverage", s.leverage) or 3)
if lev <= 0:
lev = 3.0
@@ -132,10 +139,38 @@ def assess_open_capacity(
idx, ask_book = _index_and_option_ask()
ask = float(option_ask) if option_ask is not None and float(option_ask) > 0 else ask_book
margin_need = (float(idx) * perp_qty / lev) if idx and idx > 0 else None
premium_need = (
float(ask) * opt_qty * (1.0 + fee_rate) if ask is not None and ask > 0 else None
)
if hedge == "option_option":
ca = float(call_ask) if call_ask is not None and float(call_ask) > 0 else None
pa = float(put_ask) if put_ask is not None and float(put_ask) > 0 else None
if ca is None or pa is None:
# 回退:用监控对 call/put 卖一
try:
from .session import get_session
snap = get_session().snapshot()
if ca is None and snap.call and snap.call.ask:
ca = float(snap.call.ask)
if pa is None and snap.put and snap.put.ask:
pa = float(snap.put.ask)
except Exception:
pass
cush = float(
ledger.get_setting_float("oo_budget_cushion", s.oo_budget_cushion)
or s.oo_budget_cushion
)
cush = min(1.0, max(0.5, cush))
if ca is not None and pa is not None and ca > 0 and pa > 0:
# 与定仓一致:按预留后的权利金需求估资金门
premium_need = (ca + pa) * opt_qty * (1.0 + fee_rate) * cush
else:
premium_need = None
margin_need = 0.0
perp_qty = 0.0
else:
margin_need = (float(idx) * perp_qty / lev) if idx and idx > 0 else None
premium_need = (
float(ask) * opt_qty * (1.0 + fee_rate) if ask is not None and ask > 0 else None
)
if s.is_sim:
bal = _sim_balances(db)
@@ -149,24 +184,31 @@ def assess_open_capacity(
have_opt = float(t_usdc) if t_usdc is not None else None
perp_ok: bool | None
if margin_need is None or have_perp is None:
if hedge == "option_option":
perp_ok = True
elif margin_need is None or have_perp is None:
perp_ok = None
else:
perp_ok = have_perp + 1e-9 >= margin_need
perp_ok = float(have_perp) + 1e-9 >= float(margin_need)
opt_ok: bool | None
if premium_need is None or have_opt is None:
opt_ok = None
else:
opt_ok = have_opt + 1e-9 >= premium_need
opt_ok = float(have_opt) + 1e-9 >= float(premium_need)
funds_ok = perp_ok is True and opt_ok is True
if hedge == "option_option":
funds_ok = opt_ok is True
else:
funds_ok = perp_ok is True and opt_ok is True
# 资金恢复后允许下次不足再通知一次
if funds_ok:
_notified_while_short = False
lev_i = int(round(lev)) if abs(lev - round(lev)) < 1e-9 else lev
if perp_ok is True:
if hedge == "option_option":
perp_label = "永续 —(期期)"
elif perp_ok is True:
perp_label = f"永续{lev_i}x 可开"
elif perp_ok is False:
perp_label = f"永续{lev_i}x 不可开"
@@ -181,6 +223,7 @@ def assess_open_capacity(
opt_label = "期权 —"
return {
"hedge_mode": hedge,
"leverage": lev,
"perp_qty_eth": perp_qty,
"option_qty_eth": opt_qty,
@@ -201,11 +244,22 @@ def assess_open_capacity(
def funds_gate_blocks(cap: dict[str, Any] | None) -> tuple[bool, str]:
"""
Fail-closed仅当永续期权均为 True 才放行
None(未知,如币安未接余额)或 False → 拦截。
Fail-closed永期需永续+期权均为 True;期期仅需期权为 True
None(未知)或 False → 拦截。
"""
if not cap:
return True, "资金可开判定结果为空,拒绝开仓"
hedge = str(cap.get("hedge_mode") or "perp_option").strip().lower()
if hedge == "option_option":
if cap.get("option_can_open") is not True:
detail = (
f"{cap.get('option_label')}"
f"期权需≈{cap.get('option_need_usdc')}U/有{cap.get('option_have_usdc')}U"
)
if cap.get("option_can_open") is None:
detail += "(余额/盘口未知,fail-closed 拒绝开仓)"
return True, f"资金不足或状态未知,暂不可开新仓:{detail}"
return False, ""
if cap.get("perp_can_open") is not True or cap.get("option_can_open") is not True:
detail = (
f"{cap.get('perp_label')} · {cap.get('option_label')}"