Skip to content

Dev#14

Merged
cloudQuant merged 278 commits into
developmentfrom
dev
Jun 1, 2026
Merged

Dev#14
cloudQuant merged 278 commits into
developmentfrom
dev

Conversation

@cloudQuant
Copy link
Copy Markdown
Owner

Backtrader 去元编程项目 - Pull Request

📋 PR 概述

基本信息

  • PR类型: [ ] 元编程移除 [ ] 性能优化 [ ] Bug修复 [ ] 文档更新 [ ] 测试增强
  • 影响范围: [ ] 核心系统 [ ] 单个模块 [ ] 测试代码 [ ] 文档 [ ] 工具
  • 破坏性变更: [ ] 是 [ ] 否
  • 关联Issue: #[Issue编号]

变更摘要

修改范围

  • backtrader/[模块名].py: [变更描述]
  • tests/[测试文件].py: [变更描述]

🎯 元编程移除验证 ⭐⭐⭐⭐⭐

移除的元编程模式

  • 移除的元类:

    • MetaStrategy - [替代方案描述]
    • MetaIndicator - [替代方案描述]
    • MetaLineSeries - [替代方案描述]
    • MetaParams - [替代方案描述]
    • MetaSingleton - [替代方案描述]
    • 其他: [具体元类名] - [替代方案描述]
  • 移除的动态类创建:

    • type() 动态创建 - [替代方案描述]
    • setattr 动态属性 - [替代方案描述]
    • 其他: [具体模式] - [替代方案描述]

替代方案实现

  • 使用的替代技术:

    • 描述符 (Descriptors)
    • 混入类 (Mixins)
    • 组合模式 (Composition)
    • __init_subclass__ 方法
    • 依赖注入
    • 其他: [具体技术]
  • 实现完整性验证:

    • 所有原有功能已被替代实现
    • 边界条件处理完善
    • 错误处理机制保持一致

工具验证

# 运行元编程检测工具
python tools/metaclass_detector.py

# 检查结果:
# - 元类使用点数量: [之前] → [现在]
# - 动态创建点数量: [之前] → [现在]
# - 验证结果: [ ] 通过 [ ] 未通过

🔄 API兼容性验证 ⭐⭐⭐⭐

公共接口检查

  • 方法签名保持不变:

    • 所有公共方法签名未变化
    • 参数名称和默认值保持一致
    • 返回值类型保持兼容
  • 属性访问保持一致:

    • 关键属性 (如 lines, params) 访问方式不变
    • 属性值类型和格式保持一致
    • 动态属性访问行为保持相同

行为兼容性

  • 核心功能验证:

    • 策略执行行为一致
    • 指标计算结果一致
    • 数据访问行为一致
    • 参数处理行为一致
  • 异常处理验证:

    • 异常类型保持一致
    • 错误信息保持有用性
    • 边界条件处理一致

兼容性测试结果

# 运行兼容性测试框架
python tools/compatibility_tester.py

# 测试结果:
# - API兼容性: [通过率]%
# - 行为兼容性: [通过率]%
# - 性能对比: [变化百分比]%
# - 总体评估: [ ] PASS [ ] FAIL

⚡ 性能影响评估 ⭐⭐⭐

性能基准测试

  • 执行性能对比:

    • 策略执行时间: [变化] ([百分比]%)
    • 指标计算时间: [变化] ([百分比]%)
    • 数据访问时间: [变化] ([百分比]%)
    • 启动时间: [变化] ([百分比]%)
  • 内存使用分析:

    • 内存占用: [变化] ([百分比]%)
    • 无内存泄漏验证
    • 垃圾回收影响评估

性能测试命令

# 运行性能基准测试
python tools/performance_benchmark.py --before [git-ref] --after HEAD

# 性能目标:
# - 执行时间变化: ≤ 10%
# - 内存使用变化: ≤ 20%
# - 启动时间变化: ≤ 30%

关键路径优化

  • 热点代码路径检查:
    • 数据访问路径优化
    • 指标计算路径优化
    • 策略执行路径优化

🧪 测试覆盖验证 ⭐⭐⭐

测试覆盖率

# 运行测试覆盖率检查
python -m pytest tests/ --cov=backtrader --cov-report=term-missing

# 覆盖率要求:
# - 新增代码覆盖率: ≥ 85%
# - 修改代码覆盖率: ≥ 90%
# - 关键路径覆盖率: ≥ 95%
  • 测试类型覆盖:
    • 单元测试: [数量] 个
    • 集成测试: [数量] 个
    • 回归测试: [数量] 个
    • 性能测试: [数量] 个

新增测试用例

  • 功能测试:

    • 测试用例1: [描述]
    • 测试用例2: [描述]
  • 边界条件测试:

    • 测试用例1: [描述]
    • 测试用例2: [描述]
  • 异常情况测试:

    • 测试用例1: [描述]
    • 测试用例2: [描述]

📊 代码质量检查 ⭐⭐⭐

静态分析结果

# 代码格式检查
black --check backtrader/

# 静态分析
pylint backtrader/ --score=yes

# 类型检查
mypy backtrader/
  • 代码规范检查:
    • Black 格式化: [ ] 通过 [ ] 未通过
    • Pylint 评分: [分数]/10 (要求 ≥ 8.0)
    • MyPy 类型检查: [ ] 通过 [ ] 有警告

