32b 透明化 流程图 + Source of Truth Run Replay

32b 实盘交易逻辑透明页

这页不是回测页,也不是只看结果的 Dashboard。它专门把 信号 → 选币 → 新鲜度 → 风控 → 下单 → 挂 TP/SL → timeout → 状态文件 整条链路透明化,方便你以后更快定位 bug 和理解能力边界。

页面生成时间:2026-05-13 06:03:44 北京时间 / 2026-05-12 22:03:44 UTC

当前 live 配置快照

先记住一句最重要的:扫描最近 72h 信号,但只允许 <= 3 分钟的信号真正进入交易队列。这就是“陈旧信号”问题最核心的透明化结论。

当前代码版本
ad9bcfaefa15
页面生成时 HEAD commit
当前 config hash
a8039ab451a1f02b
phase6_status.json 记录
最近完成 bar
2026-05-04 23:30:00 北京时间 / 2026-05-04 15:30:00 UTC
最近真的参与计算的 bar
最近信号时间
-
最近一次生成信号,不等于最近计算时间
窗口信号 / 新处理 / 风险拒绝
0 / 0 / 0
最近一轮 run 的统计
外部仓位告警
1
非 canary 白名单仓位数

总览流程图(SVG)

先看这张图,知道 32b 实盘现在到底是怎么从一根 K 线走到一笔交易的。后面每一步都有展开说明。

① 定时器唤醒systemd timer / 每 5 分钟 / 但逻辑 bar 仍按 15m每轮先同步 pending/live,再看新的交易候选。② 扫描候选信号variant=ema_cross_plus_slope_floor, recent_hours=72hSignal adapter 从最近窗口里捞出候选信号,附带 ATR14、slope_strength。③ strongest-only 选币selection=strongest_signal_only, metric=slope_strength同一 timestamp 多币同时触发时,只保留 strongest 一个。④ 新鲜度过滤扫描 72h,但 trade freshness <= 3m即使 adapter 扫到了旧信号,只要超龄,也会被 signal_too_old 拒绝。⑤ 风控放行 / 拒绝trade_enabled=否, kill_switch=是, max_positions=1检查 symbol、并发、live/pending、ATR、data_delay、API 健康等。⑥ 入场当前 live entry=market, TTL=15m当前 live 默认走 MARKET;如果未来改回 limit_gtx,则可能走 TTL 后 fallback。⑦ 挂退出计划TP=1.00 ATR, SL=1.00 ATR, timeout=120m入场后立即挂 STOP_MARKET 止损 + LIMIT 止盈,并设置 timeout。⑧ 写状态与回放status.json / state.json / recent_* / events.jsonl所有页面、巡检、邮件、排障都依赖这些文件。

Alpha 基本原理(信号层)

如果你只想先搞懂 32b 的 alpha 到底在“看什么”,先看这张图。它回答的是 哪根 15m bar 会被认成 long/short 信号,还没进入 strongest-only、freshness、risk、下单这些执行层。

Alpha:ema_cross_plus_slope_floor(信号触发) 结构(1h EMA) + 触发(15m close 穿越 fast EMA) + 动量(slope floor) ⇒ signal LONG signal 1) long_structure = (ema_fast_1h > ema_slow_1h) 2) cross_only_long:prev_close ≤ prev_fast 且 close > ema_fast_1h 3) slope_floor_long:fast_slope > 0.0004 且 slow_slope > 0 4) slope_floor_long_signal = cross_only_long ∧ slope_floor_long 注意:fast_slope/slow_slope = 1h EMA 的 pct_change spread_mid = (ema_fast_1h + ema_slow_1h) / 2 SHORT signal 1) short_structure = (ema_fast_1h < ema_slow_1h) 2) cross_only_short:prev_close ≥ prev_fast 且 close < ema_fast_1h 3) slope_floor_short:fast_slope < -0.0004 且 slow_slope < 0 4) slope_floor_short_signal = cross_only_short ∧ slope_floor_short slope_strength = |fast_slope| + |slow_slope|(用于 strongest-only 选币) 信号 timestamp 是 15m bar 的 close 时间(完成 bar)

一句话版本

32b 不是裸追涨杀跌。它先用 1h EMA fast/slow 定结构方向,再要求 15m close 穿越 fast EMA,同时要求 EMA slope 够强,才把这根 bar 记成可交易信号。

