第 9 章 · 01 Alpha 信号与 Selection 选股 本节摘要:本节进入全书第二个高潮(★)——Algorithm Framework 五层模型(约 0.75 万行)。这是 Lean"策略工程化/可组装"的核心:把一个完整策略拆成五个可独立替换的层——Alpha(生成信号)、Selection(选股)、Portfolio(组合构建)、Execution(执行)、Risk(风控),每层都是接口 + 多个实现,且每个实现都提供 .cs + .py 双版本。
本节摘要:本节进入全书第二个高潮(★)——Algorithm Framework 五层模型(约 0.75 万行)。这是 Lean"策略工程化/可组装"的核心:把一个完整策略拆成五个可独立替换的层——Alpha(生成信号)、Selection(选股)、Portfolio(组合构建)、Execution(执行)、Risk(风控),每层都是接口 + 多个实现,且每个实现都提供 .cs + .py 双版本。本节先钻取前两层:Alpha 层的
IAlphaModel.Update怎么把Slice转成Insight[](方向 Up/Down/Flat + 持续时间 + 置信度 + 标的),典型实现 RSI 跨越 30/70、EMA 快慢交叉、MACD、配对交易;Selection 层的IUniverseSelectionModel怎么定期刷新选股池(CoarseFundamental 粗筛 + FineFundamental 细筛 + QC500)。Selection 在另一个维度并行决定"标的有谁",与 Alpha 在"标的怎么交易"维度上正交组合。
内容来源:原项目源码
Algorithm.Framework/Alphas/、Algorithm.Framework/Selection/、Algorithm/Alphas/IAlphaModel.cs、Common/Algorithm/Framework/Alphas/Insight.cs,精读并套用体系化模板。
⚠️ 注意:本章是★高潮章,内容更充实。Framework 五层是一种与第 4 章 QCAlgorithm 不同的策略写作范式——传统范式继承
QCAlgorithm在OnData里写订单,Framework 范式用SetAlpha/SetUniverseSelection/...装配五个独立模型。两种范式可在同一算法里混用,但 Framework 更适合"模块化、可重用、可回测累积 alpha 评分"的场景。
阅读完本节,你应当能够:
Slice→Alpha→Insight[]→Portfolio→IPortfolioTarget[]→Risk→Execution→订单)。IAlphaModel.Update(QCAlgorithm, Slice)→IEnumerable<Insight> 接口。Insight 的核心字段(方向/持续时间/置信度/标的/周期)。IUniverseSelectionModel 怎么定期刷新,Coarse/Fine 两级筛选有何差别。回顾第 4 章,传统 Lean 策略继承 QCAlgorithm,在 OnData(Slice) 里手写订单逻辑。这种写法自由,但策略越大越乱——选股、信号、仓位、执行、风控代码全混在 OnData 里,改一处牵动全身。Framework 模型把这堆职责切成五段流水线,每段一个接口、独立测试、自由替换:
Slice(市场数据) │ ▼ 1. Alpha IAlphaModel.Update(algorithm, slice) → IEnumerable<Insight> │ (生成"我预测某标的会涨/跌"的信号) ▼ 2. Portfolio IPortfolioConstructionModel.CreateTargets(algorithm, insights) → IPortfolioTarget[] │ (把信号转成"每个标的目标持仓多少") ▼ 3. Risk IRiskManagementModel.ManageRisk(algorithm, targets) → filtered IPortfolioTarget[] │ (风控过滤,可减仓或清零) ▼ 4. Execution IExecutionModel.Execute(algorithm, targets) │ (按目标持仓下单,可拆单/限价) ▼ 真实订单
并行的还有第 0 层——Selection(Universe 选股),它独立决定"哪些标的进可交易池",与上面四层数据流在另一个维度上正交运行。
QCAlgorithm.Framework.cs:31-79 把五个接口挂成属性:
31 public partial class QCAlgorithm 32 { 49 public IUniverseSelectionModel UniverseSelection { get; set; } 55 public IAlphaModel Alpha { get; set; } 67 public IPortfolioConstructionModel PortfolioConstruction { get; set; } 73 public IExecutionModel Execution { get; set; } 79 public IRiskManagementModel RiskManagement { get; set; }
五个属性都默认指向 Null*Implementation(空实现),不调用 SetAlpha 等方法时就走传统 OnData 范式;一旦装配任意一层,五层责任链就激活。
💡 钻取要点:这是经典的管道-过滤器(Pipe-Filter)架构——每段单一职责,通过
IEnumerable<Insight>/IPortfolioTarget[]等强类型契约串联。改信号不动选股、改执行不动风控,每段都能单独替换、单独测试、单独积累"alpha 评分"做策略研究。这与第 3 章的 Handler 插件化是同一种设计哲学,只不过 Handler 钻取的是引擎层,Framework 钻取的是策略层。
五个接口怎么串起来?QCAlgorithm.Framework.cs:106-244 的两个方法是数据流的全部入口。OnFrameworkData(Slice) 由 AlgorithmManager(第 2 章)在每个时间步回调:
106 public void OnFrameworkData(Slice slice) 107 { 108 if (UtcTime >= UniverseSelection.GetNextRefreshTimeUtc()) 109 { // 到了刷新时点,重建 Universe(Selection 层独立运行) 126 foreach (var universe in UniverseSelection.CreateUniverses(this)) { ... } 141 } 144 Insights.Step(UtcTime); // 更新已有 insight 的累计评分 147 if (!slice.HasData) return; 153 var insightsEnumerable = Alpha.Update(this, slice); // ① Alpha 层 155 var insights = insightsEnumerable == Enumerable.Empty<Insight>() 156 ? new Insight[] { } : insightsEnumerable.ToArray(); 159 if (insights.Length != 0) { insights = InitializeInsights(insights); OnInsightsGenerated(insights); } 165 ProcessInsights(insights); // ②③④ 串接 Portfolio/Risk/Execution 166 }
而 ProcessInsights 把后三层串起来:
176 private void ProcessInsights(Insight[] insights) 177 { 179 var targetsEnumerable = PortfolioConstruction.CreateTargets(this, insights); // ② Portfolio 185 foreach (var target in targets) { Securities[target.Symbol].Holdings.Target = target; } 200 var riskTargetOverridesEnumerable = RiskManagement.ManageRisk(this, targets); // ③ Risk 227 riskAdjustedTargets = riskTargetOverrides.Concat(targets).DistinctBy(pt => pt.Symbol).ToArray(); 243 Execution.Execute(this, riskAdjustedTargets); // ④ Execution 244 }
注意 L227——风控产出的是"覆盖项"(riskTargetOverrides),与原 targets 按 Symbol 去重拼接,风控优先。这就是为什么风控"清零"持仓能生效:同一 Symbol 在覆盖表里出现,DistinctBy 保留前面的风控版本。
Algorithm/Alphas/IAlphaModel.cs:24-34:
24 public interface IAlphaModel : INotifiedSecurityChanges 25 { 33 IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data); 34 }
整个 Alpha 层就一个方法 Update(algorithm, slice),返回若干 Insight。INotifiedSecurityChanges 提供 OnSecuritiesChanged 回调,让模型在标的进出时清理/初始化自身状态(如指标预热)。
Insight.cs:32-106 是 Framework 的核心数据结构,描述"对某标的未来某段时间方向的预测":
32 public class Insight 39 { public Guid Id { get; } 71 public Symbol Symbol { get; } // 标的 76 public InsightType Type { get; } // Price/Volatility/... 91 public InsightDirection Direction { get; } // Up/Down/Flat 96 public TimeSpan Period { get; } // 预测生效周期 101 public double? Magnitude { get; } // 预测涨跌幅 106 public double? Confidence { get; } // 置信度 0-1 111 public double? Weight { get; } // 持仓权重提示 116 public InsightScore Score { get; } // 评分(回填:这个预测准不准)
方向枚举(InsightDirection.cs:25-41)是核心三态:
25 public enum InsightDirection 26 { 30 Down = -1, // 预测跌 35 Flat = 0, // 预测横盘 40 Up = 1 // 预测涨
Insight.Price(symbol, period, direction) 是最常见的构造快捷方式,生成"价格类型"的预测。Confidence 和 Magnitude 是可选的——只给方向也能跑,Portfolio 层的等权模型只用 Direction。
Algorithm.Framework/Alphas/RsiAlphaModel.cs 是最经典的入门实现。它用 Wilder 平滑(默认 14 周期)的 RSI,跌破 30 翻多(RSI 超卖反弹)、突破 70 翻空(超买回落)。核心 Update(RsiAlphaModel.cs:58-88):
58 public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data) 59 { 60 var insights = new List<Insight>(); 61 foreach (var kvp in _symbolDataBySymbol) 62 { 63 var rsi = kvp.Value.RSI; 64 var previousState = kvp.Value.State; 65 var state = GetState(rsi, previousState); 68 if (state != previousState && rsi.IsReady) 69 { 70 var insightPeriod = _resolution.ToTimeSpan().Multiply(_period); 72 switch (state) 73 { 74 case State.TrippedLow: // 跌破 30,预测上涨 75 insights.Add(Insight.Price(symbol, insightPeriod, InsightDirection.Up)); break; 78 case State.TrippedHigh: // 突破 70,预测下跌 79 insights.Add(Insight.Price(symbol, insightPeriod, InsightDirection.Down)); break; 82 } 83 } 84 kvp.Value.State = state; 85 } 87 return insights; 88 }
状态机 GetState(RsiAlphaModel.cs:140-169)用 35/65 做回弹缓冲,防止 RSI 在 30 附近抖动反复触发——这是"信号去抖"的标准工程做法。
| 实现 | 触发逻辑 |
|---|---|
EmaCrossAlphaModel |
快慢 EMA(默认 12/26)金叉翻 Up、死叉翻 Down |
MacdAlphaModel |
MACD 信号线交叉,默认 12/26/9 |
HistoricalReturnsAlphaModel |
用过去 N 日收益率作为预测方向 |
ConstantAlphaModel |
按配置固定输出 Up/Down,用于测试/基准 |
BasePairsTradingAlphaModel |
配对交易基类,价差偏离阈值翻向 |
PearsonCorrelationPairsTradingAlphaModel |
用皮尔逊相关性挑配对 + 价差阈值 |
EmaCrossAlphaModel.cs:67-95 的核心与 RSI 结构一致——遍历每个标的,检查"快慢线交叉事件"是否发生,发生就发 Insight。差异只在"用什么指标判断方向变化"。每个模型都有同名 .py 版本(Python.NET 互操作,逻辑一致)。
💡 钻取要点:所有 Alpha 模型都遵循同一模式——
Update内部维护每个 Symbol 的指标状态,只在"状态变化"(交叉/穿越/反转)时才发Insight,避免每个 bar 都刷信号。OnSecuritiesChanged负责在 Universe 进出新标的时用历史数据预热指标(algorithm.History(...).PushThrough(...)),让新标的一进入就有可用的指标值。
Selection 层独立于 Alpha→Portfolio→Risk→Execution 这条责任链,它回答的是"算法可交易的标的池里有哪些"。Algorithm/Selection/IUniverseSelectionModel.cs:26-39:
26 public interface IUniverseSelectionModel 27 { 31 DateTime GetNextRefreshTimeUtc(); // 下次刷新时点 38 IEnumerable<Universe> CreateUniverses(QCAlgorithm algorithm); // 创建选股宇宙 39 }
引擎在 OnFrameworkData 开头(L108)检查 UtcTime >= GetNextRefreshTimeUtc(),到点了就调 CreateUniverses 重建选股池。刷新频率可以是每日、每周、每月,由具体实现决定。
美股数千只股票,直接每只都跑基本面筛选太重。Lean 的标准做法是两级漏斗:
CoarseFundamentalUniverseSelectionModel 提供基类。QC500UniverseSelectionModel.cs:29-60 是经典示例——按月调仓、模拟标普 500 选股逻辑:
31 private const int _numberOfSymbolsCoarse = 1000; // 粗筛保留 1000 32 private const int _numberOfSymbolsFine = 500; // 细筛保留 500 35 private int _lastMonth = -1; // 月度调仓标记
策略:每月第一个交易日,从全市场按美元成交额排序取前 1000(Coarse),再按基本面取前 500(Fine)。这就是 Lean 复刻"S&P 500"选股的方法。
FineFundamentalUniverseSelectionModel:只做基本面细筛的基类。ETFConstituentsUniverseSelectionModel:选 ETF 成分股(如 SPY 成分股)。FutureUniverseSelectionModel / OptionUniverseSelectionModel:期货/期权合约链选股。ScheduledUniverseSelectionModel:按时间规则触发选股(每周/每月/自定义)。EnergyETFUniverse / MetalsETFUniverse / TechnologyETFUniverse 等内置行业 ETF 池。💡 钻取要点:Selection 在**"标的维度"上并行运行,与 Alpha 在"信号维度"**上正交。这意味着你可以:换 Selection(扩大/缩小选股池)不动 Alpha;或换 Alpha(换信号逻辑)不动 Selection。这种正交性是"策略工程化"的关键——研究员可独立研究每个维度,而第 6 章讲的 Universe 机制是它的底层支撑。
Slice→Alpha.Update→Insight[]→Portfolio.CreateTargets→IPortfolioTarget[]→Risk.ManageRisk→filtered→Execution.Execute→订单,管道-过滤器模式,每段单一职责。OnData 范式,装配任意一层激活五层链。装配方法是 SetAlpha/SetUniverseSelection/SetPortfolioConstruction/SetExecution/SetRiskManagement。IAlphaModel.Update(QCAlgorithm,Slice)→IEnumerable<Insight>,极简一个方法;INotifiedSecurityChanges.OnSecuritiesChanged 负责标的进出时的指标预热与清理。.cs + .py 双版本。IUniverseSelectionModel(GetNextRefreshTimeUtc + CreateUniverses),在标的维度并行运行,与 Alpha 信号维度正交。下一节钻取 Framework 第三层——Portfolio 组合构建与优化器(★),这是全书量化含量最高的一节:Black-Litterman、Markowitz 均值方差、最大夏普、风险平价四个组合优化器,以及独立成类的
IPortfolioOptimizer数学求解。