代码质量评估

  • 可读性和维护性:

    • 函数复杂度适中 (≤ 10)
    • 类设计合理 (≤ 500行)
    • 命名清晰一致
    • 注释和文档充分
  • 架构设计:

    • 单一职责原则
    • 开闭原则
    • 依赖倒置原则
    • 接口隔离原则

📚 文档更新 ⭐⭐

文档变更

  • API文档:

    • 新增API文档
    • 修改API文档
    • 废弃API说明
  • 用户文档:

    • 使用指南更新
    • 迁移指南更新
    • 示例代码更新
  • 开发文档:

    • 架构文档更新
    • 贡献指南更新
    • 故障排除指南更新

文档质量

  • 内容质量:
    • 信息准确完整
    • 示例代码可执行
    • 链接和引用有效

🔍 回归测试验证

完整测试套件

# 运行完整的回归测试
python -m pytest tests/ -v --timeout=300

# 测试结果统计:
# - 总测试数: [数量]
# - 通过数: [数量]
# - 失败数: [数量]
# - 跳过数: [数量]
# - 通过率: [百分比]%
  • 核心功能测试:

    • 策略系统测试: [ ] 通过
    • 指标系统测试: [ ] 通过
    • 数据系统测试: [ ] 通过
    • 经纪商系统测试: [ ] 通过
  • 集成测试:

    • 端到端测试: [ ] 通过
    • 多组件协作测试: [ ] 通过
    • 用户场景测试: [ ] 通过

已知问题

  • 当前已知问题:
    • 问题1: [描述] - [状态]
    • 问题2: [描述] - [状态]

📝 变更详情

主要变更

  1. [变更类别]: [详细描述]

    • 文件: [文件路径]
    • 变更: [具体变更内容]
    • 原因: [变更原因]
  2. [变更类别]: [详细描述]

    • 文件: [文件路径]
    • 变更: [具体变更内容]
    • 原因: [变更原因]

技术实现细节

设计决策

🚨 风险评估

潜在风险

  • 高风险:

    • 风险1: [描述] - [缓解措施]
    • 风险2: [描述] - [缓解措施]
  • 中等风险:

    • 风险1: [描述] - [缓解措施]
    • 风险2: [描述] - [缓解措施]

回滚计划

  • 回滚方案:
    • 回滚步骤已准备
    • 回滚测试已验证
    • 数据备份已完成

✅ 审查检查清单

自检完成项

  • 元编程完全移除
  • 兼容性测试通过
  • 性能测试通过
  • 代码质量检查通过
  • 测试覆盖率达标
  • 文档已更新
  • 工具验证通过

待审查项

  • 代码架构设计
  • 实现方案合理性
  • 边界条件处理
  • 错误处理机制
  • 性能优化机会
  • 安全性考虑

📞 联系信息

PR提交者: [姓名]
联系方式: [邮箱/Slack]
提交时间: [日期时间]
预计审查时间: [时间估算]


🔧 审查者使用指南

审查步骤

  1. 克隆分支: git checkout [branch-name]
  2. 运行自动化检查: 按照上述命令执行各项检查
  3. 手动代码审查: 重点关注元编程移除和兼容性
  4. 测试验证: 运行完整测试套件
  5. 文档审查: 检查文档更新的准确性

审查标准

  • 必须通过: 元编程移除、API兼容性、性能要求
  • 建议改进: 代码质量、文档完整性、测试覆盖
  • 可选优化: 性能优化、架构改进

审查反馈模板

请使用代码审查规范中的反馈模板进行审查反馈。


模板版本: 1.0
最后更新: 2025年05月30日

- All Phase headers (0-4) marked ✅
- All milestones M1-M6 marked completed
- All 16 task tickets (T01-T10, R1-R6) marked completed with evidence
- Appendix A audit findings updated with fix status per item
- Test coverage section updated: 730 → 753 tests (+23 new)
- Fixed outdated LiveStoreBase.start() description
- Added completion date header
- cerebro.py/strategy.py: 异常可见性改进(try/finally, 静默except改为日志)
- 8个核心模块: 清理23处commented-out print/debug残留
- feed.py: 修正错误FIXME注释(sessionstart/sessionend实际被使用)
- 代码质量改进优化.md: 新增Step5提交流程,明确已有测试不可修改

pytest tests -n 8: 760 passed, 1 skipped, 0 failed
- order.py: 删除7行commented-out旧代码块, 修正todo注释为正式文档注释
- brokers/bbroker.py: 清理11处(commented-out函数/变量/print/stale TODO)
- functions.py: 删除1处stale todo注释
- indicators/sma.py: 删除2处DEBUG commented-out print块
- indicators/basicops.py: 清理1处commented-out print + 无用注释

pytest tests -n 8: 760 passed, 1 skipped, 0 failed
analyzers: annualreturn.py(2处dead code+todo), sharpe.py(3处TODO+Hack),
  vwr.py(1处print), logreturnsrolling.py(3处print)
feeds: pandafeed.py(1处print), yahoo.py(2处todo), rollover.py(1处todo)
plot: plot.py(26处print/Hack注释), locator.py(1处print)
core: analyzer.py(1处Hack→正式注释), dataseries.py(2处todo),
  position.py(1处todo)

剩余10处均为三引号注释块内/合法FIXME/正常代码注释,不属于清理范围

