Prefer open direction from ATM vs spot (below=Call/short, above=Put/long).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-25 12:39:23 +08:00
parent ee0ec57f89
commit 79c074ec6c
6 changed files with 110 additions and 25 deletions
+24 -5
View File
@@ -76,19 +76,38 @@ class MarketSnapshot:
"ask_compare": {
"call_ask": self.call.ask if self.call else None,
"put_ask": self.put.ask if self.put else None,
"bias": _ask_bias(self.call, self.put),
"bias": _open_bias(self.pair, self.index_px, self.perp, self.call, self.put),
},
}
def _ask_bias(call: Quote | None, put: Quote | None) -> str:
"""卖一比价仅用于选向展示;相等则 wait。"""
def _open_bias(
pair: OptionPair | None,
index_px: float | None,
perp: Quote | None,
call: Quote | None,
put: Quote | None,
) -> str:
"""与开仓 decide 一致:先按 ATM 相对现价,贴平时再卖一比价。"""
mark = None
if index_px is not None and index_px > 0:
mark = float(index_px)
elif perp and perp.mark_px and perp.mark_px > 0:
mark = float(perp.mark_px)
elif perp and perp.bid and perp.ask:
mark = (float(perp.bid) + float(perp.ask)) / 2
if pair is not None and mark is not None:
diff = float(pair.strike) - mark
if diff < -1e-9:
return "strike_below_spot"
if diff > 1e-9:
return "strike_above_spot"
ca = call.ask if call else None
pa = put.ask if put else None
if ca is None or pa is None:
return "unknown"
if ca > pa:
return "call_ask_gt_put" # 永续多 + 期权空(腿待拍板)
return "call_ask_gt_put"
if ca < pa:
return "put_ask_gt_call" # 永续空 + 期权多(腿待拍板)
return "put_ask_gt_call"
return "equal"
+6 -1
View File
@@ -187,7 +187,12 @@ class StrategySession:
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
sig = decide(call_ask, put_ask)
sig = decide(
call_ask,
put_ask,
strike=pair.strike,
mark_px=underlying,
)
if sig is None:
continue
opt_ask = sig.call_ask if sig.option_side == "call" else sig.put_ask
+42 -8
View File
@@ -5,30 +5,64 @@ from dataclasses import dataclass
@dataclass(slots=True)
class Signal:
bias: str # call_ask_gt_put | put_ask_gt_call
bias: str # strike_below_spot | strike_above_spot | call_ask_gt_put | put_ask_gt_call
option_side: str # call | put
perp_side: str # long | short
call_ask: float
put_ask: float
def decide(call_ask: float | None, put_ask: float | None) -> Signal | None:
def decide(
call_ask: float | None,
put_ask: float | None,
*,
strike: float | None = None,
mark_px: float | None = None,
) -> Signal | None:
"""
开仓方向:
- 行权价 < 标的 → 买 Call + 永续空(ATM 偏下)
- 行权价 > 标的 → 买 Put + 永续多(ATM 偏上)
- 行权价 ≈ 标的 → 回退 Call/Put 卖一比价
"""
if call_ask is None or put_ask is None:
return None
if call_ask > put_ask:
ca = float(call_ask)
pa = float(put_ask)
if strike is not None and mark_px is not None and float(mark_px) > 0:
diff = float(strike) - float(mark_px)
if diff < -1e-9:
return Signal(
bias="strike_below_spot",
option_side="call",
perp_side="short",
call_ask=ca,
put_ask=pa,
)
if diff > 1e-9:
return Signal(
bias="strike_above_spot",
option_side="put",
perp_side="long",
call_ask=ca,
put_ask=pa,
)
if ca > pa:
return Signal(
bias="call_ask_gt_put",
option_side="call",
perp_side="short",
call_ask=float(call_ask),
put_ask=float(put_ask),
call_ask=ca,
put_ask=pa,
)
if put_ask > call_ask:
if pa > ca:
return Signal(
bias="put_ask_gt_call",
option_side="put",
perp_side="long",
call_ask=float(call_ask),
put_ask=float(put_ask),
call_ask=ca,
put_ask=pa,
)
return None