"""OKX 期权持仓笔数上限(env: OKX_OPTIONS_MAX_ACTIVE_POSITIONS).""" from __future__ import annotations import os from typing import Any, Optional, Sequence 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 _normalize_inst_ids( opening_inst_id: str = "", opening_inst_ids: Optional[Sequence[str]] = None, ) -> list[str]: out: list[str] = [] seen: set[str] = set() for raw in list(opening_inst_ids or []) + ([opening_inst_id] if opening_inst_id else []): iid = str(raw or "").strip() if not iid or iid in seen: continue seen.add(iid) out.append(iid) return out def option_position_limit_block_msg( ex: Any, *, opening_inst_id: str = "", opening_inst_ids: Optional[Sequence[str]] = None, new_positions: Optional[int] = None, max_active: Optional[int] = None, fetch_positions=None, ) -> Optional[str]: """若禁止新开买期权则返回中文原因,否则 None. - max_active<=0:不限制 - opening_inst_ids:本次要开的合约;已在持仓中的不占新笔数 - new_positions:显式指定还需新占几笔(默认按 opening_inst_ids 推算) - 期期两腿应一次传入两个 inst_id,在开仓前预检,避免上限=1 时开出半边仓 - 拉持仓失败:拒绝开仓(避免绕过上限) """ try: from lib.hedge_plan.okx_trade_mode_lib import standalone_options_open_allowed # 对冲模式用 MAX_ACTIVE_HEDGE_PLANS 管「组数」,不占用期权笔数上限 if max_active is None and not standalone_options_open_allowed(): return None except Exception: pass 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) ids = _normalize_inst_ids(opening_inst_id, opening_inst_ids) if new_positions is None: if ids: already = sum(1 for i in ids if _inst_already_open(rows, i)) need = max(0, len(ids) - already) else: need = 1 else: need = max(0, int(new_positions)) if need <= 1 and len(ids) == 1 and _inst_already_open(rows, ids[0]): return None if need <= 0: return None if active + need <= mx: return None if need >= 2: return ( f"期期对冲需新开 {need} 笔期权,当前已有 {active} 笔、上限 {mx};" f"请将 OKX_OPTIONS_MAX_ACTIVE_POSITIONS 设为 0(不限制)或不小于 {active + need},或先平仓" ) return f"期权持仓已达上限({active}/{mx}),请先平仓后再开"