第 7 章 · 01 Indicators 项目与指标基类 本节摘要:本节钻取 Lean 的第 7 层——Indicators 项目(约 3.1 万行、231 个 .cs 文件、166+ 个指标)。从 AbsolutePriceOscillator 绝对价格振荡、BollingerBands 布林带、MACD、RSI,到 ConnorsRSI、CoppockCurve、ChandeKrollStop,覆盖趋势/动量/波动/成交量/统计五大类;另有 CandlestickPatterns 子目录收录 Doji 十字星、Hammer 锤子、ShootingStar 流星、Engulfing 吞没等 K 线形态,识别后返回 CandlestickPattern。
本节摘要:本节钻取 Lean 的第 7 层——Indicators 项目(约 3.1 万行、231 个 .cs 文件、166+ 个指标)。从 AbsolutePriceOscillator 绝对价格振荡、BollingerBands 布林带、MACD、RSI,到 ConnorsRSI、CoppockCurve、ChandeKrollStop,覆盖趋势/动量/波动/成交量/统计五大类;另有 CandlestickPatterns 子目录收录 Doji 十字星、Hammer 锤子、ShootingStar 流星、Engulfing 吞没等 K 线形态,识别后返回 CandlestickPattern。统一基类是
IndicatorBase<T>,核心机制是Update(input)——接收一个数据点,内部更新状态,返回当前值;IsReady判断窗口是否填满。指标分 DataPoint 型(只吃单一价 close)和 Bar 型(吃 OHLCV 的 TradeBar/QuoteBar),由 Consolidator 聚合器把高频数据聚成低频再喂指标。读完本节,你掌握了 Lean 指标体系的骨架与 Update 机制。
内容来源:原项目源码
Indicators/IndicatorBase.cs、BarIndicator.cs、Indicator.cs、RelativeStrengthIndex.cs、CandlestickPatterns/CandlestickPattern.cs,以及 Indicators 目录结构,精读并套用体系化模板。
⚠️ 注意:本节聚焦指标基类与 Update 机制(怎么手动 new 指标 + Update),便捷工厂方法(SMA/EMA 一行创建)在下一节。
阅读完本节,你应当能够:
IndicatorBase<T> 与 Update(input) 机制。Indicator)与 Bar 型(BarIndicator)。Indicators/ 下一级有 166 个 .cs 文件(加上 CandlestickPatterns 子目录共 231 个),按功能粗分:
| 分类 | 代表指标 |
|---|---|
| 趋势 | SimpleMovingAverage / ExponentialMovingAverage / DoubleExponentialMovingAverage / HullMovingAverage / KaufmanAdaptiveMovingAverage / ArnaudLegouxMovingAverage / MACD / IchimokuKinkoHyo |
| 动量 | RelativeStrengthIndex / Momentum / RateOfChange / CommodityChannelIndex / ChandeMomentumOscillator / ConnorsRelativeStrengthIndex / CoppockCurve / WilliamsPercentR / AwesomeOscillator |
| 波动 | BollingerBands / AverageTrueRange / StandardDeviation / DonchianChannel / KeltnerChannels / ChandeKrollStop / ChoppinessIndex / MassIndex |
| 成交量 | Volume / OnBalanceVolume / AccumulationDistribution / ChaikinMoneyFlow / MoneyFlowIndex / ForceIndex / KlingerVolumeOscillator / ArmsIndex |
| 统计/其他 | Beta / Alpha / Correlation / Covariance / HurstExponent / LogReturn / LeastSquaresMovingAverage / AugenPriceSprite / AutoRegressiveIntegratedMovingAverage |
文件名即指标名,一目了然。CompositeIndicator.cs 支持指标间的加减乘除组合(见下一节 IndicatorExtensions)。
Indicators/IndicatorBase.cs:28(非泛型基类,放公共字段):
28 public abstract partial class IndicatorBase : WindowBase<IndicatorDataPoint>, IIndicator 35 public ISet<IDataConsolidator> Consolidators { get; } // 关联的聚合器(便于反注册) 41 public override IndicatorDataPoint Previous { get; } // 上一个值 46 public string Name { get; protected set; } 51 public long Samples { get; internal set; } // 已处理样本数 56 public abstract bool IsReady { get; } // 是否就绪 61 public event IndicatorUpdatedHandler Updated; // 更新事件 101 public abstract bool Update(IBaseData input); // 喂数据的入口
IndicatorBase.cs:164(泛型基类,带输入类型):
164 public abstract class IndicatorBase<T> : IndicatorBase 165 where T : IBaseData 186 public override bool Update(IBaseData input) 273 protected abstract decimal ComputeNextValue(T input); // 子类实现:算下一个值
核心抽象就两个:
Update(input):外部喂一个数据点进来。ComputeNextValue(input):子类实现的"怎么算"。基类 Update 做通用工作:防重复、防倒序(191-197 行的 forward-only 检查)、计 Samples、调子类 ComputeNextValue、写 Current、触发 Updated 事件。
Indicators/IndicatorBase.cs:186 完整逻辑:
186 public override bool Update(IBaseData input) 188 T _previousSymbolInput = default(T); 189 if (_previousInput.TryGetValue(input.Symbol.ID, out _previousSymbolInput) 189 && input.EndTime < _previousSymbolInput.EndTime) 191 if (!_loggedForwardOnlyIndicatorError) 193 _loggedForwardOnlyIndicatorError = true; 195 Log.Error($"...This is a forward only indicator..."); // 倒序拒绝 197 return IsReady; 199 if (!ReferenceEquals(input, _previousSymbolInput)) 202 Samples++; // 计数 204 if (!(input is T)) 206 if (typeof(T) == typeof(IndicatorDataPoint)) 208 input = new IndicatorDataPoint(input.Symbol, input.EndTime, input.Value); // 自动装箱 215 _previousInput[input.Symbol.ID] = (T)input; 217 var nextResult = ValidateAndComputeNextValue((T)input); 218 if (nextResult.Status == IndicatorStatus.Success) 220 Current = new IndicatorDataPoint(input.Symbol, input.EndTime, nextResult.Value); 223 OnUpdated(Current); // 触发事件 226 return IsReady;
四步:
💡 钻取要点:
_previousInput是按 Symbol.ID 存的字典——支持一个指标被多个标的的 Updated 事件级联喂入(IndicatorExtensions 组合时常见)。每个标的独立维护"上次时间",互不干扰。
指标按输入类型分两大族:
Indicators/Indicator.cs:22:
22 public abstract class Indicator : IndicatorBase<IndicatorDataPoint>
Indicators/BarIndicator.cs:25:
25 public abstract class BarIndicator : IndicatorBase<IBaseDataBar>
Indicator):只吃一个标量值(通常是 close)。代表:SMA、EMA、RSI、MACD、Momentum。BarIndicator):吃完整的 OHLCV bar(实现 IBaseDataBar,即 TradeBar/QuoteBar)。代表:AverageTrueRange(用 HLC)、BollingerBands(用 close 但也接受 bar)、BalanceOfPower(用 OHLC)。TradeBar/QuoteBar 都实现 IBaseDataBar,所以 Bar 型指标两者都能吃。DataPoint 型指标靠基类的自动装箱(206-208 行)也能吃 bar——它只取 input.Value(即收盘价)。
IndicatorBase.cs:56:
56 public abstract bool IsReady { get; } // 窗口是否填满
每个指标自己定义 IsReady。看 RSI(Indicators/RelativeStrengthIndex.cs:71):
71 public override bool IsReady => AverageGain.IsReady && AverageLoss.IsReady; 76 public int WarmUpPeriod { get; } // period + 1
RSI 内部由 AverageGain/AverageLoss 两个子指标组成,IsReady 就是两者都 IsReady。WarmUpPeriod(来自 IIndicatorWarmUpPeriodProvider)告诉引擎"至少喂多少个点才 ready"——RSI(14) 的 WarmUpPeriod 是 15。
策略里典型写法:
if (!_rsi.IsReady) return; // 还没攒够 15 根,不算信号不下单 var value = _rsi.Current.Value;
这和 vnpy 的 am.inited 机制(攒够 100 根)是同一思想,只是 Lean 用 IsReady 精确到每个指标自己的周期。
RelativeStrengthIndex.cs:83:
83 protected override decimal ComputeNextValue(IndicatorDataPoint input) 85 if (_previousInput != null && input.Value >= _previousInput.Value) 87 AverageGain.Update(input.EndTime, input.Value - _previousInput.Value); // 涨幅 88 AverageLoss.Update(input.EndTime, 0m); 90 else if (_previousInput != null && input.Value < _previousInput.Value) 92 AverageGain.Update(input.EndTime, 0m); 93 AverageLoss.Update(input.EndTime, _previousInput.Value - input.Value); // 跌幅 96 _previousInput = input; 98 // 防负值(某些 MA 如 DEMA 可能算出负) 100 var averageLoss = AverageLoss < 0 ? 0 : AverageLoss.Current.Value; 101 var averageGain = AverageGain < 0 ? 0 : AverageGain.Current.Value;
RSI 的算法:逐根比较当前价与上一价,涨了把涨幅喂给 AverageGain、跌幅喂给 AverageLoss,最后 100 - 100/(1 + avgGain/avgLoss)。注意它复用了 AverageGain/AverageLoss 两个子指标——Lean 的指标是可组合的,大指标由小指标拼装。
指标通常不要 tick 级噪声,要分钟/小时 bar。Consolidator(聚合器)负责把高频数据聚成低频 bar,再喂给指标:
典型手动用法:
var sma = new SimpleMovingAverage(14); var consolidator = new TradeBarConsolidator(TimeSpan.FromMinutes(1)); consolidator.DataConsolidated += (sender, bar) => sma.Update(bar); // 数据流接入 consolidator 后,每分钟一根 bar 喂给 sma
IndicatorBase.Consolidators(35 行)就是用来保存指标关联的 consolidator 引用,方便后续 Unregister(反注册时连同 consolidator 一起拆)。下一节讲的便捷方法 SMA(symbol, 14) 会自动完成这整套接线。
Indicators/CandlestickPatterns/ 下约 60 个形态:Doji(十字星)、Hammer(锤子)、ShootingStar(流星)、Engulfing(吞没)、Harami(孕线)、MorningStar(晨星)、EveningStar(暮星)、ThreeWhiteSoldiers(三白兵)等。
CandlestickPatterns/CandlestickPattern.cs:25(基类,继承 BarIndicator):
25 public abstract class CandlestickPattern : BarIndicator 31 protected CandlestickPattern(string name, int period) : base(name, period) 40 protected static CandleColor GetCandleColor(IBaseDataBar tradeBar) 42 return tradeBar.Close >= tradeBar.Open ? CandleColor.White : CandleColor.Black; 49 protected static decimal GetRealBody(IBaseDataBar tradeBar) 52 return Math.Abs(tradeBar.Close - tradeBar.Open); // 实体 58 protected static decimal GetHighLowRange(IBaseDataBar tradeBar) 60 return tradeBar.High - tradeBar.Low; // 全距
形态识别都基于实体的几个几何量:RealBody(实体)、HighLowRange(全距)、UpperShadow/LowerShadow(上下影线)。识别成功返回 CandlestickPattern(值 > 0 表示看涨形态,< 0 看跌,= 0 不匹配),它本身也是 Indicator,可以塞进策略链路。
💡 钻取要点:K 线形态本质是"模式匹配"指标——输入 TradeBar,输出"是否匹配某形态 + 强度"。Lean 用 talib 风格的 C 函数移植,形态定义参数(实体阈值、影线比例)集中在
CandleSettings,可全局调整。
Update(input) + 子类 ComputeNextValue(input)。Indicator) 吃单一价(close),Bar 型(BarIndicator) 吃 OHLCV bar。IndicatorBase.Consolidators 保存引用便于反注册。下一节,我们看
QCAlgorithm.Indicators.cs——一行SMA("SPY", 14)如何自动创建指标、注册 consolidator、预热,极大降低使用门槛。