第 7 章 · 02 QCAlgorithm.Indicators 便捷方法 本节摘要:本节钻取 (partial 类,约 4607 行)——这是用户最常打交道的指标入口。它把上一节"new 指标 + 手动 Update + 自己接 Consolidator + 自己预热"的一整套繁琐流程,封装成一行工厂方法: / / / / / / 等。这些方法做四件事:(1) new 出指标实例;(2) 自动接好 Consolidator,数据来了自动喂指标(用户无需手动 Update);(3) 用 History 拿历史数据填满指标窗口,避免回测初期 IsReady=false 的"无效期";(4) 配合 (Plus/Minus/Times/Over/Of)实现指标间组合运算。
本节摘要:本节钻取
QCAlgorithm.Indicators.cs(partial 类,约 4607 行)——这是用户最常打交道的指标入口。它把上一节"new 指标 + 手动 Update + 自己接 Consolidator + 自己预热"的一整套繁琐流程,封装成一行工厂方法:SMA(symbol, period)/EMA/RSI/MACD/BB/STOCH/ATR等。这些方法做四件事:(1) new 出指标实例;(2)RegisterIndicator自动接好 Consolidator,数据来了自动喂指标(用户无需手动 Update);(3)WarmUpIndicator用 History 拿历史数据填满指标窗口,避免回测初期 IsReady=false 的"无效期";(4) 配合IndicatorExtensions(Plus/Minus/Times/Over/Of)实现指标间组合运算。结果就是用户一行var sma = SMA("SPY", 14);即可获得自动预热、自动更新的均线,极大降低使用门槛。读完本节,你理解了 Lean 指标"便捷层"的全部自动化逻辑。
内容来源:原项目源码
Algorithm/QCAlgorithm.Indicators.cs、Indicators/IndicatorExtensions.cs、Indicators/CompositeIndicator.cs,精读并套用体系化模板。
⚠️ 注意:本节是指标体系的"用户视角"——便捷工厂方法 + 自动管理。与上一节手动 new + Update 形成对照。
阅读完本节,你应当能够:
SMA(symbol, period) 创建并注册指标。InitializeIndicator 内部做的三件事(Register / 自动 WarmUp)。Algorithm/QCAlgorithm.Indicators.cs:2195 的 SMA 是模板:
2195 public SimpleMovingAverage SMA(Symbol symbol, int period, Resolution? resolution = null, 2195 Func<IBaseData, decimal> selector = null) 2196 { 2197 var name = CreateIndicatorName(symbol, $"SMA({period})", resolution); 2198 var simpleMovingAverage = new SimpleMovingAverage(name, period); // ① new 2199 InitializeIndicator(simpleMovingAverage, resolution, selector, symbol); // ② 接线 + 预热 2200 return simpleMovingAverage; 2202 }
所有便捷方法都是这个三步模板:
CreateIndicatorName 生成统一命名(如 SPY_SMA(14)_minute)。.Current.Value。其他方法同理,只是参数不同。EMA(811)、RSI(1959)、MACD(1398)、BB(402)、ATR(363)每个都是几十行,核心都是 new + InitializeIndicator。
| 方法 | 签名要点 | 返回类型 |
|---|---|---|
SMA(symbol, period, resolution?, selector?) |
单价输入 | SimpleMovingAverage |
EMA(symbol, period, ...) |
单价输入 | ExponentialMovingAverage |
RSI(symbol, period, movingAverageType?, ...) |
Wilders 默认 | RelativeStrengthIndex |
MACD(symbol, fast, slow, signal, ...) |
多参数 | MovingAverageConvergenceDivergence |
BB(symbol, period, k, ...) |
布林带 | BollingerBands |
ATR(symbol, period, movingAverageType?, ...) |
需 HLC | AverageTrueRange |
STOCH(...) |
随机指标 | Stochastic |
💡 钻取要点:
selector参数让你选择"喂指标哪个字段"。默认是x => x.Value(收盘价),也可传x => x.High(给最高价)或x => ((TradeBar)x).Volume(给成交量)。这让同一指标可用于任意数据维度。
QCAlgorithm.Indicators.cs:4378:
4378 private void InitializeIndicator(IndicatorBase<IndicatorDataPoint> indicator, 4378 Resolution? resolution = null, Func<IBaseData, decimal> selector = null, 4378 params Symbol[] symbols) 4381 var dataType = GetDataTypeFromSelector(selector); 4382 foreach (var symbol in symbols) 4384 RegisterIndicator(symbol, indicator, ResolveConsolidator(symbol, resolution, dataType), selector); 4387 if (Settings.AutomaticIndicatorWarmUp) 4389 WarmUpIndicator(symbols, indicator, resolution, selector);
两件事:
Settings.AutomaticIndicatorWarmUp = true(默认),立刻调 WarmUpIndicator,用历史数据把指标窗口填满。这就是为什么回测里 SMA("SPY", 14) 拿到的指标在第一根 bar 就 IsReady——它背后已经偷偷喂了 14 根历史数据。
QCAlgorithm.Indicators.cs:3316(带 consolidator 的重载):
3316 public void RegisterIndicator<T>(Symbol symbol, IndicatorBase<T> indicator, 3316 IDataConsolidator consolidator, Func<IBaseData, T> selector = null) 3320 var selectorToUse = selector ?? (x => (T)x); 3322 RegisterConsolidator(symbol, consolidator, null, indicator); // consolidator 加进订阅 3343 consolidator.DataConsolidated += (sender, consolidated) => 3345 var value = selectorToUse(consolidated); 3346 indicator.Update(value); // 每聚合一根 bar,自动喂指标 3347 };
核心是 consolidator.DataConsolidated += ... 这一行——把"consolidator 每产出一根 bar"与"指标 Update"事件绑定。从此用户无需手动 Update,数据流自动驱动指标。
RegisterConsolidator(4450)还把 consolidator 引用存进 indicator.Consolidators(上一节 IndicatorBase 的字段),便于后续 UnregisterIndicator 一起拆掉。
QCAlgorithm.Indicators.cs:3517 起的多个 WarmUpIndicator 重载,核心实现 GetIndicatorWarmUpHistory(3541):
3544 if (!AssertIndicatorHasWarmupPeriod(indicator)) return Enumerable.Empty<Slice>(); 3549 var periods = ((IIndicatorWarmUpPeriodProvider)indicator).WarmUpPeriod; // 取预热期 3558 periods *= (int)(timeSpan.Ticks / resolutionTicks); // 换算成根数 3563 return History(symbols, periods, resolution, ...); // 拿历史
逻辑:
IIndicatorWarmUpPeriodProvider,否则给警告跳过。History(symbols, periods, resolution) 拉历史 Slice。WarmUpIndicatorImpl 里)。预热完成后,指标的 Samples 已达 WarmUpPeriod,IsReady 立即为 true。回测里这意味着"算法启动那一刻,指标就可用";实盘里这避免了"刚启动指标无效"的窗口期。
💡 钻取要点:预热是用
History拉的数据——所以预热效果取决于历史数据是否可得。无历史数据(如刚上市的新股、自定义数据无存档)的标的,预热会拿到空集合,指标仍处于 IsReady=false。策略里若要严格依赖指标就绪,仍应自己检查IsReady。
Indicators/IndicatorExtensions.cs 提供一组扩展方法,把指标当算子组合。116/175/263/219:
116 public static CompositeIndicator Plus(this IndicatorBase left, decimal constant) 131 public static CompositeIndicator Plus(this IndicatorBase left, IndicatorBase right) 175 public static CompositeIndicator Minus(this IndicatorBase left, IndicatorBase right) 219 public static CompositeIndicator Over(this IndicatorBase left, IndicatorBase right) // 除 263 public static CompositeIndicator Times(this IndicatorBase left, IndicatorBase right) // 乘
每个返回 CompositeIndicator(Indicators/CompositeIndicator.cs:32):
32 public class CompositeIndicator : IndicatorBase<IndicatorDataPoint> 85 public CompositeIndicator(string name, IndicatorBase left, IndicatorBase right, IndicatorComposer composer)
CompositeIndicator 持有 left/right 两个子指标 + 一个 composer(怎么组合,如加减乘除)。当任一子指标 Updated 时,Composite 重新计算自己的值。
典型用法——价差(两个 SMA 的差):
var smaFast = SMA("SPY", 10); var smaSlow = SMA("SPY", 30); var spread = smaFast.Minus(smaSlow); // 一个新的 CompositeIndicator // spread.Current.Value 就是两条均线的差,自动更新
Of 方法(IndicatorExtensions.cs:50)则用于"级联"——让一个指标的输出喂给另一个:
50 public static T Of<T>(this T second, IIndicator first, bool waitForFirstToReady = true) 53 first.Updated += (sender, consolidated) => 56 if (!waitForFirstToReady || first.IsReady) 58 second.Update(consolidated); // first 的输出 → second 的输入
例如 EMA.Of(SMA(...)):SMA 每出一个值就喂给 EMA,形成"SMA 的 EMA"复合指标。waitForFirstToReady 默认 true——上游没 ready 不喂下游,避免污染。
同一件事两种写法:
手动(上一节风格):
var sma = new SimpleMovingAverage(14); var consolidator = new TradeBarConsolidator(TimeSpan.FromMinutes(1)); consolidator.DataConsolidated += (s, bar) => sma.Update(bar); SubscriptionManager.AddConsolidator(symbol, consolidator); // 预热?要自己写 History 循环 foreach (var bar in History<TradeBar>(symbol, 14, Resolution.Minute)) sma.Update(bar); // 现在才能用 sma
便捷(本节风格):
var sma = SMA("SPY", 14, Resolution.Minute); // 一行,自动注册 + 自动预热
便捷方法把上面 6+ 行压缩成 1 行,且保证:consolidator 类型正确(按 resolution 解析)、selector 默认取 close、自动预热、命名规范。对绝大多数场景,便捷方法就是最佳选择;只有需要非常规接线(多 symbol 合流、自定义 consolidator 逻辑)时才退回手动。
💡 钻取要点:便捷方法返回的就是普通的 Indicator 实例,所有上一节讲的手动操作(读 Current.Value、检查 IsReady、接 Updated 事件、Reset)依然适用。便捷层不改变指标语义,只是省去接线样板代码。这也是为什么 Lean 文档里 99% 的示例都用便捷方法。
把本章两节串起来,一个完整的指标驱动策略骨架:
public class MyAlgorithm : QCAlgorithm { private SimpleMovingAverage _smaFast; private RelativeStrengthIndex _rsi; private CompositeIndicator _spread; public override void Initialize() { _smaFast = SMA("SPY", 10); // 自动注册+预热 _rsi = RSI("SPY", 14); // 自动注册+预热 var smaSlow = SMA("SPY", 30); _spread = _smaFast.Minus(smaSlow); // 组合:快线-慢线 } public override void OnData(Slice data) { if (!_smaFast.IsReady) return; // 通常已预热,双保险 if (_spread.Current.Value > 0 && _rsi.Current.Value < 30) SetHoldings("SPY", 1.0); // 金叉+超卖,做多 } }
完整链路:SMA("SPY", 10) 一行 → 引擎自动建 consolidator → 历史数据预热 → 实时数据自动 Update → _smaFast.Current.Value 立即可用 → Minus 组合成价差 → 策略读值决策。用户全程没写一行 Update。
SMA/EMA/RSI/MACD/BB/ATR/STOCH 等都是 new + InitializeIndicator + return 三步模板。RegisterIndicator 接 consolidator;若 AutomaticIndicatorWarmUp 则 WarmUpIndicator。consolidator.DataConsolidated 事件与 indicator.Update 绑定,数据自动驱动指标,用户无需手动 Update。History 拉历史数据按序 Update,填满 WarmUpPeriod,让指标启动即 IsReady。Of 实现级联(first 的输出喂 second)。第 7 章结束。你已经掌握了 Lean 的 160+ 指标体系,从基类机制到便捷工厂方法。下一章(第 8 章)钻取订单与成交模型——指标驱动下单。