/~learn/s/0fa05580
> 这篇文章的目的:用最短时间、最直白的话,让你完全理解你的系统在做什么。每句话都保证 100% 准确,附有对应的代码位置。 > > 最后更新:2026-06-28 — 反映 v2 5因子 + AdaptiveWeightEngine + 板块差异化阈值 + 防御类软惩罚 + 截面网站板块
---
每周五收盘后,系统扫描你选的 107 只 ETF,用 5 个角度给每只打分,算出全市场排名,并从中筛选出 Top 10 投资组合,告诉你下周一该买什么、买多少。
它不是算命,它是一个有纪律的助手。它不预测未来,它只做一件事:从过去的数据中找出规律,然后假设这个规律会延续。
---
拉数据 → 判断天气 → 5因子打分+防御惩罚 → 分散化去重 → 计算权重 → 保存全市场快照 → 推送结果
系统从本地缓存加载 107 只 ETF 最近 1 年的日 K 线数据(含开、高、低、收、成交量、涨跌幅)。缓存由每周推送自动维护,加载只需几秒。
代码位置:engine.py:L216-L256(fetch_all_data_from_cache)
# 优先读本地 CSV 缓存(DataCacheManager.load_best)
# 缓存失效(滞后>30天)或缺失时,降级到 tushare/adata 实时拉取
# 首次拉取约 5 分钟,后续增量只需几秒
系统用沪深 300 ETF(510300.SH)作为温度计,看三件事:
| 指标 | 含义 | 怎么算 |
| ------ | ------ | -------- |
| 价格 vs 200日均线 | 当前价格在年线上还是线下 | (当前价 - 200日均价) / 200日均价 |
| 波动率比 | 最近波动是偏高还是偏低 | 近60日波动率 / 历史波动率 |
| 趋势强度 | 上涨有没有规律 | 近60日平均收益 / 标准差 |
代码位置:regime.py:L12-L58
根据这三个指标,系统判断出 6 种天气:
| 天气 | 判断条件 | 仓位上限 |
| ------ | ---------- | :---: |
| 🟢 强势牛市 (BULL) | 价格 > 年线 5%+,且波动率正常 | 95% |
| 🟡 温和上涨 (MILD_BULL) | 价格 > 年线,波动率 < 1.5 倍 | 90% |
| 🟠 震荡盘整 (RANGING) | 不上不下 | 60% |
| 🔴 高波动 (HIGH_VOL) | 波动率 > 1.5 倍历史平均 | 40% |
| 🔴 熊市 (BEAR) | 价格 < 年线 5%+,波动率走高 | 30% |
| ⛔ 严重熊市 (SEVERE_BEAR) | 价格 < 年线 10%+ | 15% |
代码位置:regime.py:L60-L77
---
这是系统的核心。每只 ETF 从 5 个角度被打分(每个角度 0-1 分,1 分最高):
问的问题:「过去 2-6 个月涨了多少?涨得干净吗?」
计算方式:momentum_factor.py:L27-L54
raw_momentum = 60日收益率 × 0.5 + 120日收益率 × 0.5
final_momentum = raw × max(趋势质量R², 0.3) × 回撤惩罚
趋势质量(R²)修正:用线性回归 R² 判断过去 60 天的上涨是否「稳步推升」而非「剧烈震荡」:
回撤惩罚:momentum_factor.py:L75-L89
> 大白话:这个因子不会盲目追涨——它检查上涨的质量。震荡中的上涨会被打骨折,真正的稳步推升才给高分。
问的问题:「每承担 1% 的波动,赚了多少?」
代码位置:quality_factor.py:L40-L68
两个 ETF 都涨了 10%,但一个每天稳稳涨、另一个上蹿下跳——稳稳涨的那个夏普更高。
问的问题:「是不是涨太疯了,该回避?」
代码位置:mean_reversion_factor.py:L55-L169
v2 核心改进(2026-06 更新):
| 改进点 | v1(旧) | v2(新) |
| -------- | --------- | --------- |
| 打分方式 | 阶梯硬切断(RSI>72→0分) | 连续打分(避免阈值附近跳变) |
| 板块差异化 | 所有品种相同阈值 | 成长板块放宽、防御板块收紧 |
| 阈值—成长(科技/新能源/科创板等) | RSI 72 | RSI 82,涨幅 40% 才开始惩罚 |
| 阈值—中性(跨境/行业等) | RSI 72 | RSI 76,涨幅 25% 开始惩罚 |
| 阈值—防御(债券/商品/红利等) | RSI 72 | RSI 74,涨幅 20% 开始惩罚 |
| 泡沫识别 | 无 | 三信号共振(RSI>85 + 跌破5日线 + 放量滞涨)才给接近 0 分 |
| 市场状态联动 | 无 | 熊市/高波自动收紧阈值(RSI-8,涨幅-0.10) |
成长板块分类:mean_reversion_factor.py:L8 — 科技、新能源、成长、科创板、传媒、军工、汽车 防御板块分类:mean_reversion_factor.py:L10-L12 — 债券、商品、REITs、红利、央企、公用事业等
> 这个因子专门防止追高站岗,但对科技股的容忍度更高——科技股天生高波动、高 RSI 是常态而非异常。
问的问题:「波动大不大?涨得稳定吗?」
代码位置:mean_reversion_factor.py:L191-L233
低波动、稳定上涨的品种得分高。国债 ETF 在这项上接近满分。
问的问题:「当前价格在历史区间中偏高还是偏低?」
代码位置:valuation_factor.py
用距 200 日均线的偏离度作为估值锚:
同时结合 PE 分位数(如有数据),提供双重估值参考。这个因子是温和的纠偏力量——不会否决动量,只是在估值极端时给一个轻度的拉回。
---
v2 核心变化:权重不再是固定的离散表格,而是由 AdaptiveWeightEngine 基于市场状态连续调节。
代码位置:weight_engine.py
权重计算公式:
adjustment_factor = clamp(1.0 / (1.0 + (vol_ratio - 1.0) × α), 0.4, 1.0)
× clamp(trend_strength, 0.5, 1.2)
momentum_weight = 0.30 × (1 + (vol_ratio - 1.0) × 0.3) × adjustment_factor
sharpe_weight = 0.15 × adjustment_factor
overbought_w = 0.15 × (1 - (vol_ratio - 1.0) × 0.5) × adjustment_factor
vol_stability_w = 0.20 × (1 - (vol_ratio - 1.0) × 0.5) × adjustment_factor
valuation_w = 0.20 × adjustment_factor
参考输出(BULL 市况,vol_ratio=1.17,trend_strength=0.154):
| 因子 | 权重 |
| ------ | :----: |
| 动量 | 25.6% |
| 夏普 | 18.0% |
| 反追高 | 22.2% |
| 稳定性 | 22.2% |
| 估值锚 | 12.0% |
代码位置:multi_factor_selector.py:L51-L66(adaptive 配置)和 L98-L112(权重计算)
> 牛市中动量权重不会过高(~26%),反追高也不会过低(~22%)——系统在"追趋势"和"不追高"之间保持平衡。 > 熊市中反追高+稳定性的合计权重可达 60-70%,大幅转向防御。
---
截面选基聚焦高波动资产的进攻性选择,择时策略已经承担了防御职责。因此对所有低波动资产施加软惩罚:
| 类别 | 惩罚系数 | 被惩罚品种举例 |
| ------ | :------: | ------ |
| 债券(国债/信用债/可转债/利率债) | ×0.82 | 城投债ETF、十年国债ETF、可转债ETF |
| REITs | ×0.88 | 张江产业园REIT、普洛斯REIT、京东仓储REIT |
| 商品(贵金属) | ×0.95 | 黄金ETF(有避险价值,轻惩罚) |
代码位置:multi_factor_selector.py:L118-L133
> 这些惩罚是用分数软乘,而不是硬排除——如果某只债券确实综合表现极好,仍可能进 Top 20,但几乎不可能进 Top 10。
---
综合评分(未惩罚) = Σ(每个因子分 × 该因子权重)
综合评分(最终) = 未惩罚分 × 类别惩罚系数(如有)
因子分都是用 Rank 百分比(在所有 ETF 中的相对排名)标准化的,每个因子都是 0~1,越高越好。最终按降序排列得到全市场排名。
---
全市场排名(完整排序) 出来后,系统对这个排名做三道过滤来选出 Top N 投资组合:
A. 同克隆去重(两层):
underlying_index 相同,如 513100/159941 都跟 NASDAQ100)只留一只代表。有行情快照时按「组内成交额百分位 50% + 跨境溢价分 50%」综合打分选代表(不只看规模和因子分,避免挑到高溢价品种);无快照(历史回测)时回退为因子分最高者。FAMILY_MAX_MEMBERS = 6 防极端堆叠。无快照时保守回退为只留排名最高者。代码位置:diversification.py
例如:跟踪 NASDAQ100 的有 513100/513300/513390/159501 四只,只保留综合打分最高的那只;而 纳指ETF(513100) 与 纳指生物科技ETF(513290) 虽同属「纳指家族」,实测相关仅 0.200,可以同时持有。
B. 相关性剔除:从去重后的候选前若干名中,用贪心算法剔除两两相关性 ≥0.92 的冗余品种(不足时放宽到 0.95 补,仍不足则按排名补满)。
代码位置:diversification.py:L503-L548
1. 取排名前 top_n×6 只作为候选池
2. 计算候选池中所有配对的 60 日收益率相关性矩阵
3. 从排名最高的开始,依次加入结果集
4. 每次加入前检查:与已入选品种的相关性 < 0.92?
5. 若不够 top_n 只,放宽阈值至 0.95 再补
6. 仍不够则直接按排名补满
C. 大类+子类限数量:每个大类(宽基/行业/跨境/商品等)和子类有数量上限。
| 类别 | 上限 | 子类特别限制 |
| ------ | :---: | ------ |
| 跨境 | 4 | 美股≤3、港股≤1、日股≤1 |
| 行业主题 | 4 | 科技≤2、新能源≤2、医药≤2 |
| 宽基指数 | 3 | 大盘≤1、科创板≤2 |
| 债券 | 2 | 利率债≤1 |
| 商品 | 2 | 贵金属≤1 |
| REITs | 1 | — |
| 策略风格 | 2 | — |
> ⚠️ 重要区分:这三道过滤只用于生成消息推送中的投资组合(10 只买入清单)。网站展示的 Top 20/Bottom 10 是全市场排名,不受分散化过滤影响。
---
每只 ETF 权重 = 该 ETF 的评分 / Top 10 总评分 × 仓位上限
代码位置:engine.py:L384-L401
---
全市场 107 只 ETF 完整排名(不经过分散化过滤)存入 SQLite 数据库 cross_section_weekly_snapshot 表。每条记录包含排名、代码、名称、综合评分、类别、子类、四个因子值(动量/夏普/反追高/稳定性)、连续在榜周数、市场状态、仓位上限。
代码位置:cross_section.py
用途:
排名变化 列 = 前一期排名 - 本期排名)因子Δ = 本期因子分 - 前一期因子分)---
系统记录每只 ETF 连续上榜的周数,帮你区分真趋势和假突破:
| 状态 | 含义 |
| ------ | ------ |
| 🆕 新晋 | 本周刚进榜,可能是假突破 |
| 📈 趋势确认 | 连续 2-3 周在榜 |
| 🔥 持续强势 | 连续 4 周+在榜,置信度高 |
| ⚡ 回榜 | 曾上榜→掉出→又回来,轮动快 |
| 🔻 掉出 | 本周已不在 Top 10 |
代码位置:signal_tracker.py:L31-L71
---
你的系统会:
你的系统不会:
> 💡 系统不会但这些风险真实存在,怎么办? > 答案是把截面选基和择时策略结合起来——用择时控制仓位上限,用截面决定买什么。详见 04-选基择时融合方案.md。
---
📊 截面选基策略 | 2026-06-27
共扫描 107 只ETF
================================================================================
📈 🟢 强势牛市 | 仓位上限 95%
价格vs年线:+6.8% 波动率比:1.17x 趋势强度:0.154
↑ 价格比年线高6.8%,比年线低=熊市预警
↑ 当前波动只有历史1.17倍,<1.5说明不算异常高波
↑ 趋势强度0.154,在正常范围(不极弱也不极强)
因子权重(AdaptiveWeightEngine 实时计算):
动量 25.6% 夏普 18.0% 反追高 22.2% 稳定性 22.2% 估值锚 12.0%
Top10 因子分解:
动量 0.99 ← 近2-6个月涨了多少(经过R²趋势质量过滤+回撤惩罚)
夏普 0.86 ← 每承担1%波动赚了多少
反追高 0.45 ← 越低说明越超买(连续打分,科技股阈值更宽松)
稳定性 0.86 ← 越低说明波动越大
估值锚 0.35 ← 越低说明价格越偏离200日均线(偏贵)
趋势质量过滤:
R²=+0.81 (趋势稳定) ← 过去60天价格拟合直线,R²越高涨得越稳
dd=-1.6% (正常) ← 距120日高点仅回撤1.6%,几乎在新高
→ ×0.81 ← 动量保留81%,19%被趋势噪声过滤
---