pytest tests -n 8: 760 passed, 1 skipped, 0 failed
…eption as e with debug logging

Systematically replaced all silent exception swallowing patterns across 20 files:
- Core: feed.py(3), parameters.py(4), functions.py(5), lineroot.py(2), linebuffer.py(13)
- Core: lineiterator.py(14), metabase.py(5), lineseries.py(4)
- Indicators: sma.py(1), ema.py(1)
- Observers: trade_logger.py(4)
- Reports: performance.py(7)
- Plot: plot.py(2), plot_plotly.py(1)
- Live: btapistore.py(3), btapifeed.py(2), btapibroker.py(1)
- Bokeh: app.py(4), tabs/performance.py(2), tabs/metadata.py(2), tabs/config.py(1), analyzers/recorder.py(1), utils/helpers.py(1)

Each file now has import logging and logger = logging.getLogger(__name__).
All blocks use logger.debug() with descriptive messages including the exception.
No behavior changes - all catch blocks retain their original control flow.
Test baseline maintained: 760 passed, 1 skipped.
P0 fixes:
- performance.py: truthy checks (if value) → is not None, fixes zero-value (0.0) silent skips
- performance.py: _get_analyzer_result exact match first, substring fallback second
- performance.py: _get_backtest_days bare except → except Exception as e with logging
- charts.py: plot_return_bars/get_periodicity bare except → debug logging

P1 fixes:
- performance.py: get_risk_metrics accepts pre-computed pnl_metrics to avoid double computation
- charts.py: close_all guarded with MATPLOTLIB_AVAILABLE check
- charts.py: removed dead hasattr(start_date, 'days') branch in _get_periodicity
- charts.py: added module-level logger

760 passed, 1 skipped
P0 fixes:
- reporter.py: HTML模板truthy检查 → is not none,修复0.0值显示为N/A
- reporter.py: print_summary truthy检查修复,提取_fmt_metric helper消除重复

P1 fixes:
- reporter.py: 移除_user/_memo实例状态,user/memo作为参数传递给_build_context
- reporter.py: Jinja2 Environment添加autoescape=True防止XSS
- reporter.py: base64图片数据标记|safe避免autoescape破坏

可测试性补强:
- 新增32个edge case单元测试 (tests/unit/reports/test_performance_edge_cases.py)

792 passed, 1 skipped
P0 fixes:
- _infer_tick_direction: truthy检查 → is not None,修复ask_price/bid_price=0.0被跳过

P1 fixes:
- _order_to_payload: price or fallback → 显式None检查,修复price=0.0被忽略
- _coerce_text/_safe_text_attr/_ctp_field_to_dict: bare except添加logger.debug

可测试性补强:
- 新增33个edge case单元测试 (tests/unit/stores/test_btapistore_edge_cases.py)
  - _infer_tick_direction零值价格测试
  - _coerce_text边界值测试 (None, bytes, GBK, numeric)
  - _split_ctp_symbol格式测试 (dot, underscore, plain, empty, None)
  - _normalize_ctp_instrument CZCE规范化测试
  - _ctp_field_to_dict过滤测试 (private, callable, SWIG attrs)

825 passed, 1 skipped
P0 fixes:
- _validate_order/_reject_order/_order_runtime_details: price truthy-or → 显式is not None
  修复price=0.0被静默替换为created.price的问题

P1 fixes:
- _refresh_account: bare except → 添加logger.debug错误可见性
- _sync_positions: bare except → 添加logger.debug错误可见性

可测试性补强:
- 新增20个edge case单元测试 (tests/unit/brokers/test_btapibroker_edge_cases.py)
  - 零值价格处理 (price=0.0不被替换)
  - _refresh_account/_sync_positions错误日志验证
  - _execution_datetime格式解析 (datetime/time/compact/None/empty/invalid)
  - _position_key数据对象提取 (_name/_dataname/p.dataname/repr)
  - _should_refresh节流逻辑 (zero/negative/recent/old)

845 passed, 1 skipped
P0 fixes:
- get_equity_curve: start_cash truthy-or → 显式is not None检查
  修复start_cash=0.0被静默替换为100000的问题 (2处)

P1 fixes:
- get_kpi_metrics: sqn_score=None时sqn_human应为'N/A'而非None
  移除冗余guard,sqn_to_rating已正确处理None

可测试性补强:
- 新增40个edge case单元测试 (test_performance_calculator_edge_cases.py)
  - 零值start_cash处理 (start_cash=0.0不被替换为100000)
  - risk_metrics零回撤/缺失analyzer
  - trade_metrics零交易/全胜场景
  - kpi_metrics缺失/NaN/None SQN
  - sqn_to_rating 16个边界值+inf/-inf
  - analyzer精确匹配优先于子串匹配
  - strategy_info/data_info无数据场景
  - profit_factor零亏损/正常计算

884 passed, 1 skipped (1 pre-existing flaky in parallel)
P1 fixes (8处bare except添加logger.debug):
- _collect_indicators内层循环: 属性访问失败现可见
- _extract_indicator_values内层循环: 指标线读取失败现可见
- _save_position_snapshot: 快照文件写入失败现可见
- _insert_order_mysql: MySQL订单插入失败现可见
- _insert_trade_mysql: MySQL成交插入失败现可见
- _insert_position_mysql: MySQL持仓插入失败现可见
- _insert_indicator_mysql: MySQL指标插入失败现可见
- _insert_signal_mysql: MySQL信号插入失败现可见

