本节摘要:QuantDinger 自带可观测层——Prometheus 指标与 Grafana 面板(官方 README 口径),本节解决「拿到之后看什么、怎么看」。先按第 2 章的进程归属整理指标:backend 看请求延迟与错误率,trading-worker 看订单延迟与拒绝率,scheduler 与 celery 看任务积压,数据侧看行情新鲜度。然后聚焦三类关键指标——订单延迟(从 intent 到成交回执的耗时分布)、拒绝率(风控拦截与交易所拒单的分桶)、数据新鲜度(最新 K 线距今多久)——并各给出异常形态与第一排查动作。面板组织按「一屏一问题」原则,告警入门清单防两个极端:告警疲劳与告警缺失。指标体系的方法论展开见《LLM 可观测与成本治理》,本节专注 QuantDinger 语境。
第 2 章的六进程各有自己的健康问题,指标也要按归属理解:
| 进程 | 核心指标 | 异常信号(示意) |
|---|---|---|
| backend(HTTP API) | 请求延迟、错误率 | P99 抬升、5xx 占比上升 |
| trading-worker | 订单延迟、持仓对账差 | 成交回执超时、对账不一致 |
| scheduler-worker | 调度延迟、漏跑 | 定时任务跳档 |
| celery-worker/beat | 队列深度、任务耗时 | 积压增长、任务超时 |
| 数据侧(第 3 章) | 行情新鲜度、缺口数 | 最新 K 线滞后、断点增多 |
归属的价值在排查路径:订单延迟恶化先看 trading-worker 自身还是数据侧拖累,两条链路的指标分属不同进程,一眼可辨。
订单延迟:从 intent 折算成订单到交易所回执的耗时分布,看 P50/P95/P99 三个分位而不是平均值。异常形态两种:整体抬升(交易所侧拥堵或网络问题)与长尾恶化(个别订单卡重试)。第一动作:对照实盘适配器(第 8 章)的重试日志,区分「慢」与「重试后成功」。
拒绝率:分两桶看——风控拦截(第 5.3 节的降级、拆分、拒绝)与交易所拒单(参数错、精度错、权限不足)。两桶的含义完全不同:前者是策略激进度与风控配置的博弈,健康系统里稳定存在;后者全是 bug 或配置错误,出现一次就要查。第一动作:先看拒单桶的错误码分布。
数据新鲜度:最新 K 线时间距今多久。交易系统里这是最容易被忽视又最致命的指标——策略在陈旧数据上决策等于闭眼开车。第一动作:对照第 3 章数据源的健康状态,区分源断流与入库卡住。
走一个具体场景(示意)。某日快照里数据新鲜度变红,订单延迟与拒单均正常。排查顺序:第一步,看数据侧新鲜度指标的分源明细——若所有源同时滞后,问题在本地下游(入库卡住);若只有主源滞后,是源断流,查自动切换是否已发生。第二步,若判断是入库卡住,看第 2 章 celery 队列深度是否积压——积压则问题在消费端,不积压则在采集端。第三步,处置期间联动第 10 章停新单:陈旧数据上的策略决策是这次事件里最大的风险敞口,先停再看。整个走查的要点是:红灯只是入口,分源明细与队列深度才是路径——这正是指标按进程归属组织的用处。
# prometheus_rules.yml —— 三类指标的告警规则示意(阈值示意,按品种调整) groups: - name: trading-critical rules: - alert: OrderLatencyP99High expr: order_latency_seconds{quantile="0.99"} > 5 for: 5m labels: {severity: warning} - alert: DataStaleness expr: time() - max(last_bar_timestamp) > 120 for: 2m labels: {severity: critical} # 数据陈旧比订单慢更紧急 - alert: ExchangeRejectSpike expr: rate(exchange_rejects_total[10m]) > 0 labels: {severity: critical} # 交易所拒单出现即告警
Grafana 面板容易长成「指标博物馆」——什么都放,什么都不看。组织原则是一屏一问题:
| 面板 | 回答的问题 | 必放组件 |
|---|---|---|
| 今天还能交易吗 | 系统整体健康度 | 三类关键指标红绿灯、进程存活 |
| 订单链路慢在哪 | 延迟分解 | 订单延迟分位图、适配器重试率 |
| 数据可信吗 | 行情质量 | 新鲜度、缺口数、源切换事件 |
| 策略在干什么 | 决策留痕 | intent 流量、风控拦截分桶、持仓变化 |
面板顶部的红绿灯(三类关键指标各一格)是给值班看的;下面的分解图是给排查看的。两层分开,值班扫一眼就能决定要不要往下挖。
红绿灯也可以脱离面板做每日快照——一段纯标准库脚本拉取指标文本并给出三色判定(指标名与端点为示意,以实际部署为准):
# redlight.py —— 三色灯每日快照(纯标准库,指标名与端口以实际部署为准) import urllib.request METRICS_URL = "http://127.0.0.1:8000/metrics" # 示意地址,以部署实际为准 def fetch_text(url): with urllib.request.urlopen(url, timeout=10) as resp: return resp.read().decode("utf-8") def sample(text, name): """取第一条 name 样本行的值(示意解析,格式以 Prometheus 文本协议为准)""" for line in text.splitlines(): parts = line.split() if len(parts) >= 2 and parts[0].split("{")[0] == name: return float(parts[1]) return None def snapshot(): text = fetch_text(METRICS_URL) staleness = sample(text, "data_staleness_seconds") order_p99 = sample(text, "order_latency_p99_seconds") rejects = sample(text, "exchange_rejects_total") return { "数据新鲜度": "红" if staleness is None or staleness > 120 else "绿", "订单延迟": "红" if order_p99 is None or order_p99 > 5 else "绿", "拒单": "红" if rejects is None or rejects > 0 else "绿", } if __name__ == "__main__": for name, light in snapshot().items(): print(f"{name}: {light}")
两个讲法。其一,脚本里的三个阈值与上面告警规则的示意阈值一一对应——这是有意的:脚本快照与告警规则必须同源,否则会出现「脚本说绿、告警说红」的两套事实,排查时先要仲裁谁对。其二,「取不到值即判红」的兜底逻辑:指标断流时 sample 返回 None,直接判红——这与本章反复强调的「指标新鲜度本身就是指标」是同一立场,观测层自己的故障也要表现为红灯,而不是表现为安静。
告警的两个失败极端:什么都告(疲劳,真警报被淹)与什么都攒着周报看(缺失,事故过夜才知道)。入门清单:
| 问题 | 排查方向 | 要点 |
|---|---|---|
| 面板上所有指标同时消失 | 采集链路而非业务 | 先查采集器与网络,全消失多为观测层自身故障 |
| 告警发了没人看到 | 通知通道与分级 | 复核通知路由,critical 要能找到人 |
| P99 正常但订单偶发超时 | 长尾在更深的分位 | 看重试日志,区分「慢」与「重试后成功」 |
| 拒绝率抬升但全在风控桶 | 策略激进度变化 | 属两桶含义中的前者,查策略参数变更记录 |
| Grafana 面板越加越多 | 「一屏一问题」失守 | 定期删面板,博物馆化是慢性病 |
第一行是观测层的特殊盲区:业务全红往往反而是好消息的镜像——业务真停了,指标会停在最后值而不是消失;指标全部消失、时间戳不再前进,通常是采集链路自己断了。所以「看板一片空白」的第一嫌疑人不是交易系统,而是 Prometheus 到各进程的抓取路径。这也是把 redlight 快照做成独立脚本而不是只依赖面板的原因之一:面板本身也在被观测的链路上。
看得见之后,下一节解决保得住:备份、恢复演练与按次序升级——让最坏的一天(磁盘坏了、升级坏了)也有剧本可执行。