arbitrage-engine/docs/ai/06-decision-log.md
fanziqi 22787b3e0a docs: add AI documentation suite and comprehensive code review report
- Generate full AI-consumable docs (docs/ai/): system overview, architecture,
  module cheatsheet, API contracts, data model, build guide, decision log,
  glossary, and open questions (deep tier coverage)
- Add PROBLEM_REPORT.md: categorized bug/risk summary
- Add DETAILED_CODE_REVIEW.md: full line-by-line review of all 15 backend
  files, documenting 4 fatal issues, 5 critical deployment bugs, 4 security
  vulnerabilities, and 6 architecture defects with prioritized fix plan
2026-03-03 19:01:18 +08:00

8.0 KiB
Raw Permalink Blame History

generated_by version created last_updated source_commit coverage
repo-insight 1 2026-03-03 2026-03-03 0d9dffa deep

06 — Decision Log

Purpose

记录项目中关键的技术决策、选型原因及权衡取舍(从代码注释和架构特征推断)。

TL;DR

  • 选择 PostgreSQL 作为唯一消息总线NOTIFY/LISTEN避免引入 Kafka/Redis 等额外组件。
  • signal_engine 改为 15 秒循环(原 5 秒CPU 降 60%,信号质量无影响。
  • 双写 Cloud SQL 作为灾备,失败不阻断主流程。
  • agg_trades 按月分区,避免单表过大影响查询性能。
  • 认证采用自研 HMAC-SHA256 JWT不依赖第三方库。
  • 前端使用 Next.js App Router + 纯客户端轮询,不使用 WebSocket 推送。
  • 策略参数外置为 JSON 文件,支持热修改无需重启进程。
  • 信号评分采用多层加权体系5层每层独立可调支持多策略并行。

Canonical Facts

决策 1PostgreSQL 作为进程间消息总线

决策:使用 PostgreSQL NOTIFY/LISTEN 在 signal_engine 和 live_executor 之间传递信号,而非 Redis pub/sub 或消息队列。

原因(从代码推断):

  • 系统已强依赖 PG避免引入新的基础设施依赖。
  • 信号触发频率低(每 15 秒最多一次PG NOTIFY 完全满足延迟要求。
  • 信号 payload 直接写入 signal_indicatorsNOTIFY 仅做触发通知,消费者可直接查表。

取舍:单点依赖 PGPG 宕机时信号传递和持久化同时失败(可接受,因为两者本就强耦合)。

来源live_executor.py:1-10 架构注释,signal_engine.py:save_indicator 函数。


决策 2信号引擎循环间隔从 5 秒改为 15 秒

决策LOOP_INTERVAL = 15(原注释说明原值为 5

原因:代码注释明确写道 "CPU降60%,信号质量无影响"。

取舍:信号触发延迟最坏增加 10 秒对于短线但非高频的策略TP/SL 以 ATR 倍数计,通常 >1% 波动10 秒的额外延迟影响可忽略不计。

来源signal_engine.py:39 LOOP_INTERVAL = 15 # 秒从5改15CPU降60%,信号质量无影响)


决策 3agg_trades 表按月范围分区

决策agg_trades 使用 PARTITION BY RANGE(time_ms),按月创建子表(如 agg_trades_202603)。

原因

  • aggTrades 是最大的写入表(每秒数百条),无分区会导致单表膨胀。
  • 按月分区支持高效的时间范围查询PG 分区裁剪)。
  • 旧分区可独立归档或删除,不影响主表。

取舍:分区管理需要维护(ensure_partitions() 自动创建当月+未来2个月分区需定期执行跨分区查询性能取决于分区裁剪是否生效time_ms 条件必须是常量)。

来源db.py:191-201, 360-393


决策 4Cloud SQL 双写(非阻塞)

决策:所有写入操作在本地 PG 成功后,尝试相同写入到 Cloud SQL10.106.0.3Cloud SQL 失败不影响主流程。

原因提供数据异地备份Cloud SQL 作为只读副本或灾备使用。

取舍

  • 本地 PG 和 Cloud SQL 可能出现数据不一致local 成功 + cloud 失败)。
  • 双写增加每次写操作的延迟(两个网络 RTT但因为是 best-effort 且使用独立连接池,实际阻塞极少。
  • live_executor 直接连 Cloud SQLDB_HOST=10.106.0.3),绕过本地 PG。

来源db.py:23-29, 80-118live_executor.py:50-55


决策 5自研 JWT不用 PyJWT 等第三方库)

决策:使用 Python 标准库 hmachashlibbase64 手动实现 JWT 签发和验证。

