第 9 章 · 03 Execution 执行与 Risk 风控 本节摘要:本节钻取 Framework 五层的最后两层——Execution(执行)与 Risk(风控),并用 五件套演示完整组装。Execution 层的 把"目标持仓"变成"真实订单",策略空间在于怎么拆—— 一把全发、 按 VWAP 拆单避开冲击、 按波动率分散、 按价差驱动。Risk 层的 是风控闸门,可减仓或清零目标持仓,典型实现有 (单标的最大回撤)、 (组合最大回撤)、 (行业暴露)、 (最大浮盈)、 (移动止损)。读完本节,Framework 五层全部讲清,你能用五个 装配一个完整可组装策略。 内容来源:原项目源码 、 、 、 、 ,精读并套用体系化模板。
本节摘要:本节钻取 Framework 五层的最后两层——Execution(执行)与 Risk(风控),并用
BasicTemplateFrameworkAlgorithm五件套演示完整组装。Execution 层的IExecutionModel.Execute(QCAlgorithm, IPortfolioTarget[])把"目标持仓"变成"真实订单",策略空间在于怎么拆——ImmediateExecutionModel一把全发、VolumeWeightedAveragePriceExecutionModel按 VWAP 拆单避开冲击、StandardDeviationExecutionModel按波动率分散、SpreadExecutionModel按价差驱动。Risk 层的IRiskManagementModel.ManageRisk(QCAlgorithm, IPortfolioTarget[])→filtered是风控闸门,可减仓或清零目标持仓,典型实现有MaximumDrawdownPercentPerSecurity(单标的最大回撤)、MaximumDrawdownPercentPortfolio(组合最大回撤)、MaximumSectorExposure(行业暴露)、MaximumUnrealizedProfitPercentPerSecurity(最大浮盈)、TrailingStopRiskManagementModel(移动止损)。读完本节,Framework 五层全部讲清,你能用五个Set*装配一个完整可组装策略。
内容来源:原项目源码
Algorithm.Framework/Execution/、Algorithm.Framework/Risk/、Algorithm/Execution/IExecutionModel.cs、Algorithm/Risk/IRiskManagementModel.cs、Algorithm.CSharp/BasicTemplateFrameworkAlgorithm.cs,精读并套用体系化模板。
⚠️ 注意:Execution 与 Risk 是两个职责相反的层——Execution 想尽快达成目标持仓,Risk 想在亏损/暴露超限时减仓或清零。它们通过
IPortfolioTarget这一数据契约协作:Portfolio 输出目标 → Risk 过滤覆盖 → Execution 执行剩余。
阅读完本节,你应当能够:
IExecutionModel.Execute + OnOrderEvent 接口,以及它与 Portfolio 层目标的差异。IRiskManagementModel.ManageRisk 是"过滤/覆盖"语义而非"生成"。BasicTemplateFrameworkAlgorithm 的五件套装配方法写一个最小 Framework 策略。Algorithm/Execution/IExecutionModel.cs:24-41:
24 public interface IExecutionModel : INotifiedSecurityChanges 25 { 33 void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets); 40 void OnOrderEvent(QCAlgorithm algorithm, OrderEvent orderEvent); 41 }
两个方法:Execute 接收组合层 + 风控层过滤后的目标持仓数组,把它们转成订单;OnOrderEvent 在订单成交/取消/拒绝时回调,让执行模型更新内部状态(如已成交量、待发量)。
注意 Execute 注释的关键——"This model is free to delay or spread out these orders as it sees fit"(模型可以自由延迟或分散下单)。这就是 Execution 层的策略空间:不一定要一次性下完整目标数量。大单拆小单、择时下单、按市场流动性分批,都由具体模型决定。
| 模型 | 拆单策略 | 用途 |
|---|---|---|
ImmediateExecutionModel |
一把全发市价单 | 默认/小资金,简单快速 |
VolumeWeightedAveragePriceExecutionModel |
按 VWAP 与当前价对比,顺势拆单 | 大单避免冲击成本 |
StandardDeviationExecutionModel |
按价格波动率(2σ)分散下单 | 高波动品种避免追涨杀跌 |
SpreadExecutionModel |
按买卖价差驱动,价差窄才下 | 流动性差的品种等价差收窄 |
继承 ExecutionModel 基类,Execute 内部对每个 target 调 algorithm.MarketOrder(symbol, quantity) 立即下市价单。没有拆单逻辑,适合资金量小或测试用。
VolumeWeightedAveragePriceExecutionModel.cs:32-79:
32 public class VolumeWeightedAveragePriceExecutionModel : ExecutionModel 33 { 34 private readonly PortfolioTargetCollection _targetsCollection = new PortfolioTargetCollection(); 35 private readonly Dictionary<Symbol, SymbolData> _symbolData = new Dictionary<Symbol, SymbolData>(); 42 public decimal MaximumOrderQuantityPercentVolume { get; set; } = 0.01m; // 默认每单不超过当前 bar 成交量的 1% 59 public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets) 60 { 62 _targetsCollection.AddRange(targets); // 收集目标,允许跨 bar 慢慢执行 65 if (!_targetsCollection.IsEmpty) 66 { 67 foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm)) 68 { 72 var unorderedQuantity = OrderSizing.GetUnorderedQuantity(algorithm, target); // 还差多少没下 74 SymbolData data; 75 if (!_symbolData.TryGetValue(symbol, out data)) continue; // 仅当当前价 < VWAP(买入有利)或 > VWAP(卖出有利)才下单,顺势拆 // 单笔数量限制为 Max(当前成交量 × 1%, 目标剩余量) 80 ...
核心机制:只有当前价格对执行方有利时才下单(买入时价低于 VWAP,卖出时价高于 VWAP),且每单最多占当前 bar 成交量的 1%(MaximumOrderQuantityPercentVolume = 0.01m)。这样大单被分散到多个 bar、多个有利时点慢慢执行,避免一次性大单冲击市场价格。
PortfolioTargetCollection 缓存未完成的目标,允许跨多个 Execute 调用累积执行,这是"拆单"的关键——一个目标可以拆成几十笔小单分多次完成。
💡 钻取要点:VWAP 模型体现"算法交易(Algo Trading)"的精髓——把大单转成"看不见的小单流",规避冲击成本(market impact)。实盘里如果一个大单一次性砸进去,会吃掉整层 order book,实际成交均价远劣于信号生成时的价格。Lean 把这套机构级拆单逻辑封装成一个可插拔的 Execution 模型,小散户用 Immediate 即可,机构资金用 VWAP 必备。
Algorithm/Risk/IRiskManagementModel.cs:24-32:
24 public interface IRiskManagementModel : INotifiedSecurityChanges 25 { 31 IEnumerable<IPortfolioTarget> ManageRisk(QCAlgorithm algorithm, IPortfolioTarget[] targets); 32 }
接口签名和 Portfolio 层很像(都吃 IPortfolioTarget[],返回 IEnumerable<IPortfolioTarget>),但语义相反——Risk 层不生成目标,只过滤或覆盖。回顾第 01 节 QCAlgorithm.Framework.cs:200-227,风控产出的是"覆盖项"(riskTargetOverrides),与原目标按 Symbol 去重,风控优先。
风控输出场景:
PortfolioTarget(symbol, 减少后的 quantity)。PortfolioTarget(symbol, 0),触发平仓。Algorithm.Framework/Risk/MaximumDrawdownPercentPerSecurity.cs:27-72 是最经典的风控实现——单标的浮亏超阈值就平仓:
27 public class MaximumDrawdownPercentPerSecurity : RiskManagementModel 28 { 29 private readonly decimal _maximumDrawdownPercent; 36 public MaximumDrawdownPercentPerSecurity(decimal maximumDrawdownPercent = 0.05m) // 默认 5% 40 _maximumDrawdownPercent = -Math.Abs(maximumDrawdownPercent); // 取负,统一比较 48 public override IEnumerable<IPortfolioTarget> ManageRisk(QCAlgorithm algorithm, IPortfolioTarget[] targets) 49 { 50 foreach (var kvp in algorithm.Securities) 51 { 52 var security = kvp.Value; 54 if (!security.Invested) continue; // 只看持仓中的标的 59 var pnl = security.Holdings.UnrealizedProfitPercent; // 当前浮盈百分比 60 if (pnl < _maximumDrawdownPercent) // 浮亏超阈值(如 -5%) 61 { 62 var symbol = security.Symbol; 65 algorithm.Insights.Cancel(new[] { symbol }); // 取消相关 Insight,停止 alpha 评分 68 yield return new PortfolioTarget(symbol, 0); // 清零 → 平仓 69 } 70 } 71 } 72 }
三个细节体现工程素养:(1) L40 -Math.Abs(...) 强制取负,防止用户误传正数;(2) L54 !security.Invested 跳过空仓,空仓标的没必要查浮亏;(3) L65 Insights.Cancel 取消 Insight——平仓同时停掉该标的的 alpha 评分(否则 Insight 还在算分,会污染 alpha 报表)。
| 实现 | 触发条件 | 动作 |
|---|---|---|
MaximumDrawdownPercentPortfolio |
组合整体回撤超阈值 | 全组合按比例减仓 |
MaximumSectorExposureRiskManagementModel |
某行业总持仓占比超阈值 | 削减该行业仓位 |
MaximumUnrealizedProfitPercentPerSecurity |
单标的浮盈超阈值(止盈) | 平仓锁定利润 |
TrailingStopRiskManagementModel |
移动止损:从最高点回撤超阈值 | 平仓保住浮盈 |
TrailingStopRiskManagementModel 维护每个标的自建仓以来的最高价,从最高点回撤超阈值(如 5%)就平仓——这是趋势跟踪策略的标准风控,让利润奔跑同时在回撤时及时离场。
MaximumSectorExposureRiskManagementModel 是组合级风控——避免某一行业(如科技)仓位过重,行业系统性风险爆发时损失可控。它依赖标的的行业分类(Fundamental.AssetClassification),按行业聚合后比较。
💡 钻取要点:风控可以串联多个——
SetRiskManagement设置一个,AddRiskManagement追加(内部组成CompositeRiskManagementModel)。实战常见组合:先加MaximumDrawdownPercentPortfolio(组合级止损,防爆仓)+MaximumUnrealizedProfitPercentPerSecurity(止盈)+TrailingStopRiskManagementModel(趋势跟踪)。多个风控按顺序依次过滤,任何一个清零都生效。这种"风控链"思路与第 3 章 Handler 插件化异曲同工。
Algorithm.CSharp/BasicTemplateFrameworkAlgorithm.cs:39-67 是 Lean 官方最小 Framework 示例,五个 Set* 一目了然:
39 public override void Initialize() 40 { 42 UniverseSettings.Resolution = Resolution.Minute; 44 SetStartDate(2013, 10, 07); 45 SetEndDate(2013, 10, 11); 46 SetCash(100000); 54 SetUniverseSelection(new ManualUniverseSelectionModel( Symbol.Create("SPY", SecurityType.Equity, Market.USA))); 55 SetAlpha(new ConstantAlphaModel(InsightType.Price, InsightDirection.Up, TimeSpan.FromMinutes(20), 0.025, null)); 59 SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel(Resolution.Daily)); 65 SetExecution(new ImmediateExecutionModel()); 66 SetRiskManagement(new MaximumDrawdownPercentPerSecurity(0.01m)); 67 }
五件套完整装配:
| 层 | 实现 | 作用 |
|---|---|---|
| Selection | ManualUniverseSelectionModel |
手动指定 SPY 一只标的 |
| Alpha | ConstantAlphaModel |
固定每 20 分钟发一个 Up 信号(测试用) |
| Portfolio | EqualWeightingPortfolioConstructionModel |
等权,每日调仓 |
| Execution | ImmediateExecutionModel |
一把市价单 |
| Risk | MaximumDrawdownPercentPerSecurity(0.01m) |
单标的浮亏 1% 平仓 |
每个 Set* 都可独立替换。比如把 Alpha 换成 RsiAlphaModel(14),整个策略就从"固定做多 SPY"变成"RSI 跨越触发 SPY 多空",其余四层代码不动。把 Execution 换成 VolumeWeightedAveragePriceExecutionModel,立刻从"小资金测试"升级为"机构级拆单"。这种任意替换的组合自由度是 Framework 五层的核心价值。
QCAlgorithm.Framework.cs 提供两种装配方法:
SetXxx(model):替换该层(覆盖)。AddXxx(model):追加该层(内部组成 Composite)。AddAlpha 可叠加多个 Alpha 信号源;AddUniverseSelection 可叠加多个选股池;AddRiskManagement 可叠加多个风控闸门。不调用 Set* 时,五层默认是 Null*Implementation(空实现,不产生任何输出),OnData 走传统范式。一旦调用任意一个 Set*,五层责任链激活——这是因为 Framework 范式要么五层全装配,要么走传统范式,半装配无意义。
回顾前两节,Framework 五层的每个实现都提供 .cs + .py 双版本(RsiAlphaModel.cs / RsiAlphaModel.py,VolumeWeightedAveragePriceExecutionModel.cs / .py 等)。.py 版本通过 Python.NET 调用底层 C# 指标和订单 API,逻辑与 .cs 一致,让 Python 用户能用熟悉的语法写策略:
class MyAlgorithm(QCAlgorithm): def initialize(self): self.set_alpha(RsiAlphaModel(14)) self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel()) self.set_execution(ImmediateExecutionModel()) self.set_risk_management(MaximumDrawdownPercentPerSecurity(0.05))
这是 Lean 工程化的另一面——同一套 Framework 模型,C# 和 Python 两种语言都能用,研究员和工程师各取所长。Python 写策略快,C# 跑得快,两者共享同一份 Framework 实现。
IExecutionModel.Execute(targets) + OnOrderEvent,把目标持仓变真实订单;可延迟/分散拆单,"可自由 delay or spread out"。PortfolioTargetCollection 跨 bar 缓存目标,只在价对执行方有利时下单,体现"算法交易"避冲击成本的精髓。IRiskManagementModel.ManageRisk(targets)→filtered,是"过滤/覆盖"语义而非生成;可减仓或清零 PortfolioTarget(symbol, 0)。SetRiskManagement 设置、AddRiskManagement 串联多个。-Math.Abs 强制负、跳过空仓、Insights.Cancel 平仓同时停 alpha 评分。BasicTemplateFrameworkAlgorithm 用五个 Set* 装配一个完整 Framework 策略,每层可独立替换,体现"可组装策略工程化"。.cs + .py,Python.NET 互操作,C# 性能、Python 易用兼得。第 9 章结束。你已经理解了 Framework 五层的完整责任链(Alpha→Selection→Portfolio→Execution→Risk)与可组装工程化思想。下一章(第 10 章)钻取券商对接——Framework 产生的订单怎么通过
IBrokerage接口发到真实券商跑实盘,以及 Docker 部署和全书回顾。