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:
@@ -76,19 +76,38 @@ class MarketSnapshot:
|
|||||||
"ask_compare": {
|
"ask_compare": {
|
||||||
"call_ask": self.call.ask if self.call else None,
|
"call_ask": self.call.ask if self.call else None,
|
||||||
"put_ask": self.put.ask if self.put 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:
|
def _open_bias(
|
||||||
"""卖一比价仅用于选向展示;相等则 wait。"""
|
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
|
ca = call.ask if call else None
|
||||||
pa = put.ask if put else None
|
pa = put.ask if put else None
|
||||||
if ca is None or pa is None:
|
if ca is None or pa is None:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
if ca > pa:
|
if ca > pa:
|
||||||
return "call_ask_gt_put" # 永续多 + 期权空(腿待拍板)
|
return "call_ask_gt_put"
|
||||||
if ca < pa:
|
if ca < pa:
|
||||||
return "put_ask_gt_call" # 永续空 + 期权多(腿待拍板)
|
return "put_ask_gt_call"
|
||||||
return "equal"
|
return "equal"
|
||||||
|
|||||||
@@ -187,7 +187,12 @@ class StrategySession:
|
|||||||
put_bids, put_asks, _ = self.ex.fetch_book(pair.put_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
|
call_ask = call_asks[0].px if call_asks else None
|
||||||
put_ask = put_asks[0].px if put_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:
|
if sig is None:
|
||||||
continue
|
continue
|
||||||
opt_ask = sig.call_ask if sig.option_side == "call" else sig.put_ask
|
opt_ask = sig.call_ask if sig.option_side == "call" else sig.put_ask
|
||||||
|
|||||||
@@ -5,30 +5,64 @@ from dataclasses import dataclass
|
|||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class Signal:
|
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
|
option_side: str # call | put
|
||||||
perp_side: str # long | short
|
perp_side: str # long | short
|
||||||
call_ask: float
|
call_ask: float
|
||||||
put_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:
|
if call_ask is None or put_ask is None:
|
||||||
return 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(
|
return Signal(
|
||||||
bias="call_ask_gt_put",
|
bias="call_ask_gt_put",
|
||||||
option_side="call",
|
option_side="call",
|
||||||
perp_side="short",
|
perp_side="short",
|
||||||
call_ask=float(call_ask),
|
call_ask=ca,
|
||||||
put_ask=float(put_ask),
|
put_ask=pa,
|
||||||
)
|
)
|
||||||
if put_ask > call_ask:
|
if pa > ca:
|
||||||
return Signal(
|
return Signal(
|
||||||
bias="put_ask_gt_call",
|
bias="put_ask_gt_call",
|
||||||
option_side="put",
|
option_side="put",
|
||||||
perp_side="long",
|
perp_side="long",
|
||||||
call_ask=float(call_ask),
|
call_ask=ca,
|
||||||
put_ask=float(put_ask),
|
put_ask=pa,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -28,6 +28,31 @@ def test_signal_equal() -> None:
|
|||||||
assert decide(10.0, 10.0) is None
|
assert decide(10.0, 10.0) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_signal_strike_below_spot_call_short() -> None:
|
||||||
|
# 现价 1859、ATM 1850:即使 Put 卖一更高,也走 Call+空
|
||||||
|
s = decide(10.0, 20.0, strike=1850, mark_px=1859)
|
||||||
|
assert s is not None
|
||||||
|
assert s.option_side == "call"
|
||||||
|
assert s.perp_side == "short"
|
||||||
|
assert s.bias == "strike_below_spot"
|
||||||
|
|
||||||
|
|
||||||
|
def test_signal_strike_above_spot_put_long() -> None:
|
||||||
|
# 现价 1859、ATM 1875:即使 Call 卖一更高,也走 Put+多
|
||||||
|
s = decide(20.0, 10.0, strike=1875, mark_px=1859)
|
||||||
|
assert s is not None
|
||||||
|
assert s.option_side == "put"
|
||||||
|
assert s.perp_side == "long"
|
||||||
|
assert s.bias == "strike_above_spot"
|
||||||
|
|
||||||
|
|
||||||
|
def test_signal_strike_flat_falls_back_to_ask() -> None:
|
||||||
|
s = decide(20.0, 15.0, strike=1860, mark_px=1860)
|
||||||
|
assert s is not None
|
||||||
|
assert s.bias == "call_ask_gt_put"
|
||||||
|
assert s.option_side == "call"
|
||||||
|
|
||||||
|
|
||||||
def test_exit_fixed_and_premium_multiple() -> None:
|
def test_exit_fixed_and_premium_multiple() -> None:
|
||||||
fixed = check_exits(
|
fixed = check_exits(
|
||||||
net_pnl=15.0,
|
net_pnl=15.0,
|
||||||
|
|||||||
+11
-9
@@ -32,17 +32,19 @@
|
|||||||
|
|
||||||
永续杠杆默认 **3×**(可配)。同时最多 **1 组**仓,禁止叠仓开下一组。
|
永续杠杆默认 **3×**(可配)。同时最多 **1 组**仓,禁止叠仓开下一组。
|
||||||
|
|
||||||
### 2.1 开仓方向(卖一比价)
|
### 2.1 开仓方向(ATM 相对现价优先)
|
||||||
|
|
||||||
比较同一 ATM 行权价的 **Call 卖一** 与 **Put 卖一**:
|
行权价相对标的有偏离时(币安粗档常见),**先按 ATM 偏上/偏下选向**;仅当行权价与标的贴平(≈)时,才回退卖一比价。
|
||||||
|
|
||||||
| 条件 | 期权 | 永续 |
|
| 条件 | 期权 | 永续 | bias |
|
||||||
|------|------|------|
|
|------|------|------|------|
|
||||||
| Call 卖一 > Put 卖一 | 买入 Call | 做空 |
|
| ATM 行权价 **<** 标的(如 1850 vs 1859) | 买入 Call | 做空 | `strike_below_spot` |
|
||||||
| Call 卖一 < Put 卖一 | 买入 Put | 做多 |
|
| ATM 行权价 **>** 标的(如 1875 vs 1859) | 买入 Put | 做多 | `strike_above_spot` |
|
||||||
| 相等 | 不开仓,继续等待 | — |
|
| ATM ≈ 标的,且 Call 卖一 > Put 卖一 | 买入 Call | 做空 | `call_ask_gt_put` |
|
||||||
|
| ATM ≈ 标的,且 Put 卖一 > Call 卖一 | 买入 Put | 做多 | `put_ask_gt_call` |
|
||||||
|
| ATM ≈ 标的且卖一相等 | 不开仓,继续等待 | — | — |
|
||||||
|
|
||||||
直觉:卖一更高的一侧往往隐含更强的短期方向/溢价偏好,买入该侧期权;永续反向以平衡部分 Delta。
|
直觉:偏下行权价用 Call+空,偏上行权价用 Put+多;贴平时再按权利金溢价侧选向。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@
|
|||||||
→ 选到期:剩余时长 ≥ min_option_hours(默认 12h)
|
→ 选到期:剩余时长 ≥ min_option_hours(默认 12h)
|
||||||
→ 该到期 ATM 行权价(最接近标的)
|
→ 该到期 ATM 行权价(最接近标的)
|
||||||
→ |ATM − 标的| ≤ max_atm_open_offset(默认 3)否则跳过该到期
|
→ |ATM − 标的| ≤ max_atm_open_offset(默认 3)否则跳过该到期
|
||||||
→ Call/Put 卖一比价选方向
|
→ 选向:ATM 偏下→Call+空;偏上→Put+多;贴平→卖一比价
|
||||||
→ 期权杠杆 = 标的价 ÷ 卖一权利金 ≥ min_option_leverage(默认 100)
|
→ 期权杠杆 = 标的价 ÷ 卖一权利金 ≥ min_option_leverage(默认 100)
|
||||||
→ 开永续 + 开期权(一组)
|
→ 开永续 + 开期权(一组)
|
||||||
→ 锁定 initial_premium = 期权成交价 × 期权名义(不含费)
|
→ 锁定 initial_premium = 期权成交价 × 期权名义(不含费)
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ export default function PlanPage() {
|
|||||||
|
|
||||||
const bias = snap?.ask_compare?.bias;
|
const bias = snap?.ask_compare?.bias;
|
||||||
const biasTag =
|
const biasTag =
|
||||||
bias === "call_ask_gt_put" ? (
|
bias === "strike_below_spot" || bias === "call_ask_gt_put" ? (
|
||||||
<span className="tag up">买 Call + 永续空</span>
|
<span className="tag up">买 Call + 永续空</span>
|
||||||
) : bias === "put_ask_gt_call" ? (
|
) : bias === "strike_above_spot" || bias === "put_ask_gt_call" ? (
|
||||||
<span className="tag down">买 Put + 永续多</span>
|
<span className="tag down">买 Put + 永续多</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="tag">等待 / 相等</span>
|
<span className="tag">等待 / 相等</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user