第 1 章 · 02 核心包与外部插件的架构边界


文档摘要

第 1 章 · 02 核心包与外部插件的架构边界 本节摘要:本节是全教程最重要的"认知澄清"。当你 clone 下 仓库,翻遍目录会发现——没有一行 CTP 连接代码,没有一个完整的 CTA 策略,没有数据库实现,没有数据下载逻辑。这不是项目残缺,而是 VeighNa 在 3.0 版本刻意做的架构拆分:核心仓库只保留"骨架",所有网关/应用/数据库/数据服务都拆成独立的 PyPI 包。这种"核心 + 插件"设计让框架活了十年仍能持续扩展,是整个 VeighNa 最值得学的工程决策。本节讲清拆分的来龙去脉、核心包里到底有什么没有什么、以及怎么按需装配外部插件。 内容来源:原项目源码 目录结构、 、 、 ,精读并套用体系化模板。

第 1 章 · 02 核心包与外部插件的架构边界

本节摘要:本节是全教程最重要的"认知澄清"。当你 clone 下 vnpy 仓库,翻遍目录会发现——没有一行 CTP 连接代码,没有一个完整的 CTA 策略,没有数据库实现,没有数据下载逻辑。这不是项目残缺,而是 VeighNa 在 3.0 版本刻意做的架构拆分:核心仓库只保留"骨架",所有网关/应用/数据库/数据服务都拆成独立的 PyPI 包。这种"核心 + 插件"设计让框架活了十年仍能持续扩展,是整个 VeighNa 最值得学的工程决策。本节讲清拆分的来龙去脉、核心包里到底有什么没有什么、以及怎么按需装配外部插件。

内容来源:原项目源码 vnpy/ 目录结构、vnpy/trader/database.pyvnpy/trader/datafeed.pyREADME.md,精读并套用体系化模板。

学习目标

阅读完本节,你应当能够:

  1. 不再困惑为什么本仓库找不到 CTP 代码——理解架构拆分。
  2. 画出 VeighNa 的三层结构:核心包 / 外部插件 / 用户代码。
  3. 说清 3.0 拆分的动机(解耦)与收益(可独立升级)。
  4. 列举核心包包含什么(事件引擎/装配/抽象/工具/alpha)和不包含什么(网关/应用/数据库/数据服务)。
  5. 理解 import_module(f"vnpy_{name}") 这个动态加载魔法

一、一个会让人困惑的发现

假设你刚 clone 了 vnpy 仓库,想学"VeighNa 是怎么连 CTP 的"。你打开 vnpy/ 目录,翻遍所有 .py 文件,大概率会困惑:

  • ctp 相关?——没有。
  • connect(连接登录)的真正实现?——只有抽象方法签名,没有实现。
  • 找一个能跑的 CTA 策略?——找不到 CtaTemplate
  • 找数据库读写?——只有抽象接口,没有 sqlite 实现。

这是因为:

⚠️ 架构边界(全教程最重要的认知):本仓库 vnpy 只是核心包(skeleton)。所有对接具体交易所/数据库/数据服务的代码,都拆成了独立的 PyPI 包,需要单独 pip installimport

二、核心包里到底有什么

我们把 vnpy/ 目录摊开,看核心包实际包含的五大组件:

组件 路径 行数 作用
事件引擎 vnpy/event/engine.py 145 Queue+Thread 发布订阅总线,框架心脏
主框架 vnpy/trader/ ~8000 MainEngine 装配、object 数据建模、gateway/app 抽象、utility 工具、converter 开平转换、optimize 优化、ui 界面
K 线图表 vnpy/chart/ ~1100 基于 pyqtgraph 的 K 线可视化
RPC 分布式 vnpy/rpc/ ~320 基于 ZeroMQ 的跨进程通讯
AI 量化 vnpy/alpha/ ~3700 多因子 + 机器学习 + 组合回测(4.0 新增)

合计约 12,840 行 / 67 个 Python 文件。注意:

  • 上述每一块都是纯 Python 逻辑,不依赖任何具体交易所。
  • 网关(CTP/IB/币安等 30+ 个)、应用(cta_strategy/spread_trading 等 17 个)、数据库驱动(sqlite/mysql 等 8 个)、数据服务(rqdata/tushare 等 9 个)都不在核心包里

三、三层结构总览

三层职责清晰:

  • 核心层:定义抽象 + 提供通信总线 + 提供通用工具。对插件零硬依赖
  • 插件层:每个插件实现一个具体接口,继承核心的抽象基类。彼此独立,可单独 pip install / 单独升级
  • 用户层:在 run.py 里把需要的插件"装配"进 MainEngine,写自己的策略。

四、3.0 拆分的动机与收益

为什么拆

3.0 之前,VeighNa 是一个"巨石仓库"——所有网关、应用都在一个 repo 里。这带来几个痛点:

  1. 升级牵一发动全身:CTP 接口变了,要发版整个框架,用户被迫整体升级。
  2. 依赖膨胀:你只用 CTP,却得装上一堆 IB/币安的依赖。
  3. 发布周期被锁:框架核心想发版,得等所有网关测试通过。

拆完之后

拆成独立包后:

  • vnpy_ctp 单独发版,CTP 接口变了只升 vnpy_ctp,核心 vnpy 不动。
  • 用户只 pip install 自己用的几个插件,依赖精简。
  • 核心包可以独立演进(比如 4.0 加 alpha 模块,不碰任何网关)。

