代码质量审计报告

代码质量审计报告

日期:2026-02-23
审计范围src/services/src/trading/src/utils/src/scripts/src/config.py


一、死代码

1.1 _safe_float() / _safe_int() — services 中重复定义

  • 文件src/services/realtime_kline_service_base.py:509–547
  • 问题_safe_float()_safe_int() 作为静态方法定义在 RealtimeKlineServiceBase,但 src/trading/config.py 中已有同名同功能的独立函数,形成跨模块重复定义
  • 证据
@staticmethod
def _safe_float(value, field_name='unknown', default=0.0) -> float:
    try:
        if value is None or value == '': return default
        return float(value)
    except (ValueError, TypeError): return default

1.2 calculate_cointegration_params_ols() — 明确标注不可用于生产

  • 文件src/utils/analysis/analysis_core.py:185–278
  • 问题:函数 docstring 明确写"存在 look-ahead bias,仅用于验证性分析,不适用于实时交易";生产路径全部使用 calculate_cointegration_params_dual_window(),此函数仅作为 analyze_pair_advanced() 的备选入口存在
  • 证据
def calculate_cointegration_params_ols(...):
    """
    注意:此方法存在 look-ahead bias,仅用于验证性分析,
    不适用于实时交易。实时交易请使用 dual_window 版本。
    """

1.3 KlineDataFillerLazy._init_info() — 标志位使父类初始化永远不执行

  • 文件src/utils/analysis/kline_data_filler_lazy.py:28–37
  • 问题__init__ 中设置 self._skip_parent_init = True,然后重写 _init_info() 检查该标志并返回 None,父类版本的初始化逻辑被完全绕过,属于通过隐性标志实现的死代码
  • 证据
self._skip_parent_init = True   # 行28

def _init_info(self) -> Optional[Info]:
    if getattr(self, '_skip_parent_init', False):
        return None             # 父类逻辑永远不执行
    return Info(constants.MAINNET_API_URL, ...)

二、无调用路径的函数

2.1 eth_btc.py — 整文件无外部调用方

  • 文件src/services/eth_btc.py(全文 82 行)
  • 问题RealtimeKlineServiceEthBtc 类只有自身 __main__ 块引用,主服务路径无任何 import,是一个写死 BTC/ETH 配置的一次性测试版本
  • 证据grep -r "RealtimeKlineServiceEthBtc" src --include="*.py" 仅返回自身文件

2.2 scripts/ — 33 个孤立 CLI 脚本

  • 文件src/scripts/ 下 38 个文件,其中 33 个对主程序无调用路径
  • 有内部依赖的(保留):backtest_base.py(被 5 个 backtest 脚本 import);optimize_adaptive_zscore.py(被 compare_exit_methods.py 调用)
  • 完全孤立的代表backfill_all_data.pyvalidate_*.py(4 个)、clear_analysis_results.py 等 33 个脚本,可归档而不影响主流程

三、重复逻辑块

3.1 去重 lambda 4 次复制

  • 文件src/services/realtime_kline_service_base.py
  • 位置:行 752、792(K 线去重),行 837、884(分析结果去重)
  • 问题:相同的 key lambda 和 _deduplicate_batch() 调用在"正常写入"和"停止前写入"两条路径下各复制一次,任何字段名变更需同时修改 4 处
  • 已修复:提取为 _kline_dedup_key()_analysis_dedup_key() 静态方法
  • 证据
# 行752 和 行792(完全相同)
kline_key = lambda x: (x['time'], x['symbol'], x['timeframe'])
dedup_batch, batch_count = self._deduplicate_batch(batch, kline_key, kline_key)

# 行837 和 行884(完全相同)
analysis_key = lambda x: (
    x['symbol'], x['base_symbol'],
    x.get('kline_time') or x['analysis_time'].replace(microsecond=0),
)

3.2 PositionManager 平仓价格获取逻辑复制 3 次

  • 文件src/trading/position_manager.py
  • 位置:行 456–467、591–597、668–683
  • 问题:获取 alt 退出价格的回退策略(alt_current_price → get_all_mids() → 0.0)在三条不同的平仓路径下各自独立实现,任何价格源变更需同步 3 处
  • 已修复:提取为 _get_alt_mid_price() 辅助方法