可测试性补强:
- 新增14个edge case单元测试 (test_trade_logger_edge_cases.py)
  - _collect_indicators属性访问异常日志验证
  - _extract_indicator_values指标线读取异常日志验证
  - 防御性访问器 (_store_provider/_session_id/_get_datetime_str/_get_strategy_name)
  - _safe_order_inf
P1 fixes (8处bare except添加logger.debug):
- _collect_indicators内层循环: 属性访问失败现可见
- _extrassed, 1 skipped
P1 fixes (pandafeed.py, 4处bare except添加logger.debug):
- PandasData.start(): to_numpy()失败现可见
- PandasData.start(): datetime转换三级降级链现可见
  (ts.to_pydatetime → ts.dt.to_pydatetime → element-wise)
- PandasData.start(): datetime预计算整体失败现可见

charts.py审查通过: 所有exception handler已有logger.debug,无P0/P1问题

可测试性补强:
- 新增10个edge case单元测试 (test_pandafeed_edge_cases.py)
  - PandasData基本加载 (简单/单行/datetime列)
  - 列名自动检测 (nocase=True/False, 缺失列)
  - numpy转换失败日志验证
  - PandasDirectData itertuples加载
  - 零值OHLCV正确加载 (close=0.0, volume=0)

909 passed, 1 skipped
清理内容:
- indicators/mabase.py: 删除263行注释掉的手动MovingAverage注册系统(已被__init_subclass__自动注册取代)
- plot/plot.py: 删除380行注释掉的legacy run_cerebro_plot函数 + 1个debug print语句
- brokers/bbroker.py: 删除26行注释掉的__getattribute__重写(已被get_cash/set_cash取代)
- comminfo.py: 删除9行注释掉的__getattribute__重写(已被_StockLikeDescriptor取代)

验证: pytest tests -n 8 → 909 passed, 1 skipped
- commissions/dc_commission.py: ComminfoDC的旧版重复实现,含bug(self.self._creditrate)
- 该文件未被任何模块引用,comminfo.py中已有正确的ComminfoDC实现
- 同时确认utils/flushfile.py被tests/testcommon.py引用(保留)
- P2: comminfo.py子类中get_margin重复逻辑已记录但不改(避免破坏外部继承)

验证: pytest tests -n 8 → 909 passed, 1 skipped
- order.py L563: 'not self.price' → 'self.price is None' 避免price=0.0被当作未设置
- bbroker.py L1407: 'order.pannotated' → 'order.pannotated is not None' 避免收盘价0.0被忽略
- 新增8个边界测试覆盖零价格保留、None回退、pannotated语义

验证: pytest tests -n 8 → 917 passed, 1 skipped
- comminfo.py getsize(): price=0时直接返回0,避免cash//0 ZeroDivisionError
- comminfo.py getsize(): margin=0时直接返回0,避免cash//0
- percents_sizer.py _getsizing(): close_price=0时返回0,避免除零
- 新增9个边界测试覆盖零价格、零保证金、正常计算场景

验证: pytest tests -n 8 → 926 passed, 1 skipped
- leverage.py: portfolio value=0时返回leverage=0,避免ZeroDivisionError
- logreturnsrolling.py: 添加logger.debug到bare except块,log计算失败可追踪
- annualreturn.py: 添加logger.debug到日期转换bare except块
- 新增7个边界测试覆盖零值杠杆、负值、log失败日志

验证: pytest tests -n 8 → 933 passed, 1 skipped
- leverage.py: portfolio value=0时返回leverage=0,避免ZeroDivisionError
- drawdown.py: peak=0时返回dd=0,避免ZeroDivisionError
- timereturn.py: _value_start=0时返回return=0,避免ZeroDivisionError
- calmar.py: 捕获ZeroDivisionError和ValueError(负值log),返回rann=0
- logreturnsrolling.py: bare except块添加logger.debug
- annualreturn.py: bare except块添加logger.debug
- 新增17个边界测试覆盖零值杠杆、零峰值回撤、零起始收益、Calmar边界

验证: pytest tests -n 8 → 943 passed, 1 skipped
- vwr.py: pi=0时VWR期间收益计算返回0,避免ZeroDivisionError
- vwr.py: sdev_max=0时返回vwr=0,避免ZeroDivisionError
- 捕获TypeError防御pn=None残留

验证: pytest tests -n 8 → 943 passed, 1 skipped
- observers/drawdown.py: peak=0时返回dd=0,避免ZeroDivisionError
- analyzers/vwr.py: pi=0时期间收益返回0,sdev_max=0时返回vwr=0

验证: pytest tests -n 8 → 943 passed, 1 skipped
P0 修复:
- basicops.py ExponentialSmoothing.once(): 移除错误的 prev<=0.0 判断,零/负值是合法的金融数据
- basicops.py Average.once(): 预填充数组改用 NaN 替代 0.0,防止 warmup 期间产生错误信号

