Revise strategy: TTM+ATM+leverage option pick, % exit, perp leverage/margin.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,7 +4,10 @@ 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,
|
||||
@@ -48,3 +51,26 @@ def test_select_option_pair_atm() -> 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
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from app.strategy.signal import decide
|
||||
from app.strategy.exits import check_exits
|
||||
from app.sim.pricing import option_fill, perp_fill
|
||||
from app.strategy.clock import can_open_new, window_key
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from app.sim.pricing import option_fill, perp_fill
|
||||
from app.strategy.clock import can_open_new, window_key
|
||||
from app.strategy.exits import check_exits
|
||||
from app.strategy.signal import decide
|
||||
|
||||
_SH = ZoneInfo("Asia/Shanghai")
|
||||
|
||||
|
||||
@@ -26,13 +27,19 @@ def test_signal_equal() -> None:
|
||||
assert decide(10.0, 10.0) is None
|
||||
|
||||
|
||||
def test_exit_premium_and_move() -> None:
|
||||
assert check_exits(
|
||||
perp_upl=50, initial_premium=40, move_points=1, exit_move_points=30
|
||||
).reason == "premium_cover"
|
||||
assert check_exits(
|
||||
perp_upl=1, initial_premium=40, move_points=30, exit_move_points=30
|
||||
).reason == "move_points"
|
||||
def test_exit_premium_and_move_pct() -> None:
|
||||
assert (
|
||||
check_exits(
|
||||
perp_upl=50, initial_premium=40, move_pct=0.1, exit_move_pct=2
|
||||
).reason
|
||||
== "premium_cover"
|
||||
)
|
||||
assert (
|
||||
check_exits(
|
||||
perp_upl=1, initial_premium=40, move_pct=2.0, exit_move_pct=2
|
||||
).reason
|
||||
== "move_pct"
|
||||
)
|
||||
|
||||
|
||||
def test_perp_pricing() -> None:
|
||||
@@ -47,15 +54,10 @@ def test_option_open_close_pricing() -> None:
|
||||
assert c.fill_px < 10
|
||||
|
||||
|
||||
def test_window() -> None:
|
||||
# 17:00 can open, window key today
|
||||
def test_window_always_open() -> None:
|
||||
n = datetime(2026, 7, 24, 17, 0, tzinfo=_SH)
|
||||
assert can_open_new(n) is True
|
||||
assert window_key(n) == "20260724"
|
||||
# 10:00 cannot open
|
||||
n2 = datetime(2026, 7, 24, 10, 0, tzinfo=_SH)
|
||||
assert can_open_new(n2) is False
|
||||
# 07:00 still previous window, can open
|
||||
n3 = datetime(2026, 7, 24, 7, 0, tzinfo=_SH)
|
||||
assert can_open_new(n3) is True
|
||||
assert window_key(n3) == "20260723"
|
||||
assert can_open_new(n2) is True
|
||||
assert window_key(n2) == "20260724"
|
||||
|
||||
Reference in New Issue
Block a user