Files
CoinOX/lib/hedge_plan/hedge_po_mode_lib.py
T
dekun 5d161b4220 Add HEDGE_PO_MODE instance modes with shared sim/live path.
Lock one mode per instance/subaccount, cross-asset BTC option/ETH perp sizing, combo-U exit, and ship docs/tests/audit.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-14 20:05:25 +08:00

297 lines
9.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""永期对冲实例模式(env: HEDGE_PO_MODE).
一实例一种模式,对应一个 OKX 子账户;模拟盘与实盘同一执行路径.
eth → 期权 ETH + 永续 ETH
btc → 期权 BTC + 永续 BTC
btc_eth → 期权 BTC + 永续 ETH;永续名义 = 期权名义 × HEDGE_CROSS_NOTIONAL_K(默认 0.5)
方向(以期权为主):Call→永续空 / Put→永续多.
出场:组合盈利金额(U),扣开平双边手续费.
"""
from __future__ import annotations
import logging
import os
from typing import Any, Optional
logger = logging.getLogger(__name__)
MODE_ETH = "eth"
MODE_BTC = "btc"
MODE_BTC_ETH = "btc_eth"
VALID_PO_MODES = frozenset({MODE_ETH, MODE_BTC, MODE_BTC_ETH})
DEFAULT_CROSS_NOTIONAL_K = 0.5
_ALIASES = {
"eth_eth": MODE_ETH,
"e": MODE_ETH,
"eth-only": MODE_ETH,
"btc_btc": MODE_BTC,
"b": MODE_BTC,
"btc-only": MODE_BTC,
"cross": MODE_BTC_ETH,
"btc-eth": MODE_BTC_ETH,
"btc/eth": MODE_BTC_ETH,
"跨币": MODE_BTC_ETH,
}
def _sf(v: Any) -> Optional[float]:
if v is None or v == "":
return None
try:
return float(v)
except (TypeError, ValueError):
return None
def normalize_hedge_po_mode(raw: Optional[str]) -> str:
s = str(raw or "").strip().lower().replace("-", "_").replace("/", "_")
if s in VALID_PO_MODES:
return s
if s in _ALIASES:
return _ALIASES[s]
return ""
def infer_hedge_po_mode_from_underly() -> str:
u = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper()
if u == "BTC":
return MODE_BTC
return MODE_ETH
def get_hedge_po_mode() -> str:
m = normalize_hedge_po_mode(os.getenv("HEDGE_PO_MODE"))
if m:
return m
return infer_hedge_po_mode_from_underly()
def cross_notional_k() -> float:
"""跨币:永续名义 / 期权名义.默认 0.5."""
v = _sf(os.getenv("HEDGE_CROSS_NOTIONAL_K"))
if v is None or v <= 0:
return DEFAULT_CROSS_NOTIONAL_K
# 合理钳制,避免误配把永续打到极端
return max(0.05, min(float(v), 5.0))
def default_option_perp_ratio(mode: Optional[str] = None) -> float:
"""UI/定仓用比例:期权名义 ÷ 比例 = 永续名义 → ratio = 1/k."""
m = mode or get_hedge_po_mode()
if m == MODE_BTC_ETH:
k = cross_notional_k()
return round(1.0 / k, 6) if k > 0 else 2.0
return 2.0
def option_underlying(mode: Optional[str] = None) -> str:
m = mode or get_hedge_po_mode()
if m in (MODE_BTC, MODE_BTC_ETH):
return "BTC"
return "ETH"
def perp_underlying(mode: Optional[str] = None) -> str:
m = mode or get_hedge_po_mode()
if m == MODE_BTC:
return "BTC"
return "ETH"
def is_cross_asset(mode: Optional[str] = None) -> bool:
return (mode or get_hedge_po_mode()) == MODE_BTC_ETH
def mode_label(mode: Optional[str] = None) -> str:
m = mode or get_hedge_po_mode()
return {
MODE_ETH: "ETH永期",
MODE_BTC: "BTC永期",
MODE_BTC_ETH: "跨币BTC期权/ETH永续",
}.get(m, m or "—")
def mode_short_label(mode: Optional[str] = None) -> str:
m = mode or get_hedge_po_mode()
return {
MODE_ETH: "ETH",
MODE_BTC: "BTC",
MODE_BTC_ETH: "BTC→ETH",
}.get(m, m or "—")
def clamp_hedge_po_mode_on_startup() -> dict[str, Any]:
"""启动钳制:规范化 HEDGE_PO_MODE,并同步默认期权标的.
不写回 .env 文件(避免热更/多实例踩踏);仅 os.environ 进程内生效.
返回审计摘要供日志/测试.
"""
raw = (os.getenv("HEDGE_PO_MODE") or "").strip()
normalized = normalize_hedge_po_mode(raw) if raw else ""
warnings: list[str] = []
if raw and not normalized:
warnings.append(f"非法 HEDGE_PO_MODE={raw!r},回退推断")
mode = infer_hedge_po_mode_from_underly()
elif normalized:
mode = normalized
else:
mode = infer_hedge_po_mode_from_underly()
warnings.append(f"未配置 HEDGE_PO_MODE,按 OKX_OPTIONS_DEFAULT_UNDERLY 推断为 {mode}")
os.environ["HEDGE_PO_MODE"] = mode
opt_u = option_underlying(mode)
prev_u = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "").strip().upper()
if prev_u != opt_u:
os.environ["OKX_OPTIONS_DEFAULT_UNDERLY"] = opt_u
if prev_u:
warnings.append(f"同步 OKX_OPTIONS_DEFAULT_UNDERLY {prev_u}→{opt_u}")
k = cross_notional_k()
os.environ["HEDGE_CROSS_NOTIONAL_K"] = str(k)
summary = {
"ok": True,
"mode": mode,
"label": mode_label(mode),
"option_underlying": opt_u,
"perp_underlying": perp_underlying(mode),
"cross_notional_k": k,
"option_perp_ratio": default_option_perp_ratio(mode),
"warnings": warnings,
}
for w in warnings:
logger.warning("[HEDGE_PO_MODE] %s", w)
logger.info(
"[HEDGE_PO_MODE] mode=%s option=%s perp=%s k=%s (一实例一模式一子账户)",
mode,
opt_u,
summary["perp_underlying"],
k,
)
return summary
def apply_mode_to_start_body(body: dict[str, Any], *, mode: Optional[str] = None) -> dict[str, Any]:
"""启动前按实例模式钳制 body(模拟/实盘同路径)."""
m = mode or get_hedge_po_mode()
out = dict(body or {})
opt_u = option_underlying(m)
perp_u = perp_underlying(m)
out["po_mode"] = m
out["underlying"] = opt_u
out["option_underlying"] = opt_u
out["perp_underlying"] = perp_u
# 以期权为主 + 组合盈利金额出场
out["option_primary"] = True
out["exit_mode"] = "profit"
ratio = default_option_perp_ratio(m)
# 允许用户略调比例,但跨币默认锁到 1/k;若未填则写入默认
if out.get("option_perp_ratio") in (None, ""):
out["option_perp_ratio"] = ratio
elif m == MODE_BTC_ETH:
# 跨币强制名义系数,避免页内误改导致与子账户策略不一致
out["option_perp_ratio"] = ratio
# 永续 symbol:若未传或与模式不符则按 perp 标的重建
sym = str(out.get("exchange_symbol") or "").upper()
want_prefix = f"{perp_u}/"
if not sym or not sym.startswith(want_prefix):
out["exchange_symbol"] = f"{perp_u}/USDT:USDT"
return out
def validate_body_matches_mode(body: dict[str, Any], *, mode: Optional[str] = None) -> Optional[str]:
m = mode or get_hedge_po_mode()
opt_u = option_underlying(m)
perp_u = perp_underlying(m)
uly = str(body.get("underlying") or body.get("option_underlying") or "").upper()
if uly and uly != opt_u:
return f"本实例模式 {mode_label(m)} 期权标的须为 {opt_u},收到 {uly}"
inst = str(body.get("opt_inst_id") or "").upper()
if inst and not inst.startswith(f"{opt_u}-"):
return f"期权合约须为 {opt_u} 标的(本实例 {mode_label(m)})"
sym = str(body.get("exchange_symbol") or "").upper()
if sym and not sym.startswith(f"{perp_u}/"):
return f"永续合约须为 {perp_u}(本实例 {mode_label(m)})"
return None
def combo_net_usdt(
net: dict[str, Any] | None,
*,
option_index: float,
) -> Optional[float]:
"""币本位期权净利×期权指数 + 永续 U 净利 → 组合 U.
监控出场与持仓卡必须同口径;禁止把币与 U 直接相加.
"""
if not net:
return None
idx = float(option_index or 0)
if idx <= 0:
return None
opt_coin = float(net.get("opt_net") or 0)
perp_u = float(net.get("perp_net") or 0)
return round(opt_coin * idx + perp_u, 4)
def size_cross_notional(
*,
option_coin_qty: float,
option_index: float,
perp_index: float,
contract_size: float,
notional_k: Optional[float] = None,
) -> dict[str, Any]:
"""跨币/同币统一:期权币名义 → U 名义 → 永续张数.
同币时 option_index≈perp_index,等价于 coin_qty * k / cs.
"""
k = float(notional_k if notional_k is not None else cross_notional_k())
oc = float(option_coin_qty or 0)
oi = float(option_index or 0)
pi = float(perp_index or 0)
cs = float(contract_size or 0.01)
if oc <= 0 or oi <= 0 or pi <= 0 or cs <= 0 or k <= 0:
return {
"ok": False,
"msg": "跨币定仓参数无效",
"option_notional_usdt": 0.0,
"perp_notional_usdt": 0.0,
"perp_coin": 0.0,
"contracts": 0.0,
"notional_k": k,
}
opt_n = oc * oi
perp_n = opt_n * k
perp_coin = perp_n / pi
contracts = perp_coin / cs
return {
"ok": True,
"msg": "",
"option_notional_usdt": round(opt_n, 4),
"perp_notional_usdt": round(perp_n, 4),
"perp_coin": round(perp_coin, 8),
"contracts": contracts,
"notional_k": k,
}
def public_mode_payload(mode: Optional[str] = None) -> dict[str, Any]:
m = mode or get_hedge_po_mode()
return {
"po_mode": m,
"po_mode_label": mode_label(m),
"po_mode_short": mode_short_label(m),
"option_underlying": option_underlying(m),
"perp_underlying": perp_underlying(m),
"cross_asset": is_cross_asset(m),
"cross_notional_k": cross_notional_k() if is_cross_asset(m) else None,
"option_perp_ratio": default_option_perp_ratio(m),
"exit_mode": "profit",
"option_primary": True,
"instance_rule": "一实例一种模式对应一个子账户;模拟盘与实盘同路径;账户互不干涉",
}