边界要分清

这张图只解释 alpha 信号如何触发。信号触发后,是否真的变成交易,还要继续经过 same-bar strongest-onlysignal_too_old、风控、并发和下单链路。

把图翻成人话

逐步拆解(适合排障时展开看)

① 定时器唤醒|systemd timer / 每 5 分钟 / 但逻辑 bar 仍按 15m

这一步在做什么:每轮先同步 pending/live,再看新的交易候选。

为什么重要:先同步状态,避免系统以为自己 flat,但交易所里其实还有仓或挂单。

排障先看:phase6_last_run_summary.json 的 latest_evaluated_bar_time、managed_pending_entries、managed_live_positions。

源码位置:ops/systemd/momentum-rank32b-canary-phase6.timer + run_rank32b_canary_phase6.py main()

② 扫描候选信号|variant=ema_cross_plus_slope_floor, recent_hours=72h

这一步在做什么:Signal adapter 从最近窗口里捞出候选信号,附带 ATR14、slope_strength。

为什么重要:这里负责“看到什么机会”,不是最终是否能交易。

排障先看:phase6_status.json 的 recent_signal_count、phase6_events.jsonl 里的 SignalReceived。

源码位置:src/momentum/execution/canary32b/signal_adapter.py:80

③ strongest-only 选币|selection=strongest_signal_only, metric=slope_strength

这一步在做什么:同一 timestamp 多币同时触发时,只保留 strongest 一个。

为什么重要:把策略从“多币全追”收敛成“同窗只打最强”。

排障先看:phase6_last_run_summary.json 的 skipped_weaker_signals、recent_rejections 的 weaker_than_strongest_signal_in_same_bar。

源码位置:run_rank32b_canary_phase6.py:201

④ 新鲜度过滤|扫描 72h,但 trade freshness <= 3m

这一步在做什么:即使 adapter 扫到了旧信号,只要超龄,也会被 signal_too_old 拒绝。

为什么重要:这是修复陈旧信号 bug 的关键逻辑。

排障先看:phase6_recent_rejections.json 是否出现 signal_too_old;payload 里看 signal_age_seconds。

源码位置:run_rank32b_canary_phase6.py:1554-1602

⑤ 风控放行 / 拒绝|trade_enabled=否, kill_switch=是, max_positions=1

这一步在做什么:检查 symbol、并发、live/pending、ATR、data_delay、API 健康等。

为什么重要:它回答的是“这个信号能不能真的变成一笔交易”。

排障先看:recent_rejections 的 reason;phase6_last_run_summary.json 的 risk_rejections。

源码位置:src/momentum/risk/canary32b_guard.py:53

⑥ 入场|当前 live entry=market, TTL=15m

这一步在做什么:当前 live 默认走 MARKET;如果未来改回 limit_gtx,则可能走 TTL 后 fallback。

为什么重要:这里决定 admission/fill 确定性与成本结构。

排障先看:phase6_recent_orders.json 的 order_role=entry;events 里的 ORDER_PLACED / PositionOpened。

源码位置:run_rank32b_canary_phase6.py:1720+ / 1155+

⑦ 挂退出计划|TP=1.00 ATR, SL=1.00 ATR, timeout=120m

这一步在做什么:入场后立即挂 STOP_MARKET 止损 + LIMIT 止盈,并设置 timeout。

为什么重要:这是 live 风险闭环;任何 attach 失败都要被重点监控。

排障先看:warnings.json、recent_orders、recent_closed_trades 的 exit_reason。

源码位置:run_rank32b_canary_phase6.py:558

⑧ 写状态与回放|status.json / state.json / recent_* / events.jsonl

这一步在做什么:所有页面、巡检、邮件、排障都依赖这些文件。

为什么重要:如果状态文件不透明,后面就只能靠猜。

排障先看:phase6_status.json、phase6_state.json、phase6_events.jsonl。

源码位置:run_rank32b_canary_phase6.py run end checkpoint + dashboard builders

一张图看懂:从 K 线到成交的完整流程

① 定时器唤醒 Phase6

systemd timerOnUnitActiveSec=5min先同步再看新信号

Runner 大约每 5 分钟跑一次,但信号本身仍然是按 15m bar 来定义。每次 run 会先同步 pending/live/交易所状态,再看新的交易机会。

② 扫描候选信号

