第 6 章 · 01 LEAN 数据格式 本节摘要:本节钻取 Lean 的第 6 层——磁盘上的数据格式。Lean 从一开始就坚持"开放、人类可读、与任何特定数据库解耦"的哲学:数据是磁盘上的扁平文件,用 zip 压缩,内部是单文件 CSV 或 JSON;高频数据(Tick/Second/Minute)按 分日存档,低频(Hour/Daily)直接 一个文件;无成交时省略价格,只记录新 tick 和价格变化,省空间。三大核心数据类型 TradeBar(成交 OHLCV 聚合)/QuoteBar(报价 bid/ask 各成 bar)/Tick(单笔瞬时)各自实现 工厂方法,把一行 CSV 解析成一个对象——自定义数据也走同一 Reader 接口。
本节摘要:本节钻取 Lean 的第 6 层——磁盘上的数据格式。Lean 从一开始就坚持"开放、人类可读、与任何特定数据库解耦"的哲学:数据是磁盘上的扁平文件,用 zip 压缩,内部是单文件 CSV 或 JSON;高频数据(Tick/Second/Minute)按
/data/securityType/marketName/resolution/ticker/date_tradeType.zip分日存档,低频(Hour/Daily)直接ticker.zip一个文件;无成交时省略价格,只记录新 tick 和价格变化,省空间。三大核心数据类型 TradeBar(成交 OHLCV 聚合)/QuoteBar(报价 bid/ask 各成 bar)/Tick(单笔瞬时)各自实现Reader()工厂方法,把一行 CSV 解析成一个对象——自定义数据也走同一 Reader 接口。读完本节,你理解了 Lean 数据从磁盘到内存对象的"语法基础"。
内容来源:原项目源码
Data/readme.md、Common/Data/Market/TradeBar.cs、Common/Data/Market/QuoteBar.cs、Common/Data/Market/Tick.cs,精读并套用体系化模板。
⚠️ 注意:本章是"数据格式与多资产层",对应文档 4-security-object 的前置。本节聚焦"磁盘上数据长什么样、怎么被读进来",不展开各资产子目录(那是下一节)。
阅读完本节,你应当能够:
marketName 为什么用来区分"同 ticker 不同市场"(如 EURUSD 在多家券商)。Reader() 工厂方法的解析模式(一行 CSV → 一个对象)。Data/readme.md:4-8 开宗明义:
4 From the beginning, LEAN has strived to use an open, human-readable data format - 5 independent of any specific database or file format. From this core philosophy, 6 we built LEAN to read its financial data from flat files on disk. Data compression 7 is done in zip format, and all individual files are CSV or JSON. 8 When there is no activity for a security, the price is omitted from the file. 8 Only new ticks and price changes are recorded.
三句话奠定 Lean 数据格式的三大原则:
💡 钻取要点:这种"扁平文件哲学"的好处是数据可移植、可校验、可离线。坏处是随机读取不如数据库(每次都要解 zip 读 CSV)。Lean 用内存缓存和懒加载弥补,工程上完全够用。
Data/readme.md:18-25:
18 Data files are separated and nested in a few predictable layers: 19 - Tick, Second and Minute Financial Data: 20 `/data/securityType/marketName/resolution/ticker/date_tradeType.zip` 22 - Hour, Daily Financial Data: 23 `/data/securityType/marketName/resolution/ticker.zip` 25 The `marketName` value is used to separate different tradable assets with 25 the same ticker. E.g. EURUSD is traded on multiple brokerages all with 25 slightly different prices.
两类路径组织:
| 频率 | 路径模板 | 例子 |
|---|---|---|
| Tick/Second/Minute(高频) | /data/{securityType}/{marketName}/{resolution}/{ticker}/{date}_{tradeType}.zip |
data/equity/usa/minute/aapl/20131007_trade.zip |
| Hour/Daily(低频) | /data/{securityType}/{marketName}/{resolution}/{ticker}.zip |
data/forex/oanda/daily/eurusd.zip |
高频按"日期 + 交易类型"分文件——一天一个 zip,因为是按日推送、按日回放。低频一个 ticker 一个 zip,文件不大,合并存档即可。
tradeType 后缀区分这一份 zip 里是成交(trade)还是报价(quote)。同一只股票同一天可能既有 20131007_trade.zip 也有 20131007_quote.zip。
marketName 的设计意图:EURUSD 这个 ticker 在 OANDA、FXCM、FXOne 多家券商都交易,价格略有差异。用 marketName(oanda / fxcm ...)区分,避免数据相互覆盖。
Data/readme.md:27-36:
27 LEAN has a few core data types represented in all the asset classes we support. 31 - TradeBar - TradeBar represents trade ticks of assets consolidated for a 31 period. TradeBar file format is slightly different for high resolution 31 (second, minute) and low resolution (daily, hour). 33 - QuoteBar - QuoteBar represents top of book quote data consolidated over a 33 period of time (bid and ask bar). 35 - Tick - Tick data represents an individual record of trades ("trade ticks") 35 or quote updates ("quote tick") for an asset. Tick data is instantaneous - 35 it does not have a period.
三类数据,承载粒度递增:
| 类型 | 含义 | 关键字段 | 有无 Period |
|---|---|---|---|
| TradeBar | 一段时间内的成交聚合(OHLCV) | Open/High/Low/Close/Volume | 有(分钟/秒等) |
| QuoteBar | 一段时间内的报价聚合(bid/ask 各成 bar) | Bid(Bar)/Ask(Bar)/LastBidSize/LastAskSize | 有 |
| Tick | 单笔成交或单次报价更新(瞬时) | Value(价)/Quantity/TickType/BidPrice/AskPrice | 无 |
Common/Data/Market/TradeBar.cs:33(类声明 + 关键成员):
33 public class TradeBar : BaseData, IBaseDataBar 34 { 36 private const decimal _scaleFactor = 1 / 10000m; // 股价缩放因子 ... 46 public virtual decimal Volume { get; set; } // 成交量 52 public virtual decimal Open { get; set; } // 开 67 public virtual decimal High { get; set; } // 高 81 public virtual decimal Low { get; set; } // 低 95 public virtual decimal Close { get => Value; set {...} } // 收 120 public virtual TimeSpan Period { get; set; } // 周期
要点:
BaseData,实现 IBaseDataBar(说明它是一个"bar")。Close 其实就是基类的 Value(收盘价是这只 bar 的代表价)。_scaleFactor = 1/10000m:历史遗留的股票数据存盘时乘了 10000 整数化,读回时除回去。TradeBar.cs:794 的 Update 是聚合逻辑——一根根 tick 来时,bar 内部如何累积:
794 public override void Update(decimal lastTrade, decimal bidPrice, decimal askPrice, 794 decimal volume, decimal bidSize, decimal askSize) 795 { 796 Initialize(lastTrade); // 首次设置 open=low=high 797 if (lastTrade > High) High = lastTrade; // 更新最高 798 if (lastTrade < Low) Low = lastTrade; // 更新最低 799 //Volume is the total summed volume of trades in this bar: 800 Volume += volume; // 累加成交量 801 //Always set the closing price; 802 Close = lastTrade; // 最新价即收盘 803 }
实时模式下,tick 一笔笔喂进来,TradeBar 用 Update 持续累积;回测模式下则直接从磁盘读整根 bar。
Common/Data/Market/QuoteBar.cs:33:
33 public class QuoteBar : BaseData, IBaseDataBar 34 { 43 public decimal LastBidSize { get; set; } // 平均买盘量 50 public decimal LastAskSize { get; set; } // 平均卖盘量 56 public Bar Bid { get; set; } // 买盘的 OHLC bar 62 public Bar Ask { get; set; } // 卖盘的 OHLC bar
QuoteBar 不存 OHLC,而是存两个独立的 Bar:Bid(买盘 OHLC)和 Ask(卖盘 OHLC)。Open/High/Low/Close 是计算属性,优先取 bid/ask 的均值(中价),只有一边有值时取那边:
67 public decimal Open 72 if (Bid.Open != 0m && Ask.Open != 0m) 74 return (Bid.Open + Ask.Open) / 2m; // 中价 76 if (Bid.Open != 0) return Bid.Open; 79 if (Ask.Open != 0) return Ask.Open;
这种设计保证外汇/CFD 这类"无成交、只有报价"的资产也能用统一接口拿到一个"价格"。
Common/Data/Market/Tick.cs(关键字段):
[ProtoInclude(1000, typeof(OpenInterest))] public class Tick : BaseData { public TickType TickType { get; set; } = TickType.Trade; // Trade 或 Quote public decimal Quantity { get; set; } // 成交量 public string ExchangeCode { get; set; } // 交易所代码 public string SaleCondition { get; set; } // 成交条件标签
Tick 是最细粒度,两类:TickType.Trade(成交 tick,带 lastPrice/Quantity)和 TickType.Quote(报价 tick,带 BidPrice/AskPrice/BidSize/AskSize)。Tick 没有 Period——它是一个瞬时点,不是一段时间。
注意 [ProtoInclude(1000, typeof(OpenInterest))]:OpenInterest(持仓量)在 Lean 里被建模成 Tick 的子类,复用同一套 Reader 通路。
Data/readme.md:38-41:
38 All data is parsed from disk via `Reader()` methods. The Reader takes a single 38 line of the file and converts it the appropriate type. i.e. 40 `TradeBar.Reader()` method is a factory which returns TradeBar objects. When 40 implementing custom data, Readers are used.
每种数据类型都实现 Reader(config, line, date, isLiveMode),把一行字符串解析成对象。看 TradeBar.cs:195:
195 public override BaseData Reader(SubscriptionDataConfig config, string line, 195 DateTime date, bool isLiveMode) 196 { 197 if (line == null) return null; // EOF 203 if (isLiveMode) return new TradeBar(); // 实时模式空架子 208 try 210 switch (config.SecurityType) // 按资产类型分派 212 case SecurityType.Equity: return ParseEquity(config, line, date); 217 case SecurityType.Forex: return ParseForex(config, line, date); 221 case SecurityType.Crypto: 221 case SecurityType.CryptoFuture: return ParseCrypto(config, line, date); 224 case SecurityType.Cfd: return ParseCfd(config, line, date); 230 case SecurityType.Option: 230 case SecurityType.FutureOption: 230 case SecurityType.IndexOption: return ParseOption(config, line, date); 236 case SecurityType.Future: return ParseFuture(config, line, date);
Reader 是个工厂 + 分派器:同一份 TradeBar 类,根据 SecurityType 调不同的 ParseXxx(因为各资产的 CSV 列含义、是否带 volume、是否带缩放因子不同)。
TradeBar.cs:627 的 LineParseNoScale 揭示了通用解析(以 Forex/Index 这类不带缩放为例):
635 var csv = line.ToCsv(hasVolume ? 6 : 5); // 切成 5~6 列 636 if (config.Resolution == Resolution.Daily || config.Resolution == Resolution.Hour) 639 tradeBar.Time = DateTime.ParseExact(csv[0], DateFormat.TwelveCharacter, ...); 644 else 644 tradeBar.Time = date.Date.AddMilliseconds(csv[0].ToInt32()); // 毫秒数 646 tradeBar.Open = csv[1].ToDecimal(); 647 tradeBar.High = csv[2].ToDecimal(); 648 tradeBar.Low = csv[3].ToDecimal(); 649 tradeBar.Close = csv[4].ToDecimal(); 650 if (hasVolume) tradeBar.Volume = csv[5].ToDecimal();
经典格式:时间,开,高,低,收,成交量。高频用"午夜起的毫秒数"压缩时间戳(快),低频用十二字符日期(慢但可读)。
💡 钻取要点:自定义数据(如拉 Twitter 情绪、天气数据)也走同一套——你继承
BaseData,重写Reader()和GetSource(),Lean 就把它当作一种新的数据类型接入。这是 Lean 扩展性的核心入口。
date_tradeType.zip 按日存,低频 ticker.zip 单文件;tradeType 区分 trade/quote。时间,开,高,低,收,成交量,高频时间用毫秒数、低频用十二字符日期。下一节,我们钻进
Common/Securities——14 个资产子目录,看各资产的 BuyingPower/Volatility/Positions/Fees 配置与 Security 对象结构。