第 4 章 · 02 BasicTemplateAlgorithm 最简示例(C# + Python) 本节摘要:本节精读 Lean 仓库的"Hello World"—— 。C# 版与 Python 版各 40 行不到,完整展示了用户写策略的最小骨架:继承 ,override 两个方法 (配置回测期/资金/订阅)与 (策略逻辑)。C# 版多实现一个 接口,提供回归测试用的元数据( 等)。本节还会讲清 这个百分比再平衡高层 API 的语义,以及 Python 版通过 配置切换的机制。 内容来源:原项目源码 (共 126 行)与 (共 44 行),精读并对照。 ⚠️ 注意:这是用户视角的"最简示例",不涉及引擎内部。看完本节你应该能独立写出第一个 Lean 策略。
本节摘要:本节精读 Lean 仓库的"Hello World"——
BasicTemplateAlgorithm。C# 版与 Python 版各 40 行不到,完整展示了用户写策略的最小骨架:继承QCAlgorithm,override 两个方法Initialize(配置回测期/资金/订阅)与OnData(Slice)(策略逻辑)。C# 版多实现一个IRegressionAlgorithmDefinition接口,提供回归测试用的元数据(ExpectedStatistics等)。本节还会讲清SetHoldings(symbol, 1)这个百分比再平衡高层 API 的语义,以及 Python 版通过algorithm-language配置切换的机制。
内容来源:原项目源码
Algorithm.CSharp/BasicTemplateAlgorithm.cs(共 126 行)与Algorithm.Python/BasicTemplateAlgorithm.py(共 44 行),精读并对照。
⚠️ 注意:这是用户视角的"最简示例",不涉及引擎内部。看完本节你应该能独立写出第一个 Lean 策略。具体下单/订阅/历史 API 的所有重载见 03 节。
阅读完本节,你应当能够:
SetStartDate/SetEndDate/SetCash/AddEquity 这"初始化四件套"。SetHoldings(symbol, 1) 的百分比再平衡语义(不是"买 1 股")。IRegressionAlgorithmDefinition 的 ExpectedStatistics/DataPoints 回归契约。algorithm-language=Python)。BasicTemplateAlgorithm.cs:29-31:
29 public class BasicTemplateAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition 30 { 31 private Symbol _spy = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
类头继承 QCAlgorithm(上一节讲的主类),额外实现 IRegressionAlgorithmDefinition——这是 Lean 回归测试系统用的接口,提供 CanRunLocally/Languages/DataPoints/ExpectedStatistics 等元数据,普通用户策略不需要实现它,只有官方回归算法才标。
_spy 是预定义的 Symbol 对象。Symbol.Create(ticker, securityType, market) 三参工厂方法,返回一个不可变的 Symbol 实例。Symbol 是 Lean 全程用来标识标的的核心类型(第 6 章详讲),这里提前缓存到字段,后续 OnData 里下单用。
💡 钻取要点:为什么用
Symbol.Create而不是直接字符串"SPY"?因为 Lean 支持多市场(USA/Oanda/CME 等),同一个 ticker 在不同市场是不同标的。Symbol 内部带SecurityType和Market信息,身份唯一。Lean 也允许直接传字符串(会自动解析),但缓存 Symbol 对象更高效、更安全。
BasicTemplateAlgorithm.cs:36-50:
36 public override void Initialize() 37 { 38 SetStartDate(2013, 10, 07); //Set Start Date 39 SetEndDate(2013, 10, 11); //Set End Date 40 SetCash(100000); //Set Strategy Cash 41 42 // Find more symbols here: http://quantconnect.com/data 43 // Forex, CFD, Equities Resolutions: Tick, Second, Minute, Hour, Daily. 44 // Futures Resolution: Tick, Second, Minute 45 // Options Resolution: Minute Only. 46 AddEquity("SPY", Resolution.Minute); 47 }
四个调用就是"初始化四件套":
SetStartDate(2013, 10, 07):回测起点。上一节看过,内部存到 _startDate,并做 _locked 检查。SetEndDate(2013, 10, 11):回测终点。注意是"日期"不是"时刻",内部会取该日 23:59:59.9999999(RoundDown(1天).AddDays(1).AddTicks(-1))。SetCash(100000):起始资金,默认币种 USD。也有重载 SetCash("EUR", 10000, 1.1) 设定非主币种并指定汇率。AddEquity("SPY", Resolution.Minute):订阅 SPY 股票,分钟频率。AddEquity 是 QCAlgorithm.Universe.cs 里的方法(03 节),返回一个 Equity 对象,内部把订阅塞到 UniverseManager。Initialize 是用户唯一必须 override 的方法。基类默认实现是 throw new NotImplementedException("Please override the Initialize() method")(QCAlgorithm.cs:773),强制用户写。
BasicTemplateAlgorithm.cs:56-63:
56 public override void OnData(Slice slice) 57 { 58 if (!Portfolio.Invested) 59 { 60 SetHoldings(_spy, 1); 61 Debug("Purchased Stock"); 62 } 63 }
OnData(Slice) 是策略主入口,引擎每个时间步把数据包 Slice 喂进来(第 5 章详讲 Slice 怎么来)。这个最简策略的逻辑是:
Portfolio.Invested:组合是否已持有任何仓位(布尔)。OnData 进来时 Portfolio.Invested == false,所以执行 SetHoldings(_spy, 1)。SetHoldings(symbol, weight) 是百分比再平衡——1 表示把总资产的 100% 投到 SPY。它内部算出目标持仓数 = 总资产 * 1 / 当前价,与现有持仓求差,发市价单。Debug("Purchased Stock") 把消息塞进上一节讲的 _debugMessages 队列。OnData 进来时 Portfolio.Invested == true,什么都不做,直到回测结束。⚠️ 重要说明:
SetHoldings(_spy, 1)不是"买 1 股 SPY",是"把 100% 仓位放到 SPY"。如果总资产是 100000 美元,SPY 价 168,它会买约 595 股(100000/168)。weight范围 0 到 1(可加杠杆时 >1,但有上限)。这是 Lean 的高层 API,避免用户手算股数。
OnData 默认实现有一段反射 hack(QCAlgorithm.cs:1079-1108),自动找用户漏写 override 关键字时的同名方法。这是 Python 用户(没有 override 概念)和 C# 新手的兜底。
BasicTemplateAlgorithm.cs:68-123:
68 public bool CanRunLocally { get; } = true; 73 public List<Language> Languages { get; } = new() { Language.CSharp, Language.Python }; 78 public long DataPoints => 3943; 83 public int AlgorithmHistoryDataPoints => 0; 88 public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; 93 public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string> 94 { 95 {"Total Orders", "1"}, 96 {"Average Win", "0%"}, ... 101 {"Start Equity", "100000"}, 102 {"End Equity", "101691.92"}, 103 {"Net Profit", "1.692%"}, 104 {"Sharpe Ratio", "8.854"}, ... 122 {"OrderListHash", "3da9fa60bf95b9ed148b95e02e0cfc9e"} 123 };
这是 Lean 回归测试系统的核心:
CanRunLocally:本地仓库是否有数据跑这个算法。Languages:支持哪些语言(C#/Python)。这个算法两种语言都跑。DataPoints:整个回测会消耗多少数据点(3943),用于 CI 资源预估。AlgorithmStatus:期望的结束状态(Completed)。ExpectedStatistics:期望统计字典——回测跑完后实际算出的统计(Total Orders/Sharpe/Net Profit 等)必须与这个字典精确匹配,任何一项偏差都判回归失败。OrderListHash 是订单列表的哈希,连订单顺序都校验。💡 钻取要点:这套机制是 Lean 仓库 CI 的基石——100+ 回归算法每个都有期望统计,每次提交代码自动跑一遍,统计变了立刻报警。这保证引擎行为不漂移,用户策略跨版本可复现。普通用户策略不需要实现这个接口。
BasicTemplateAlgorithm.py:23-44:
23 class BasicTemplateAlgorithm(QCAlgorithm): 24 '''Basic template algorithm simply initializes the date range and cash''' 25 26 def initialize(self): 27 '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' 28 29 self.set_start_date(2013,10, 7) #Set Start Date 30 self.set_end_date(2013,10,11) #Set End Date 31 self.set_cash(100000) #Set Strategy Cash 32 # Find more symbols here: http://quantconnect.com/data 33 self.add_equity("SPY", Resolution.MINUTE) 34 self.debug("numpy test >>> print numpy.pi: " + str(np.pi)) 35 36 def on_data(self, data): 37 '''OnData event is the primary entry point for your algorithm. 38 Each new data point will be pumped in here.''' 39 if not self.portfolio.invested: 40 self.set_holdings("SPY", 1)
结构与 C# 版完全对应,只是命名风格转 Python:
BasicTemplateAlgorithm,继承 QCAlgorithm(通过 PythonNet 桥接 C# 类)。Initialize → initialize,C# 的 PascalCase 转 Python 的 snake_case。Lean 用 PythonNet 自动做这个映射,所以 C# 的 SetStartDate 在 Python 里是 self.set_start_date。OnData(Slice) → on_data(data),slice 参数名改成 data。Slice/Portfolio/Symbol 等 C# 类型在 Python 里直接可用,无需导入。self.debug(...) 在 initialize 里直接调,证明框架在 initialize 阶段已经初始化好消息队列。self.set_holdings("SPY", 1) 直接传字符串,Lean 自动用 SymbolCache 解析成 Symbol。语言切换:同一个 BasicTemplateAlgorithm 名字,C# 和 Python 各一份。在配置文件 config.json 里设:
"algorithm-language": "Python", "algorithm-type-name": "BasicTemplateAlgorithm", "algorithm-location": "Algorithm.Python/BasicTemplateAlgorithm.py"
引擎据此选择用 PythonInitializer 还是 C# Loader 加载。Lean 整个引擎是 C#,Python 算法通过 PythonNet 互操作(见 QCAlgorithm.Python.cs 与 QCAlgorithm.Framework.Python.cs)。
QCAlgorithm,override Initialize(配置)+ OnData(Slice)(逻辑)。SetStartDate/SetEndDate/SetCash/AddEquity。SetHoldings(symbol, 1) 是百分比再平衡(100% 仓位),不是买 1 股。IRegressionAlgorithmDefinition 提供 ExpectedStatistics/DataPoints 给回归测试比对,是 Lean CI 基石。algorithm-language 配置切换,PythonNet 自动做 PascalCase → snake_case 映射。下一节,我们钻取主类之外的几个关键 partial 文件——Trading(下单)/Universe(订阅)/History(历史)/Indicators(指标)/Plotting(绘图)/Framework(框架五段)的 API 全貌。