Let semi-auto reopen nearest expiry; ignore one-expiry-per-day.
Far quarterly skips were masking usable near OTM quotes like 260810. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1186,7 +1186,12 @@ class StrategyEngine:
|
||||
one_expiry_per_day = self.ledger.get_setting_bool(
|
||||
"one_expiry_per_day", s.one_expiry_per_day
|
||||
)
|
||||
if pick is not None and one_expiry_per_day:
|
||||
# 半自动人工授权:允许同到期再开;全自动仍受一日一到期约束
|
||||
if (
|
||||
pick is not None
|
||||
and one_expiry_per_day
|
||||
and not is_semi_auto(self.ledger)
|
||||
):
|
||||
from .clock import (
|
||||
expiry_blocked_by_one_per_day,
|
||||
used_expiry_ymds,
|
||||
|
||||
@@ -89,10 +89,10 @@ def _as_bool_setting(raw: str | None, default: bool) -> bool:
|
||||
return str(raw).strip().lower() in ("1", "true", "yes", "on")
|
||||
|
||||
|
||||
def _skip_expiry_ymds_for_next() -> set[str]:
|
||||
def _skip_expiry_ymds_for_next(*, include_used: bool = True) -> set[str]:
|
||||
"""
|
||||
空仓选约/监控应跳过的到期日:
|
||||
- 历史上已开过该到期(one_expiry_per_day,跨日)
|
||||
- 历史上已开过该到期(one_expiry_per_day,跨日);半自动可关
|
||||
- 仍有待结算残留期权的到期档(该档已「完成」开平,盯下一档)
|
||||
"""
|
||||
skip: set[str] = set()
|
||||
@@ -102,12 +102,13 @@ def _skip_expiry_ymds_for_next() -> set[str]:
|
||||
|
||||
s = get_settings()
|
||||
db = get_db()
|
||||
one_exp_day = _as_bool_setting(
|
||||
db.get_setting("one_expiry_per_day", str(s.one_expiry_per_day)),
|
||||
s.one_expiry_per_day,
|
||||
)
|
||||
if one_exp_day:
|
||||
skip |= used_expiry_ymds(db)
|
||||
if include_used:
|
||||
one_exp_day = _as_bool_setting(
|
||||
db.get_setting("one_expiry_per_day", str(s.one_expiry_per_day)),
|
||||
s.one_expiry_per_day,
|
||||
)
|
||||
if one_exp_day:
|
||||
skip |= used_expiry_ymds(db)
|
||||
skip |= pending_residual_expiry_ymds(db)
|
||||
except Exception:
|
||||
logger.exception("skip-expiry lookup failed; continue without skip")
|
||||
@@ -765,14 +766,25 @@ class StrategySession:
|
||||
self._last_pick_fail = f"无剩余≥{min_hours:g}h 的到期"
|
||||
return None
|
||||
|
||||
skip_expiries = _skip_expiry_ymds_for_next()
|
||||
# 半自动:人工授权可重复开同到期,不受 one_expiry_per_day 挡住;
|
||||
# 仍跳过有残余腿的到期。全自动保持原「一日一到期」。
|
||||
skip_expiries = _skip_expiry_ymds_for_next(include_used=not semi_on)
|
||||
first_skip = ""
|
||||
last_skip = ""
|
||||
|
||||
def _note_skip(msg: str) -> None:
|
||||
nonlocal first_skip, last_skip
|
||||
last_skip = msg
|
||||
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: used today and/or residual pending",
|
||||
"skip expiry=%s: residual pending (semi=%s)",
|
||||
ymd,
|
||||
semi_on,
|
||||
)
|
||||
continue
|
||||
pair = select_option_pair(
|
||||
@@ -785,7 +797,7 @@ class StrategySession:
|
||||
)
|
||||
if pair is None:
|
||||
if semi_on and semi_mny == "otm":
|
||||
last_skip = (
|
||||
_note_skip(
|
||||
f"{ymd} 无{opt_side_hint or '?'}虚值"
|
||||
f"(偏离≤{float(semi_otm_off or 0):g})"
|
||||
)
|
||||
@@ -797,7 +809,7 @@ class StrategySession:
|
||||
underlying,
|
||||
)
|
||||
else:
|
||||
last_skip = f"{ymd} 无合格行权价"
|
||||
_note_skip(f"{ymd} 无合格行权价")
|
||||
continue
|
||||
if fixed_on:
|
||||
from .selection import is_otm
|
||||
@@ -809,13 +821,13 @@ class StrategySession:
|
||||
strike=pair.strike,
|
||||
mark_px=underlying,
|
||||
):
|
||||
last_skip = f"{ymd} K{pair.strike:g} 非虚值"
|
||||
_note_skip(f"{ymd} K{pair.strike:g} 非虚值")
|
||||
continue
|
||||
if (
|
||||
atm_open_offset(pair.strike, underlying)
|
||||
> float(semi_otm_off or 0) + 1e-9
|
||||
):
|
||||
last_skip = (
|
||||
_note_skip(
|
||||
f"{ymd} K{pair.strike:g} 偏离>"
|
||||
f"{float(semi_otm_off or 0):g}"
|
||||
)
|
||||
@@ -828,7 +840,7 @@ class StrategySession:
|
||||
strike=pair.strike,
|
||||
mark_px=underlying,
|
||||
):
|
||||
last_skip = f"{ymd} K{pair.strike:g} 非实值/平值"
|
||||
_note_skip(f"{ymd} K{pair.strike:g} 非实值/平值")
|
||||
logger.info(
|
||||
"skip expiry=%s strike=%.0f not ITM/ATM for %s mark=%.2f",
|
||||
ymd,
|
||||
@@ -845,7 +857,7 @@ class StrategySession:
|
||||
max_offset=max_atm_off,
|
||||
enabled=atm_off_on,
|
||||
):
|
||||
last_skip = f"{ymd} ATM偏离{offset:.1f}>{max_atm_off:g}"
|
||||
_note_skip(f"{ymd} ATM偏离{offset:.1f}>{max_atm_off:g}")
|
||||
logger.info(
|
||||
"skip expiry=%s strike=%.0f atm_offset=%.1f > max=%.1f",
|
||||
ymd,
|
||||
@@ -878,13 +890,13 @@ class StrategySession:
|
||||
need = "Call" if (opt_side_hint == "call") else (
|
||||
"Put" if opt_side_hint == "put" else "Call/Put"
|
||||
)
|
||||
last_skip = f"{ymd} K{pair.strike:g} 缺{need}卖一"
|
||||
_note_skip(f"{ymd} K{pair.strike:g} 缺{need}卖一")
|
||||
continue
|
||||
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)
|
||||
if lev is None or lev + 1e-9 < min_lev:
|
||||
last_skip = (
|
||||
_note_skip(
|
||||
f"{ymd} {sig.option_side.upper()}@{pair.strike:g} "
|
||||
f"杠杆{(f'{lev:.0f}x' if lev else 'n/a')}<{min_lev:g}x"
|
||||
)
|
||||
@@ -921,14 +933,16 @@ class StrategySession:
|
||||
underlying_px=underlying,
|
||||
hedge_mode="perp_option",
|
||||
)
|
||||
if last_skip:
|
||||
# 报最近到期(列表最前)的原因,避免只显示远月 261225 造成误会
|
||||
why = first_skip or last_skip
|
||||
if why:
|
||||
hint = ""
|
||||
if semi_on:
|
||||
hint = (
|
||||
f"(半自动{opt_side_hint or '?'}·"
|
||||
f"{semi_mny or '?'}·≥{min_lev:g}x·≥{min_hours:g}h)"
|
||||
)
|
||||
self._last_pick_fail = f"最近跳过: {last_skip}{hint}"
|
||||
self._last_pick_fail = f"{why}{hint}"
|
||||
else:
|
||||
self._last_pick_fail = "合格到期均被跳过(一日一到期/残余等)"
|
||||
return None
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
**以损定仓(半自动)**:系统设置 `sizing_mode=risk_based` 时,开仓前用**盘口卖一**估算单位成本 → 预算反推 k →
|
||||
`期权量 = 期权单位 × k`,`永续量 = 永续单位 × k`(配比不变)。首页「半自动 · 本单」下方按当前报价档卖一实时预览开仓量。
|
||||
|
||||
选约到期与报价列表一致:取剩余 ≥ 最短 h 的**最近**到期。半自动**不受**全自动「一日一到期」限制(仍可跳过有残余腿的到期)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 出场
|
||||
|
||||
Reference in New Issue
Block a user