ee0ec57f89
Co-authored-by: Cursor <cursoragent@cursor.com>
86 lines
2.9 KiB
Python
86 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from app.market.instruments import (
|
|
hours_until_expiry,
|
|
list_eligible_expiry_ymds,
|
|
next_session_expiry_ymd,
|
|
option_leverage,
|
|
parse_option_inst_id,
|
|
pick_atm_strike,
|
|
select_option_pair,
|
|
)
|
|
|
|
_SH = ZoneInfo("Asia/Shanghai")
|
|
|
|
|
|
def test_parse_option_inst_id() -> None:
|
|
y, s, o = parse_option_inst_id("ETH-USD_UM-260725-3500-C")
|
|
assert y == "260725"
|
|
assert s == 3500.0
|
|
assert o == "C"
|
|
|
|
|
|
def test_pick_atm_strike() -> None:
|
|
assert pick_atm_strike([3400, 3500, 3600], 3510) == 3500
|
|
|
|
|
|
def test_next_session_expiry_before_open() -> None:
|
|
now = datetime(2026, 7, 24, 15, 0, tzinfo=_SH)
|
|
assert next_session_expiry_ymd(now) == "260724"
|
|
|
|
|
|
def test_next_session_expiry_after_open() -> None:
|
|
now = datetime(2026, 7, 24, 16, 0, tzinfo=_SH)
|
|
assert next_session_expiry_ymd(now) == "260725"
|
|
|
|
|
|
def test_select_option_pair_atm() -> None:
|
|
rows = [
|
|
{"instId": "ETH-USD_UM-260725-3490-C", "state": "live"},
|
|
{"instId": "ETH-USD_UM-260725-3490-P", "state": "live"},
|
|
{"instId": "ETH-USD_UM-260725-3500-C", "state": "live"},
|
|
{"instId": "ETH-USD_UM-260725-3500-P", "state": "live"},
|
|
{"instId": "ETH-USD_UM-260726-3500-C", "state": "live"},
|
|
{"instId": "ETH-USD_UM-260726-3500-P", "state": "live"},
|
|
]
|
|
pair = select_option_pair(rows, mark_px=3502, expiry_ymd="260725")
|
|
assert pair is not None
|
|
assert pair.strike == 3500
|
|
assert pair.call_inst_id.endswith("-3500-C")
|
|
assert pair.put_inst_id.endswith("-3500-P")
|
|
|
|
|
|
def test_eligible_skips_short_ttm() -> None:
|
|
# 2026-07-25 08:30 SH:当日 16:00 到期仅约 7.5h,应跳过 260725,选 260726
|
|
now = datetime(2026, 7, 25, 8, 30, tzinfo=_SH)
|
|
rows = [
|
|
{"instId": "ETH-USD_UM-260725-1850-C", "state": "live"},
|
|
{"instId": "ETH-USD_UM-260725-1850-P", "state": "live"},
|
|
{"instId": "ETH-USD_UM-260726-1850-C", "state": "live"},
|
|
{"instId": "ETH-USD_UM-260726-1850-P", "state": "live"},
|
|
]
|
|
assert hours_until_expiry("260725", now) < 12
|
|
assert hours_until_expiry("260726", now) >= 12
|
|
eligible = list_eligible_expiry_ymds(rows, min_hours=12, now=now)
|
|
assert eligible == ["260726"]
|
|
pair = select_option_pair(rows, mark_px=1853, min_hours=12, now=now)
|
|
assert pair is not None
|
|
assert pair.expiry_ymd == "260726"
|
|
|
|
|
|
def test_option_leverage() -> None:
|
|
assert abs((option_leverage(1850, 18.5) or 0) - 100) < 1e-9
|
|
assert option_leverage(1850, 0) is None
|
|
|
|
|
|
def test_atm_allows_open_offset() -> None:
|
|
from app.strategy.selection import atm_allows_open, atm_open_offset
|
|
|
|
assert atm_open_offset(1850, 1852.5) == 2.5
|
|
assert atm_allows_open(1850, 1852.5, max_offset=3) is True
|
|
assert atm_allows_open(1850, 1854, max_offset=3) is False
|
|
assert atm_allows_open(1850, 1860, max_offset=3) is False
|