P1 修复:
- sma.py: import math 从 once() 方法体内移至模块顶部
- sma.py next(): 移除无效的 NaN 比较死代码 (sma_value == float('nan') 永远为 False)
- sma.py/basicops.py: 4 处 bare except Exception 增加 logger.debug 日志,提升异常可见性
统一修复 MA 相关指标的 once() 方法中数组预填充值:
- ema.py: larray.append(0.0) → larray.append(float('nan'))
- dema.py: DEMA + TEMA 两处 larray.append(0.0) → float('nan')
- wma.py: larray.append(0.0) → float('nan')
- smma.py: larray.append(0.0) → float('nan')
- basicops.py ExponentialSmoothing.once(): larray.append(0.0) → float('nan')

原因: 预填充 0.0 在 warmup 期间可能被策略误当作有效指标值,
触发错误的交易信号。NaN 比较永远返回 False,安全地阻止提前交易。
将 SMA 从 ~360 行简化至 ~130 行,移除不必要的复杂度:
- 移除 numpy 依赖(sum()/period 足够准确)
- 移除结果缓存机制(_result_cache, _cache_size_limit, _vectorized_enabled)
- 移除 _calculate_sma_optimized 和 _calculate_sma_manual 多层 fallback
- 移除 3 种获取价格的冗余方法和过度 hasattr 防御检查
- 移除 deque 滚动窗口(改用每 bar 重新计算,避免浮点漂移)
- 简化 __init__: 仅调用 super().__init__()
- 简化 nextstart: 委托给 next()
- 简化 next: 直接 sum(last_period_prices) / period
- once() 保持不变(已经简洁)
- 保留 logger.debug 异常可见性(第一轮成果)
将 ExponentialSmoothingDynamic.__init__() 内定义的 Alpha1Line 类
提取为模块级 _Alpha1Line 类:
- 避免每次实例化 ExponentialSmoothingDynamic 时创建新类对象
- 移除内部 'from . import Indicator' 延迟导入(模块顶部已有)
- 提高可读性和可测试性
- 行为完全不变
…cator files

Changed array pre-fill pattern from 0.0 to float('nan') in once() methods
across 28 indicator files. This prevents warmup-period zeros from being
mistaken for valid indicator values, which could trigger incorrect signals.

Files changed: momentum.py, bollinger.py, deviation.py, dpo.py, hma.py,
kama.py, hurst.py, tsi.py, trix.py, aroon.py, macd.py, priceoscillator.py,
pivotpoint.py, williams.py, envelope.py, kst.py, rsi.py (RSI line only),
atr.py, directionalmove.py, hadelta.py, heikinashi.py, myind.py, ols.py,
percentchange.py, prettygoodoscillator.py, accdecoscillator.py,
awesomeoscillator.py, dma.py, zlema.py, ultimateoscillator.py

Intentionally kept 0.0 pre-fill for:
- stochastic.py: %K/%D warmup 0.0 used by downstream strategies
- crossover.py: 0.0 = 'no crossover' is a valid signal value
- rsi.py UpDay/DownDay/Bool: 0.0 = 'no movement' is semantically correct

All 943 tests pass.
Collapse ExponentialSmoothingDynamic alpha-source branching into a
single _alpha_is_line flag computed in __init__.

Changes:
- compute _alpha_is_line once from alpha array capability
- reuse one formula path in next()
- reuse one formula path in once()
- preserve existing behavior for both line-based and scalar alpha

This removes duplicated hasattr-based dispatch logic and keeps the
runtime behavior easier to reason about.

Verification: pytest tests -n 8 => 943 passed, 1 skipped.
… edge-case hardening

- P0: ComminfoDC.get_credit_interest: .seconds → .total_seconds() — was losing
  the days component for multi-day durations
- P1: BarPointPerc: guard against parts ≤ 0 (degenerate OHLC data)
- P1: ComminfoFundingRate.get_credit_interest: robust try/except fallback chain
  instead of fragile getattr(..., [price])[0]
- Added 9 regression tests in test_comminfo_fillers_edge_cases.py
- pytest tests -n 8: 1003 passed, 1 skipped
cloudQuant added 29 commits May 31, 2026 06:22
Move the entire Closed-trade statistics body (streak / pnl / won-lost /
long-short / length / length-won-lost / length-long-short updates) out of
notify_trade into _on_trade_closed(trade). notify_trade is now a trivial
dispatcher on justopened/Closed.

Logic moved verbatim (de-indented one level); no behavior change. notify_trade
CC E(32) -> A(3); _on_trade_closed D(30).

Verified: import clean, ruff/black green, analyzer + trade functional tests
(238) pass.
Pull the execution-mode flag resolution (runonce/preload/exactbars/replay/
live) plus writer instantiation out of run() into _resolve_run_flags(). run()
keeps its channel/empty bail-out, signal-strategy setup and single-run-vs-
optimization dispatch.

Logic moved verbatim; no behavior change. run CC F(41) -> E(32);
_resolve_run_flags B(10).

Verified: import clean, ruff green, cerebro/run/optimization/writer/replay
tests (224) pass.
Update the S5 table and metrics: 7 high-complexity functions now reduced
(runstrategies 57->37, _next_signal 84->28, run 41->32, SharpeRatio.stop
40->2, inherit_from 33->5, _derive_params 30->14, notify_trade 32->3).
Remaining F-rated functions (process_orderbook / _runnext / _periodset /
line-system __init__/donew/_once/__setitem__/__setattr__/_register_line_
assignment_child / plot) stay deferred with documented rationale.
Extract the dual-side and net portfolio-value accumulation loops into
_get_value_dual_side / _get_value_net. Each returns
(direct, pos_value, unrealized, pos_value_unlever): `direct` is non-None only
for the single-data raw-value early-return path (caller returns it
immediately), preserving the original early-return semantics exactly.
_get_value keeps the cash-addition prologue, the fundhist vs normal value
calculation, and _valuemkt update.

