代码质量审计报告
日期: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.py、validate_*.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_lock 和 blacklist_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() 移至脚本层 |
小 |