variant=ema_cross_plus_slope_floorlookback=30dscan=72h

Signal adapter 会为 2 个币读取 bars、构建 frame、计算 ATR14,并在最近 72 小时窗口内找出满足 ema_cross_plus_slope_floor 的信号,同时产出 slope_strengthatr14signal_id 等元数据。

③ 先做“同窗最强”筛选

strongest_signal_onlystrongest_only_per_bar=是metric=slope_strength

若多个币在同一个 timestamp 同时出信号,系统只保留 slope_strength 最强的那个,其余信号会被记录成 weaker_than_strongest_signal_in_same_bar,而不是“静默消失”。

④ 再做“新鲜度”过滤

freshness <= 3m当前占扫描窗口的 0.07%

这里是陈旧信号 bug 的核心修复点:即便 adapter 扫描最近 72h,真正进入交易决策的信号也必须满足 now - signal.timestamp <= 3 分钟。超龄信号会被记为 signal_too_old,并写入 rejections / warnings。

⑤ 风控判定:到底能不能交易

trade_enabled=否kill_switch=是max_concurrent_positions=1

风控会依次检查:交易开关、kill switch、symbol 是否在 universe、同币是否已有 live/pending、总并发是否超限、是否达到日内交易数上限、API 健康、data delay、ATR 是否可用等。任何一条不过,都会给出明确 reject reason。

⑥ 选币与仓位

Universe=2 symbolsmax_new_signals_per_run=1desired_notional=20U

当前是“六选一、单席位”模型:Universe 为 BTCUSDT, ETHUSDT,每轮最多只处理 1 个新信号,同时总并发仓位上限为 1。这意味着它更像“扩机会池”,而不是“6 个币一起上仓”。

⑦ 下单执行

当前 live entry=marketTTL=15mfallback_to_market=是

当前 live 默认是市价单入场。 如果以后切回 limit_gtx,则会先挂限价单,等到 TTL 到期后,若配置允许,可自动 fallback 到 market。这里要特别注意:phase6.entry 才是当前 live 的主配置入口。

⑧ 入场后立即挂退出计划

TP=1.00 ATRSL=1.00 ATRtimeout=120m

一旦 entry 成交,系统会立即尝试挂:STOP_MARKET 止损 + LIMIT 止盈,并记录 timeout 时间。若既没 hit TP、也没 hit SL,到 120 分钟会走 timeout_market 市价平仓。

⑨ 写状态文件,供 Dashboard / 邮件 / 巡检读取

status.jsonrun_summary.jsonstate.jsonevents.jsonl

每轮 run 结束后,phase6 会写出状态、最近订单、最近仓位、最近平仓、最近拒绝、warnings、以及带 checkpoint 的 phase6_state.json。这就是你以后定位“策略到底在想什么”的透明化基础设施。

当前最重要的透明结论

现在的逻辑已经把“历史扫描窗口”和“真实可交易新鲜度”拆开了:

  • 扫描窗口:72h
  • 交易新鲜度:<= 3m
  • 超龄信号:显式记为 signal_too_old

当前最容易被误解的地方

execution.entry 这块配置还留着旧 phase/辅助语义,容易让人误以为 live 还在用 post-only maker-first 入口。当前真正 controlling live 入场行为的是 phase6.entry

最近一次 run 回放

这不是历史大盘,而是 最近一轮 phase6 自己留下的事件时间线。以后你想知道“这轮到底做了什么”,先看这里,不用先翻日志。

本次 run:2026-05-04 23:37:03 北京时间 / 2026-05-04 15:37:03 UTC → 2026-05-04 23:37:05 北京时间 / 2026-05-04 15:37:05 UTC

最近一次 run 没有留下可展示的逐步事件,通常意味着这轮只是常规同步,没有新 signal / intention / order / rejection。

“信号是否延迟”到底怎么判定?

你之前最痛的 bug 就卡在这里,所以这块我单独拆清楚:

所以“最近 72h 扫描”本身不等于“最近 72h 都能下单”。真正能下单的是:最近 3 分钟内、未 seen、未 same-bar consumed、且风控放行的信号。

下单路径:当前 live 与可选路径

当前 live 路径(默认)

  • 入场:MARKET
  • 挂止损:STOP_MARKET
  • 挂止盈:LIMIT
  • 超时退出:MARKET
  • 优点:admission/fill 更确定,适合 canary 小仓位
  • 代价:成本通常高于纯 maker-first

