Fix strategy-logic P0s from audit: monitor false-flat, fill-confirmed open/close, mode gates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-30 09:56:18 +08:00
parent fa7ff739a0
commit 4b4dca9e3c
15 changed files with 820 additions and 167 deletions
+21 -4
View File
@@ -50,12 +50,28 @@ class TestHedgePlanOrderPath(unittest.TestCase):
"tp": 1900,
"sl": 1700,
"contracts": 1,
"opt_inst_id": "X",
"opt_inst_id": "ETH-USD-260731-1800-P",
"opt_type": "P",
"sheets": 1,
"exchange_symbol": "ETH/USDT:USDT",
},
)
self.assertIsNone(ok)
bad_type = validate_start_body(
"perp_options",
{
"direction": "long",
"entry": 1800,
"tp": 1900,
"sl": 1700,
"contracts": 1,
"opt_inst_id": "ETH-USD-260731-1800-C",
"opt_type": "C",
"sheets": 1,
"exchange_symbol": "ETH/USDT:USDT",
},
)
self.assertIsNotNone(bad_type)
def test_gate_can_start_when_live(self):
g = gate_status(
@@ -196,7 +212,7 @@ class TestHedgePlanOrderPath(unittest.TestCase):
self.assertIn("卖一", out["msg"])
cfg["place_option_limit_order"].assert_not_called()
def test_buy_caps_sheets_to_ask_depth(self):
def test_buy_rejects_when_ask_depth_below_request(self):
from lib.hedge_plan.hedge_plan_orders_lib import _buy_option
quote = MagicMock(
@@ -217,8 +233,9 @@ class TestHedgePlanOrderPath(unittest.TestCase):
"td_mode_for_option_buy": lambda x: "isolated",
}
out = _buy_option(cfg, inst_id="X", sheets=9, dry_run=True)
self.assertTrue(out["ok"])
self.assertEqual(out["sheets"], 2)
self.assertFalse(out["ok"])
self.assertIn("不足请求", out["msg"])
cfg["place_option_limit_order"].assert_not_called()
if __name__ == "__main__":
+101
View File
@@ -0,0 +1,101 @@
"""永期监控:假平仓防护与 TP/SL 分类."""
import unittest
from unittest.mock import MagicMock, patch
from lib.hedge_plan.hedge_plan_monitor_lib import (
_classify_po_flat_reason,
_tick_po,
_within_open_grace,
)
class TestPoMonitorSafety(unittest.TestCase):
def test_classify_sl_before_tp(self):
self.assertEqual(
_classify_po_flat_reason(
direction="long", entry=1800, mark=1690, tp=1900, sl=1700
),
"perp_sl",
)
self.assertEqual(
_classify_po_flat_reason(
direction="long", entry=1800, mark=1910, tp=1900, sl=1700
),
"perp_tp",
)
def test_classify_ambiguous_prefers_sl(self):
# mark 在中间:偏 SL,避免误判 TP 跳过强平期权
self.assertEqual(
_classify_po_flat_reason(
direction="long", entry=1800, mark=1800, tp=1900, sl=1700
),
"perp_sl",
)
def test_classify_unknown_without_mark(self):
self.assertEqual(
_classify_po_flat_reason(
direction="long", entry=1800, mark=None, tp=1900, sl=1700
),
"perp_flat_unknown",
)
def test_tick_po_skips_when_live_none(self):
cfg = {"get_live_position_contracts": lambda *_a, **_k: None}
plan = {"id": 1, "direction": "long", "entry_mark": 1800, "tp": 1900, "sl": 1700}
legs = [{"leg_role": "perp", "status": "open", "symbol": "ETH/USDT:USDT", "size": 1}]
self.assertIsNone(_tick_po(cfg, MagicMock(), plan, legs))
def test_tick_po_skips_when_live_positive(self):
cfg = {"get_live_position_contracts": lambda *_a, **_k: 2.0}
plan = {"id": 1, "direction": "long", "entry_mark": 1800, "tp": 1900, "sl": 1700}
legs = [{"leg_role": "perp", "status": "open", "symbol": "ETH/USDT:USDT", "size": 1}]
self.assertIsNone(_tick_po(cfg, MagicMock(), plan, legs))
def test_within_grace_true_without_opened_at(self):
self.assertTrue(_within_open_grace({}))
def test_pending_sl_sticky(self):
"""perp_sl_pending_opt 粘滞,不因 mark 反弹改判 TP."""
cfg = {
"get_live_position_contracts": lambda *_a, **_k: 0.0,
"get_contract_size": lambda *_a, **_k: 0.01,
"exchange": None,
}
plan = {
"id": 9,
"direction": "long",
"entry_mark": 1800,
"tp": 1900,
"sl": 1700,
"premium_total": 5,
"opened_at": "2000-01-01 00:00:00",
"close_reason": "perp_sl_pending_opt",
}
legs = [
{"id": 1, "leg_role": "perp", "status": "open", "symbol": "ETH/USDT:USDT", "size": 1, "avg_open": 1800},
{
"id": 2,
"leg_role": "option_hedge",
"status": "open",
"inst_id": "ETH-USD-260731-1700-P",
"size": 1,
"avg_open": 10,
},
]
with patch("lib.hedge_plan.hedge_plan_monitor_lib._sell_option") as sell:
sell.return_value = {"ok": False, "msg": "thin book", "fully_closed": False}
with patch("lib.hedge_plan.hedge_plan_monitor_lib.notify_hedge"):
with patch("lib.hedge_plan.hedge_plan_monitor_lib.update_plan") as up:
conn = MagicMock()
out = _tick_po(cfg, conn, plan, legs)
self.assertTrue(out and out.get("retry"))
up.assert_called()
self.assertEqual(up.call_args.kwargs.get("close_reason"), "perp_sl_pending_opt")
self.assertNotEqual(up.call_args.kwargs.get("status"), "closed")
if __name__ == "__main__":
unittest.main()