feat(hedge): option-primary watch entry with leverage gate

Start strategy arms a watching plan instead of opening immediately; list filters by leverage; type is a dropdown defaulting to OTM.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-09 09:27:42 +08:00
parent 6ab27cebcd
commit eca6d091e9
10 changed files with 608 additions and 39 deletions
+44 -1
View File
@@ -9,9 +9,11 @@ from lib.hedge_plan.hedge_plan_option_primary_lib import (
opt_type_for_view,
option_bid_liquidity_ok,
perp_direction_for_view,
pick_option_primary_candidate,
size_from_premium,
target_hit,
validate_option_primary_start,
validate_option_primary_watch,
)
from lib.hedge_plan.hedge_plan_orders_lib import build_po_path_plan, validate_start_body
@@ -93,9 +95,25 @@ class TestOptionPrimary(unittest.TestCase):
self.assertEqual(path[1]["direction"], "short")
self.assertFalse(path[1]["attach_tpsl"])
def test_validate_option_primary_start(self):
def test_validate_option_primary_watch_and_start(self):
watch_body = {
"option_primary": True,
"watch_entry": 1,
"direction": "long",
"exchange_symbol": "ETH/USDT:USDT",
"premium_budget": 100,
"option_target_points": 50,
"perp_target_points": 30,
"option_perp_ratio": 4,
"option_leverage": 200,
"moneyness": "otm",
}
self.assertIsNone(validate_option_primary_watch(watch_body))
self.assertIsNone(validate_start_body("perp_options", watch_body))
body = {
"option_primary": True,
"watch_entry": 0,
"direction": "long",
"contracts": 1,
"opt_inst_id": "ETH-USD-260831-1900-C",
@@ -120,6 +138,31 @@ class TestOptionPrimary(unittest.TestCase):
bad = dict(body, opt_type="P")
self.assertIsNotNone(validate_start_body("perp_options", bad))
def test_pick_candidate_respects_leverage(self):
chain = {
"index_px": 1900,
"expiries": [
{
"exp_time": 9_999_999_999_999,
"contracts": [
{"inst_id": "LOW", "opt_type": "C", "strike": 1920, "ask": 20, "ct_mult": 0.01},
{"inst_id": "OK", "opt_type": "C", "strike": 1925, "ask": 8, "ct_mult": 0.01},
],
}
],
}
# 1900/20=95 < 200; 1900/8=237.5 ≥ 200
picked = pick_option_primary_candidate(
chain,
direction="long",
moneyness="otm",
strike_interval=50,
min_hours=1,
min_opt_leverage=200,
)
self.assertIsNotNone(picked)
self.assertEqual(picked["inst_id"], "OK")
def test_preview_builds_scenarios(self):
body = {
"direction": "long",