3.3 同名函数两个文件各自独立维护

  • 文件
    • src/scripts/query_analyze_result/query_purr_zscore.py:31
    • src/scripts/query_analyze_result/query_purr_zscore_beyond2_5.py:31
  • 问题:两个文件定义了同名函数 query_eth_zscore_history(),逻辑完全相同,仅 SQL WHERE 条件不同(后者多了 ABS(zscore_4h) > 2.5

3.4 K 线获取 + zscore 回测逻辑跨 3 个脚本重复

  • 文件
    • src/scripts/backtest_eth_btc_zscore_4h.py:47–140
    • src/scripts/backtest_eth_btc_zscore_4h_binance.py:47–130
    • src/scripts/backtest_purr_hype_zscore_4h_hyperliquid.py:90–140
  • 问题:K 线获取框架(DataFrame 构造、时间列处理)和 Z-score 计算+方向判断逻辑在 3 个脚本中复制,修改阈值需同步 3 处

四、无状态隔离的共享变量(P0 高危)

4.1 new_coin_blacklist 检查与写入之间有竞态窗口

  • 文件src/services/realtime_kline_service_base.py
  • 位置:行 1170–1176(_analysis_worker 中)、行 1411–1429(_all_pairs_blacklisted() 中)
  • 问题
    • 行 1411-1429:在 with blacklist_lock 内先 check 再 add,看似安全,但 _all_pairs_blacklisted() 在 1411 获取锁外的 known_bases,然后在另一个锁块内和黑名单对比,两次锁之间 known_bases 可能已过期
    • 两个不同的锁(_pair_cache_lockblacklist_lock)保护相互依赖的状态,无法保证跨锁的原子一致性
  • 已修复known_bases = list(...) 复制快照,防止锁释放后内部列表被原地修改
  • 证据
# 行1411-1429:两个锁分开获取,状态不一致窗口
with self._pair_cache_lock:
    known_bases = self._pair_cache.get(symbol, [])  # 锁1释放
# ← 此处另一线程可能修改 _pair_cache
all_bases = set(known_bases)
with self.blacklist_lock:  # 锁2
    return all(...)  # 基于已可能过期的 all_bases 判断

4.2 PositionManager._close_cooldown — 检查与使用之间 TOCTOU

  • 文件src/trading/position_manager.py:296–314
  • 问题:冷却时间"检查 → 使用"之间没有原子保证,另一线程可能在检查后修改或删除该键。time.time() 被多次调用导致 remaining 计算不一致
  • 已修复:捕获单次 now = time.time() 快照,统一用于所有条件判断和剩余时间计算
  • 证据
cooldown_until = self._close_cooldown.get(key)   # 读
if cooldown_until is not None:
    if time.time() < cooldown_until:              # 再次读 time(非原子)
        if force_market:
            self._close_cooldown.pop(key, None)   # 写(可能已被其他线程删除)
        else:
            remaining = cooldown_until - time.time()  # 三次读,时间已变

4.3 HyperliquidExecutor._cached_positions — 5s TTL 缓存无强制失效

  • 文件src/trading/executor.py:92–100
  • 问题:缓存由 WebSocket 事件驱动更新,但 TTL=5s 不保证最新;高频决策场景下可能基于 5s 前的陈旧仓位数据判断

4.4 RepairExecutor.kline_filler — 共享实例内部状态无线程同步

  • 文件src/utils/data_healing/repair_executor.py:47–50
  • 问题KlineDataFiller 实例在多个修复任务中共享,其内部 fill_cooldown: Dict[Tuple[str, str], float] 字典无锁保护,并发修复时存在竞态

4.5 _signal_history 字典整体替换非原子(策略层)

  • 文件src/trading/strategy.py:541–550
  • 位置_process_tick_unlocked()
  • 问题:字典整体替换(dict comprehension 赋值)不是原子操作,虽在 self._lock 内,但 GIL 不能保证复合操作原子性;如果清理期间另一线程获取了 GIL 切片,可能读到半替换的 _signal_history
  • 证据
self._signal_history = {       # 整体替换开始
    k: v for k, v in          # 迭代旧字典
    self._signal_history.items()
    if v > now                 # 过滤
}                              # 整体替换结束(CPython 下接近原子,但严格不保证)

五、可被删除而不影响主流程的模块

文件 / 目录 原因 风险
src/services/eth_btc.py 无外部调用方,写死 BTC/ETH 配置,仅含 if __name__ == '__main__' 零风险
src/scripts/backfill_*.py(2个) 一次性数据回填脚本,主程序无调用 零风险
src/scripts/validate_*.py(4个) 验证脚本,无调用方 零风险
src/scripts/clear_analysis_results.py 等数据管理脚本(5个) 手动运维脚本,无主程序调用 零风险
src/scripts/query_analyze_result/(10个) 纯查询 CLI,无被调用 零风险
src/scripts/fix_buffer_loading.py 名称含 fix_,一次性修复脚本 低风险(确认已修复后删除)

保留backtest_base.py(被 5 个脚本 import)、optimize_adaptive_zscore.py(被 compare 脚本调用)

5.2 calculate_cointegration_params_ols() — 仅回测用,可移出核心模块

  • 文件src/utils/analysis/analysis_core.py:185–278(94 行)
  • 问题:明确标注不适用于实时交易,挤占核心分析模块空间,应移至脚本层

5.3 KlineDataFillerLazy — 仅 repair_executor.py 使用且用的是基础版

  • 文件src/utils/analysis/kline_data_filler_lazy.py
  • 问题repair_executor.py import 的是 KlineDataFiller(基础版),而非 Lazy 版;KlineDataFillerLazy 无实际调用方

六、违反单一职责的类

6.1 RealtimeKlineServiceBase — 7 个职责,46 个方法

  • 文件src/services/realtime_kline_service_base.py(全文)
  • 职责混合
职责 方法示例 行范围
WebSocket 连接管理 on_message(), on_state_change() 327–722
队列/缓冲管理 _batch_writer(), _analysis_result_batch_writer() 728–911
数据库 I/O _retry_on_deadlock(), _batch_upsert_with_retry() 553–597
K线分析与相关性计算 _fetch_and_validate_price_data(), _run_correlation_and_analysis() 1045–1291
黑名单与配对管理 _is_blacklisted(), _load_pair_cache() 1295–1442
交易信号触发 _trigger_strategy_if_ready() 1505–1570
监控与告警线程 _monitor_queue_health(), _monitor_new_symbols() 1898–2073
数据自愈 _run_data_healing() 1766–1870

6.2 PositionManager — 5 个职责,1603 行

  • 文件src/trading/position_manager.py
  • 职责混合:仓位生命周期(open/close)、孤儿检测 _detect_and_adopt_orphans()、数据库同步 _load_position_from_db_row()、交易所对账 sync_with_exchange()、价格实时更新 update_position_prices()

6.3 HyperliquidExecutor — 5 个职责,2006 行

  • 文件src/trading/executor.py
  • 职责混合:钱包初始化与私钥管理、币种元数据维护、杠杆设置、订单执行(市价/限价)、WebSocket 事件订阅与仓位缓存

6.4 TradingOrchestrator — 4 个职责,1054 行

  • 文件src/trading/orchestrator.py
  • 职责混合:组件生命周期管理、交易信号处理(入场/退场)、止损监控线程、仓位同步线程、告警发送

七、仅用于兼容旧逻辑的结构

7.1 WebSocket 消息双字段路由(Bug F 兼容)

  • 文件src/services/realtime_kline_service_base.py:668
  • 问题:同时解析 "channel""type" 两种字段,注释明确标注为"Bug F 修复"的临时兼容
  • 证据
# 防御性 channel 路由:兼容 "channel" 和 "type" 两种字段(Bug F)
channel = msg.get("channel") or msg.get("type", "")

7.2 expected_side=None 向后兼容参数

  • 文件src/trading/executor.py
  • 位置_get_actual_position_size() 方法
  • 问题expected_side: str | None = None,docstring 明确写"None 不过滤(向后兼容)",但新调用方均传入明确值,None 路径已成旧逻辑残留

7.3 entry_adaptive_z 孤儿回填补丁

  • 文件src/trading/strategy.py:589–596
  • 问题entry_adaptive_z 为 0 时强制用当前值回填,是对早期设计缺陷(仓位初始化时未捕获该值)的事后补救,以 logger.warning("🔧 回填孤儿...") 标记

7.4 旧版 WebSocket 架构 fail-fast 检查

  • 文件src/utils/websocket/enhanced_ws_manager.py:267–274
  • 问题:明确注释"历史遗留值 'websocket'(旧版单 WS 架构)已完全禁止",但仍保留 fail-fast 检查代码,属于防御性兼容

八、量化汇总

类别 问题数 可删除行数估计 严重度
死代码 3 处 ~160 行
无调用路径函数/模块 36 个 ~2,200 行(含 scripts)
重复逻辑块 5 处 ~200 行
无状态隔离共享变量 5 处
可删除模块 37 个文件 ~4,200 行
SRP 违反 4 个类
兼容旧逻辑结构 4 处 ~20 行

九、已实施的修复

编号 问题 修复方式 文件
§4.1 _all_pairs_blacklisted() 跨锁一致性 list() 复制快照防止原地修改 realtime_kline_service_base.py
§4.2 _close_cooldown TOCTOU 单次捕获 now = time.time() position_manager.py
§3.1 去重 lambda 4 次复制 提取为 _kline_dedup_key/_analysis_dedup_key 静态方法 realtime_kline_service_base.py
§3.2 平仓价格获取逻辑 3 次复制 提取为 _get_alt_mid_price() 辅助方法 position_manager.py
§2.1 eth_btc.py 无调用方 删除文件 services/eth_btc.py

十、建议后续行动(未实施)

优先级 项目 工作量
P1 §4.3 _cached_positions 5s TTL 缓存强制失效机制
P1 §4.4 RepairExecutor.kline_filler 加锁保护
P2 §1.1 统一 _safe_float/_safe_int 到共享工具模块
P2 §3.3/3.4 脚本层重复逻辑提取到 backtest_base.py
P3 §6.x SRP 重构(RealtimeKlineServiceBase 拆分)
P3 §5.2 calculate_cointegration_params_ols() 移至脚本层

Read more

数据自愈模块提速3

数据自愈并发优化设计方案 一、现状分析 当前流程 _run_data_healing(realtime_kline_service_base.py:1807): for (symbol, base_symbol) in heal_pairs: # ~50+ 配对,串行 DataHealingOrchestrator(...) # → RepairExecutor.__init__ │ # → KlineDataFiller.__init__ │ # → Info(MAINNET_API_URL) HTTP 握手 ~0.85s │ # ← 第 2-50 个握手完成后才被 shared_executor 覆盖,全部浪费! _load_zscore_history()

By SHI XIAOLONG

数据自愈模块提速2

数据自愈并发优化设计方案 一、现状分析 当前流程 _run_data_healing(realtime_kline_service_base.py:1807): for (symbol, base_symbol) in heal_pairs: # ~50+ 配对,串行 DataHealingOrchestrator(...) # → RepairExecutor.__init__ │ # → KlineDataFiller.__init__ │ # → Info(MAINNET_API_URL) HTTP 握手 ~0.85s │ # ← 第 2-50 个握手完成后才被 shared_executor 覆盖,全部浪费! _load_zscore_history()

By SHI XIAOLONG

数据自愈模块综合问题分析报告2

数据自愈模块优化设计文档 版本:v1.0 日期:2026-02-23 范围:src/utils/data_healing/ + src/services/realtime_kline_service_base.py(_run_data_healing 部分) 目录 1. 问题全景与优先级总表 2. BUG-01:加载语义与修复语义不一致 3. BUG-02/03:单条/少量记录无法生成修复目标 4. BUG-04:日志误导性 5. BUG-05:重复写入(BUG-01 副作用) 6. BUG-06:test_basic.py 与 Diagnosis 定义失同步 7.

By SHI XIAOLONG