💡 核心心法:这是经典的**"开闭原则"**落地——核心对扩展开放(随时加新网关新应用),对修改关闭(加新插件不用改核心一行代码)。十年里 VeighNa 从纯 CTP 扩展到全球几十个市场,核心包的主干结构几乎没变,靠的就是这套设计。

五、动态加载魔法:import_module

核心包怎么做到"零硬依赖插件"却又"能用插件"?答案是 Python 标准库的 importlib.import_module。看 vnpy/trader/database.py:139-159 的工厂函数:

def get_database() -> BaseDatabase: global database if database: return database # 单例缓存 database_name = SETTINGS["database.name"] # 如 "sqlite"/"mysql" module_name = f"vnpy_{database_name}" try: module = import_module(module_name) # 动态导入 vnpy_sqlite except ModuleNotFoundError: print(_("找不到数据库驱动{},使用默认的SQLite数据库").format(module_name)) module = import_module("vnpy_sqlite") # 兜底用 vnpy_sqlite database = module.Database() # 约定:模块暴露 Database 类 return database

逐行解读:

  1. 从配置 SETTINGS["database.name"] 读你想要的数据库,比如 "mysql"
  2. 拼出模块名 vnpy_mysql
  3. import_module("vnpy_mysql") 运行时导入——如果没装这个包会抛 ModuleNotFoundError
  4. 按约定,每个驱动模块都暴露一个 Database 类,实例化即得到具体实现。
  5. datafeed.py:39-68get_datafeed() 是完全同构的设计(暴露 Datafeed 类)。

vnpy/trader/datafeed.py:39-68:

def get_datafeed() -> BaseDatafeed: global datafeed if datafeed: return datafeed datafeed_name = SETTINGS["datafeed.name"] # 如 "rqdata" if not datafeed_name: datafeed = BaseDatafeed() # 没配置就用空实现 print(_("没有配置要使用的数据服务...")) else: module_name = f"vnpy_{datafeed_name}" try: module = import_module(module_name) datafeed = module.Datafeed() except ModuleNotFoundError: datafeed = BaseDatafeed() print(_("无法加载数据服务模块,请运行 pip install {} 尝试安装").format(module_name)) return datafeed

💡 核心心法:import_module 是这套架构的"粘合剂"。核心包写死了字符串约定(vnpy_<name> 包 + Database/Datafeed 类名),但不 import 任何具体包。运行时才按用户配置"现用现导"。这是 Python 实现"插件架构"最轻量的方式——不需要插件注册中心,不需要 entry_points,纯靠命名约定 + 动态导入。

六、网关和应用的装配方式

数据库/数据服务用 import_module 自动加载(读配置即可),但网关和应用需要用户在 run.py 里显式装配——因为同一个核心包可能要同时装多个网关(CTP + IB),无法靠单一配置项表达。

装配方式是 MainEngine.add_gateway(GatewayClass) / add_app(AppClass),传入已 import 的类。例如:

from vnpy_ctp import CtpGateway # 你 pip install vnpy_ctp 后才能 import from vnpy_ctastrategy import CtaStrategyApp main_engine.add_gateway(CtpGateway) # 装配网关 main_engine.add_app(CtaStrategyApp) # 装配应用

注意:这里没有 import_module——用户自己 import 外部包,把类对象传给 add_gateway。核心包仍然不 import 任何外部网关包,是用户的 run.py 把两边"接"上的。

⚠️ 澄清一个常见误解:有人说"VeighNa 用 importlib 自动发现所有网关",这是错的。数据库/数据服务是自动发现的(读 SETTINGS 配置 import_module),网关/应用是用户手动装配的(run.py 里 import + add_*)。两者的"插件化"实现方式不同,第 4、5 章会详讲。

七、按需装配外部插件

实际使用时,你按业务需求 pip install 对应插件即可:

# 做国内期货 CTA pip install vnpy vnpy_ctp vnpy_ctastrategy vnpy_sqlite # 加美股期权 pip install vnpy_ib vnpy_optionmaster # 用 RQData 下载数据 pip install vnpy_rqdata vnpy_datamanager # 用 AI 量化(alpha 已内置,无需额外装) pip install vnpy # vnpy.alpha 在核心包里

核心包 vnpy 是必装的底座,其他全按需。这就是"核心 + 插件"在用户侧的体验。

本节要点回顾

  1. 本仓库只是核心包:不含 CTP/CTA 策略/数据库实现,这是 3.0 刻意的架构拆分。
  2. 三层结构:核心层(抽象+总线+工具)/ 插件层(外部 PyPI 包)/ 用户层(run.py 装配)。
  3. 拆分收益:解耦、依赖精简、独立升级,落地了开闭原则
  4. 动态加载:database.py/datafeed.pyimport_module(f"vnpy_{name}") 按配置现用现导,核心零硬依赖。
  5. 网关/应用靠手动装配:用户在 run.py 里 import + add_gateway/add_app,不是自动发现。
  6. alpha 例外:vnpy.alpha 是内置在核心包的 AI 量化模块,无需额外 pip install。

下一节,我们用 examples/veighna_trader/run.py 这份"装配清单",实操演示怎么从零搭起一个完整的 VeighNa 交易程序——七步装配,一步不少。


发布者: 作者: 灏天文库 转发
评论区 (0)
U