diff --git a/lib/strategy/strategy_roll_lib.py b/lib/strategy/strategy_roll_lib.py
index 2a1186b..6be7a3c 100644
--- a/lib/strategy/strategy_roll_lib.py
+++ b/lib/strategy/strategy_roll_lib.py
@@ -176,21 +176,20 @@ def roll_breakout_trigger_crossed(
mark: float,
breakthrough_price: float,
) -> bool:
- """突破:多=向上穿越突破价;空=向下穿越突破价。"""
+ """突破:多=mark 在突破价之上;空=mark 在突破价之下。
+
+ 提交时已校验 mark 在逆势侧(多低于突破价、空高于突破价),触价侧到达即成交。
+ 不再要求单 tick 内穿越,避免 mark 已破位但 last_mark 也落在突破价另一侧时永久漏触发。
+ """
try:
m = float(mark)
bp = float(breakthrough_price)
- pm = float(prev_mark) if prev_mark is not None else None
except (TypeError, ValueError):
return False
direction = (direction or "long").strip().lower()
if direction == "long":
- if pm is None:
- return m > bp
- return pm <= bp and m > bp
- if pm is None:
- return m < bp
- return pm >= bp and m < bp
+ return m > bp
+ return m < bp
def roll_fib_invalidate(direction: str, mark: float, upper: float, lower: float) -> bool:
diff --git a/lib/strategy/templates/strategy_roll_panel.html b/lib/strategy/templates/strategy_roll_panel.html
index fbbaf3e..99620e0 100644
--- a/lib/strategy/templates/strategy_roll_panel.html
+++ b/lib/strategy/templates/strategy_roll_panel.html
@@ -6,7 +6,7 @@
仅人工提交;须先在「实盘下单」有同向持仓。仅以损定仓模式可用。
做多/做空各最多滚仓 3 次(仅计已成交腿);止盈锁定首仓不变。
风险比例读取所选监控单,不可手改;打到新止损时合并持仓亏损 ≈ 1 个风险单位(当前基数 × 监控 risk%)。
- 斐波/突破为程序监控(mark 价穿越触发),触价后市价加仓;填写后直接点「执行滚仓」(无需预览)。同时仅允许 1 条监控中腿,提交后不可修改,可删除。
+ 斐波/突破为程序监控(交易所 mark 价),触价后市价加仓;填写后直接点「执行滚仓」(无需预览)。同时仅允许 1 条监控中腿,提交后不可修改,可删除。
手动平仓后滚仓监控自动结束;已成交腿历史保留供复盘。
→ 顺势加仓完整逻辑说明
{% if roll_trend_active %}当前有运行中的趋势回调计划,请先结束后再滚仓。{% endif %}
diff --git a/tests/test_strategy_roll_lib.py b/tests/test_strategy_roll_lib.py
index db35285..e09050d 100644
--- a/tests/test_strategy_roll_lib.py
+++ b/tests/test_strategy_roll_lib.py
@@ -49,10 +49,18 @@ def test_fib_cross_long_down():
def test_breakout_cross_long_up():
assert roll_breakout_trigger_crossed("long", 99.0, 100.5, 100.0) is True
+ assert roll_breakout_trigger_crossed("long", 99.0, 100.0, 100.0) is False
assert roll_breakout_invalidate("long", 98.0, 99.0) is True
assert roll_fib_invalidate("long", 110.0, 105.0, 95.0) is True
+def test_breakout_short_below_breakthrough():
+ assert roll_breakout_trigger_crossed("short", 81.0, 80.57, 80.65) is True
+ assert roll_breakout_trigger_crossed("short", 80.64, 80.57, 80.65) is True
+ assert roll_breakout_trigger_crossed("short", 80.57, 80.57, 80.65) is True
+ assert roll_breakout_trigger_crossed("short", 81.0, 80.70, 80.65) is False
+
+
def test_preview_breakout_mode_label():
preview, err = preview_roll(
direction="long",