Logic moved verbatim; no behavior change. _get_value CC E(36) -> C(11);
helpers C(15)/C(13).

Verified: import clean, ruff/black green, 202 broker tests + fund/multidata/
commission functional tests pass.
_get_value E(36)->C(11) via _get_value_dual_side/_get_value_net. Update S5
table, acceptance summary and metrics; note _execute/_execute_dual_side
(order accounting) and _initialize_indicator_aliases (import-time init) added
to the documented-deferred set.
… -> 12)

Move the TradeAnalyzer-derived metrics (rpl / won-lost totals / profit_factor /
rpl_per_trade) out of PerformanceCalculator.get_pnl_metrics into
_apply_trade_metrics(metrics, trade_analysis). get_pnl_metrics keeps the
basic cash-return and annual-return calculations.

Logic moved verbatim; no behavior change. get_pnl_metrics CC D(26) -> C(12);
_apply_trade_metrics C(15).

Verified: import clean, ruff green, 132 report/performance tests pass.
The (value_end/value_start - 1) computation with zero/NaN/inf/complex guards
appeared twice in AnnualReturn.stop (year-boundary and final-pending paths).
Extract it into a shared static helper _safe_annual_return(value_start,
value_end). Logic identical; removes the duplication.

stop CC D(21) -> B(7); _safe_annual_return B(8).

Verified: import clean, ruff green, 17 annual-return tests pass.
Pull the column-name -> integer-index resolution loop out of PandasData.start
into _resolve_colmapping(). start() keeps the index reset, loaditems build and
numpy/datetime pre-computation.

Logic moved verbatim (honors nocase, same ValueError fallback semantics); no
behavior change. start CC D(22) -> C(12); _resolve_colmapping C(11).

Verified: import clean, ruff green, 25 pandas-feed tests pass.
Behavior-preserving extractions (verbatim moves), all in non-hot-path modules:
- analyzers/sharpe_ratio_stats.estimated_sharpe_ratio_stdev: extract input
  validation/normalization into _validate_srstdev_params. CC D(25)->C(15).
- bokeh/analyzers/recorder.RecorderAnalyzer.next: extract _record_datas +
  shared _record_lineiterators(store, ltype) (dedups the identical indicator
  and observer recording loops). CC D(21)->A(5).
- bokeh/app.BacktraderBokeh._add_equity_data: split into
  _equity_from_broker_observer / _equity_from_timereturn / _compute_drawdown.
  CC D(24)->A(3).
- feeds/btapifeed.BtApiFeed.islive: extract _api_indicates_live (flattens the
  three repeated supports_live_* probes; returns None when the API has no
  opinion so the caller falls through unchanged). CC D(22)->C(11).

Verified per file: import clean, ruff/black green, radon CC down.
Add get_pnl_metrics, AnnualReturn.stop, PandasData.start,
estimated_sharpe_ratio_stdev, RecorderAnalyzer.next, _add_equity_data and
BtApiFeed.islive to the S5 table; update acceptance summary (15 functions
reduced: 8 core/analysis + 7 non-hot-path) and metrics.
… CLI

Behavior-preserving extractions (verbatim moves):
- bokeh/tabs/config.ConfigTab._get_panel: split into _build_params_widgets /
  _build_data_widgets. CC D(21)->A(4).
- bokeh/tabs/metadata.MetadataTab._get_panel: extract _collect_metadata
  (time/strategy/data/indicator/analyzer/broker info dict). CC D(23)->C(11).
- btrun/btrun.btrun: extract _add_datas (resample/replay data-feed wiring) and
  _print_analyzers (pranalyzer/ppranalyzer output). CC D(28)->C(15).

Verified: ruff/black green, radon CC down; backtrader imports clean; btrun.py
parses (its pre-existing `from .. import strategies` quirk is unchanged).
The getobjects() helper appends tuples of arity 2 (class, kwargs) and
arity 3 (class, kwargs, sigtype) to the same list. mypy inferred the
element type from the first append, flagging the signal branch. Annotate
retobjects as a bare `list` to accept the heterogeneous tuples, matching
the existing `kwargs: dict` style in the same function. Restores the
established 139-error mypy baseline.
…eserving)

All extractions are verbatim phase splits with unchanged computation
order, signatures, side-effects and error semantics. Each verified with
import + black + ruff + radon + targeted tests.

- profiles.LiveProfile.__post_init__: split into _normalize_mode_frequency
  / _validate_store_config / _normalize_symbols / _validate_data_source
  (CC 23 -> 1). test_live_profile.py 33 passed.
- analyzers.tradeanalyzer.TradeAnalyzer._on_trade_closed: split into
  per-category helpers (_update_streak / _update_gross_net_pnl /
  _update_won_lost / _update_long_short / _update_length /
  _update_length_won_lost / _update_length_long_short) (CC 30 -> 1).
- brokers.btapibroker.BtApiBroker._sync_positions: extract per-item parse
  into _sync_one_position (CC 24 -> 12). 81 broker tests passed.
