第 10 章 · 02 WebSocket 基础设施与部署 本节摘要:本节钻取 Brokerages 项目的两块基础设施——实时数据怎么进来、订单怎么发出去。WebSocket 公共基础设施( / / )是所有券商实时行情和订单推送的底座,加密券商(Binance/Bybit/Coinbase/Kraken)和部分传统券商都基于它实现。实盘独有的两个 Handler—— (订单发到真实券商、成交由券商推送 )和 (实时行情源,如 既做 brokerage 又做 data-queue-handler)——是回测没有的额外注入项。
本节摘要:本节钻取 Brokerages 项目的两块基础设施——实时数据怎么进来、订单怎么发出去。WebSocket 公共基础设施(
BaseWebsocketsBrokerage/IWebSocket/WebSocketClientWrapper)是所有券商实时行情和订单推送的底座,加密券商(Binance/Bybit/Coinbase/Kraken)和部分传统券商都基于它实现。实盘独有的两个 Handler——BrokerageTransactionHandler(订单发到真实券商、成交由券商推送OrderEvent)和IDataQueueHandler(实时行情源,如InteractiveBrokersBrokerage既做 brokerage 又做 data-queue-handler)——是回测没有的额外注入项。最后讲 Docker 多架构部署(Dockerfile基础 /DockerfileLeanFoundation含 Python+R+Java /DockerfileLeanFoundationARM树莓派等 ARM)以及 lean CLI(pip install lean,init/backtest/live-trade/research 一条龙)。读完本节,你将理解实盘与回测在基础设施层面的全部差异。
内容来源:原项目源码
Brokerages/BaseWebsocketsBrokerage.cs、Brokerages/IWebSocket.cs、Engine/TransactionHandlers/BrokerageTransactionHandler.cs、Common/Interfaces/IDataQueueHandler.cs、Dockerfile*、Launcher/config.json,精读并套用体系化模板。
⚠️ 注意:本节出现的
BrokerageTransactionHandler和IDataQueueHandler是实盘专属,回测分别用BacktestingTransactionHandler和FileSystemDataFeed。这正是第 3 章讲的"配置字符串注入同一引擎"在实盘层的具体落地,下一节(03)会做完整对照。
阅读完本节,你应当能够:
BaseWebsocketsBrokerage / IWebSocket / WebSocketClientWrapper 三层 WebSocket 抽象的作用。BrokerageTransactionHandler 与 BacktestingTransactionHandler 在订单处理上的差异。IDataQueueHandler 是实盘独有的"额外注入项",为什么很多券商类同时实现 IBrokerage 和 IDataQueueHandler。现代券商(尤其加密货币交易所)都用 WebSocket 推送实时行情和订单状态——它比 REST 轮询高效得多(单连接双向流,毫秒级延迟)。Lean 在 Brokerages/ 项目里提供三层 WebSocket 抽象,所有券商实现共享同一套基础设施。
Brokerages/IWebSocket.cs 定义 WebSocket 客户端的统一接口(连接/断开/发送/事件),屏蔽底层实现差异。核心成员:Connect() / Disconnect() / IsOpen / Send(string) / 事件 Message / Error / Closed。
Brokerages/WebSocketClientWrapper.cs 是 IWebSocket 的标准实现,封装 .NET 的 ClientWebSocket,提供自动重连、消息队列、心跳等通用能力。每个券商实例化一个 WebSocketClientWrapper 指向自己的 WebSocket 服务端(Binance: wss://stream.binance.com:9443/ws、Coinbase: wss://advanced-trade-ws.coinbase.com、Bybit: wss://stream.bybit.com),消息回调里解析 JSON 转 BaseData 或 OrderEvent。
Brokerages/BaseWebsocketsBrokerage.cs:33-87:
30 /// <summary> 31 /// Provides shared brokerage websockets implementation 32 /// </summary> 33 public abstract class BaseWebsocketsBrokerage : Brokerage 34 { 35 private const int ConnectionTimeout = 30000; 43 protected bool IsInitialized { get; set; } 48 protected IWebSocket WebSocket { get; set; } // 统一 WS 抽象 54 protected IRestClient RestClient { ... } // [Obsolete] 旧 REST 73 protected HttpClient HttpClient { ... } // 新 HTTP 89 protected static JsonSerializerSettings JsonSettings { ... } // 统一 JSON 解析
BaseWebsocketsBrokerage 继承 Brokerage(实现 IBrokerage),预置了 WebSocket / HttpClient / JsonSettings 三个共享字段。具体券商(如 BinanceBrokerage / BybitBrokerage / CoinbaseBrokerage)继承它,只需实现:
HttpClient)。Tick / OrderEvent。OrdersStatusChanged / Message 等事件回调给引擎。加密券商典型结构:REST 用于动作(下单/撤单/查账户),WebSocket 用于推送(成交回报/行情)。这是因为加密交易所的 REST 有速率限制(每秒几十次),而 WebSocket 无限制且实时。传统券商(如 IB)用自家协议(TWS API / FIX),不直接继承 BaseWebsocketsBrokerage,但底层同样是长连接 + 事件回调。
💡 钻取要点:WebSocket 是"实时"的关键——回测从磁盘按时间顺序读历史数据是"伪实时"(任意加速),实盘必须等真实 tick 到达才能推进时间。所以实盘的
IDataQueueHandler通过 WebSocket 接收真实 tick,推到LiveTradingDataFeed(第 5 章),后者再走与回测相同的 Slice 装配流程。实时性差异只在这一层,引擎主体对回测/实盘一视同仁——这是"回测实盘统一"架构的胜利。
回顾第 3 章和第 8 章,ITransactionHandler 是订单的"门房"。回测用 BacktestingTransactionHandler——订单内部消化,BacktestingBrokerage 用 Fill 模型算成交价,立即产生 OrderEvent(因为回测不需要等真实撮合)。
实盘用 BrokerageTransactionHandler(Engine/TransactionHandlers/BrokerageTransactionHandler.cs)——订单真的发到券商:
策略调 SetHoldings / MarketOrder │ ▼ BrokerageTransactionHandler.Process(orderRequest) ← 接收订单请求 │ ▼ IBrokerage.PlaceOrder(order) ← 调券商 API 真实下单 │ (券商撮合,可能几毫秒到几秒) ▼ IBrokerage.OrdersStatusChanged 事件触发 ← 券商推送成交回报 │ ▼ BrokerageTransactionHandler.HandleOrderEvent(...) ← 接收成交事件 │ ▼ 走与回测完全相同的 OrderEvent 处理(Portfolio 更新持仓 + 现金)
关键差异:回测成交是同步的(下单后立刻算出成交价),实盘成交是异步的(下单后等券商推送)。BrokerageTransactionHandler 内部维护订单状态机(Submitted → PartiallyFilled → Filled / Canceled / Invalid),用并发队列处理券商推送的事件流。它还实现了错误重试——券商返回临时错误(如网络抖动、速率限制)时自动重试,只在永久错误(资金不足、参数错)时才把订单置 Invalid。
Common/Interfaces/IDataQueueHandler.cs:
[InheritedExport(typeof(IDataQueueHandler))] public interface IDataQueueHandler : IDisposable { IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler); void Unsubscribe(SubscriptionDataConfig dataConfig); void SetJob(LiveNodePacket job); bool IsConnected { get; } }
这是实盘独有的注入项。回测的行情来自 FileSystemDataFeed(读磁盘历史),实盘的行情来自 IDataQueueHandler(实时数据流)。引擎在实盘启动时根据 config.json 的 data-queue-handler 字段实例化它,挂到 LiveTradingDataFeed。
注意 config.json 的 live environment:
"live-interactive": { "live-mode-brokerage": "InteractiveBrokersBrokerage", "data-queue-handler": [ "InteractiveBrokersBrokerage" ], ... }
live-mode-brokerage 和 data-queue-handler 都填 InteractiveBrokersBrokerage——同一个类同时实现 IBrokerage 和 IDataQueueHandler。这是因为 IB 通过同一根 TWS 连接既下单又推行情,行情和订单共用会话,没必要分两个连接。
加密券商也一样——BinanceBrokerage 同时实现两个接口,WebSocket 流里既有用户事件(成交)又有市场事件(tick)。少数情况下两者分离,比如 live-interactive-iqfeed environment:
"live-interactive-iqfeed": { "live-mode-brokerage": "InteractiveBrokersBrokerage", // IB 下单 "data-queue-handler": [ "QuantConnect.IQFeed.IQFeedDataQueueHandler" ], // IQFeed 推行情 ... }
——IB 只下单,IQFeed(专业付费数据源)推行情。这种"下单/行情分离"在专业机构常见,因为专业数据源比券商自带行情更全更快。
💡 钻取要点:
IDataQueueHandler标了[InheritedExport(typeof(IDataQueueHandler))]——MEF(Managed Extensibility Framework)的导出特性,让引擎启动时能通过反射按字符串名("InteractiveBrokersBrokerage")自动发现并实例化。这与第 3 章的 Handler 字符串注入、第 4 章的 Composer 是同一套"配置字符串 → 类型解析"机制,只是底座用 MEF 而非自定义 Composer。
Lean 提供三个 Dockerfile,组成"基础镜像 → 富镜像 → 应用镜像"的分层构建:
| Dockerfile | 用途 | 内容 |
|---|---|---|
DockerfileLeanFoundation |
基础镜像 quantconnect/lean:foundation |
Ubuntu base + Python(miniconda) + R + Java + C++ 库 + IB Gateway + 各种数据/数学依赖,约 8 GB |
DockerfileLeanFoundationARM |
ARM 镜像 quantconnect/lean:foundation-arm |
同 Foundation 但适配 ARM 架构(树莓派、Apple Silicon、AWS Graviton) |
Dockerfile |
应用镜像 quantconnect/lean:latest |
FROM foundation,COPY 编译产物(Lean DLL、Data 目录),ENTRYPOINT 跑 Launcher |
Dockerfile(已读,28 行)极简——大部分内容来自 Foundation:
7 FROM quantconnect/lean:foundation 12 RUN pip install --no-cache-dir ptvsd==4.3.2 debugpy~=1.6.7 pydevd-pycharm~=231.9225.15 # 远程 Python 调试 15 RUN wget https://aka.ms/getvsdbgsh -O - 2>/dev/null | /bin/sh /dev/stdin -v 17.10.20209.7 -l /root/vsdbg # 远程 C# 调试 17 COPY ./DataLibraries /Lean/Launcher/bin/Debug/ 19 COPY ./Lean/Data/ /Lean/Data/ 20 COPY ./Lean/Launcher/bin/Debug/ /Lean/Launcher/bin/Debug/ # 编译产物 25 WORKDIR /Lean/Launcher/bin/Debug 27 ENTRYPOINT [ "dotnet", "QuantConnect.Lean.Launcher.dll" ]
镜像设计哲学:Foundation 镜像稳定且大(8 GB,装 Python/R/IB/数学库),很少重建;应用镜像小,只 COPY 编译产物,每次发版重建。这样 CI/CD 推一次代码只需重新 COPY 几十兆的 DLL,不用重装 8 GB 依赖。这种"基础镜像 + 应用镜像分层"是 Docker 化量化系统的标准做法。
DockerfileLeanFoundation 的内容(DockerfileLeanFoundation.cs:17-40)装了 Python 3.11、Java 17(给 h2o 等 JVM 库)、各种数学库(liblapack/pyomo 求解器)、IB Gateway headless 等——这就是为什么 Foundation 这么大。
DockerfileLeanFoundationARM 让 Lean 能跑在树莓派、Apple Silicon Mac、AWS Graviton 实例上。ARM 的优势是功耗低(树莓派 5W)、性价比高(Graviton 比 x86 便宜 20%)。交易系统通常不是计算密集型(指标和优化器除外),ARM 足够。这让 Lean 能部署在便宜的边缘设备上做长期实盘。
QuantConnect 还提供官方 Python CLI 工具 lean(pip install lean),封装本地开发常用操作:
pip install lean # 安装 CLI lean init # 初始化项目目录,拉取 Docker 镜像 lean create-project "MyAlgo" # 创建算法模板项目(C# 或 Python) lean backtest "MyAlgo" # 用 Docker 跑回测 lean live-trade "MyAlgo" --brokerage "Interactive Brokers" --ib-user-name ... --ib-password ... lean research "MyAlgo" # 启 Jupyter 研究环境(用 Docker) lean optimize "MyAlgo" # 参数优化
lean CLI 的核心价值是封装 Docker 调用和 config.json 管理——用户不用手写 config.json,CLI 根据参数(起止日期、券商、API 密钥等)生成 config,挂载数据目录,启动 Docker 容器跑。这让"本地写代码 + 本地回测 + 本地实盘"的闭环只需几条命令。
回顾第 01 节,实盘券商由 config.json 的 environment 块决定。每个 environment(live-interactive / live-binance / live-alpaca ...)指定三个关键字段:
live-mode-brokerage:订单发到哪个券商(字符串类名,如 "InteractiveBrokersBrokerage")。data-queue-handler:实时行情从哪个 IDataQueueHandler 取(数组,通常与 brokerage 同名,有时分离如 IQFeed)。transaction-handler:几乎总是 BrokerageTransactionHandler(模拟盘 PaperBrokerage 例外,用 BacktestingTransactionHandler)。加上券商特定的连接配置(如 ib-account / ib-user-name / ib-password / binance-api-key / binance-api-secret)。lean CLI 在 lean live-trade 时把这些参数拼成对应的 environment。
💡 钻取要点:回测和实盘切换只改 config.json 的
environment字符串——把"environment": "backtesting"改成"environment": "live-interactive",引擎启动时读不同 environment 块的 handler 配置,实例化不同的 Brokerage/DataFeed/TransactionHandler。策略代码(用户 QCAlgorithm)零改动。这是第 3 章"配置驱动 + 接口插件"哲学在部署层的最终胜利,下一节(03)会做完整对照。
IWebSocket(接口)→ WebSocketClientWrapper(实现,封装 .NET ClientWebSocket + 自动重连)→ BaseWebsocketsBrokerage(券商基类,预置 WebSocket/HttpClient/JsonSettings)。OrdersStatusChanged 推送);实盘有错误重试和状态机。Subscribe / Unsubscribe / IsConnected,实盘行情源;回测用 FileSystemDataFeed 读磁盘。IBrokerage 和 IDataQueueHandler(InteractiveBrokersBrokerage、BinanceBrokerage 等),共用一根连接既下单又推行情;少数分离(如 IB 下单 + IQFeed 推行情)。Foundation(8 GB,Python/R/Java/IB base)→ FoundationARM(ARM 适配,树莓派/Graviton)→ Dockerfile(FROM foundation + COPY 编译产物)。init / create-project / backtest / live-trade / research / optimize,封装 Docker 调用与 config.json 生成。environment 字符串,策略代码零改动;environment 块里 live-mode-brokerage / data-queue-handler / transaction-handler 决定实盘行为。下一节是全书最后一节——回测实盘切换的完整对照(三个分叉点总结)与全书 10 章分层钻取回顾。