"""OKX 期权持仓笔数上限(env: OKX_OPTIONS_MAX_ACTIVE_POSITIONS).""" from __future__ import annotations import os from typing import Any, Optional def options_max_active_positions() -> int: """同时持有的期权合约笔数上限;0=不限制.热更读 env.""" raw = os.getenv("OKX_OPTIONS_MAX_ACTIVE_POSITIONS", "0") try: v = int(float(str(raw).strip())) except (TypeError, ValueError): return 0 return max(0, v) def count_live_option_positions(rows: Optional[list[dict[str, Any]]]) -> int: if not rows: return 0 n = 0 for r in rows: if not isinstance(r, dict): continue try: pos = float(r.get("pos") or 0) except (TypeError, ValueError): continue if abs(pos) >= 1e-12: n += 1 return n def _inst_already_open(rows: list[dict[str, Any]], inst_id: str) -> bool: want = (inst_id or "").strip() if not want: return False for r in rows: if str(r.get("instId") or r.get("inst_id") or "").strip() == want: return True return False def option_position_limit_block_msg( ex: Any, *, opening_inst_id: str = "", max_active: Optional[int] = None, fetch_positions=None, ) -> Optional[str]: """若禁止新开买期权则返回中文原因,否则 None. - max_active<=0:不限制 - 加仓已有合约(opening_inst_id 已在持仓中):不占新笔数,放行 - 拉持仓失败:拒绝开仓(避免绕过上限) """ mx = options_max_active_positions() if max_active is None else int(max_active) if mx <= 0: return None fetch = fetch_positions if fetch is None: from lib.exchange.okx_options_lib import fetch_option_positions fetch = fetch_option_positions try: rows = fetch(ex) except Exception: rows = None if rows is None: return f"无法获取期权持仓,暂不可开仓(上限 {mx} 笔)" active = count_live_option_positions(rows) if _inst_already_open(rows, opening_inst_id): return None if active >= mx: return f"期权持仓已达上限({active}/{mx}),请先平仓后再开" return None