arbitrage-engine/docs/DETAILED_CODE_REVIEW.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

18 KiB
Raw Permalink Blame History

全面代码审阅报告

生成时间2026-03-03 审阅范围全部后端文件15个完整阅读 基于 commit a17c143 的代码


摘要

本报告基于对所有后端文件的逐行阅读。发现 4个致命级别问题(直接导致实盘无法运行)、5个高危问题(全新部署直接报错)、4个安全漏洞6个架构设计缺陷。其中若干问题在前一份报告PROBLEM_REPORT.md中已提及但本报告基于完整代码阅读提供了更精确的定位和更全面的覆盖。


🔴 致命问题(实盘链路完全断裂)

[F1] live_executor 永远读不到信号

定位live_executor.py:fetch_pending_signals() + signal_engine.py:save_indicator()

证据链

  1. signal_engine.py:690-706save_indicator()get_sync_conn()(即 db.py 的 PG_HOST=127.0.0.1)将信号写入本地 PG 的 signal_indicators
  2. live_executor.py:50-55(已知):DB_HOST 默认 10.106.0.3Cloud SQL
  3. signal_engine.py:704-705NOTIFY new_signal 发送到本地 PGlive_executor 的 LISTEN 连在 Cloud SQL 上

结论

动作 写入位置
signal_engine 写 signal_indicators 本地 PG127.0.0.1
live_executor 的 LISTEN 监听 Cloud SQL10.106.0.3
live_executor 的轮询查 signal_indicators Cloud SQL10.106.0.3
Cloud SQL 的 signal_indicators 表内容 永远为空(无双写机制)

live_executor 即便轮询也是查 Cloud SQL 的空表NOTIFY 也发到本地 PG 收不到。只要实盘进程跑在不同数据库实例上,永远不会执行任何交易。


[F2] risk_guard 的数据新鲜度检查永远触发熔断

定位risk_guard.py:check_data_freshness()

代码逻辑(从已读内容重建):

# risk_guard 连 Cloud SQLDB_HOST=10.106.0.3
MAX(ts) FROM signal_indicators    NULL表为空
stale_seconds = now - NULL    Python 抛异常或返回极大值
  触发 block_all 熔断

/tmp/risk_guard_state.jsonblock_all=truelive_executor 执行前读此文件Fail-Closed所有交易被直接拒绝

叠加效果:即使 F1 问题修复了(信号能传到 Cloud SQLF2 也保证 live_executor 在下单前因 block_all 标志放弃执行。


[F3] risk_guard 与 live_executor 必须同机运行,但无任何保障

定位risk_guard.py(写 /tmp/risk_guard_state.json)、live_executor.py(读同一路径)

问题两个进程通过本地文件系统文件交换状态。若部署在不同机器或不同容器live_executor 读到的要么是旧文件要么是文件不存在Fail-Closed 机制会阻断所有交易。目前无任何文档说明"两进程必须共机",无任何启动脚本检查,无任何报警。


[F4] signal_pusher.py 仍使用 SQLite与 V5 PG 系统完全脱节

定位signal_pusher.py:1-20

import sqlite3
DB_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "arb.db")
SYMBOLS = ["BTCUSDT", "ETHUSDT"]   # ← XRP/SOL 不在监控范围

