Add fixed-direction switch for perp/option open side.

When enabled, lock perp long→buy Put or short→buy Call with ITM/ATM only; off keeps ATM/ask rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-29 19:18:12 +08:00
parent 55109e82d3
commit 0ee1d8be5d
16 changed files with 427 additions and 46 deletions
+56 -4
View File
@@ -46,6 +46,46 @@ def pick_atm_strike(strikes: list[float], mark_px: float) -> float | None:
return min(strikes, key=lambda s: (abs(s - mark_px), s))
def pick_itm_or_atm_strike(
strikes: list[float],
mark_px: float,
*,
option_side: str,
) -> float | None:
"""
固定方向选约:只要实值或平值,不要虚值。
- Call:行权价 ≤ 标的(平值/实值)
- Put:行权价 ≥ 标的(平值/实值)
在合格档中取最接近标的者(优先平值)。
"""
if not strikes or mark_px <= 0:
return None
side = (option_side or "").strip().lower()
if side == "call":
cands = [float(s) for s in strikes if float(s) <= float(mark_px) + 1e-9]
elif side == "put":
cands = [float(s) for s in strikes if float(s) >= float(mark_px) - 1e-9]
else:
return None
if not cands:
return None
return min(cands, key=lambda s: (abs(s - float(mark_px)), s))
def is_itm_or_atm(*, option_side: str, strike: float, mark_px: float) -> bool:
"""Call: K≤SPut: K≥S。"""
if mark_px <= 0:
return False
side = (option_side or "").strip().lower()
k = float(strike)
s = float(mark_px)
if side == "call":
return k <= s + 1e-9
if side == "put":
return k >= s - 1e-9
return False
def atm_open_offset(strike: float, mark_px: float) -> float:
"""开仓用:ATM 行权价相对标的的绝对点差。"""
return abs(float(strike) - float(mark_px))
@@ -125,7 +165,13 @@ def select_option_pair(
expiry_ymd: str | None = None,
min_hours: float | None = None,
now: datetime | None = None,
option_side: str | None = None,
) -> OptionPair | None:
"""
选到期 + 行权价。
option_side 为 call/put 时:按实值/平值选档(固定方向模式);
否则仍选 ATM(现有规则)。
"""
complete = _complete_by_expiry(contracts)
if not complete:
return None
@@ -148,14 +194,20 @@ def select_option_pair(
ymd = eligible[0]
ems, strikes_map = complete[ymd]
atm = pick_atm_strike(list(strikes_map.keys()), mark_px)
if atm is None:
side = (option_side or "").strip().lower() or None
if side in ("call", "put"):
strike = pick_itm_or_atm_strike(
list(strikes_map.keys()), mark_px, option_side=side
)
else:
strike = pick_atm_strike(list(strikes_map.keys()), mark_px)
if strike is None:
return None
legs = strikes_map[atm]
legs = strikes_map[strike]
return OptionPair(
expiry_ymd=ymd,
expiry_ms=ems,
strike=atm,
strike=strike,
call_inst_id=legs["C"],
put_inst_id=legs["P"],
)