- 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
163 lines
8.0 KiB
Markdown
163 lines
8.0 KiB
Markdown
---
|
||
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`
|