备用路径(若切回 maker-first)

  • 入场:limit_gtx
  • 等待:最多 15m
  • 若没成交:视配置决定是否 fallback 到 MARKET
  • 适合:更想压手续费 / 滑点时
  • 风险:挂单未成、TTL、fallback 链路更复杂

真实配置(Source of Truth)

配置项当前值
运行模式live_canary
UniverseBTCUSDT, ETHUSDT
Signal variantema_cross_plus_slope_floor
Signal lookback days30
Signal scan window72h
Trade freshness gate&lt;= 3m
Selectionstrongest_signal_only / slope_strength
Max new signals per run1
Max concurrent positions1
Require ATR
Entry order typemarket
Entry TTL15m
Limit TTL fallback to market
Desired notional20.00 USDT / symbol
Default leverage1x
TP / SL / Timeout1.00 ATR / 1.00 ATR / 120m
Safety pause cooldown0m (当前关闭)
Data delay coarse limit259200s ≈ 72.0h

最容易出 bug / 误判的地方

风险点为什么容易出问题你该先看什么
历史信号 backlog当前 adapter 仍扫描最近 72h;真正防 bug 的是 3m 新鲜度门。优先看 phase6_recent_rejections.json 是否出现 signal_too_old
同窗多币竞争同一 timestamp 只留最强 slope_strength;其余会被 weaker_than_strongest_signal_in_same_bar 拒掉。不要把“没下 ETH”误判成“没看到 ETH 信号”
单席位限制max_concurrent_positions=1;有 live/pending 后,后续新币信号会被 too_many_positions 拦截。看 recent_rejections 和 phase6_state
entry 配置双轨当前 live 真正使用的是 phase6.entry.order_type=market;execution.entry 更像旧 phase/辅助配置。改配置时优先改 phase6.entry,避免改错块
外部账户干扰交易所里存在非 whitelist 仓位时,Dashboard 可能提示 unexpected_exchange_positions。它不会自动替你清仓,但会影响保证金和判断
安全暂停当前已关闭;若未来打开,只会看当前 code_version 之后发生的 attach-failure。旧版本失败不应继续封锁新版本测试

排障时该看哪些文件

文件它回答什么问题
phase6_status.json当前 live 总状态:latest bar、trade_enabled、kill_switch、system_health
phase6_last_run_summary.json最近一轮到底看到了几个信号、处理了几个、下了几笔单、有没有 safety pause
phase6_state.jsonseen_signal_ids / consumed_signal_bars / pending_entries / live_positions / closed_trades 的真实 checkpoint
phase6_recent_rejections.json为什么没交易:signal_too_old / same_bar / risk reject / safety pause
phase6_recent_orders.json下单明细:entry / stop_loss / take_profit / timeout_close
phase6_recent_closed_trades.json已平仓明细:exit_reason / pnl / fee / code_version / config_version
phase6_warnings.jsonattach 失败、query 失败、外部仓位干扰等告警
phase6_events.jsonl最细的逐步事件时间线,适合还原一次 run 的全过程

源码地图:这页内容对应哪里

逻辑块源码位置说明
信号扫描src/momentum/execution/canary32b/signal_adapter.py:80load_recent_signals():扫 recent_hours 内的候选信号,并生成 signal_id / atr14 / slope_strength
同窗最强选择scripts/run_rank32b_canary_phase6.py:201select_signals_for_execution():同一 timestamp 只保留 slope_strength 最强的币
风险拦截src/momentum/risk/canary32b_guard.py:53evaluate_entry_risk():trade_enabled / kill_switch / 并发 / ATR / data_delay / API 健康等
安全暂停 + 新鲜度scripts/run_rank32b_canary_phase6.py:1524-1602先检查 exit_attach failure 安全暂停,再做 signal_too_old 新鲜度过滤
挂单成交 / TTL fallbackscripts/run_rank32b_canary_phase6.py:1155manage_pending_entries():limit_gtx 若超时,可 fallback 市价进场
入场后挂退出计划scripts/run_rank32b_canary_phase6.py:558attach_exit_plan():挂 STOP_MARKET 止损 + LIMIT 止盈 + timeout 时间

这页适合怎么用?

目标不是把页面做得花,而是把“策略现在到底怎么工作”说人话、说透明、说能排障。