--- generated_by: repo-insight version: 1 created: 2026-03-03 last_updated: 2026-03-03 source_commit: 0d9dffa coverage: 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 ### 决策 1:PostgreSQL 作为进程间消息总线 **决策**:使用 PostgreSQL `NOTIFY/LISTEN` 在 signal_engine 和 live_executor 之间传递信号,而非 Redis pub/sub 或消息队列。 **原因**(从代码推断): - 系统已强依赖 PG;避免引入新的基础设施依赖。 - 信号触发频率低(每 15 秒最多一次),PG NOTIFY 完全满足延迟要求。 - 信号 payload 直接写入 `signal_indicators` 表,NOTIFY 仅做触发通知,消费者可直接查表。 **取舍**:单点依赖 PG;PG 宕机时信号传递和持久化同时失败(可接受,因为两者本就强耦合)。 **来源**:`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改15,CPU降60%,信号质量无影响)` --- ### 决策 3:agg_trades 表按月范围分区 **决策**:`agg_trades` 使用 `PARTITION BY RANGE(time_ms)`,按月创建子表(如 `agg_trades_202603`)。 **原因**: - aggTrades 是最大的写入表(每秒数百条),无分区会导致单表膨胀。 - 按月分区支持高效的时间范围查询(PG 分区裁剪)。 - 旧分区可独立归档或删除,不影响主表。 **取舍**:分区管理需要维护(`ensure_partitions()` 自动创建当月+未来2个月分区,需定期执行);跨分区查询性能取决于分区裁剪是否生效(`time_ms` 条件必须是常量)。 **来源**:`db.py:191-201, 360-393` --- ### 决策 4:Cloud SQL 双写(非阻塞) **决策**:所有写入操作在本地 PG 成功后,尝试相同写入到 Cloud SQL(`10.106.0.3`),Cloud SQL 失败不影响主流程。 **原因**:提供数据异地备份;Cloud SQL 作为只读副本或灾备使用。 **取舍**: - 本地 PG 和 Cloud SQL 可能出现数据不一致(local 成功 + cloud 失败)。 - 双写增加每次写操作的延迟(两个网络 RTT),但因为是 best-effort 且使用独立连接池,实际阻塞极少。 - live_executor 直接连 Cloud SQL(`DB_HOST=10.106.0.3`),绕过本地 PG。 **来源**:`db.py:23-29, 80-118`,`live_executor.py:50-55` --- ### 决策 5:自研 JWT(不用 PyJWT 等第三方库) **决策**:使用 Python 标准库 `hmac`、`hashlib`、`base64` 手动实现 JWT 签发和验证。 **原因**(推断):减少依赖;JWT 结构相对简单,HMAC-SHA256 签名几十行代码即可实现。 **取舍**: - 需要自行处理过期、revoke、refresh token 等逻辑(代码中已有 `refresh_tokens` 表)。 - 非标准实现可能在边界情况(时钟偏差、特殊字符等)上与标准库行为不同。 - 无 JWT 生态工具支持(调试工具、密钥轮转库等)。 **来源**:`auth.py:1-6`(import hashlib, secrets, hmac, base64, json),`auth.py:16-19` --- ### 决策 6:策略配置外置为 JSON 文件 **决策**:V5.x 策略的权重、阈值、TP/SL 倍数等参数存放在 `backend/strategies/*.json`,signal_engine 每次 `load_strategy_configs()` 读取。 **原因**: - 策略调优频繁(v51→v52 权重变化显著),外置避免每次改参数都要修改代码。 - 多策略并行:signal_engine 同时运行 v51_baseline 和 v52_8signals,对每个 symbol 分别评分。 - [inference] 支持未来通过前端或 API 修改策略参数而不重启进程(目前 signal_engine 每次循环重读文件 —— 需确认)。 **取舍**:JSON 文件无类型检查,配置错误在运行时才发现;缺少配置 schema 校验。 **来源**:`signal_engine.py:41-67`,`backend/strategies/v51_baseline.json`,`backend/strategies/v52_8signals.json` --- ### 决策 7:信号评分采用五层加权体系 **决策**:信号评分分为 5 个独立层次(方向层、拥挤层、资金费率层、环境层、确认层、清算层、辅助层),每层有独立权重,总分 0~100,阈值 75 触发信号。 **设计特点**: - 方向层(CVD)权重最高(V5.1: 45分,V5.2: 40分),是核心指标。 - "standard" 档位:score ≥ threshold(75);"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` --- ### 决策 9:live_executor 和 risk_guard 直连 Cloud SQL **决策**:`live_executor.py` 和 `risk_guard.py` 默认 `DB_HOST=10.106.0.3`(Cloud SQL),而不是本地 PG。 **原因**(推断):这两个进程运行在与 signal_engine 不同的环境(可能是另一台 GCP VM 或容器),直连 Cloud SQL 避免通过本地 PG 中转。 **取舍**:live_executor 和 signal_engine 使用不同的 PG 实例,理论上存在数据读取延迟(双写同步延迟)。 **来源**:`live_executor.py:50-55`,`risk_guard.py:47-53` ## Interfaces / Dependencies 无额外接口依赖,均为内部架构决策。 ## Unknowns & Risks - [inference] 所有决策均从代码推断,无明确的 ADR(Architecture Decision Record)文档。 - [unknown] 策略配置是否支持热重载(signal_engine 是否每次循环都重读 JSON)未确认。 - [risk] 决策 4(双写)+ 决策 9(live 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`