b09d1b0886
Semi sizing uses market ask with semi units; Plan panel previews option/perp qty under the form. Co-authored-by: Cursor <cursoragent@cursor.com>
165 lines
5.5 KiB
Python
165 lines
5.5 KiB
Python
"""开仓统一管道:定仓 → 兑 USDC → 资金门(fail-closed)。
|
|
|
|
策略自动开仓与手动开一组共用,避免双路径打架。
|
|
基线假设:运行中不在交易所人工开平仓、也不人工手动平仓。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from ..models.db import Database, get_db
|
|
from .open_capacity import assess_open_capacity, funds_gate_blocks
|
|
from .risk_sizing import apply_risk_sizing_to_ledger
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class OpenPrepResult:
|
|
ok: bool
|
|
detail: str = ""
|
|
capacity: dict[str, Any] | None = None
|
|
sizing_detail: str = ""
|
|
convert_detail: str = ""
|
|
|
|
|
|
def prepare_usdc_while_waiting(db: Database | None = None) -> dict[str, Any]:
|
|
"""空仓等待选约:预览名义检测 USDC 并兑换,不落库改写 qty/exit。"""
|
|
from .auto_usdc import prepare_okx_trading_usdc
|
|
|
|
return prepare_okx_trading_usdc(db)
|
|
|
|
|
|
def size_and_gate(
|
|
*,
|
|
index_px: float,
|
|
option_ask: float,
|
|
db: Database | None = None,
|
|
call_ask: float | None = None,
|
|
put_ask: float | None = None,
|
|
hedge_mode: str | None = None,
|
|
) -> OpenPrepResult:
|
|
"""
|
|
选约成功后:写入以损定仓 → 交易账户兑 USDC → 资金门。
|
|
资金门 fail-closed:异常 / can_open 非 True 一律拦截。
|
|
"""
|
|
database = db or get_db()
|
|
mode = str(hedge_mode or "").strip().lower()
|
|
if not mode:
|
|
try:
|
|
from ..config import get_settings
|
|
from ..sim.ledger import Ledger
|
|
|
|
s = get_settings()
|
|
mode = str(
|
|
Ledger(database).get_setting_str("hedge_mode", s.hedge_mode)
|
|
or s.hedge_mode
|
|
).strip().lower()
|
|
except Exception:
|
|
mode = "perp_option"
|
|
|
|
try:
|
|
if mode == "option_option":
|
|
from .risk_sizing import apply_oo_sizing_to_ledger
|
|
|
|
if call_ask is None or put_ask is None:
|
|
return OpenPrepResult(ok=False, detail="期期定仓缺少 call/put 卖一")
|
|
rs = apply_oo_sizing_to_ledger(
|
|
call_ask=float(call_ask),
|
|
put_ask=float(put_ask),
|
|
index_px=float(index_px),
|
|
db=database,
|
|
)
|
|
if not rs.ok:
|
|
return OpenPrepResult(ok=False, detail=rs.detail, sizing_detail=rs.detail)
|
|
else:
|
|
from ..sim.ledger import Ledger
|
|
from .risk_sizing import is_risk_based
|
|
from .semi_auto import is_semi_auto, read_semi_params
|
|
|
|
led = Ledger(database)
|
|
semi_perp = None
|
|
semi_opt = None
|
|
semi_exit = None
|
|
semi_basis = None
|
|
if is_semi_auto(led):
|
|
sp = read_semi_params(led)
|
|
semi_perp = float(sp["perp_unit"])
|
|
semi_opt = float(sp["option_unit"])
|
|
semi_exit = float(sp["perp_exit_unit"])
|
|
# 半自动以损定仓:按盘口卖一推期权量,再按配比乘 k 得永续量
|
|
semi_basis = "actual"
|
|
if not is_risk_based(led):
|
|
# 手动仓:半自动直接写入 永续:期权 配比名义
|
|
database.set_setting("perp_qty_eth", f"{semi_perp:.4f}")
|
|
database.set_setting("option_qty_eth", f"{semi_opt:.4f}")
|
|
rs = apply_risk_sizing_to_ledger(
|
|
index_px=float(index_px),
|
|
option_ask=float(option_ask),
|
|
db=database,
|
|
perp_unit=semi_perp,
|
|
option_unit=semi_opt,
|
|
exit_unit=semi_exit,
|
|
leverage_basis=semi_basis,
|
|
)
|
|
if not rs.ok:
|
|
return OpenPrepResult(ok=False, detail=rs.detail, sizing_detail=rs.detail)
|
|
except Exception as e:
|
|
logger.exception("risk sizing failed in open pipeline")
|
|
return OpenPrepResult(ok=False, detail=f"以损定仓计算异常:{e}")
|
|
|
|
convert_detail = ""
|
|
try:
|
|
from .auto_usdc import ensure_okx_trading_usdc
|
|
|
|
# 选约后名义已更新;仍受冷却约束,禁止 force 砸单
|
|
conv = ensure_okx_trading_usdc(
|
|
database,
|
|
cap=assess_open_capacity(
|
|
database,
|
|
option_ask=float(option_ask),
|
|
call_ask=call_ask,
|
|
put_ask=put_ask,
|
|
),
|
|
force=False,
|
|
)
|
|
convert_detail = str(conv.get("detail") or "")
|
|
if conv.get("acted"):
|
|
logger.info("open_pipeline auto_usdc: %s", convert_detail)
|
|
except Exception:
|
|
logger.exception("auto USDC in open pipeline failed")
|
|
convert_detail = "自动兑 USDC 异常(已记日志)"
|
|
|
|
try:
|
|
cap = assess_open_capacity(
|
|
database,
|
|
option_ask=float(option_ask),
|
|
call_ask=call_ask,
|
|
put_ask=put_ask,
|
|
)
|
|
except Exception as e:
|
|
logger.exception("open capacity assess failed")
|
|
return OpenPrepResult(
|
|
ok=False,
|
|
detail=f"资金可开判定失败,拒绝开仓:{e}",
|
|
convert_detail=convert_detail,
|
|
)
|
|
|
|
blocked, why = funds_gate_blocks(cap)
|
|
if blocked:
|
|
return OpenPrepResult(
|
|
ok=False,
|
|
detail=why or "资金不足或状态未知,拒绝开仓",
|
|
capacity=cap,
|
|
convert_detail=convert_detail,
|
|
)
|
|
return OpenPrepResult(
|
|
ok=True,
|
|
detail="ready",
|
|
capacity=cap,
|
|
convert_detail=convert_detail,
|
|
)
|