Try all OTM strikes in offset when nearest fails leverage.
Closest Call@1920 at 152x no longer blocks 1930/1940 that already clear 200x. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -778,39 +778,12 @@ class StrategySession:
|
||||
if not first_skip:
|
||||
first_skip = msg
|
||||
|
||||
for ymd in eligible:
|
||||
if ymd in skip_expiries:
|
||||
_note_skip(f"{ymd} 有残余期权(跳过该到期)")
|
||||
logger.info(
|
||||
"skip expiry=%s: residual pending (semi=%s)",
|
||||
ymd,
|
||||
semi_on,
|
||||
)
|
||||
continue
|
||||
pair = select_option_pair(
|
||||
contracts,
|
||||
mark_px=underlying,
|
||||
expiry_ymd=ymd,
|
||||
option_side=opt_side_hint,
|
||||
moneyness=semi_mny if semi_on else None,
|
||||
otm_max_offset=semi_otm_off if semi_on else None,
|
||||
)
|
||||
if pair is None:
|
||||
if semi_on and semi_mny == "otm":
|
||||
_note_skip(
|
||||
f"{ymd} 无{opt_side_hint or '?'}虚值"
|
||||
f"(偏离≤{float(semi_otm_off or 0):g})"
|
||||
)
|
||||
logger.info(
|
||||
"skip expiry=%s no OTM within offset=%.1f for %s mark=%.2f",
|
||||
ymd,
|
||||
float(semi_otm_off or 0),
|
||||
opt_side_hint,
|
||||
underlying,
|
||||
)
|
||||
else:
|
||||
_note_skip(f"{ymd} 无合格行权价")
|
||||
continue
|
||||
from .selection import _complete_by_expiry, list_otm_strikes
|
||||
|
||||
complete = _complete_by_expiry(contracts)
|
||||
|
||||
def _try_pair(pair: OptionPair) -> OpenPick | None:
|
||||
"""验盘口/方向/杠杆;不合格记 skip 并返回 None(调用方换下一档)。"""
|
||||
if fixed_on:
|
||||
from .selection import is_otm
|
||||
|
||||
@@ -822,7 +795,7 @@ class StrategySession:
|
||||
mark_px=underlying,
|
||||
):
|
||||
_note_skip(f"{ymd} K{pair.strike:g} 非虚值")
|
||||
continue
|
||||
return None
|
||||
if (
|
||||
atm_open_offset(pair.strike, underlying)
|
||||
> float(semi_otm_off or 0) + 1e-9
|
||||
@@ -831,9 +804,8 @@ class StrategySession:
|
||||
f"{ymd} K{pair.strike:g} 偏离>"
|
||||
f"{float(semi_otm_off or 0):g}"
|
||||
)
|
||||
continue
|
||||
return None
|
||||
elif semi_on and semi_mny == "atm":
|
||||
# 平值:须为该到期最接近标的的档
|
||||
pass
|
||||
elif not is_itm_or_atm(
|
||||
option_side=side,
|
||||
@@ -848,7 +820,7 @@ class StrategySession:
|
||||
opt_side_hint,
|
||||
underlying,
|
||||
)
|
||||
continue
|
||||
return None
|
||||
else:
|
||||
offset = atm_open_offset(pair.strike, underlying)
|
||||
if not atm_allows_open(
|
||||
@@ -865,12 +837,11 @@ class StrategySession:
|
||||
offset,
|
||||
max_atm_off,
|
||||
)
|
||||
continue
|
||||
return None
|
||||
call_bids, call_asks, _ = self.ex.fetch_book(pair.call_inst_id, depth=5)
|
||||
put_bids, put_asks, _ = self.ex.fetch_book(pair.put_inst_id, depth=5)
|
||||
call_ask = call_asks[0].px if call_asks else None
|
||||
put_ask = put_asks[0].px if put_asks else None
|
||||
# REST 被限流时回退 WS/缓存盘口
|
||||
if call_ask is None:
|
||||
cq = self.ex.quote(pair.call_inst_id)
|
||||
call_ask = cq.ask if cq else None
|
||||
@@ -891,7 +862,7 @@ class StrategySession:
|
||||
"Put" if opt_side_hint == "put" else "Call/Put"
|
||||
)
|
||||
_note_skip(f"{ymd} K{pair.strike:g} 缺{need}卖一")
|
||||
continue
|
||||
return None
|
||||
opt_ask = sig.call_ask if sig.option_side == "call" else sig.put_ask
|
||||
lev = option_leverage(underlying, opt_ask)
|
||||
hours_left = hours_until_expiry(ymd, expiry_ms=pair.expiry_ms)
|
||||
@@ -909,12 +880,10 @@ class StrategySession:
|
||||
min_lev,
|
||||
hours_left,
|
||||
)
|
||||
continue
|
||||
return None
|
||||
self._apply_pair(pair, mark=underlying, idx=idx)
|
||||
# warm_and_subscribe 已写盘口;再覆盖刚拉的 ask 侧
|
||||
from ..exchange.book_cache import BookCache
|
||||
|
||||
# 直接通过 exchange quote path:再 upsert
|
||||
if hasattr(self.ex, "cache"):
|
||||
cache: BookCache = self.ex.cache # type: ignore[attr-defined]
|
||||
cache.upsert_book(pair.call_inst_id, bids=call_bids, asks=call_asks)
|
||||
@@ -933,6 +902,72 @@ class StrategySession:
|
||||
underlying_px=underlying,
|
||||
hedge_mode="perp_option",
|
||||
)
|
||||
|
||||
for ymd in eligible:
|
||||
if ymd in skip_expiries:
|
||||
_note_skip(f"{ymd} 有残余期权(跳过该到期)")
|
||||
logger.info(
|
||||
"skip expiry=%s: residual pending (semi=%s)",
|
||||
ymd,
|
||||
semi_on,
|
||||
)
|
||||
continue
|
||||
|
||||
# 半自动虚值:同到期内由近到远试偏离内各档,近档杠杆不够再试 1930/1940
|
||||
if semi_on and semi_mny == "otm" and opt_side_hint in ("call", "put"):
|
||||
if ymd not in complete:
|
||||
_note_skip(f"{ymd} 无完整对")
|
||||
continue
|
||||
ems, strikes_map = complete[ymd]
|
||||
otm_ks = list_otm_strikes(
|
||||
list(strikes_map.keys()),
|
||||
underlying,
|
||||
option_side=str(opt_side_hint),
|
||||
max_offset=float(semi_otm_off or 0),
|
||||
)
|
||||
if not otm_ks:
|
||||
_note_skip(
|
||||
f"{ymd} 无{opt_side_hint}虚值"
|
||||
f"(偏离≤{float(semi_otm_off or 0):g})"
|
||||
)
|
||||
logger.info(
|
||||
"skip expiry=%s no OTM within offset=%.1f for %s mark=%.2f",
|
||||
ymd,
|
||||
float(semi_otm_off or 0),
|
||||
opt_side_hint,
|
||||
underlying,
|
||||
)
|
||||
continue
|
||||
for k in otm_ks:
|
||||
legs = strikes_map.get(float(k)) or strikes_map.get(k)
|
||||
if not legs or "C" not in legs or "P" not in legs:
|
||||
continue
|
||||
pair = OptionPair(
|
||||
expiry_ymd=ymd,
|
||||
expiry_ms=ems,
|
||||
strike=float(k),
|
||||
call_inst_id=legs["C"],
|
||||
put_inst_id=legs["P"],
|
||||
)
|
||||
picked = _try_pair(pair)
|
||||
if picked is not None:
|
||||
return picked
|
||||
continue
|
||||
|
||||
pair = select_option_pair(
|
||||
contracts,
|
||||
mark_px=underlying,
|
||||
expiry_ymd=ymd,
|
||||
option_side=opt_side_hint,
|
||||
moneyness=semi_mny if semi_on else None,
|
||||
otm_max_offset=semi_otm_off if semi_on else None,
|
||||
)
|
||||
if pair is None:
|
||||
_note_skip(f"{ymd} 无合格行权价")
|
||||
continue
|
||||
picked = _try_pair(pair)
|
||||
if picked is not None:
|
||||
return picked
|
||||
# 报最近到期(列表最前)的原因,避免只显示远月 261225 造成误会
|
||||
why = first_skip or last_skip
|
||||
if why:
|
||||
|
||||
Reference in New Issue
Block a user