- cerebro.Cerebro._run_channel: extract _instantiate_channel_strategies +
  _wire_channel_strategies setup phases (CC 33 -> 21; event loop left
  intact as hot path). channel-mode integration tests passed.
Behavior-preserving verbatim extractions; verified with import + black +
ruff + radon + targeted tests.

- analyzers.sharpe.SharpeRatio._timeframe_ratio: extract the rate/returns
  timeframe-factor conversion into _convert_rate_returns (CC 26 -> 15).
  103 sharpe analyzer tests passed.
- plot.plot_plotly.PlotlyPlot: extract the duplicated leading-pre-warmup-
  zero trimming logic shared by _plot_indicator (CC 31 -> 20) and
  _plot_indicator_on_ax (CC 25 -> 14) into a single _trim_prewarmup_zeros
  static helper, removing the duplication. 13 plotly integration tests
  passed.
… collection

Behavior-preserving verbatim extractions; verified with import + black +
ruff + radon + targeted tests.

- parameters.ParameterizedBase._compute_parameter_descriptors: extract
  STEP 1 inheritance collection into _collect_inherited_descriptors and
  STEP 2/3 current-class merge into _collect_own_descriptors (CC 22 -> 2).
  Lazy/cached, not a hot path. 57 parameter-system tests passed.
- plot.plot_plotly.PlotlyPlot._collect_buysell_signals: split the four
  fallback sources into _buysell_from_transactions / _broker_orders /
  _strategy_attr / _observer, preserving the first-source-wins
  short-circuit (CC 21 -> 4). plotly integration tests passed.
Document profiles/tradeanalyzer/btapibroker/cerebro-channel-startup/
sharpe/plotly(×2)/parameters decompositions, add Replayer.__call__ to the
deliberately-deferred hot-path/state-machine list, and bump the metrics
dashboard from 18 to 26 functions reduced.
…> 139)

Moving the per-item/per-strategy population loops into helper methods
removed the local usages mypy relied on to infer container element types.
Re-annotate the affected locals/params to keep the established 139-error
baseline:

- btapibroker._sync_positions: annotate synced/long_synced/short_synced
  as defaultdict[str, Position] and add matching params to
  _sync_one_position.
- cerebro._run_channel: split the chained assignment and annotate
  runstrats: list.

Annotations only; no runtime behavior change. 83 broker/channel tests
pass.
Iteration 10: complete module headers (Data Used / Strategy Principle /
Strategy Logic) and class/method/function docstrings across
tests/functional/strategies. Comments/docstrings only; executable code
unchanged. Also restored two concurrency-corrupted values (analyzer
compression and source_ea path) to match HEAD.
…pdate

- backtrader: ruff R2 cleanups (PIE/C4/RET/SIM/B) and minor refactors
- pyproject: expand ruff lint families with documented ignores
- CI: add dev branch triggers + nightly full-suite schedule
- README: update strategy-suite benchmark (master 438.96s vs dev 236.36s,
  -46.2% / 1.86x; 1271 passed) and runtime hints
- docs/scripts/tests: iteration tracking, analyze_docstrings, new unit/bench tests
… credential masking

迭代12 代码质量与安全加固,M1–M3/M5 落地(安全主线):

S-1 异常可见性分类治理:
- trade_logger._safe_order_info: 静默 except 改为 logger.debug 记录上下文
- bokeh/app.py: 跳过坏 executed dt 时补 debug 日志
- strategy/log_message: 热路径补理由注释
- line 系统热路径 23 处 except 追加带理由的 # nosec B110/B112(仅注释,零逻辑改动)

S-2 网络鲁棒性:
- utils/py3.urlopen 增加默认 30s 超时(setdefault,兼容显式覆盖),
  避免 quandl 等数据源在远端无响应时无限挂起

S-3 实盘凭证暴露面收敛:
- BtApiStore 新增安全 __repr__/__str__,屏蔽 password/auth_code
- 新增 _SENSITIVE_KEYS + _mask_sensitive() 供日志脱敏复用

S-4/S-5 低危 triage 闭环:
- btrun 弱随机加 # nosec B311(内部模块名,非安全用途)
- 复核 SQL 全参数化、influxfeed 字段名可信(已有 # nosec B608)

T-1/T-2 CI 防回流:
- lint 作业新增 pip-audit 依赖漏洞扫描 + Bandit Low 未 triage 看板(基线 0)

新增测试:
- tests/unit/stores/test_credential_safety.py(6)
- tests/unit/utils/test_urlopen_timeout.py(3)

验收:bandit HIGH/MED/LOW=0;ruff/black/isort pass;mypy 139 未回流;
pytest tests -n 8 → 3044 passed, 1 skipped, 0 failures。

公共 API、数值结果、订单逻辑、事件顺序均未变。
验收复查发现两处 nosec 噪声并修正:
- bokeh/app.py: 该 except 已补 logger.debug,不再触发 B112,移除多余
  `# nosec B112` 并修正自相矛盾的注释(原写"logging would be noisy"但实际已记日志)
- btrun/btrun.py: 多行 `# nosec B311:` 注释被 bandit 误当成 test id 解析并告警,
  改为说明注释置于上方、行尾仅保留干净的 `# nosec B311`

bandit -r backtrader -c pyproject.toml 仍为 HIGH/MED/LOW=0;
functions.py 嵌套 except 的 4 处 # nosec B110 经实测确属必要(移除会暴露真实
B110),其 line 676/698 告警为 bandit 对嵌套 except 的行归属怪癖,无害。