完整问题列表

  1. arb.dbSQLiteV5 信号全在 PG 的 signal_indicators 表,此脚本从不读取
  2. 只覆盖 BTC/ETHXRP/SOL 的信号永远不会被推送
  3. Discord Bot Token 硬编码(详见 [S1]
  4. 是一个一次性运行脚本不是守护进程PM2 管理无意义
  5. 查询的 SQLite 表 signal_logs 在 V5 体系下已废弃

结论signal_pusher.py 是遗留代码,从未迁移到 V5 PG 架构。如果 PM2 中运行的是此文件,通知系统完全失效。


🔴 高危问题(全新部署直接崩溃)

[H1] signal_indicators 表缺少 strategyfactors

定位db.py:205-224(建表 SQLvs signal_engine.py:695-701INSERT 语句)

SCHEMA_SQL 中的列 id, ts, symbol, cvd_fast, cvd_mid, cvd_day, cvd_fast_slope, atr_5m, atr_percentile, vwap_30m, price, p95_qty, p99_qty, buy_vol_1m, sell_vol_1m, score, signal

save_indicator() 实际 INSERT 的列 ts, symbol, **strategy**, cvd_fast, cvd_mid, cvd_day, cvd_fast_slope, atr_5m, atr_percentile, vwap_30m, price, p95_qty, p99_qty, score, signal, **factors**

多了 strategy TEXTfactors JSONB 两列。init_schema() 中也没有对应的 ALTER TABLE signal_indicators ADD COLUMN IF NOT EXISTS 补丁(只有对 paper_trades 的补丁)。

后果:全新环境 init_schema()signal_engine 每次写入都报 column "strategy" of relation "signal_indicators" does not exist,主循环崩溃。

补充main.py:/api/signals/latest 的查询也包含 strategyfactors 字段,全新部署 API 也会报错。


[H2] paper_trades 表缺少 risk_distance

定位db.py:286-305(建表 SQLvs signal_engine.py:762-781INSERT 语句)

SCHEMA_SQL 中的列(无 risk_distance id, symbol, direction, score, tier, entry_price, entry_ts, exit_price, exit_ts, tp1_price, tp2_price, sl_price, tp1_hit, status, pnl_r, atr_at_entry, score_factors, created_at

init_schema()ALTER TABLE paper_trades ADD COLUMN IF NOT EXISTS strategy 补了 strategy 列,但没有补 risk_distance

paper_open_trade() 的 INSERT 包含 risk_distancepaper_monitor.py:59signal_engine.py:800 也从 DB 读取 risk_distance

后果:全新部署后,第一次模拟开仓就报 column "risk_distance" does not exist。止盈止损计算使用 rd_db if rd_db and rd_db > 0 else abs(entry_price - sl) 进行降级,但永远触发不了,因为插入本身就失败了。


[H3] users 表双定义,banneddiscord_id 字段在新环境缺失

定位db.py:269-276 vs auth.py:28-37

字段 db.py SCHEMA_SQL auth.py AUTH_SCHEMA
email
password_hash
role
created_at
discord_id
banned

FastAPI startup 先调 init_schema()db.py 版建表),再调 ensure_auth_tables()auth.py 版),CREATE TABLE IF NOT EXISTS 第二次静默跳过。实际建的是旧版本,缺少 discord_idbanned

后果:封禁用户功能在新部署上完全失效(banned 字段不存在)。


[H4] /api/kline 只支持 BTC/ETHXRP/SOL 静默返回错误数据

定位main.py:151-152

rate_col = "btc_rate" if symbol.upper() == "BTC" else "eth_rate"
price_col = "btc_price" if symbol.upper() == "BTC" else "eth_price"

XRP 和 SOL 请求均被路由到 ETH 的数据列。返回的是 ETH 的费率 K 线,但 symbol 标记为 XRP/SOL。前端图表展示完全错误。根本原因rate_snapshots 表只有 btc_rateeth_rate 两列,不支持 4 个币种的独立存储。


[H5] subscriptions.py 是孤立 SQLite 路由,定义了重名的 /api/signals/history

定位subscriptions.py:1-23

import sqlite3
DB_PATH = "arb.db"  # SQLite

@router.get("/api/signals/history")  # ← 与 main.py 同名
def signals_history(): ...

三个问题

  1. 路由路径与 main.py:221@app.get("/api/signals/history") 完全相同
  2. 查询 SQLite arb.dbV5 体系已无此数据
  3. main.py 从未 include_router(subscriptions.router),所以目前是死代码

若将来有人误把 subscriptions.router 加进来,会与现有 PG 版本的同名路由冲突FastAPI 会静默使用先注册的那个,导致难以排查的 bug。


🟠 安全漏洞

[S1] Discord Bot Token 硬编码在源代码(高危)

定位signal_pusher.py:~25

DISCORD_TOKEN = os.getenv("DISCORD_BOT_TOKEN", "MTQ3Mjk4NzY1NjczNTU1OTg0Mg.GgeYh5.NYSbivZKBUc5S2iKXeB-hnC33w3SUUPzDDdviM")

这是一个真实的 Discord Bot Token格式合法base64_encoded_bot_id.timestamp.signature。任何有代码库读权限的人都可以用此 Token 以 bot 身份发消息、读频道历史、修改频道。

立即行动:在 Discord 开发者后台吊销此 Token 并重新生成,从代码中删除默认值。


[S2] 数据库密码硬编码(三处)

定位

  • db.py:19os.getenv("PG_PASS", "arb_engine_2026")
  • live_executor.py:44os.getenv("DB_PASSWORD", "arb_engine_2026")
  • risk_guard.py:42os.getenv("DB_PASSWORD", "arb_engine_2026")