原因推断减少依赖JWT 结构相对简单HMAC-SHA256 签名几十行代码即可实现。

取舍

  • 需要自行处理过期、revoke、refresh token 等逻辑(代码中已有 refresh_tokens 表)。
  • 非标准实现可能在边界情况(时钟偏差、特殊字符等)上与标准库行为不同。
  • 无 JWT 生态工具支持(调试工具、密钥轮转库等)。

来源auth.py:1-6import hashlib, secrets, hmac, base64, jsonauth.py:16-19


决策 6策略配置外置为 JSON 文件

决策V5.x 策略的权重、阈值、TP/SL 倍数等参数存放在 backend/strategies/*.jsonsignal_engine 每次 load_strategy_configs() 读取。

原因

  • 策略调优频繁v51→v52 权重变化显著),外置避免每次改参数都要修改代码。
  • 多策略并行signal_engine 同时运行 v51_baseline 和 v52_8signals对每个 symbol 分别评分。
  • [inference] 支持未来通过前端或 API 修改策略参数而不重启进程(目前 signal_engine 每次循环重读文件 —— 需确认)。

取舍JSON 文件无类型检查,配置错误在运行时才发现;缺少配置 schema 校验。

来源signal_engine.py:41-67backend/strategies/v51_baseline.jsonbackend/strategies/v52_8signals.json


决策 7信号评分采用五层加权体系

决策:信号评分分为 5 个独立层次(方向层、拥挤层、资金费率层、环境层、确认层、清算层、辅助层),每层有独立权重,总分 0~100阈值 75 触发信号。

设计特点

  • 方向层CVD权重最高V5.1: 45分V5.2: 40分是核心指标。
  • "standard" 档位score ≥ threshold75"heavy" 档位score ≥ max(threshold+10, 85)。
  • 信号冷却:同一 symbol 同一策略触发后 10 分钟内不再触发。
  • CVD 快慢线需同向才产生完整方向信号;否则标记 no_direction=True 不触发。

取舍:权重缩放逻辑较复杂(各层原始满分不统一,需先归一化再乘权重);market_indicators 缺失时给默认中间分,保证系统在数据不完整时仍能运行。

来源signal_engine.py:410-651


决策 8前端使用轮询而非 WebSocket

决策React 前端对 /api/rates 每 2 秒轮询慢速数据stats/history/signals每 120 秒轮询K 线图每 30 秒刷新。

原因(推断):

  • 实现简单,无需维护 WebSocket 连接状态和断线重连逻辑。
  • 数据更新频率2 秒/30 秒对轮询友好WebSocket 的优势在于毫秒级推送。
  • FastAPI 已支持 WebSocket但实现 SSE/WS 推送需要额外的后端状态管理。

取舍:每 2 秒轮询 /api/rates 会产生持续的服务器负载;当用户量增加时需要加缓存或换 WebSocket。

来源frontend/app/page.tsx:149-154


决策 9live_executor 和 risk_guard 直连 Cloud SQL

决策live_executor.pyrisk_guard.py 默认 DB_HOST=10.106.0.3Cloud SQL而不是本地 PG。

原因(推断):这两个进程运行在与 signal_engine 不同的环境(可能是另一台 GCP VM 或容器),直连 Cloud SQL 避免通过本地 PG 中转。

取舍live_executor 和 signal_engine 使用不同的 PG 实例,理论上存在数据读取延迟(双写同步延迟)。

来源live_executor.py:50-55risk_guard.py:47-53

Interfaces / Dependencies

无额外接口依赖,均为内部架构决策。

Unknowns & Risks

  • [inference] 所有决策均从代码推断,无明确的 ADRArchitecture Decision Record文档。
  • [unknown] 策略配置是否支持热重载signal_engine 是否每次循环都重读 JSON未确认。
  • [risk] 决策 4双写+ 决策 9live executor 直连 Cloud SQL组合下若本地 PG 和 Cloud SQL 数据不一致live_executor 可能读到滞后的信号或重复执行。

Source Refs

  • backend/signal_engine.py:39 — LOOP_INTERVAL 注释
  • backend/signal_engine.py:44-67 — load_strategy_configs
  • backend/signal_engine.py:410-651 — evaluate_signal 完整评分逻辑
  • backend/db.py:23-29, 80-118 — Cloud SQL 双写连接池
  • backend/live_executor.py:50-55 — DB_HOST 配置
  • backend/auth.py:1-6 — 自研 JWT import
  • frontend/app/page.tsx:149-154 — 轮询间隔
  • backend/strategies/v51_baseline.json, v52_8signals.json