验收:ruff pass;mypy 139 未回流;pip-audit 无 CVE;
pytest tests -n 8 → 3044 passed, 1 skipped。
台账补充验收复查记录与完成度小结:M1–M3(安全主线)+ M5(CI 防回流)
已完成并通过验收;M4(mypy/复杂度)、M6(结构评估/TODO)、T-3(pre-commit)
按计划属后续协同批次,尚未开始。
CI run 26727816157 (dev) failed with 8 collection errors + 3 test failures.
诊断(gh run view + 本地隔离 venv 复现):

1) 缺失可选依赖导致 collection error(8 个):sklearn/hmmlearn/bt_api_py 在
   CI 镜像未安装,但 7 个 ML 测试 + btapistore CTP 测试在模块顶层硬导入。
   修复:按项目既有约定(seaborn/pandas 已用 pytest.importorskip)为这些
   模块/用例加 `pytest.importorskip(...)`,缺失即跳过而非 collection error。

2) numpy 2.x 计数漂移(buy_count 442vs440、rebalance_count 124vs123 等):
   实证——numpy 1.26.4 下整套策略回归(单测+并行)全绿;numpy 2.4.6 下少数
   exact-count 断言漂移 ±1-2(源于 reduction/sort/dtype 数值变化使浮点边界翻转,
   含 score.rank/sort_values 的 tie-break)。回归基线按 numpy 1.x 标定。
   修复:setup.py / requirements.txt 将 numpy 收紧为 `>=1.20.0,<2.0.0`。
   不修改任何断言/期望值/策略逻辑;numpy 2.x 支持需另立批次重标定基线。

验收:
- 隔离 venv(py3.12 + numpy2.4.6,无 sklearn/hmmlearn/bt_api_py)下,原 8 个
  报错文件现可正常收集(20 collected)并干净 skip;
- ruff pass;bandit -ll pass;mypy 137(未回流);
- pytest tests -n 8 → 3044 passed, 1 skipped。
…re-commit

收口迭代12 剩余里程碑(质量辅线 + 评估 + 工具链):

M4 Q-1 安全模块 mypy 清零:
- influxfeed.py: InfluxDBClientError fallback 加 type: ignore[no-redef];
  biter 注解为 Optional[Iterator[Any]] 并在 _load 加 None 守卫(修 arg-type)
- 实测 stores/* 与 trade_logger 已无 mypy 错误;全仓 139→137
M4 Q-2 复杂度:实测 btapistore 最高仅 C 级(CC13),无 F/E 级,无需重构

M6 Q-3 结构评估:新增 M6-结构评估与TODO复核.md,结论为本迭代不拆
  btapistore(无高复杂度热点 + 与 bt_api_py 动态导入强耦合),给出
  常量→纯函数→客户端工厂 的分批拆分路线图供后续参考
M6 Q-4 源码 TODO:bbroker int2pnl 翻译遗留注释改写为正式文档;
  vchartfile FIXME 作为已知限制保留

T-3 pre-commit:新增 bandit 安全钩子(-ll,镜像 CI Medium/High 门禁),
  把安全扫描左移到本地提交前

台账:M4/M5/M6/T-3 全部置「验收通过」,并记录 M7 CI 修复。

验收:ruff pass;bandit -ll pass;mypy 137;pytest tests -n 8 → 3044 passed。
…py3.13

第二轮 CI 修复(push 后 run 26732118071 暴露的剩余问题):

CI-C test_0061 SVD 符号不确定性(根因):
- `_latent_scores` 用 np.linalg.svd,LAPACK 不固定奇异向量符号,vt 行符号随
  平台/Python 构建/BLAS 后端翻转 → 潜在因子分数翻转 → 边界交易信号漂移
  (py3.11→209,py3.12/3.13→210,即使同为 numpy 1.26.4)。
- 修复:加确定性符号约定(每个奇异向量最大幅值分量取正)。实测 py3.11/3.12/3.13
  × numpy1/2 全部复现同一结果,测试从"平台相关"变为"完全可移植"。
- 按确定性结果重标定 11 处断言(非弱化:计算先变确定性,再以确定值精确断言)。

CI-B numpy pin 改为条件式(修复 py3.13/Windows 段错误):
- 上一轮 `numpy<2.0` 导致 py3.13 无 1.x wheel → 1.26.4 源码构建在 win/py3.13
  段错误(exit 139,import backtrader 即崩)。
- 改为:py<3.13 用 `numpy>=1.20,<2.0`(验证基线);py>=3.13 用 `numpy>=2.1`
  (有 3.13 wheel)。实测 py3.13+numpy2 下三个敏感测试 + 策略功能子集全绿。

验收:
- test_0061 在 py3.11/3.12/3.13(numpy1 与 numpy2)均通过且数值一致;
- py3.13+numpy2 策略功能子集 385+1243 passed 无漂移;
- 主环境 pytest tests -n 8 → 3044 passed, 1 skipped。

CI-A(上一轮 commit af929b0 的 importorskip 守卫)已使 8 个 collection error
转为 skip,本轮不再涉及。
@cloudQuant cloudQuant merged commit c874506 into development Jun 1, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants