Keep demo stops at least 1 x ATR14(5m) from the limit price

When the limit price sat near the far edge of the zone, the stop beyond
the far edge was well inside normal 5m noise (plan #34: 0.11%). The stop
is now the farther of far edge +/- 0.5 x ATR and entry +/- 1 x ATR.
This commit is contained in:
eezd committed 2026-10-15 17:33:06 +08:00
1 parent 250b16b389
commit 665c12fc31
4 files changed
+23 -13

No files matched your search

+1 -1
View File
@@ -61,7 +61,7 @@ INSTRUCTIONS = f"""你是 AlphaPulse 的模拟盘交易分析器:为一个币
## 程序如何执行 ## 程序如何执行
- 计划(action=plan)被采纳后,程序立即在 entry_price 挂限价单(maker 手续费),同时在交易所挂只减仓止损(按标记价触发)。到 valid_hours 仍未成交则撤单,并重新请你分析。 - 计划(action=plan)被采纳后,程序立即在 entry_price 挂限价单(maker 手续费),同时在交易所挂只减仓止损(按标记价触发)。到 valid_hours 仍未成交则撤单,并重新请你分析。
- 止损:区间远端外 {rules.STOP_ATR_FACTOR}×ATR14(5m),做多放在 zone_low 下方,做空放在 zone_high 上方。 - 止损:区间远端外 {rules.STOP_ATR_FACTOR}×ATR14(5m),做多放在 zone_low 下方,做空放在 zone_high 上方;且距挂单价至少 {rules.MIN_STOP_ATR}×ATR14(5m),挂单价贴近远端时按这条算。
- 止盈:按成交均价,做多 {_ladder("long")};做空 {_ladder("short")}。止盈 1 成交后撤销未成交的入场部分,止损移到保本价。 - 止盈:按成交均价,做多 {_ladder("long")};做空 {_ladder("short")}。止盈 1 成交后撤销未成交的入场部分,止损移到保本价。
- 首次成交后最长持仓 {rules.MAX_HOLD_HOURS} 小时,到时市价平仓。平仓后程序会请你重新分析。 - 首次成交后最长持仓 {rules.MAX_HOLD_HOURS} 小时,到时市价平仓。平仓后程序会请你重新分析。
- 仓位:单笔风险为交易资金的 {_pct(rules.RISK_PCT)},置信度低时减半;名义仓位合计不超过 {rules.MAX_LEVERAGE} 倍交易资金。 - 仓位:单笔风险为交易资金的 {_pct(rules.RISK_PCT)},置信度低时减半;名义仓位合计不超过 {rules.MAX_LEVERAGE} 倍交易资金。
+11 -8
View File
@@ -1,7 +1,7 @@
"""交易计划的纯计算:模型提交的区间和限价、校验、止损与仓位、止盈阶梯、应有订单。 """交易计划的纯计算:模型提交的区间和限价、校验、止损与仓位、止盈阶梯、应有订单。
只做短线做波动:模型划出支撑区(压力区)并给出区内的限价,采纳后立即挂限价单(maker 手续费); 只做短线做波动:模型划出支撑区(压力区)并给出区内的限价,采纳后立即挂限价单(maker 手续费);
止损放在区间远端外,与入场单一起挂;止盈按成交均价的固定比例。 止损放在区间远端外(且离挂单价至少 1×ATR),与入场单一起挂;止盈按成交均价的固定比例。
下方常量与 main/README 的数字重复,并写进了分析器的提示词;改 README 时要同步修改。 下方常量与 main/README 的数字重复,并写进了分析器的提示词;改 README 时要同步修改。
""" """
@@ -24,8 +24,10 @@ SETUP_VALID_HOURS = 2
MAX_HOLD_HOURS = 4 MAX_HOLD_HOURS = 4
ATR_INTERVAL = "5m" ATR_INTERVAL = "5m"
ATR_PERIOD = 14 ATR_PERIOD = 14
# 止损放在区间远端外 STOP_ATR_FACTOR × ATR14(5m);离挂单价超过 MAX_STOP_PCT 的计划不做 # 止损放在区间远端外 STOP_ATR_FACTOR × ATR14(5m),且离挂单价至少 MIN_STOP_ATR × ATR:
# 挂单价贴近远端时,只按远端算的止损会落在 5m 正常波动里(#34 只有 0.11%)。离挂单价超过 MAX_STOP_PCT 的计划不做
STOP_ATR_FACTOR = Decimal("0.5") STOP_ATR_FACTOR = Decimal("0.5")
MIN_STOP_ATR = Decimal("1")
MAX_STOP_PCT = Decimal("0.01") MAX_STOP_PCT = Decimal("0.01")
# 止盈阶梯:(距成交均价的涨跌幅,平仓比例)。牛市里空单不恋战,第一档就平掉大部分 # 止盈阶梯:(距成交均价的涨跌幅,平仓比例)。牛市里空单不恋战,第一档就平掉大部分
TAKE_PROFIT_STEPS: dict[str, tuple[tuple[Decimal, Decimal], ...]] = { TAKE_PROFIT_STEPS: dict[str, tuple[tuple[Decimal, Decimal], ...]] = {
@@ -145,11 +147,11 @@ def atr(bars: list[Bar], period: int = ATR_PERIOD) -> Decimal:
return sum(ranges, Decimal(0)) / period return sum(ranges, Decimal(0)) / period
def setup_stop(direction: Direction, far_edge: Decimal, atr_value: Decimal, tick: Decimal) -> Decimal: def setup_stop(direction: Direction, entry: Decimal, far_edge: Decimal, atr_value: Decimal, tick: Decimal) -> Decimal:
"""止损 = 区间远端外 STOP_ATR_FACTOR × ATR,向远离入场的方向取整""" """止损 = 区间远端外 STOP_ATR_FACTOR × ATR 与挂单价外 MIN_STOP_ATR × ATR 中离挂单价更远的一个,向远离入场的方向取整"""
if direction == "long": if direction == "long":
return round_to_step(far_edge - STOP_ATR_FACTOR * atr_value, tick, ROUND_FLOOR) return round_to_step(min(far_edge - STOP_ATR_FACTOR * atr_value, entry - MIN_STOP_ATR * atr_value), tick, ROUND_FLOOR)
return round_to_step(far_edge + STOP_ATR_FACTOR * atr_value, tick, ROUND_CEILING) return round_to_step(max(far_edge + STOP_ATR_FACTOR * atr_value, entry + MIN_STOP_ATR * atr_value), tick, ROUND_CEILING)
def size_entry(direction: Direction, price: Decimal, stop: Decimal, *, capital: Decimal, other_notional: Decimal, def size_entry(direction: Direction, price: Decimal, stop: Decimal, *, capital: Decimal, other_notional: Decimal,
@@ -242,12 +244,13 @@ def check_setup(plan: TradePlan, ctx: PlanContext) -> dict:
errors.append(f"valid_hours 不能超过 {SETUP_VALID_HOURS}") errors.append(f"valid_hours 不能超过 {SETUP_VALID_HOURS}")
if errors: if errors:
raise PlanError(errors) raise PlanError(errors)
stop = setup_stop(direction, setup.zone_low if long else setup.zone_high, ctx.atr, rules.tick_size) stop = setup_stop(direction, entry, setup.zone_low if long else setup.zone_high, ctx.atr, rules.tick_size)
try: try:
sizing = size_entry(direction, entry, stop, capital=ctx.capital, other_notional=ctx.other_notional, sizing = size_entry(direction, entry, stop, capital=ctx.capital, other_notional=ctx.other_notional,
confidence=plan.confidence, rules=rules) confidence=plan.confidence, rules=rules)
except PlanError as e: except PlanError as e:
raise PlanError([f"止损放在区间{'下沿' if long else '上沿'}外 {STOP_ATR_FACTOR}×ATR(5m)={stop}:{e}。" raise PlanError([f"止损 {stop}(区间{'下沿' if long else '上沿'}外 {STOP_ATR_FACTOR}×ATR(5m),且距挂单价至少 "
f"{MIN_STOP_ATR}×ATR(5m),ATR={ctx.atr.quantize(rules.tick_size)}):{e}。"
"区间太宽或挂单价离区间远端太远,请收窄区间或调整挂单价"]) from e "区间太宽或挂单价离区间远端太远,请收窄区间或调整挂单价"]) from e
return { return {
"direction": direction, "direction": direction,
+10 -3
View File
@@ -82,14 +82,21 @@ def test_atr_and_stop_from_example():
value = atr(bars_until("10:25")) value = atr(bars_until("10:25"))
assert value.quantize(Decimal("0.0001")) == Decimal("302.9714") assert value.quantize(Decimal("0.0001")) == Decimal("302.9714")
# 做多:支撑区 80200–80700 内挂 80500;止损 80200 − 0.5 × 302.97 = 80048.51 → 向下取整 80048.5,距挂单价 0.56% # 做多:支撑区 80200–80700 内挂 80500;止损 80200 − 0.5 × 302.97 = 80048.51 → 向下取整 80048.5,距挂单价 0.56%
stop = setup_stop("long", Decimal("80200"), value, BTC.tick_size) stop = setup_stop("long", Decimal("80500"), Decimal("80200"), value, BTC.tick_size)
assert stop == Decimal("80048.5") assert stop == Decimal("80048.5")
entry = size_entry("long", Decimal("80500"), stop, capital=Decimal("1000"), other_notional=Decimal("0"), confidence="medium", rules=BTC) entry = size_entry("long", Decimal("80500"), stop, capital=Decimal("1000"), other_notional=Decimal("0"), confidence="medium", rules=BTC)
assert entry["stop_pct"] == "0.56" assert entry["stop_pct"] == "0.56"
# 10 ÷ (451.5 + (80500 + 80048.5) × 0.0004 = 515.72) = 0.01939… → 0.0193 # 10 ÷ (451.5 + (80500 + 80048.5) × 0.0004 = 515.72) = 0.01939… → 0.0193
assert entry["quantity"] == "0.0193" assert entry["quantity"] == "0.0193"
# 做空:压力区上沿 81700 + 0.5 × 362.56 = 81881.28 → 向上取整 81881.3 # 做空:区间上沿 81700 + 0.5 × 362.56 = 81881.28 离挂单价 81600 不到 1×ATR,取 81600 + 362.56 = 81962.56 → 向上取整 81962.6
assert setup_stop("short", Decimal("81700"), atr(bars_until("10:55")), BTC.tick_size) == Decimal("81881.3") assert setup_stop("short", Decimal("81600"), Decimal("81700"), atr(bars_until("10:55")), BTC.tick_size) == Decimal("81962.6")
def test_stop_keeps_one_atr_from_entry():
# 计划 #34:做空挂 81690,区间上沿 81720.9,ATR 118.5。按远端算是 81780.2(0.11%),至少 1×ATR 后为 81808.5
assert setup_stop("short", Decimal("81690"), Decimal("81720.9"), Decimal("118.5"), BTC.tick_size) == Decimal("81808.5")
# 做多挂在下沿上方 50:80250 − 302.97 = 79947.03 → 向下取整 79947.0
assert setup_stop("long", Decimal("80250"), Decimal("80200"), atr(bars_until("10:25")), BTC.tick_size) == Decimal("79947.0")
def test_size_entry_rejects_far_stop_and_halves_low_confidence(): def test_size_entry_rejects_far_stop_and_halves_low_confidence():
@@ -135,7 +135,7 @@ const ladder = computed(() => sizing.value?.entry && entryRow.value
(名义 {{ sizing.entry.notional }} USDT,{{ sizing.entry.leverage }} 倍交易资金) (名义 {{ sizing.entry.notional }} USDT,{{ sizing.entry.leverage }} 倍交易资金)
</div> </div>
<div> <div>
<span class="label">止损</span>{{ currentStop ?? sizing.entry.stop }}(初始 {{ sizing.entry.stop }},距挂单价 {{ sizing.entry.stop_pct }}%;区间远端外 0.5×ATR(5m),止盈 1 后移到保本价) <span class="label">止损</span>{{ currentStop ?? sizing.entry.stop }}(初始 {{ sizing.entry.stop }},距挂单价 {{ sizing.entry.stop_pct }}%;区间远端外 0.5×ATR(5m)、距挂单价至少 1×ATR(5m),止盈 1 后移到保本价)
</div> </div>
<div> <div>
<span class="label">止损亏损</span>{{ sizing.entry.risk_at_stop }} USDT(交易资金的 {{ sizing.entry.risk_pct }}%){{ sizing.entry.capped_by_leverage ? ",受 3 倍上限限制" : "" }} <span class="label">止损亏损</span>{{ sizing.entry.risk_at_stop }} USDT(交易资金的 {{ sizing.entry.risk_pct }}%){{ sizing.entry.capped_by_leverage ? ",受 3 倍上限限制" : "" }}