代码质量审计报告

代码质量审计报告

日期: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

跑步的技巧(滚动落地)

“滚动落地(rolling contact / rolling foot strike)”不是一种教条式的“脚法”,而是一种 让冲击沿着整只脚、整条后链逐级传递的落地机制。 它的核心不是“你先用哪儿着地”,而是: 你的脚落地之后,冲击是不是像轮子一样滚过去,而不是像锤子一样砸下去。 这就是滚动落地的本质。 一、什么叫“滚动落地”? 你可以把它理解成两种完全不同的落地方式: 1. 砸地(撞击式) 脚像锤子一样拍到地上: * 要么后跟先砸 * 要么前掌先戳 * 冲击集中在一个点 * 一个结构瞬间吃掉大部分载荷 结果就是: * 后跟砸 → 膝盖难受 * 前掌戳 → 前脚掌磨烂 * 都不是长跑友好模式 这叫 撞击式着地(impact strike)。 2. 滚地(滚动式) 脚像轮胎一样“滚”过地面: * 不是某一点硬砸 * 而是外侧中足先轻触 * 再向前滚到前掌 * 最后从大脚趾蹬离

By SHI XIAOLONG

AMI的优越性

世界模型(World Models)的具体例子 如下,我按类型分类,便于理解。每类都附带实际实现、演示效果和应用场景。 1. Yann LeCun / Meta 的 JEPA 系列(最直接对应“世界模型”概念) 这些是 LeCun 主张的非生成式抽象预测世界模型代表。 * I-JEPA(Image JEPA,2023) 输入一张图像,模型把不同区域(context 和 target)编码成抽象表示,然后预测 target 的表示(不在像素级别重建)。 例子:给定一张遮挡了部分物体的图片,模型能预测“被遮挡物体的大致位置和属性”,构建对物体持久性和空间关系的理解。 这是一个“原始世界模型”,能学习物理常识(如物体不会凭空消失)。 * V-JEPA / V-JEPA 2(Video JEPA,

By SHI XIAOLONG

什么是:“世界模型(World Models)”

世界模型(World Models) 是人工智能领域的一个核心概念,尤其在 Yann LeCun 等研究者推动的下一代 AI 架构中占据中心位置。它指的是 AI 系统在内部构建的对现实世界的抽象模拟或内部表示,让机器能够像人类或动物一样“理解”物理世界、预测未来、规划行动。 简单比喻 想象你闭上眼睛也能“看到”房间里的物体会如何移动、碰撞或掉落——这就是你大脑里的世界模型。AI 的世界模型就是类似的“数字孪生”(digital twin)或“内部模拟器”:它不是简单记住数据,而是学习世界的动态、因果关系和物理直觉(如重力、物体持久性、遮挡、因果等)。 为什么需要世界模型? 当前主流的大型语言模型(LLM) 擅长处理文本(统计模式预测),但存在根本局限: * 缺乏对物理世界的真正理解 → 容易“幻觉”、无法可靠规划。 * 样本效率低 → 人类/

By SHI XIAOLONG

K线周期可配置化设计方案

K线周期可配置化设计方案 1. 背景与目标 当前 Beta 套利策略的 K 线周期硬编码为 "1h",分散在多个文件中。需要: 1. 将 K 线周期从 1h 改为 2h 2. 提取为环境变量 BETA_ARB_KLINE_INTERVAL,使其可在 .env 中配置 2. 影响范围分析 2.1 需要修改的文件(共 6 个) 文件 硬编码位置 修改内容 src/trading/config.py BetaArbConfig dataclass 新增 kline_interval 字段,

By SHI XIAOLONG