"""开仓统一管道:定仓 → 兑 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, )