77627d5f19
chore: 添加docs/paper目录到gitignore
...
忽略学术论文PDF文件,避免大型二进制文件进入版本控制
2026-06-21 19:59:37 +08:00
474a4b943b
docs: 补充实证文档头部Git版本元信息
2026-06-21 19:48:09 +08:00
59686b5da9
docs: 补充实证文档Git版本信息,移至experiments目录统一管理
2026-06-21 19:47:06 +08:00
aa4a8b2b62
docs: 移除实验文档编号前缀,简化命名规则
2026-06-21 19:39:36 +08:00
7cc882dade
docs: 强化Git可复现性规范,区分实证与非实证文档类型
2026-06-21 19:31:30 +08:00
1cb854f2d8
docs: 添加文档创建规范,定义命名格式与头部元信息标准
2026-06-21 19:25:32 +08:00
c2bd31ac2a
docs: 按git创建日期重命名文档为YYYYMMDD_中文名称格式
2026-06-21 19:21:26 +08:00
e5381d54fb
docs(experiments): 科创板ETF接入与收益对比实验报告
...
## 内容
- 背景描述:ETF代码识别逻辑不支持科创板前缀
- 实验设置:三版本对比(创业板/科创板/双A股)
- Commit版本:7f2a968修复记录
- 实验结果:创业板48.51% > 科创板45% > 双A股38.95%
- 结论:建议保持创业板单一配置
## 发现
双A股配置收益反而最低,年化收益下降9.56%
原因:组内竞争、信号稀释、相关性高
2026-06-21 18:49:08 +08:00
7f2a968be0
fix(datasource): ETF代码识别支持科创板(58)和沪市新ETF(56)前缀
...
## 问题
科创板ETF(588000.SH)和沪市新ETF(56xxxx.SH)数据获取失败(HTTP 404)
## 根因
tushare_source.py._is_etf_code()仅识别['51','52','15','16']前缀
遗漏科创板ETF(58xxxx.SH)和沪市新ETF(56xxxx.SH)
## 修复
- 添加'58'前缀支持科创板ETF
- 添加'56'前缀支持沪市新ETF(如红利低波ETF)
## 验证
Tushare fund_daily接口实测证明支持科创板ETF:
- 588000.SH: 111条数据 ✅
- 588080.SH: 111条数据 ✅
- 000688.SH(科创板指数): 111条数据 ✅
## 附带修改
- simple_rotation.py添加--config参数支持
2026-06-21 17:11:45 +08:00
2716eec511
feat(config): select_num从3调整为1,启用集中持仓策略
...
## 定量对比(2020-01-10 ~ 2026-06-18)
| 配置 | 年化收益 | 总收益 | 夏普比率 | 最大回撤 | Calmar |
|------|---------|--------|---------|---------|--------|
| select_num=3 (baseline) | 25.99% | 316.93% | 1.22 | -16.27% | 1.60 |
| select_num=1 rank | 48.51% | 1053.08% | 1.34 | -26.33% | 1.84 |
| select_num=1 greedy | 55.97% | 1461.35% | 1.73 | -19.07% | 2.93 |
## 核心变更说明
1. config_simple.yaml: select_num从3改为1,策略从分散持仓转为集中持仓
2. 新增greedy vs rank策略对比分析文档,揭示权重顺延机制收益贡献
3. 新增全球资产轮动策略端到端实验报告
## greedy策略收益优势归因
- 智能风险分散: ETF池容量限制(etf_max_weight=25%)实现隐式多标的分散
- 降低极端风险: 避免全仓集中在容量受限标的(如HG=F仅有1只ETF)
- 风险调整收益优化: 夏普+0.39, 回撤改善7.26%, Calmar提升1.09
## 理论意义
信号集中(select_num=1)与仓位分散(greedy顺延)可协同作用,
挑战传统"集中投资最优"假设,容量约束成为天然风控机制。
2026-06-21 16:33:06 +08:00
983d048614
feat: 短债ETF池复制4次,允许100%仓位防御
...
问题:短债只有1只ETF时,greedy模式最多分配25%仓位
修复:etf_pool复制4次,短债排第一时可100%仓位
效果(select_num=1, greedy模式):
- 修改前: 1354.99% 累计收益, 1.69 夏普
- 修改后: 1461.35% 累计收益, 1.73 夏普
- 短债100%仓位天数: 55天 (3.5%)
设计意图:短债作为防御资产,当动量最强时(通常是市场下跌),
应该允许100%配置来避险,而不是被迫分散到其他资产。
2026-06-21 14:38:00 +08:00
f5406be04f
fix: detail JSON导出时添加greedy_weights字段
...
问题:greedy模式下greedy_weights没有被导出到detail JSON
修复:在export_results的days_out.append中添加greedy_weights字段
验证结果(select_num=1, greedy模式):
- 62.8%天数:1个信号(ETF池充足,100%集中)
- 36.0%天数:2个信号(ETF池不足,发生顺延)
- 1.2%天数:3个信号(ETF池严重不足)
示例:短债(1ETF)25% + 黄金(4ETF)75% = 100%
2026-06-21 14:15:51 +08:00
9d79ef9ca0
fix: greedy模式从所有信号中顺延,而非仅holdings列表
...
问题:select_num=1时,holdings只有1个元素,ETF池不足时仓位闲置
- 有色金属(1ETF): 仓位25%,75%闲置
- 德国DAX(2ETF): 仓位50%,50%闲置
- 原油(3ETF): 仓位75%,25%闲置
修复:从所有信号中按动量排序顺延
- 有色金属(25%) → 顺延到第2名信号(如原油75%) → 100%满配
回测对比 (select_num=1):
- equal: 1053% 累计收益, 1.34 夏普
- greedy(修复前): 730% 累计收益, 1.31 夏普
- greedy(修复后): 1355% 累计收益, 1.69 夏普
2026-06-21 13:59:01 +08:00
ac022020c7
refactor: 整理rotation目录结构
...
将分析/测试/实验脚本从核心目录移出:
- enrich_etf_data.py → scripts/
- oil_tracking.py → analysis/
- tracking_error_full.py → analysis/
- tracking_error_validation.py → analysis/
- test_start_year_analysis.py → experiments/
- experiment_select_num.py → experiments/
rotation/ 目录现在只保留核心策略代码:
- simple_rotation.py (策略主逻辑)
- config_loader.py (配置加载)
- config_simple.yaml (配置文件)
- daily_scheduler.py (调度器)
2026-06-21 13:38:15 +08:00
0da0306894
fix: greedy模式仅在select_num=1时生效
...
问题:greedy在select_num>1时仓位分配取决于ETF池大小,而非动量强度
- 场景1: 有色金属(1ETF)>原油(3ETF)>黄金(4ETF) → 有色25%,原油75%,黄金0%
- 场景2: 黄金(4ETF)>创业板(4ETF)>纳指(4ETF) → 黄金100%,其他0%
修复:select_num>1时greedy退化为equal权重
回测对比:
- select_num=3, rank: 326.60%
- select_num=1, greedy: 730.61% (集中度更高,收益更好)
2026-06-21 13:13:17 +08:00
adb83d8cd7
feat: 实现贪心分配模式(greedy)
...
- config_loader.py: 添加 etf_pool 字段和 GREEDY 枚举
- config_simple.yaml: 每个资产添加 etf_pool 列表
- simple_rotation.py:
- 添加 _compute_greedy_weights 方法
- _calculate_daily_return 支持 greedy 模式
- 向后兼容原有 rank/equal 模式
贪心算法:按 ETF 池容量分配仓位,装不下的顺延给下一名
- 有色金属(1 ETF): 吸收25%,顺延75%
- 原油(3 ETF): 吸收75%
- 黄金(4 ETF): 吸收100%
回测对比 (select_num=3):
- rank: 326.60% 累计收益, 1.24 夏普
- greedy: 421.35% 累计收益, 1.03 夏普
2026-06-21 12:40:40 +08:00
b698857e49
docs: 将英文文件名重命名为中文
...
重命名13个英文文档为中文:
- etf_pool_selection.md → ETF候选池筛选报告.md
- etf_tracking_error_calculation.md → ETF跟踪误差计算方法.md
- FLASK_SERVICE_SUMMARY.md → Flask服务总结.md
- flask_api_README.md → Flask_API接口说明.md
- cross_market_effectiveness_survey.md → 跨市场有效性调研.md
- etf_rotation_deep_analysis.md → ETF轮动深度分析.md
- etf_rotation_framework.md → ETF轮动框架.md
- momentum_rotation_survey.md → 动量轮动调研.md
- strategy_evolution_report.md → 策略演进报告.md
- universal_fetcher_*.md → 通用数据源*.md
同时更新文档内部的交叉引用链接
2026-06-20 23:24:04 +08:00
a600a71aa3
docs(etf_pool_selection): 添加ETF候选池筛选报告
...
详细记录10个指数的ETF筛选过程:
- 每个指数的匹配规则、候选总数、Top4列表(含规模/成交额/费率)
- 完整的组内相关性矩阵(基于单位净值日收益率)
- 异常ETF排除记录(增强策略型、沪港通型)
- 原油商品价格型vs股票型的排除分析(R²对比)
- 最终候选池汇总
2026-06-20 23:14:28 +08:00
3b0688930d
docs: 添加ETF跟踪误差计算方法文档
...
- 完整计算流程(ETF单位净值 vs 基准指数)
- 数据源选择(Tushare指数/期货/Flask API)
- 关键注意事项(unit_nav、标的指数基准、年化因子)
- 与天天基金数据校验结果(平均差异0.009%)
- Python代码示例
2026-06-20 17:06:26 +08:00
09ecac9e56
docs(experiments): add experiment 010 - start year sensitivity analysis
...
- Reproduce historical results: ca933e4 code achieves 43.20% annual return
- Attribution analysis: crash filter simplification (+4pp) + data extension (+2pp)
- Start year traversal: 2020-2025, all years show 34-57% annual return
- Compare ca933e4 vs HEAD (cabfee2 ) across different start years
- Add test_start_year_analysis.py for reproducibility
2026-06-17 23:24:17 +08:00
cabfee20b0
docs: add min_hold_days optimization experiment (009)
2026-06-17 19:39:38 +08:00
d657f8506b
docs: add execution delay impact experiment (008)
2026-06-15 18:51:13 +08:00
6e7087a543
docs: 添加实验007动量因子回看窗口优化研究
...
- 研究多周期融合(ensemble)对策略表现的影响
- 结论:多窗口融合不适用于本策略,维持25天单窗口
2026-06-12 12:37:38 +08:00
8c3ae2269a
feat: 新增 slope_r2_idm 和 slope_r2_ensemble 动量因子
...
- slope_r2_idm: slope_r2 × IDM(信息离散动量),惩罚靠少数大涨日撑起来的假动量
- slope_r2_ensemble: 多窗口(63/126/252天) slope_r2 等权融合,捕捉不同周期趋势信号
- 新增 info_dispersal_momentum() 计算正收益天数占比
- 新增 slope_r2_idm_score() 和 slope_r2_ensemble_score() 因子函数
- ensemble 因子需要更长预加载窗口(504天)和计算窗口(252天)
- crash filter 仍使用原始 n_days 窗口
2026-06-12 12:37:29 +08:00
49b623931b
chore: 更新SSH隧道脚本密钥路径并提交私钥文件
2026-06-11 22:06:21 +08:00
fe73c0f199
refactor(rotation): simplify crash filter and add min_hold_days support
...
Changes:
- Simplify is_crash(): remove con2 (consecutive decline) condition, keep only single-day drop > 5%
- Extract _compute_base_momentum() to eliminate factor dispatch duplication
- Add min_hold_days config for forced holding constraint (currently disabled, value=1)
Backtest comparison (2020-01-10 ~ 2026-06-09):
| Metric | Old (con1 OR con2) | New (con1 only) |
|-----------------|--------------------|-----------------|
| Total Return | 241.73% | 271.98% |
| Annual Return | 22.10% | 23.79% |
| Max Drawdown | -16.27% | -16.27% |
| Sharpe Ratio | 1.09 | 1.14 |
| Calmar Ratio | 1.36 | 1.46 |
| Win Rate | 53.71% | 53.78% |
| Rebalances | 393 | 362 |
Conclusion: Relaxing crash filter improves return (+1.69% annual) with
same drawdown and fewer rebalances.
2026-06-09 22:53:52 +08:00
e2038ae722
chore: 修复 .env 注释格式 & 代码格式化
...
- .env: 第10行添加 # 注释前缀,修复 source .env 命令报错
- simple_rotation.py: is_crash 判断后添加空行,提升可读性
回测结果 (2020-01-10 ~ 2026-06-09):
- 总收益: 241.73% | 年化: 22.10%
- 最大回撤: -16.27% | Sharpe: 1.09
- 调仓次数: 393
2026-06-09 00:39:27 +08:00
5c4aeb75d2
fix(scheduler): 修复setup_schedule未传递no_detail/no_report参数的问题
...
setup_schedule() 在定时模式下未将 --no-detail 和 --no-report 参数传递给 daily_task,导致定时任务始终生成 detail JSON
2026-06-09 00:07:01 +08:00
710f3d9d68
chore(config): 启用钉钉机器人群2配置
2026-06-08 23:43:20 +08:00
0c19e45300
chore(config): 恢复 weight 为 rank 模式
2026-06-08 23:07:37 +08:00
e4bb570e5f
docs: 更新 kelly 文档 commit hash
2026-06-08 23:05:39 +08:00
8b7bcf206a
feat(weight): 实现 Kelly 仓位权重模式
...
- config_loader.py: WeightType 枚举新增 KELLY
- simple_rotation.py: compute_position_weights 新增 kelly 分支
- 公式: w_i = max(score_i, 0) / sum(max(score_j, 0))
- 负分自动排除 (Kelly: 不下注负期望)
- 全负分时 fallback 到等权
- _generate_signals 传递 scores 给 kelly 模式
- config_simple.yaml: weight 改为 kelly
- 新增策略总结文档: kelly_weight.md
回测对比 (2020-2026):
- equal: 年化 19.88%, 夏普 1.13, 回撤 -14.65%
- rank: 年化 22.90%, 夏普 1.12, 回撤 -16.27%
- kelly: 年化 30.13%, 夏普 1.15, 回撤 -20.44%
2026-06-08 23:05:26 +08:00
844e609ff7
refactor(notify): 将通知模块从归档移至正式位置
...
- 将 notify.py 和 oss_utils.py 从 archive/legacy_core 移至 core/common/
- 内联钉钉配置读取函数,移除对 config.settings 的依赖
- 删除 config/ 目录(settings.py 不再需要)
- daily_scheduler.py 移除归档路径的 sys.path hack
- 新增 --no-detail 和 --no-report 命令行参数控制导出
- 全标的排名表新增退场日期和退场价格列
2026-06-08 22:34:03 +08:00
c32ce72579
fix(report): 修复报告生成中盈亏显示缺失的多个bug
...
- 修复 market_opened 检测中 df 变量名冲突导致 KeyError: holdings
- 维持仓位盈亏使用最近可用收盘价计算(不再依赖市场是否开盘)
- 调出标的盈亏:市场已开盘用当天开盘价,未开盘用前日收盘价
- 新调入标的在市场未开盘时正确显示待开盘状态(进场日期/盈亏为空)
2026-06-08 08:35:31 +08:00
4736b64eca
feat(report): 全标的排名表新增进场日期列
...
在状态和持有天数之间插入进场日期列,显示持仓标的的连续持仓起始日期(YYYY-MM-DD格式),调出/未入选标的显示—
2026-06-08 00:55:59 +08:00
d5f35c0273
feat(report): 新增月度收益矩阵热力图面板
...
在策略绩效对比表下方新增 Panel 2 月度收益矩阵:
- 行:年份(2020~2026),列:1月~12月 + 年度累计
- 单元格显示月度收益率百分比
- 红涨绿跌配色(A股习惯),sqrt非线性映射增强小收益可见性
- 年度列使用几何连乘计算全年累计收益
- 无数据单元格浅灰显示
2026-06-08 00:43:54 +08:00
13c69c2a0b
feat(report): 全标的动量排名表替代原调仓信号表
...
Panel 0 从仅展示持仓调仓扩展为全标的排名表:
- 新增未入选标的行,按动量降序展示
- 新增排名、市场两列
- 表格按调入→维持→调出→未入选顺序排列
- 调出标的也展示真实得分(便于分析调出原因)
- 标题显示当前动态阈值
- 未入选标的浅灰背景区分
2026-06-08 00:12:17 +08:00
6a5ae8efbf
fix: generate_report now uses actual position_weights from daily_records
...
Previously hardcoded equal weight (1/select_num), ignoring config weight type.
Now reads position_weights from last daily_record, correctly showing rank-based weights.
2026-06-07 23:29:27 +08:00
d898ba0fd5
Revert "feat: add HTML report screenshot generation via Playwright"
...
This reverts commit f370caeff9 .
2026-06-07 23:12:21 +08:00
f370caeff9
feat: add HTML report screenshot generation via Playwright
...
- Add html_report.py module for Playwright-based screenshot generation
- Add generate_html_report() method to SimpleRotationStrategy
- Modify backtest_viewer.html to use window-scoped variables for external injection
- Inject monthly/yearly returns table into screenshot
- Auto-generate HTML report in __main__ after export_results()
Output: simple_rotation_html_report.png with ranking table + monthly returns
2026-06-07 22:43:12 +08:00
06df8767b9
docs: add select_num=1 strategy deep analysis report
...
- Asset contribution attribution (CL=F 59.1%, N225 -11.8%)
- IC analysis across lookback periods (only CL=F and ChiNext have robust positive IC)
- Hurst exponent analysis and asset classification
- Multi-factor direction recommendations
2026-06-07 12:26:13 +08:00
7b229ced14
docs: add strategy summary snapshot (2026-06-06, ca933e4)
...
First stage summary documenting core strategy logic, key design
decisions, and select_num/weight backtest comparison results.
Stored in dedicated docs/strategy_summaries/ directory with
date + commit hash naming for reproducibility.
2026-06-06 23:59:41 +08:00
ca933e43e4
fix: lock position weights on rebalance only, not daily ranking changes
...
Previously, position weights were recalculated every day in _generate_signals,
causing weights to change even when holdings didn't change (only ranking order
shifted). This was incorrect - weights should be locked at rebalance and remain
stable until the next rebalance.
Changes:
- _generate_signals now computes _pending_weights (for signal generation only)
- run() maintains active_weights, updated only on is_rebalance or first day
- _calculate_daily_return uses the locked active_weights
- daily_records stores active_weights in position_weights field
Result: 391 → 318 rebalances, 25.63% → 26.38% CAGR
2026-06-06 23:16:51 +08:00
8d8fd71149
feat(viewer): sort holdings cards by position weight descending
...
Display largest position first in the holdings panel for better readability
2026-06-06 22:48:40 +08:00
4d9e12886f
chore: remove *.html from gitignore to track all HTML files
2026-06-06 22:43:08 +08:00
eb3c82f05b
feat(rotation): add position weight to detail JSON and viewer
...
- Record position_weights in daily_records during backtest run
- Export weight field per held asset in detail JSON
- Display weight percentage in backtest_viewer holdings cards
- Force-add backtest_viewer.html (previously ignored by *.html rule)
2026-06-06 22:39:23 +08:00
4973a9a2a5
feat(rotation): componentize position weighting + fix bond threshold consistency
...
- Extract compute_position_weights() as pluggable pure function
- Add WeightType enum (equal/rank) and RotationConfig.weight field
- Fix bond threshold dimension mismatch: use configured factor function
for all assets instead of hardcoded weighted_momentum_score
- Default weight: equal in config, active: rank in config_simple.yaml
2026-06-06 22:28:08 +08:00
44588d5026
refactor(rotation): clean up experimental factor code
...
Remove slope_snr, slope_snr_r2, james_stein score functions and r2_alpha parameter.
slope_r2_score reverts to simple slope*R² with no alpha parameter.
Minor docstring fix: R^2 → R².
2026-06-06 18:45:11 +08:00
921f84cb6a
feat: 新增 standardized_slope (t-statistic) 因子并实验验证
...
- simple_rotation.py: 新增 standardized_slope_score 函数 (slope/SE)
- config_loader.py: FactorType 枚举新增 STANDARDIZED_SLOPE
- 对比实验结果: standardized_slope 年化 13.73% vs slope_r2 19.84%
- 结论: t-statistic 过度惩罚高波动资产的有效趋势信号,不适合本场景
- 文档更新: 动量因子对比调研报告新增 3.3 节详细分析
2026-06-06 16:40:01 +08:00
aff04318b1
chore: 动量因子对比调研报告移至 docs 目录
2026-06-06 16:19:10 +08:00