三处使用同一个默认密码。代码一旦泄露,测试网数据库直接暴露。此外 db.py:28 还有 Cloud SQL 的默认密码:os.getenv("CLOUD_PG_PASS", "arb_engine_2026")


[S3] JWT Secret 有已知测试网默认值

定位auth.py(推断行号约 15-20

_jwt_default = "arb-engine-jwt-secret-v2-2026" if _TRADE_ENV == "testnet" else None

TRADE_ENV 环境变量未设置(默认 testnetJWT secret 使用此已知字符串。所有 JWT token 均可被任何知道此 secret 的人伪造,绕过身份验证。


[S4] CORS 配置暴露两个本地端口

定位main.py:16-20

allow_origins=["https://arb.zhouyangclaw.com", "http://localhost:3000", "http://localhost:3001"]

生产环境保留了 localhost:3000localhost:3001。攻击者如果能在本地运行浏览器页面e.g. XSS 注入到其他本地网站),可以绕过 CORS 跨域限制向 API 发请求。生产环境应移除 localhost origins。


🟡 架构缺陷

[A1] 策略 JSON 不支持热重载(与文档声称相反)

定位signal_engine.py:964-966

def main():
    strategy_configs = load_strategy_configs()  # ← 只在启动时调用一次!
    ...
    while True:
        load_paper_config()  # ← 每轮循环,但只加载开关配置
        # strategy_configs 从不刷新

决策日志(06-decision-log.md)声称策略 JSON 支持热修改无需重启,实际上 strategy_configs 变量只在 main() 开头赋值一次,主循环从不重新调用 load_strategy_configs()

修改 v51_baseline.json 或 v52_8signals.json 后必须重启 signal_engine。

注:每 60 轮循环确实会 load_paper_config() 热加载"哪些策略启用"的开关,但权重/阈值/TP/SL 倍数不会热更新。


[A2] 三套数据库连接配置,极易迁移时漏改

进程 读取的环境变量 默认连接
main.py, signal_engine.py, market_data_collector.py, agg_trades_collector.py, liquidation_collector.py, paper_monitor.py PG_HOSTdb.py 127.0.0.1
live_executor.py, risk_guard.py, position_sync.py DB_HOST 10.106.0.3
market_data_collector.py 内部 PG_HOST 127.0.0.1

六个进程用 PG_HOST,三个进程用 DB_HOST,变量名不同,默认值不同,修改时需要同时更新两套 .env


[A3] market_indicators 和 liquidations 表不在主 schema 中

定位market_data_collector.py:ensure_table()liquidation_collector.py:ensure_table()

两张表由各自 collector 进程单独创建,不在 db.py:SCHEMA_SQL 里。启动顺序问题:

  • signal_enginemarket_data_collector 先启动,查 market_indicators 报表不存在,所有市场指标评分降级为中间值
  • signal_engineliquidation_collector 先启动,查 liquidations 报错,清算层评分归零

补充发现liquidation_collector.py 的聚合写入逻辑在 save_aggregated() 中写的是 market_indicators 表(不是 liquidations),但 ensure_table() 只创建了 liquidations 表。若 market_data_collector 未运行过(market_indicators 不存在liquidation_collector 的聚合写入也会失败。


[A4] paper_monitor 和 signal_engine 的止盈止损逻辑完全重复

定位signal_engine.py:788-878paper_check_positions())、paper_monitor.py:44-143check_and_close()

两个函数逻辑几乎一模一样(均检查 TP1/TP2/SL/超时)。当前 signal_engine 主循环中注释说"持仓检查由 paper_monitor.py 实时处理",所以 paper_check_positions()死函数(定义了但从不调用)。

风险:未来如果有人修改止盈止损逻辑,只改了 paper_monitor.py 或只改了 signal_engine.py两份代码就会产生不一致。


[A5] rate_snapshots 表只存 BTC/ETHXRP/SOL 数据永久丢失

定位db.py:167-177(建表)、main.py:42-55save_snapshot

rate_snapshots 表的列硬编码为 btc_rate, eth_rate, btc_price, eth_price, btc_index_price, eth_index_price。XRP/SOL 的资金费率数据只从 Binance 实时拉取,不存储,无法做历史分析或 K 线展示。


[A6] /api/signals/history 返回的是废弃表的数据

定位main.py:221-230

SELECT id, symbol, rate, annualized, sent_at, message FROM signal_logs ORDER BY sent_at DESC LIMIT 100

signal_logs 是 V4 时代用于记录资金费率报警的旧表(db.py:259-267),在 V5 体系下不再写入任何数据。这个端点对前端返回的是永久为空的结果,但没有任何错误信息,调用方无从判断是数据为空还是系统正常运行。


🟢 值得记录的正确设计

以下是审阅过程中发现的值得肯定的设计,供参考:

  1. position_sync.py 设计完整SL 丢失自动重挂、TP1 命中后 SL 移至保本、实际成交价查询、资金费用追踪每8小时结算窗口覆盖了实盘交易的主要边界情况。

  2. risk_guard Fail-Closed 模式正确/tmp/risk_guard_state.json 不存在时live_executor 默认拒绝交易,而不是放行,安全方向正确。

  3. paper_monitor.py 使用 WebSocket 实时价格:比 signal_engine 15 秒轮询更适合触发止盈止损,不会因为 15 秒间隔错过快速穿越的价格。

  4. agg_trades_collector.py 的数据完整性保障:每 60 秒做连续性检查,断点处触发 REST 补录,每小时做完整性报告,设计周全。

  5. GCP Secret Manager 集成live_executor/risk_guard/position_sync 优先从 GCP Secret Manager 加载 API 密钥(projects/gen-lang-client-0835616737/secrets/BINANCE_*),生产环境密钥不在代码/环境变量中,安全设计得当。


📋 修复优先级清单

立即(防止实盘上线后资金损失)

编号 问题 修复方向
S1 Discord Bot Token 泄露 立即在 Discord 开发者后台吊销并重新生成,代码中删除默认值
F1 signal_engine 写本地 PGlive_executor 读 Cloud SQL信号永远不传递 统一所有进程连接同一 PG 实例,或为 signal_indicators 表添加双写逻辑
F2 risk_guard 查 Cloud SQL 空表永远触发熔断 与 F1 一起解决(统一 DB 连接)
F3 risk_guard/live_executor 必须共机无文档说明 在 PM2 配置和部署文档中明确说明;或改为 DB-based 状态通信
F4 signal_pusher 是废弃 SQLite 脚本 从 PM2 配置中移除;按需重写成 PG 版本

本周(防止全新部署报错)

编号 问题 修复方向
H1 signal_indicatorsstrategyfactors SCHEMA_SQL 中补列;在 init_schema() 中加 ALTER TABLE ... ADD COLUMN IF NOT EXISTS
H2 paper_tradesrisk_distance 同上,在 init_schema() 中补 ALTER
H3 users 表双定义,banned/discord_id 缺失 SCHEMA_SQL 删除 users 建表语句,统一由 auth.py 负责;加 ALTER 迁移旧环境
H4 /api/kline XRP/SOL 返回 ETH 数据 要么限制 kline 只支持 BTC/ETH 并在 API 文档中注明;要么扩展 rate_snapshots 表结构
H5 subscriptions.py 孤立 SQLite 代码 删除或移至 archive/ 目录,防止将来误用

本月(安全加固)

编号 问题 修复方向
S2 数据库密码硬编码 移入 .env 文件,不进代码仓库;生产环境用 GCP Secret Manager
S3 JWT Secret 默认值可预测 生产部署强制要求 JWT_SECRET 环境变量,_TRADE_ENV=production 时 None 应直接启动失败
S4 CORS 包含 localhost 生产环境移除 localhost origins

长期(架构改善)

编号 问题 修复方向
A1 策略 JSON 不支持热重载 在主循环中定期(如每 60 轮)重新调用 load_strategy_configs()
A2 三套 DB 连接配置 统一用同一套环境变量(建议统一用 PG_HOST),所有进程都从 db.py 导入连接
A3 market_indicators/liquidations 不在主 schema 将两表定义移入 SCHEMA_SQLinit_schema()
A4 paper_check_positions 死代码 删除 signal_engine.py 中的 paper_check_positions() 函数(功能由 paper_monitor 承担)
A6 /api/signals/history 返回废弃表数据 重定向到查 signal_indicators 表,或废弃此端点

附录:文件审阅覆盖情况

文件 行数 本次审阅
main.py ~500 全文
db.py ~415 全文
signal_engine.py ~1085 全文
live_executor.py ~708 全文
risk_guard.py ~644 全文
auth.py ~389 全文
position_sync.py ~687 全文
paper_monitor.py ~194 全文
agg_trades_collector.py ~400 全文
market_data_collector.py ~300 全文
liquidation_collector.py ~141 全文
signal_pusher.py ~100 全文
subscriptions.py ~24 全文
trade_config.py ~15 全文
backtest.py ~300 前100行 + 签名扫描
admin_cli.py ~100 签名扫描