a1abe159fa
Add deploy/manage.sh bootstrap for git.bz121.com/dekun/crypto_okx and point docs at this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
145 lines
4.5 KiB
Python
145 lines
4.5 KiB
Python
"""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}),请先平仓后再开"
|
|
|
|
|
|
def compound_full_single_position_block_msg(
|
|
ex: Any,
|
|
*,
|
|
fetch_positions=None,
|
|
) -> Optional[str]:
|
|
"""全仓复利:账户内已有任意期权持仓则禁止再开(仅允许 1 笔)."""
|
|
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 "无法获取期权持仓,全仓复利模式暂不可开仓"
|
|
active = count_live_option_positions(rows)
|
|
if active >= 1:
|
|
return f"全仓复利模式仅允许同时持有 1 笔仓位(当前 {active} 笔),请先平仓"
|
|
return None
|