-
永续 · ETH 合约账户
+
永续 · ETH 合约账户
规则说明
-
账户:永续腿走合约账户(USDT);保险期权走期权账户(USDC)。两账户分开下单、资金不互通。
-
下单:先「计算」再「启动」。启动瞬间会再拉卖一并以 IOC 等完全成交;半腿失败可补开或「结束计划」(不平仓)。永期开仓需全仓计仓 + 对冲实盘门禁。
-
板块:左填永续开仓/止盈止损与张数;右选保险腿(做多配 Put、做空配 Call)。保险腿仅允许实值或平值(禁虚值)。止盈后保险腿默认可持有;止损会联动平期权。
+
账户:永续腿走合约账户(USDT);期权腿走期权账户(USDC)。两账户分开下单、资金不互通。
+
保险模式(开关关):做多配 Put、做空配 Call;左填开仓/止盈止损;仅实值/平值;交易所 TP/SL 出场。
+
以期权为主(开关开):做多买 Call+永续空、做空买 Put+永续多;左填权利金/杠杆/比例/目标点数;开仓先期权后市价永续;期权目标验买一且净利>0后双平;永续目标只平永续、期权持有至到期。
+
+
+
+
-
-
+
+
标记 —
加载中…
-
-
+
diff --git a/tests/test_hedge_plan_option_primary.py b/tests/test_hedge_plan_option_primary.py
new file mode 100644
index 0000000..bf3a2be
--- /dev/null
+++ b/tests/test_hedge_plan_option_primary.py
@@ -0,0 +1,144 @@
+"""永期「以期权为主」定仓/方向/目标位/校验."""
+import unittest
+
+from lib.hedge_plan.hedge_plan_option_primary_lib import (
+ PREMIUM_EXEC_FACTOR,
+ build_option_primary_preview,
+ estimate_combo_net_pnl,
+ floor2,
+ opt_type_for_view,
+ option_bid_liquidity_ok,
+ perp_direction_for_view,
+ size_from_premium,
+ target_hit,
+ validate_option_primary_start,
+)
+from lib.hedge_plan.hedge_plan_orders_lib import build_po_path_plan, validate_start_body
+
+
+class TestOptionPrimary(unittest.TestCase):
+ def test_direction_mapping(self):
+ self.assertEqual(opt_type_for_view("long"), "C")
+ self.assertEqual(opt_type_for_view("short"), "P")
+ self.assertEqual(perp_direction_for_view("long"), "short")
+ self.assertEqual(perp_direction_for_view("short"), "long")
+
+ def test_size_from_premium_095_and_eth_2dp(self):
+ # ask=10 → 1 ETH 成本 10U; 预算 100 → usable 95 → eth=9.5 → sheets=950 (ct=0.01)
+ sized = size_from_premium(
+ premium_budget=100,
+ ask=10,
+ ct_mult=0.01,
+ ratio=2,
+ contract_size=0.01,
+ )
+ self.assertTrue(sized["ok"])
+ self.assertAlmostEqual(sized["usable_premium"], 95.0)
+ self.assertEqual(sized["eth_qty"], 9.5)
+ self.assertEqual(sized["sheets"], 950.0)
+ # perp_eth = 9.5/2=4.75; contracts=4.75/0.01=475
+ self.assertAlmostEqual(sized["contracts"], 475.0)
+ self.assertEqual(PREMIUM_EXEC_FACTOR, 0.95)
+
+ def test_floor2(self):
+ self.assertEqual(floor2(1.239), 1.23)
+ self.assertEqual(floor2(0.009), 0.0)
+
+ def test_target_hit(self):
+ self.assertTrue(target_hit(view_side="long", index_px=1950, strike=1900, points=50))
+ self.assertFalse(target_hit(view_side="long", index_px=1949, strike=1900, points=50))
+ self.assertTrue(target_hit(view_side="short", index_px=1850, strike=1900, points=50))
+ self.assertFalse(target_hit(view_side="short", index_px=1851, strike=1900, points=50))
+ self.assertFalse(target_hit(view_side="long", index_px=1900, strike=1900, points=0))
+
+ def test_bid_liquidity(self):
+ ok, _ = option_bid_liquidity_ok(1.2, 10, need_sheets=5)
+ self.assertTrue(ok)
+ ok2, msg = option_bid_liquidity_ok(None, 10, need_sheets=1)
+ self.assertFalse(ok2)
+ self.assertIn("买一", msg)
+
+ def test_net_pnl_uses_buy_fee_for_sell(self):
+ net = estimate_combo_net_pnl(
+ view_side="long",
+ strike=1900,
+ index_px=1950,
+ ask_open=20,
+ bid=30,
+ sheets=2,
+ ct_mult=0.01,
+ perp_direction="short",
+ perp_entry=1900,
+ perp_mark=1950,
+ contracts=10,
+ contract_size=0.01,
+ fee=0.001,
+ )
+ # opt: proceeds=30*2*0.01=0.6; premium=0.4; fees=0.0004+0.0006; opt_net=0.6-0.4-0.001=0.199
+ self.assertIn("net", net)
+ self.assertEqual(net["fee_rate"], 0.001)
+
+ def test_path_option_primary_no_tpsl_options_first(self):
+ path = build_po_path_plan(
+ {
+ "option_primary": True,
+ "direction": "long",
+ "opt_inst_id": "ETH-USD-260831-1900-C",
+ "sheets": 2,
+ "exchange_symbol": "ETH/USDT:USDT",
+ "contracts": 1,
+ }
+ )
+ self.assertEqual(path[0]["step"], "options_buy_limit")
+ self.assertEqual(path[1]["direction"], "short")
+ self.assertFalse(path[1]["attach_tpsl"])
+
+ def test_validate_option_primary_start(self):
+ body = {
+ "option_primary": True,
+ "direction": "long",
+ "contracts": 1,
+ "opt_inst_id": "ETH-USD-260831-1900-C",
+ "opt_type": "C",
+ "sheets": 2,
+ "exchange_symbol": "ETH/USDT:USDT",
+ "premium_budget": 100,
+ "option_target_points": 50,
+ "perp_target_points": 30,
+ "option_perp_ratio": 2,
+ "strike": 1900,
+ "index_px": 1905,
+ "ask": 10,
+ "moneyness": "atm",
+ "strike_interval": 15,
+ "min_option_hours": 36,
+ "hours_to_expiry": 40,
+ "option_leverage": 100,
+ }
+ self.assertIsNone(validate_option_primary_start(body))
+ self.assertIsNone(validate_start_body("perp_options", body))
+ bad = dict(body, opt_type="P")
+ self.assertIsNotNone(validate_start_body("perp_options", bad))
+
+ def test_preview_builds_scenarios(self):
+ body = {
+ "direction": "long",
+ "strike": 1900,
+ "option_target_points": 50,
+ "perp_target_points": 30,
+ "ask": 20,
+ "sheets": 10,
+ "ct_mult": 0.01,
+ "contracts": 5,
+ "contract_size": 0.01,
+ "entry": 1900,
+ "index_px": 1900,
+ "premium_budget": 100,
+ }
+ out = build_option_primary_preview(body)
+ self.assertTrue(out["option_primary"])
+ self.assertEqual(len(out["scenarios"]), 2)
+
+
+if __name__ == "__main__":
+ unittest.main()