第一章:vn.py 概览与核心架构 (Overview and Core Architecture)
vn.py 不仅仅是一个库,它更像是一个包含了从数据获取、策略开发、回测分析到实盘交易全流程支持的综合性平台。理解其设计哲学和核心架构,是高效使用和二次开发 vn.py 的基石。
1.1 vn.py 是什么?
vn.py 的官方定义是“一套基于 Python 的开源量化交易系统开发框架,适用于个人和中小型机构投资者进行量化交易、研究和实盘。” 这个定义包含了几个关键点:
- 基于 Python: Python 语言的易用性、丰富的科学计算库 (NumPy, Pandas, SciPy, Matplotlib) 以及胶水语言特性,使得 vn.py 能够快速集成各种资源,降低了量化交易的入门门槛。
- 开源: vn.py 遵循 MIT 开源协议,这意味着用户可以自由地使用、修改和分发代码,这极大地促进了社区的发展和功能的完善。开发者可以根据自身需求对框架进行深度定制。
- 量化交易系统开发框架: 它提供了一整套构建量化交易系统所需的组件和工具,而不是一个“开箱即用”的黑盒交易软件。用户需要基于这个框架来开发自己的交易策略和应用。
- 适用对象: 主要面向个人开发者、对交易有一定理解的专业人士、小型私募基金以及需要快速搭建和验证交易想法的量化团队。
核心特性:
- 全功能覆盖: 支持从行情数据接入、策略逻辑编写、历史数据回测、模拟交易到实盘自动化交易的全过程。
- 模块化设计: 框架被拆分为多个核心模块(如行情接口、交易接口、事件引擎、策略引擎、风控模块、数据管理等)和应用模块(如CTA策略、算法交易、套利策略等)。各模块之间低耦合,方便独立开发、测试和升级。
- 事件驱动: 系统的核心是事件驱动引擎。行情、委托回报、成交回报等所有外部输入和内部状态变化都以事件的形式在系统中流转,各模块通过订阅和处理关心的事件来协同工作。这种机制使得系统具有良好的异步处理能力和扩展性。
- 多接口支持: vn.py 通过 Gateway(接口)层抽象了不同交易柜台和数据源的差异,目前已支持国内外众多主流的证券、期货、外汇、数字货币等市场的接口,例如:
- 期货:CTP (上期、中金、大商、郑商、能源)、CTP Mini、CTP Test (模拟)、易盛 (ESUNNY UFT)、鑫管家 (XTP)
- 证券:华鑫奇点 (TORASTOCK TORAOTP)、中泰证券 (XTP)
- 外盘:Interactive Brokers (IB 盈透证券)
- 数字货币:Binance (币安)、Huobi (火币)、OKX、BitMEX 等。
- 数据接口:RQData (米筐)、TuShare Pro、Wind 等。
- 易于扩展: 用户可以方便地基于 vn.py 的基类开发自己的交易接口 (Gateway) 和应用模块 (App),例如自定义的策略类型、风险管理插件、数据分析工具等。
- 图形用户界面 (GUI): vn.py 提供了一个基于 Qt (通常通过 PyQt5 或 PySide2) 的图形用户界面,方便用户监控行情、管理委托、查看资金持仓、启停策略以及进行参数配置。
- 跨平台运行: 作为 Python 应用,vn.py 可以在 Windows, macOS 和 Linux 等主流操作系统上运行。
1.2 核心架构思想
vn.py 的架构设计精髓在于其事件驱动机制和高度模块化的组件。
1.2.1 事件驱动机制 (Event Engine)
事件驱动是 vn.py 的灵魂。系统中几乎所有的交互和状态更新都是通过事件来触发和传递的。
-
事件 (Event): 事件是系统中信息的载体。每个事件都有一个类型 (
type_
),用于标识事件的性质(例如,EVENT_TICK
表示新的行情数据到达,EVENT_ORDER
表示委托状态更新),以及一个数据荷载 (data
),通常是 vn.py 定义的核心数据对象实例 (如TickData
,OrderData
)。# 文件路径: vnpy/event/engine.py (或类似的核心定义处) # 这是一个简化的示例,实际的 Event 类可能更复杂或在不同版本中有所差异 class Event: # 定义事件类 """ 事件对象,用于在事件引擎中传递信息。 """ def __init__(self, type_: str, data: any = None) -> None: # 初始化方法 """构造函数""" self.type_: str = type_ # self.type_ 存储事件的类型,字符串格式 self.data: any = data # self.data 存储事件的具体数据,可以是任何类型
代码解释:
class Event:
: 定义了一个名为Event
的类。def __init__(self, type_: str, data: any = None) -> None:
: 这是Event
类的构造函数。self
: 指向实例本身的引用。type_: str
: 参数type_
用于接收事件的类型,并强制其为字符串类型。下划线后缀是为了避免与 Python 内置的type
关键字冲突。data: any = None
: 参数data
用于接收事件携带的数据,可以是任何类型 (any
),默认为None
。-> None
: 表示这个构造函数不返回任何值。
self.type_: str = type_
: 将传入的type_
参数赋值给实例变量self.type_
。self.data: any = data
: 将传入的data
参数赋值给实例变量self.data
。
-
事件引擎 (EventEngine): 事件引擎是事件的中央处理单元。它通常包含一个事件队列和一个或多个事件处理线程。
- 注册 (Register): 其他模块(如策略、GUI组件)可以将自己的处理函数(handler)注册到事件引擎,并指定它们感兴趣的事件类型。
- 推送 (Put/Push): 当某个模块(通常是 Gateway 或某个 App)产生了新的事件,它会将事件放入事件引擎的队列中。
- 分发与处理 (Dispatch & Process): 事件引擎的线程会不断从队列中取出事件,并根据事件的类型,查找所有已注册的对应类型的处理函数,然后依次调用这些函数来处理该事件。
# 伪代码演示事件引擎的核心逻辑 # class EventEngine: # def __init__(self): # self._queue = Queue() # 事件队列,用于存放待处理的事件 # self._handlers = defaultdict(list) # 处理器字典,键为事件类型,值为对应的处理函数列表 # self._active = False # 事件引擎活动状态标志 # self._thread = Thread(target=self._run) # 事件处理线程 # # def _run(self): # 事件处理循环 # while self._active: # 当引擎处于活动状态时循环 # try: # event = self._queue.get(block=True, timeout=1) # 从队列中获取事件,阻塞等待最多1秒 # if event.type_ in self._handlers: # 如果该事件类型有注册的处理器 # for handler in self._handlers[event.type_]: # 遍历所有对应的处理器 # handler(event) # 调用处理器处理事件 # except Empty: # 如果队列在超时时间内为空 # pass # 继续循环 # # def start(self): # 启动事件引擎 # self._active = True # 设置活动状态为真 # self._thread.start() # 启动事件处理线程 # # def stop(self): # 停止事件引擎 # self._active = False # 设置活动状态为假 # self._thread.join() # 等待事件处理线程结束 # # def put(self, event: Event): # 向事件队列中放入事件 # self._queue.put(event) # 将事件对象放入队列 # # def register(self, type_: str, handler: callable): # 注册事件处理器 # self._handlers[type_].append(handler) # 将处理器添加到对应事件类型的列表中 # # def unregister(self, type_: str, handler: callable): # 注销事件处理器 # if handler in self._handlers[type_]: # 如果处理器存在于列表中 # self._handlers[type_].remove(handler) # 从列表中移除处理器 # if not self._handlers[type_]: # 如果该事件类型没有处理器了 # self._handlers.pop(type_) # 从字典中移除该事件类型
伪代码解释:
self._queue = Queue()
: 初始化一个线程安全的队列,用于存储事件。self._handlers = defaultdict(list)
: 初始化一个字典,用于存储事件类型到其处理函数列表的映射。defaultdict(list)
确保了即使某个事件类型首次注册,也会有一个空列表与之对应。self._active
: 一个布尔标志,控制事件处理循环的运行。self._thread = Thread(target=self._run)
: 创建一个新的线程来执行_run
方法,实现异步事件处理。_run()
: 事件引擎的核心循环。它不断地从队列中获取事件,并调用已注册的处理器来处理这些事件。timeout=1
确保了即使没有事件,循环也不会永久阻塞,可以响应_active
状态的变化。start()
: 启动事件引擎,将_active
设为True
并启动处理线程。stop()
: 停止事件引擎,将_active
设为False
并等待处理线程结束。put(event: Event)
: 供其他模块调用,将一个Event
对象放入事件队列。register(type_: str, handler: callable)
: 供其他模块调用,将一个处理函数handler
注册到指定的事件类型type_
。unregister(type_: str, handler: callable)
: 注销一个已注册的处理函数。
这种松耦合的事件驱动设计带来了诸多好处:
- 解耦性: 各模块无需直接相互调用,只需关注自身产生的事件和需要处理的事件。这降低了模块间的依赖,使得系统更易于维护和扩展。
- 异步性: 事件的产生和处理可以是异步的。例如,Gateway 接收到行情后放入事件队列,可以立即返回继续接收下一笔行情,而行情的具体处理(如推送给策略、更新UI)则由事件引擎的线程在后台完成。
- 灵活性与可扩展性: 新增功能模块时,只需让它注册关心的新事件类型或处理已有的事件类型,而无需修改核心引擎或其他模块的代码。
- 可测试性: 可以独立地测试某个模块的事件产生和处理逻辑。
1.2.2 主引擎 (MainEngine)
MainEngine
是 vn.py 应用程序的“大脑”和“心脏”。它在程序启动时创建,并负责:
- 初始化和管理核心组件:
- 创建并持有事件引擎 (
EventEngine
) 的实例。所有模块都通过MainEngine
来访问事件引擎。 - 加载、初始化和管理所有的 Gateway 实例。
MainEngine
提供了统一的接口来连接/断开 Gateway,查询合约、资金、持仓等。 - 加载、初始化和管理所有的 App(应用模块)实例,如 CTA策略引擎、数据记录模块等。
- 创建并持有事件引擎 (
- 提供全局服务:
- 日志服务: 提供统一的日志记录接口 (
MainEngine.write_log
),所有模块都可以通过它来输出日志信息。日志事件 (EVENT_LOG
) 也会被推送到事件引擎,供 GUI 或其他日志处理模块显示。 - 全局配置: 管理一些全局性的配置信息。
- 数据中心: 某种程度上,
MainEngine
也扮演了数据中心的角色,因为它管理着所有 Gateway,而 Gateway 是实时数据(行情、回报)和基础数据(合约)的来源。App 通常通过MainEngine
获取这些数据或直接从 Gateway 获取。
- 日志服务: 提供统一的日志记录接口 (
- 协调模块间交互: 虽然事件引擎是主要的交互方式,但
MainEngine
也提供了一些直接调用接口,方便模块获取其他模块的服务或信息。
# 文件路径: vnpy/trader/engine.py
# 简化的 MainEngine 结构示例
from vnpy.event import EventEngine # 导入事件引擎
# from .gateway import BaseGateway # 假设导入了Gateway基类
# from .app import BaseApp # 假设导入了App基类
# from .object import LogData # 假设导入了LogData数据结构
# from .constant import EVENT_LOG # 假设导入了日志事件常量
class MainEngine: # 定义主引擎类
"""
vn.py交易系统的核心引擎,负责管理事件引擎、交易接口(Gateway)和应用模块(App)。
"""
def __init__(self, event_engine: EventEngine = None) -> None: # 初始化方法
"""构造函数"""
if event_engine: # 如果传入了事件引擎实例
self.event_engine: EventEngine = event_engine # 使用传入的实例
else: # 如果没有传入事件引擎实例
self.event_engine: EventEngine = EventEngine() # 创建一个新的事件引擎实例
self.event_engine.start() # 启动事件引擎
self.gateways: dict[str, any] = {
} # 用于存储所有加载的Gateway实例,键为gateway名称,值为gateway对象
self.apps: dict[str, any] = {
} # 用于存储所有加载的App实例,键为app名称,值为app对象
self.exchanges: list = [] # 用于存储支持的交易所列表 (通常从合约数据中提取或配置)
self.contracts: dict = {
} # 用于存储合约数据,键通常是vt_symbol
# ... 其他初始化,如加载配置,初始化RPC服务等
def add_gateway(self, gateway_class: type) -> any: # 添加并初始化一个Gateway
"""
添加并初始化一个新的交易接口。
gateway_class: Gateway的类定义,而不是实例。
"""
gateway_name = gateway_class.gateway_name # 获取Gateway类的gateway_name属性
if gateway_name in self.gateways: # 检查是否已存在同名Gateway
self.write_log(f"接口{
gateway_name}已存在") # 记录日志
return self.gateways[gateway_name] # 返回已存在的实例
gateway = gateway_class(self.event_engine, gateway_name) # 创建Gateway实例,传入事件引擎和名称
self.gateways[gateway_name] = gateway # 将实例存入字典
self.write_log(f"接口{
gateway_name}加载成功") # 记录日志
return gateway # 返回创建的实例
def add_app(self, app_class: type) -> any: # 添加并初始化一个App
"""
添加并初始化一个新的应用模块。
app_class: App的类定义。
"""
app_name = app_class.app_name # 获取App类的app_name属性
if app_name in self.apps: # 检查是否已存在同名App
self.write_log(f"应用{
app_name}已存在") # 记录日志
return self.apps[app_name] # 返回已存在的实例
# App的构造函数通常需要 main_engine 和 event_engine
app = app_class(self) # 创建App实例,传入主引擎自身
self.apps[app_name] = app # 将实例存入字典
self.write_log(f"应用{
app_name}加载成功") # 记录日志
return app # 返回创建的实例
def connect(self, setting: dict, gateway_name: str) -> None: # 连接指定的Gateway
"""
连接到指定的交易接口。
setting: 连接配置字典。
gateway_name: 要连接的接口的名称。
"""
if gateway_name not in self.gateways: # 如果接口不存在
self.write_log(f"试图连接未知接口:{
gateway_name}") # 记录日志
return # 直接返回
gateway = self.gateways[gateway_name] # 获取接口实例
gateway.connect(setting) # 调用接口的connect方法
self.write_log(f"接口{
gateway_name}开始连接") # 记录日志
def get_gateway(self, gateway_name: str) -> any: # 获取指定的Gateway实例
"""获取指定的交易接口实例。"""
return self.gateways.get(gateway_name, None) # 从字典中获取,如果不存在则返回None
def get_app(self, app_name: str) -> any: # 获取指定的App实例
"""获取指定的应用模块实例。"""
return self.apps.get(app_name, None) # 从字典中获取,如果不存在则返回None
def write_log(self, msg: str, source: str = "MainEngine") -> None: # 记录日志
"""
记录一条日志信息。
msg: 日志内容。
source: 日志来源,默认为MainEngine。
"""
# log = LogData(msg=msg, gateway_name=source) # 创建LogData对象 (gateway_name字段可能用source更合适)
# event = Event(type_=EVENT_LOG, data=log) # 创建日志事件
# self.event_engine.put(event) # 将日志事件放入事件引擎
# 实际实现中,可能会直接调用日志库,或者通过事件引擎发布日志事件
print(f"LOG [{
source}]: {
msg}") # 简化版:直接打印到控制台
def get_all_contracts(self) -> list: # 获取所有合约信息
"""获取所有已加载的合约对象列表。"""
# 此处应从self.contracts中提取,或汇总所有Gateway的合约
# return list(self.contracts.values())
pass # 实际实现会更复杂
def get_contract(self, vt_symbol: str) -> any: # 获取特定合约信息
"""获取指定vt_symbol的合约对象。"""
# return self.contracts.get(vt_symbol, None)
pass # 实际实现会更复杂
def close(self) -> None: # 关闭主引擎和所有组件
"""
关闭主引擎,包括停止事件引擎、关闭所有接口和应用。
"""
self.write_log("主引擎开始关闭") # 记录日志
for app in self.apps.values(): # 遍历所有App
try: # 尝试关闭App
if hasattr(app, "close"): # 如果App有close方法
app.close() # 调用close方法
self.write_log(f"应用{
app.app_name}已关闭") # 记录日志
except Exception as e: # 捕获关闭App时的异常
self.write_log(f"关闭应用{
getattr(app, 'app_name', 'UnknownApp')}时发生错误: {
e}") # 记录错误日志
for gateway in self.gateways.values(): # 遍历所有Gateway
try: # 尝试关闭Gateway
gateway.close() # 调用Gateway的close方法
self.write_log(f"接口{
gateway.gateway_name}已关闭") # 记录日志
except Exception as e: # 捕获关闭Gateway时的异常
self.write_log(f"关闭接口{
gateway.gateway_name}时发生错误: {
e}") # 记录错误日志
if self.event_engine.is_active(): # 如果事件引擎仍在活动
self.event_engine.stop() # 停止事件引擎
self.write_log("事件引擎已停止") # 记录日志
self.write_log("主引擎关闭完成") # 记录日志
代码解释:
__init__(self, event_engine: EventEngine = None)
: 构造函数。如果外部没有传入EventEngine
实例,它会自己创建一个,并立即启动事件引擎。self.gateways
和self.apps
: 用字典来存储加载的 Gateway 和 App 实例,键是它们的名称(通常是类中定义的gateway_name
或app_name
属性),值是实例本身。add_gateway(self, gateway_class: type)
: 用于动态加载一个 Gateway。它接收 Gateway 的类作为参数,创建实例并存储。这里gateway_class(self.event_engine, gateway_name)
的调用方式是示例性的,实际 Gateway 的构造函数签名可能不同,但通常会需要事件引擎。add_app(self, app_class: type)
: 类似地加载一个 App。App 的构造函数通常需要MainEngine
自身的引用,以便访问其他组件或服务。connect(self, setting: dict, gateway_name: str)
: 根据提供的配置setting
连接到指定名称的 Gateway。get_gateway(self, gateway_name: str)
和get_app(self, app_name: str)
: 提供按名称获取已加载 Gateway 或 App 实例的方法。write_log(self, msg: str, source: str = "MainEngine")
: 一个简化的日志记录方法。在实际 vn.py 中,它会创建一个LogData
对象,并包装成EVENT_LOG
类型的事件,然后通过self.event_engine.put(event)
推送出去,由日志模块(如 GUI 中的日志监控组件)订阅并显示。get_all_contracts()
和get_contract()
: 示意性的方法,用于管理和查询合约信息。实际实现中,合约数据可能由MainEngine
统一缓存,或者从各个 Gateway 获取。close()
: 关闭MainEngine
时的清理逻辑,包括优雅地关闭所有 App 和 Gateway,最后停止事件引擎。
MainEngine
的存在使得 vn.py 的各个组件能够被有效地组织和管理起来,形成一个协同工作的整体。
1.2.3 模块化设计
vn.py 的模块化体现在其清晰的组件划分:
- 核心交易层 (
vnpy.trader
): 包含了最基础和核心的交易功能,如MainEngine
,EventEngine
, 各种核心数据结构 (TickData
,OrderData
等),BaseGateway
(Gateway基类),BaseApp
(App基类),以及全局常量 (constant.py
)。这一层是整个框架的基石。 - 接口层 (Gateways): 每一个
vnpy_xxx
(如vnpy_ctp
,vnpy_ib
) 包通常实现了一个或多个具体的Gateway
。它们继承自BaseGateway
,负责与特定的交易柜台或数据源进行通信,将外部数据转换为 vn.py 的标准事件和数据对象,并将 vn.py 内部的交易指令(如下单、撤单)发送到外部。 - 应用层 (Apps): 各种
vnpy_yyy
(如vnpy_ctastrategy
,vnpy_algotrading
,vnpy_datamanager
) 包实现了具体的交易应用逻辑或辅助功能。它们继承自BaseApp
(或者更具体的App基类,如CtaTemplate
继承自BaseApp
的一个子类),通过MainEngine
与事件引擎和 Gateway 交互,实现特定的业务功能。例如:CtaStrategy
App: 提供了CTA(Commodity Trading Advisor,商品交易顾问)策略的开发、回测和实盘运行环境。DataManager
App: 提供了历史数据的下载、管理和导入导出功能。AlgoTrading
App: 提供了执行复杂算法订单(如TWAP, VWAP)的功能。RiskManager
App: (如果单独实现为一个App)可以订阅交易事件,进行实时的风险监控和控制。
这种模块化设计使得:
- 按需加载: 用户可以只加载自己需要的接口和应用模块,保持系统的轻量化。
- 独立开发与维护: 不同模块可以由不同开发者独立开发和维护,只要遵循 vn.py 的接口规范和事件约定。
- 社区贡献: 社区开发者可以方便地贡献新的 Gateway 或 App,丰富 vn.py 的生态。
- 可插拔性: 更换交易接口或增加新的策略类型对核心框架的影响较小。
1.3 目录结构与关键模块 (以 vn.py 2.x/3.x 为例)
vn.py 的项目结构经过多次迭代,但核心思想保持一致。一个典型的 vn.py 环境通常包含以下部分:
-
主项目 (
vnpy
): 这是 vn.py 框架的核心代码库。vnpy/trader/
: 存放核心交易逻辑。engine.py
: 定义了MainEngine
和(早期版本中内置的)EventEngine
。object.py
: 定义了所有核心数据对象,如TickData
,BarData
,OrderData
,TradeData
,ContractData
,AccountData
,PositionData
,LogData
,ErrorData
,HistoryRequest
等。这是理解 vn.py 数据流转的重中之重。constant.py
: 定义了系统中使用的各种枚举常量,如Direction
(买卖方向),Offset
(开平标志),OrderType
(委托类型),Status
(委托/订单状态),Exchange
(交易所),Product
(产品类型),Interval
(K线周期) 等。gateway.py
: 定义了BaseGateway
接口基类,所有具体的交易接口都需要继承它。app.py
: 定义了BaseApp
应用基类(以及一些更具体的App类型,如EngineApp
给策略引擎类App使用)。setting.py
: 全局默认配置。utility.py
: 一些通用的工具函数,如K线合成器BarGenerator
、行情序列管理器TickArrayManager
、时间处理函数等。
vnpy/event/
: 包含一个更通用的事件引擎实现 (EventEngine
),在较新版本中vnpy.trader.engine
中的事件处理部分可能会依赖或基于此。vnpy/rpc/
: 如果使用了RPC功能,这里会定义RPC服务和客户端的实现。vnpy/chart/
: 简单的图表绘制相关功能。vnpy/database/
: 数据库接口的抽象和实现 (如 SQLite, MySQL, PostgreSQL, MongoDB 的适配器)。vnpy/api/
: 存放各种底层API的封装,例如vnpy.api.ctp
封装了 CTP 的原生Python API。Gateway通常会使用这些API封装。
-
接口插件 (
vnpy_ctp
,vnpy_ib
,vnpy_binance
等):- 每个插件通常是一个独立的 Python 包,可以单独安装 (
pip install vnpy_ctp
)。 - 它们实现了与特定交易接口或数据源的对接逻辑。
- 主要包含
gateway.py
(实现了继承自BaseGateway
的具体接口类) 和可能的 API 封装文件。 - 例如,
vnpy_ctp/gateway.py
中会定义CtpGateway
。
- 每个插件通常是一个独立的 Python 包,可以单独安装 (
-
应用插件 (
vnpy_ctastrategy
,vnpy_datamanager
,vnpy_algotrading
等):- 也是独立的 Python 包。
- 它们实现了特定的交易功能或辅助工具。
- 通常包含一个或多个继承自
BaseApp
或其子类的 App 类,以及相应的业务逻辑实现。 - 例如,
vnpy_ctastrategy
中会包含CtaEngine
(App类) 和CtaTemplate
(策略基类)。
-
运行脚本/入口点:
- 通常会有一个主运行脚本 (如
run.py
,main.py
或在examples
目录下),用于创建MainEngine
,加载所需的 Gateway 和 App,启动图形界面(如果需要),并开始事件循环。 vnstation
(vn.py 的图形化启动和管理工具) 本身也是基于这种模式构建的,它提供了一个更友好的方式来管理和运行 vn.py 实例。
- 通常会有一个主运行脚本 (如
理解这个目录结构和各个关键模块的职责,有助于开发者快速定位代码、进行调试以及进行二次开发。
第二章:核心数据结构详解 (In-depth Look at Core Data Structures)
vn.py 中定义了一系列标准化的数据结构,用于在系统内部(事件引擎、Gateway、App之间)传递行情、委托、成交、合约、账户、持仓等信息。这些数据对象是事件驱动机制中 Event.data
荷载的主要形式,也是策略逻辑处理的基础。它们通常在 vnpy.trader.object
模块中定义。
2.1 TickData
(行情切片数据)
TickData
对象封装了某一合约在某一时刻的最新市场快照信息。当 Gateway 接收到新的 Tick 行情时,会创建或更新一个 TickData
对象,并通过 EVENT_TICK
事件将其发送出去。
# 文件路径: vnpy/trader/object.py (或类似的核心定义处)
from dataclasses import dataclass, field # 使用dataclasses简化类的定义
from datetime import datetime # 导入datetime用于时间戳
from vnpy.trader.constant import Exchange # 导入交易所枚举
# 假设Exchange枚举已经定义
# class Exchange(Enum):
# SSE = "SSE" # 上交所
# SZSE = "SZSE" # 深交所
# CFFEX = "CFFEX" # 中金所
# SHFE = "SHFE" # 上期所
# DCE = "DCE" # 大商所
# CZCE = "CZCE" # 郑商所
# INE = "INE" # 能源中心
# HKEX = "HKEX" # 港交所
# SMART = "SMART" # IB智能路由
# IDEALPRO = "IDEALPRO" # IB外汇
# # ... 其他交易所
@dataclass
class TickData: # 定义TickData数据类
"""
Tick行情数据对象。
"""
gateway_name: str # gateway_name: 产生此Tick数据的Gateway名称 (字符串)
symbol: str # symbol: 合约代码 (字符串, 通常是交易所原始代码)
exchange: Exchange # exchange: 交易所对象 (Exchange枚举实例)
datetime: datetime # datetime: Tick数据的具体时间戳 (datetime对象)
name: str = "" # name: 合约名称 (字符串, 可选)
volume: float = 0 # volume: 最新成交量 (浮点数)
turnover: float = 0 # turnover: 成交额 (浮点数, 某些市场提供)
open_interest: float = 0 # open_interest: 持仓量 (浮点数, 主要用于期货)
last_price: float = 0 # last_price: 最新成交价 (浮点数)
last_volume: float = 0 # last_volume: 最新一笔成交量 (浮点数, 某些市场提供)
limit_up: float = 0 # limit_up: 涨停板价 (浮点数)
limit_down: float = 0 # limit_down: 跌停板价 (浮点数)
open_price: float = 0 # open_price: 今日开盘价 (浮点数)
high_price: float = 0 # high_price: 今日最高价 (浮点数)
low_price: float = 0 # low_price: 今日最低价 (浮点数)
pre_close: float = 0 # pre_close: 昨日收盘价 (浮点数)
bid_price_1: float = 0 # bid_price_1: 买一价 (浮点数)
bid_price_2: float = 0 # bid_price_2: 买二价 (浮点数)
bid_price_3: float = 0 # bid_price_3: 买三价 (浮点数)
bid_price_4: float = 0 # bid_price_4: 买四价 (浮点数)
bid_price_5: float = 0 # bid_price_5: 买五价 (浮点数)
ask_price_1: float = 0 # ask_price_1: 卖一价 (浮点数)
ask_price_2: float = 0 # ask_price_2: 卖二价 (浮点数)
ask_price_3: float = 0 # ask_price_3: 卖三价 (浮点数)
ask_price_4: float = 0 # ask_price_4: 卖四价 (浮点数)
ask_price_5: float = 0 # ask_price_5: 卖五价 (浮点数)
bid_volume_1: float = 0 # bid_volume_1: 买一量 (浮点数)
bid_volume_2: float = 0 # bid_volume_2: 买二量 (浮点数)
bid_volume_3: float = 0 # bid_volume_3: 买三量 (浮点数)
bid_volume_4: float = 0 # bid_volume_4: 买四量 (浮点数)
bid_volume_5: float = 0 # bid_volume_5: 买五量 (浮点数)
ask_volume_1: float = 0 # ask_volume_1: 卖一量 (浮点数)
ask_volume_2: float = 0 # ask_volume_2: 卖二量 (浮点数)
ask_volume_3: float = 0 # ask_volume_3: 卖三量 (浮点数)
ask_volume_4: float = 0 # ask_volume_4: 卖四量 (浮点数)
ask_volume_5: float = 0 # ask_volume_5: 卖五量 (浮点数)
localtime: datetime = None # localtime: 本地接收到行情的时间 (datetime对象, 可选)
# vt_symbol是vn.py内部使用的唯一合约标识符
vt_symbol: str = field(init=False) # vt_symbol: vn.py合约标识符 (字符串), 不在构造函数中初始化
def __post_init__(self) -> None: # 在__init__之后自动调用的方法
"""在初始化之后,自动生成vt_symbol。"""
self.vt_symbol = f"{
self.symbol}.{
self.exchange.value}" # 拼接 symbol 和 exchange 的值作为 vt_symbol
代码解释:
@dataclass
: 这是一个 Python 3.7+ 引入的装饰器,它可以自动为类生成一些特殊方法,如__init__
,__repr__
,__eq__
等,从而简化了数据类的定义。gateway_name: str
: 字符串,表示这个TickData
是由哪个 Gateway 实例产生的。这对于区分不同接口的数据很重要,尤其是在连接多个接口时。symbol: str
: 字符串,合约在交易所的原始代码,例如rb2401
(螺纹钢2401合约),600036.SH
(招商银行股票代码,注意后缀.SH表示上交所)。exchange: Exchange
:Exchange
枚举类型(定义在vnpy.trader.constant
中),表示合约所在的交易所。例如Exchange.SHFE
(上海期货交易所),Exchange.SSE
(上海证券交易所)。datetime: datetime
:datetime
对象,表示此 Tick 数据所代表的市场时间。通常精确到毫秒。name: str = ""
: 字符串,合约的中文名称,如“螺纹钢2401”。这个字段是可选的,并非所有接口都提供。volume: float = 0
: 浮点数,当日累计成交量。turnover: float = 0
: 浮点数,当日累计成交金额。open_interest: float = 0
: 浮点数,合约的当前持仓量(主要用于期货和期权市场)。last_price: float = 0
: 浮点数,最新成交价。last_volume: float = 0
: 浮点数,最新一笔成交的量。limit_up: float = 0
: 浮点数,当日涨停价。limit_down: float = 0
: 浮点数,当日跌停价。open_price: float = 0
: 浮点数,当日开盘价。high_price: float = 0
: 浮点数,当日最高价。low_price: float = 0
: 浮点数,当日最低价。pre_close: float = 0
: 浮点数,昨日收盘价。bid_price_1
到bid_price_5
: 浮点数,买一到买五档的申买价。ask_price_1
到ask_price_5
: 浮点数,卖一到卖五档的申卖价。bid_volume_1
到bid_volume_5
: 浮点数,买一到买五档的申买量。ask_volume_1
到ask_volume_5
: 浮点数,卖一到卖五档的申卖量。- 注意:不同的市场和接口提供的盘口深度可能不同,vn.py 定义了5档,但实际接口可能只提供1档或更多档。Gateway 在填充
TickData
时会尽力而为。
- 注意:不同的市场和接口提供的盘口深度可能不同,vn.py 定义了5档,但实际接口可能只提供1档或更多档。Gateway 在填充
localtime: datetime = None
:datetime
对象,表示本地程序接收到这条 Tick 数据的时间。这对于计算网络延迟或进行一些高频场景分析可能有用。vt_symbol: str = field(init=False)
: 这是一个非常重要的字段。vt_symbol
(vn.py Ticker Symbol) 是 vn.py 内部用来唯一标识一个合约的字符串,格式为symbol.exchange
,例如rb2401.SHFE
。field(init=False)
表示这个字段不由dataclass
自动生成的__init__
方法处理,即创建TickData
对象时不需要传递vt_symbol
。
__post_init__(self) -> None
: 这是dataclass
提供的一个特殊方法,在__init__
执行完毕后自动调用。这里用它来根据self.symbol
和self.exchange.value
自动生成self.vt_symbol
。
TickData
的来源与推送:
- Gateway 的行情接口(如 CTP 的
MdApi
)从交易所接收到原始行情数据。 - Gateway 解析原始数据,并将其填充到一个新的
TickData
对象中。 - Gateway 创建一个
Event
对象,类型为EVENT_TICK
(通常定义在vnpy.trader.constant
中,值为字符串 “eTick.”),并将TickData
对象作为事件的data
。 - Gateway 通过
MainEngine.event_engine.put(event)
将此事件推送到事件引擎。 - 事件引擎将此
EVENT_TICK
事件分发给所有已注册监听此类型事件的处理器(例如,CTA策略引擎中的策略实例、GUI中的行情显示组件、数据记录模块等)。
策略或应用模块在收到 TickData
后,就可以根据其最新价格、盘口等信息进行决策。
2.2 BarData
(K线数据)
BarData
对象封装了某一合约在特定时间周期(如1分钟、5分钟、日线等)内的行情统计信息,即我们通常所说的K线(Candlestick bar)。K线是技术分析和许多量化策略的基础。
BarData
可以通过以下几种方式产生:
- 实时合成:
- 最常见的方式是通过
BarGenerator
(K线合成器,通常在vnpy.trader.utility
中定义)。BarGenerator
接收实时推送的TickData
,并根据预设的K线周期(如1分钟、15分钟)和合成规则(如是否包含最新未走完的K线)将 Tick 数据聚合成BarData
。 - 当一根K线完成(即当前时间跨过了该K线的结束时间点)或者根据配置需要推送正在进行中的K线时,
BarGenerator
会创建一个BarData
对象,并通过EVENT_BAR
事件将其发送出去。
- 最常见的方式是通过
- 历史数据查询:
- 当从数据服务商(如RQData, TuShare)或本地数据库加载历史K线数据时,这些数据通常也会被转换成
BarData
对象列表供策略回测或分析使用。 MainEngine
或特定的数据管理 App 通常会提供加载历史K线数据的接口,这些接口返回的就是BarData
列表。
- 当从数据服务商(如RQData, TuShare)或本地数据库加载历史K线数据时,这些数据通常也会被转换成
- 其他周期转换:
- 更高周期的K线可以由低周期的K线合成而来。例如,可以使用5根1分钟K线合成1根5分钟K线。
BarGenerator
也支持这种基于Bar的合成。
- 更高周期的K线可以由低周期的K线合成而来。例如,可以使用5根1分钟K线合成1根5分钟K线。
# 文件路径: vnpy/trader/object.py (或类似的核心定义处)
from dataclasses import dataclass, field # 再次使用dataclasses简化定义
from datetime import datetime # 用于时间戳
from vnpy.trader.constant import Exchange, Interval # 导入交易所和K线周期枚举
# 假设Exchange和Interval枚举已经定义
# class Exchange(Enum): ... (同TickData中的定义)
# class Interval(Enum):
# MINUTE = "1m"
# HOUR = "1h"
# DAILY = "d"
# WEEKLY = "w"
# TICK = "tick" # 虽然BarData代表K线,但有时会用Interval.TICK表示数据来源是Tick级别
# # ... 其他周期如 "3m", "5m", "15m", "30m", "2h", "4h" 等
@dataclass
class BarData: # 定义BarData数据类
"""
K线数据对象。
"""
gateway_name: str # gateway_name: 产生此Bar数据的Gateway名称或数据源标识 (字符串)
symbol: str # symbol: 合约代码 (字符串)
exchange: Exchange # exchange: 交易所对象 (Exchange枚举实例)
datetime: datetime # datetime: K线的开始时间 (datetime对象)
# 注意:vn.py中BarData的datetime通常指这根Bar的开始时间。
# 例如,对于09:00:00到09:00:59的1分钟K线,datetime是09:00:00。
# 对于日线,通常是当天的日期(时间部分可能是00:00:00或根据数据源而定)。
interval: Interval # interval: K线周期 (Interval枚举实例)
volume: float = 0 # volume: K线内的成交量 (浮点数)
turnover: float = 0 # turnover: K线内的成交额 (浮点数, 某些市场提供)
open_interest: float = 0 # open_interest: K线结束时的持仓量 (浮点数, 主要用于期货)
# 注意:持仓量的统计口径可能因数据源和市场而异。
# 有些是K线周期内的持仓量变化(delta OI),有些是期末持仓量。
# vn.py中更倾向于表示期末持仓量。
open_price: float = 0 # open_price: K线的开盘价 (浮点数)
high_price: float = 0 # high_price: K线内的最高价 (浮点数)
low_price: float = 0 # low_price: K线内的最低价 (浮点数)
close_price: float = 0 # close_price: K线的收盘价 (浮点数)
# vt_symbol是vn.py内部使用的唯一合约标识符
vt_symbol: str = field(init=False) # vt_symbol: vn.py合约标识符 (字符串), 不在构造函数中初始化
def __post_init__(self) -> None: # 在__init__之后自动调用的方法
"""在初始化之后,自动生成vt_symbol。"""
self.vt_symbol = f"{
self.symbol}.{
self.exchange.value}" # 拼接 symbol 和 exchange 的值作为 vt_symbol
# (可选) 添加一些辅助属性或方法
@property
def end_datetime(self) -> datetime: # 计算并返回K线的结束时间 (近似)
"""
近似计算K线的结束时间。
注意:这是一个简化的计算,对于跨天、非固定交易时段等复杂情况可能不完全准确。
更精确的结束时间通常需要结合交易日历和具体品种的交易时段来确定。
"""
from vnpy.trader.utility import BarGenerator # 局部导入以避免循环依赖
return BarGenerator.generate_bar_end_time(self.datetime, self.interval) # 调用BarGenerator的辅助函数
def __repr__(self) -> str: # 自定义对象的字符串表示形式
"""自定义对象的字符串表示,方便打印和调试。"""
return (
f"BarData(gateway='{
self.gateway_name}', "
f"vt_symbol='{
self.vt_symbol}', "
f"datetime='{
self.datetime.strftime('%Y-%m-%d %H:%M:%S')}', "
f"interval='{
self.interval.value}', "
f"O={
self.open_price}, H={
self.high_price}, L={
self.low_price}, C={
self.close_price}, "
f"V={
self.volume}, OI={
self.open_interest})"
)
代码解释:
gateway_name: str
: 字符串,与TickData
中的类似,指明此BarData
的来源。如果是通过BarGenerator
从实时 Tick 合成的,通常会沿用 Tick 的gateway_name
;如果是从历史数据库加载的,可能是特定的数据源标识(如 “DB” 或数据服务商名称)。symbol: str
: 字符串,合约在交易所的原始代码。exchange: Exchange
:Exchange
枚举,合约所在的交易所。datetime: datetime
:datetime
对象。这一点非常重要:在 vn.py 的BarData
中,datetime
字段通常表示这根K线的 开始时间。- 例如,一根从 2023-10-10 09:00:00 到 2023-10-10 09:00:59 的1分钟K线,其
datetime
字段会被设置为datetime(2023, 10, 10, 9, 0, 0)
。 - 对于日线数据,它通常是当天的日期,时间部分可能是 00:00:00,或者根据数据源的具体定义(如T00:00:00,或者T15:00:00表示当天收盘形成的日线)。策略在使用时需要明确这一点。
- 例如,一根从 2023-10-10 09:00:00 到 2023-10-10 09:00:59 的1分钟K线,其
interval: Interval
:Interval
枚举类型(定义在vnpy.trader.constant
中),表示这根K线的时间周期,如Interval.MINUTE
(1分钟),Interval.HOUR
(1小时),Interval.DAILY
(日线),Interval.WEEKLY
(周线)。也可能包含如Interval.M3
(3分钟),Interval.M5
(5分钟) 等更细致的周期定义。volume: float = 0
: 浮点数,这根K线时间周期内的总成交量。turnover: float = 0
: 浮点数,这根K线时间周期内的总成交金额(并非所有市场或数据源都提供此字段)。open_interest: float = 0
: 浮点数,这根K线结束时的合约持仓量(主要用于期货)。- 需要注意,不同数据源对于K线周期内持仓量的统计口径可能不同。有些可能提供周期内的持仓量变化(delta OI),而vn.py中的
BarData.open_interest
更倾向于表示该K线结束时刻的总持仓量。在使用第三方数据时,需要确认其定义。
- 需要注意,不同数据源对于K线周期内持仓量的统计口径可能不同。有些可能提供周期内的持仓量变化(delta OI),而vn.py中的
open_price: float = 0
: 浮点数,这根K线的开盘价(即该K线周期内第一笔成交的价格,或者该周期的起始Tick的盘口价,具体取决于合成逻辑)。high_price: float = 0
: 浮点数,这根K线周期内的最高成交价。low_price: float = 0
: 浮点数,这根K线周期内的最低成交价。close_price: float = 0
: 浮点数,这根K线的收盘价(即该K线周期内最后一笔成交的价格,或者该周期的结束Tick的盘口价)。vt_symbol: str = field(init=False)
: 与TickData
中一样,是symbol.exchange
格式的 vn.py 内部唯一合约标识符,通过__post_init__
自动生成。end_datetime(self) -> datetime
(示例属性):- 这是一个通过
@property
装饰器定义的只读属性,用于尝试计算并返回K线的近似结束时间。 - 它调用了
BarGenerator.generate_bar_end_time
(假设有这样一个辅助函数,实际上BarGenerator
内部有类似的逻辑来判断K线何时结束)。 - 重要提示: 精确计算K线的结束时间可能很复杂,因为它涉及到具体品种的交易时段、节假日、早盘/午盘/夜盘的分割等。这个示例属性提供的是一个基于
interval
的简单推算,对于需要精确时间控制的策略,可能需要更完善的交易日历和时段管理工具。
- 这是一个通过
__repr__(self) -> str
: 重写了__repr__
方法,使得打印BarData
对象时能输出更易读的格式,方便调试。
BarData
的产生与应用场景:
-
实时K线合成 (Real-time Bar Generation):
- CTA策略引擎 (
CtaEngine
) 通常会为每个运行中的策略实例订阅的合约和K线周期创建一个BarGenerator
实例。 BarGenerator
内部维护当前正在形成的K线的open_price
,high_price
,low_price
,close_price
,volume
等状态。- 当新的
TickData
到达时,BarGenerator
的update_tick(tick: TickData)
方法会被调用:- 如果这是新K线的第一个Tick,则用此Tick的
last_price
初始化K线的O, H, L, C。 - 否则,更新H (取max), L (取min), C (取最新)。
- 累加成交量
tick.last_volume
(如果提供) 或估算成交量。 - 更新持仓量
tick.open_interest
。
- 如果这是新K线的第一个Tick,则用此Tick的
- 当
BarGenerator
检测到当前K线周期结束(例如,1分钟K线的秒数达到59,或时间跨过整分钟),或者根据配置(如update_bar_gh
在CtaTemplate
中,表示是否推送未完成的K线),它会:- 完成当前
BarData
对象的构建。 - 创建一个
Event
对象,类型为EVENT_BAR
(通常值为字符串 “eBar.”),并将此BarData
作为事件的data
。 - 将此事件推送到事件引擎。
- 完成当前
- CTA策略的
on_bar(bar: BarData)
方法会接收到这个事件并执行策略逻辑。
# BarGenerator 的简化逻辑示意 (实际实现更复杂,在 vnpy.trader.utility) # class BarGenerator: # def __init__(self, on_bar_callback: callable, interval: Interval = Interval.MINUTE, ...): # self.interval = interval # K线周期 # self.on_bar_callback = on_bar_callback # K线生成后的回调函数 # self.current_bar: BarData = None # 当前正在合成的K线对象 # # ... 其他状态,如上一根K线的结束时间等 # # def update_tick(self, tick: TickData) -> None: # 用Tick数据更新K线 # # 1. 检查是否应启动新的K线 # # - 如果 self.current_bar 为 None # # - 或者 tick.datetime 已跨过 self.current_bar 的预期结束时间 # new_bar_generated = False # if not self.current_bar: # 如果当前没有K线 # self.current_bar = BarData(...) # 基于tick初始化新的K线 # # ... (设置open, high, low, close, volume, datetime等) # elif tick.datetime >= self._calculate_expected_end_time(self.current_bar.datetime, self.interval): # # 当前K线已完成 # finished_bar = self.current_bar # 获取已完成的K线 # self.on_bar_callback(finished_bar) # 通过回调函数推送已完成的K线 # new_bar_generated = True # self.current_bar = BarData(...) # 基于当前tick开始新的K线 # # ... # # # 2. 更新当前K线的 H, L, C, Volume, OI # if not new_bar_generated and self.current_bar: # 如果不是新生成的K线且当前K线存在 # self.current_bar.high_price = max(self.current_bar.high_price, tick.last_price) # 更新最高价 # self.current_bar.low_price = min(self.current_bar.low_price, tick.last_price) # 更新最低价 # self.current_bar.close_price = tick.last_price # 更新收盘价 # self.current_bar.volume += tick.last_volume # (需要正确处理tick的成交量含义) 累加成交量 # self.current_bar.open_interest = tick.open_interest # 更新持仓量 # # ... # # # (可选) 如果配置了推送进行中的K线 (on_bar_in_progress_callback) # # if self.push_in_progress_bar and self.current_bar: # # self.on_bar_in_progress_callback(self.current_bar) # # def update_bar(self, bar: BarData) -> None: # 用低周期K线更新高周期K线 # # 类似 update_tick 的逻辑,但输入是 BarData # # 用于将例如5根1分钟K线合成1根5分钟K线 # pass
伪代码解释:
update_tick(self, tick: TickData)
: 当新的TickData
到达时调用此方法。- 首先判断是否需要结束当前的K线并开始一个新的K线。这通常基于时间判断(例如,1分钟K线,当
tick.datetime
的分钟数发生变化时)。 - 如果旧K线完成,则调用
self.on_bar_callback(finished_bar)
将其推送出去,然后基于当前tick
初始化新的self.current_bar
。 - 如果当前K线仍在进行中,则用
tick
的数据更新self.current_bar
的最高价、最低价、收盘价、成交量和持仓量。
- 首先判断是否需要结束当前的K线并开始一个新的K线。这通常基于时间判断(例如,1分钟K线,当
update_bar(self, bar: BarData)
: 用于从低周期BarData
合成更高周期的BarData
。例如,一个配置为合成5分钟K线的BarGenerator
可以接收1分钟的BarData
作为输入。逻辑与update_tick
类似,但聚合的是bar.open_price
,bar.high_price
,bar.low_price
,bar.close_price
,bar.volume
等。
- CTA策略引擎 (
-
策略回测 (Strategy Backtesting):
- 在回测模式下,
CtaEngine
(或其他回测引擎) 会从数据源(如数据库)加载指定合约和时间范围的历史K线数据(通常是BarData
对象列表)。 - 引擎会按照时间顺序遍历这些历史
BarData
,模拟实时推送,逐个调用策略的on_bar(bar: BarData)
方法。 - 策略根据历史
BarData
产生交易信号,回测引擎模拟执行这些信号并记录绩效。
- 在回测模式下,
-
数据分析与可视化:
BarData
对象列表可以很容易地被转换为 Pandas DataFrame,方便使用 Pandas, NumPy, Matplotlib, Seaborn, Plotly 等库进行复杂的数据分析、指标计算和图表绘制。
# 示例:将 BarData 列表转换为 Pandas DataFrame # import pandas as pd # # list_of_bars: list[BarData] = [...] # 假设这是一个包含多个BarData对象的列表 # # if list_of_bars: # # 方法1: 逐个提取属性 (如果BarData字段较多且固定) # data_for_df = [] # 初始化用于DataFrame的数据列表 # for bar in list_of_bars: # 遍历BarData列表 # data_for_df.append({ # 为每个Bar创建一个字典 # "datetime": bar.datetime, # K线开始时间 # "open": bar.open_price, # 开盘价 # "high": bar.high_price, # 最高价 # "low": bar.low_price, # 最低价 # "close": bar.close_price, # 收盘价 # "volume": bar.volume, # 成交量 # "open_interest": bar.open_interest, # 持仓量 # "vt_symbol": bar.vt_symbol, # vn.py合约代码 # "interval": bar.interval.value # K线周期值 # }) # df = pd.DataFrame(data_for_df) # 创建DataFrame # df.set_index("datetime", inplace=True) # 将datetime列设为索引 # print(df.head()) # 打印DataFrame头部 # # # 方法2: 使用 dataclasses.asdict (如果BarData是dataclass) # # from dataclasses import asdict # # df_v2 = pd.DataFrame([asdict(bar) for bar in list_of_bars]) # # df_v2.set_index("datetime", inplace=True) # # print(df_v2.head())
代码解释:
- 将
BarData
对象列表转换为 Pandas DataFrame 是非常常见的操作。 - 方法1:手动遍历
list_of_bars
,为每个BarData
对象创建一个字典,包含希望在 DataFrame 中出现的字段,然后用这个字典列表创建 DataFrame。 - 方法2:如果
BarData
是使用@dataclass
定义的,可以使用dataclasses.asdict(bar)
将每个bar
对象直接转换为字典,然后创建 DataFrame。这更简洁。 - 通常会将
datetime
列设为 DataFrame 的索引,便于进行时间序列分析。
-
指标计算:
- 许多技术指标(如均线 MA, MACD, RSI, 布林带 Bollinger Bands)都是基于K线的 O, H, L, C, V 数据计算的。
- vn.py 内置了一些常用的指标计算工具(例如,早期的
ArrayManager
或在vnpy_portfoliostrategy
、vnpy_ctastrategy
中可能包含的指标库),或者可以方便地集成 TA-Lib、Pandas-TA 等第三方指标库。 - 策略的
on_bar
方法中,通常会先将收到的BarData
更新到某个数据序列管理器(如ArrayManager
或直接是 Pandas Series/DataFrame),然后基于这个序列计算最新的指标值,再根据指标值产生交易信号。
K线周期的重要性:
BarData
的 interval
字段指明了这根K线代表的时间跨度。vn.py 支持多种常用周期,并且 BarGenerator
允许用户自定义合成任意整数倍基础周期(通常是分钟)的K线。策略开发者需要根据策略的特性选择合适的K线周期进行分析和交易。
- 短周期K线(如1分钟、5分钟)对市场价格变动更敏感,适合日内短线策略,但可能包含更多噪音。
- 长周期K线(如1小时、日线)更能反映趋势,过滤掉短期波动,适合趋势跟踪策略,但信号可能滞后。
- 多周期分析:一些高级策略会同时分析多个周期的K线,例如在日线判断大趋势,在小时线或分钟线寻找入场点。vn.py 的架构也支持这种多周期K线的处理。
理解 BarData
的结构、产生方式和时间定义,对于正确实现基于K线的量化策略至关重要。
2.3 OrderData
(委托数据)
OrderData
对象封装了一条委托(订单)的详细信息及其状态变化。当策略或用户通过 vn.py 发出交易指令(如下单、撤单)时,以及当这些指令的状态在交易所发生变化(如已提交、部分成交、全部成交、已撤销、委托失败等)时,OrderData
对象会被创建或更新,并通过 EVENT_ORDER
事件在系统中传递。
OrderData
的生命周期与状态:
一条委托从创建到最终状态,会经历一系列状态变迁。OrderData
中的 status
字段(类型为 Status
枚举)就用于记录这些状态。理解这些状态对于正确处理委托回报至关重要。Status
枚举通常定义在 vnpy.trader.constant
中,可能包含以下值(具体值可能因 vn.py 版本和接口特性略有差异):
SUBMITTING
(正在提交): 委托信息已由 vn.py 内部逻辑生成,正通过 Gateway 发往交易所。这是委托在本地的一个初始状态。NOTTRADED
(未成交/已报): 委托已成功提交到交易所,但尚未有任何部分的成交。对于限价单,它正在交易所的订单簿中等待撮合。PARTTRADED
(部分成交): 委托已有部分数量成交,但仍有剩余数量未成交,继续在交易所等待撮合。ALLTRADED
(全部成交): 委托已按其要求的数量全部成交。CANCELLED
(已撤销): 委托在未完全成交之前,已被成功撤销。这可能是用户主动撤单,也可能是某些交易所机制(如收盘清算、触发特定条件)导致的撤销。REJECTED
(拒单/委托失败): 委托在提交给交易所后,由于某些原因(如资金不足、仓位不足、价格超出涨跌停、合约不存在、风控限制等)被交易所拒绝,未能进入订单簿。
# 文件路径: vnpy/trader/object.py (或类似的核心定义处)
from dataclasses import dataclass, field # 使用dataclasses
from datetime import datetime # 时间戳
from vnpy.trader.constant import Exchange, OrderType, Direction, Offset, Status # 导入相关枚举
# 假设以下枚举已定义在 vnpy.trader.constant
# class OrderType(Enum):
# LIMIT = "LIMIT" # 限价单
# MARKET = "MARKET" # 市价单
# STOP = "STOP" # 停止单/止损单 (本地或服务器)
# FAK = "FAK" # Fill And Kill (立即成交剩余撤销)
# FOK = "FOK" # Fill Or Kill (立即全部成交否则撤销)
# # ... 其他如RFQ (询价), ICEBERG (冰山)等
# class Direction(Enum):
# LONG = "多" # 买入开仓 (期货/期权),买入 (股票)
# SHORT = "空" # 卖出开仓 (期货/期权)
# NET = "净" # (某些情况下,如查询持仓时表示净方向)
# # 注意:在实际下单时,买入平仓、卖出平仓通常通过 Offset 字段来区分。
# # Direction.LONG 通常指“买”这个动作,Direction.SHORT 通常指“卖”这个动作。
# class Offset(Enum):
# NONE = "" # 无开平(例如现货交易,或者某些接口不区分)
# OPEN = "开" # 开仓
# CLOSE = "平" # 平仓 (昨仓优先,如果交易所支持)
# CLOSETODAY = "平今" # 平今仓
# CLOSEYESTERDAY = "平昨" # 平昨仓
# class Status(Enum):
# SUBMITTING = "正在提交"
# NOTTRADED = "未成交"
# PARTTRADED = "部分成交"
# ALLTRADED = "全部成交"
# CANCELLED = "已撤销"
# REJECTED = "拒单"
# UNKNOWN = "未知" # (某些异常情况)
@dataclass
class OrderData: # 定义OrderData数据类
"""
委托数据对象。
"""
gateway_name: str # gateway_name: 发出此委托的Gateway名称 (字符串)
symbol: str # symbol: 合约代码 (字符串)
exchange: Exchange # exchange: 交易所对象 (Exchange枚举实例)
orderid: str # orderid: 委托号 (字符串)。这是由Gateway或交易所生成的唯一标识此委托的ID。
# 不同接口的orderid格式可能不同。
# vn.py内部通常会有一个本地委托号(vn_orderid)和交易所系统号(sys_orderid)的概念。
# 此处的orderid通常指可以用于在交易所层面查询或撤销的那个ID。
type: OrderType # type: 委托类型 (OrderType枚举实例)
direction: Direction # direction: 委托方向 (Direction枚举实例)
offset: Offset # offset: 开平标志 (Offset枚举实例)
price: float # price: 委托价格 (浮点数)。对于市价单,此字段可能为0或特定约定值。
volume: float # volume: 委托数量 (浮点数)
traded: float = 0 # traded: 已成交数量 (浮点数),初始为0,随成交回报更新
status: Status = Status.SUBMITTING # status: 委托状态 (Status枚举实例),初始通常为SUBMITTING
datetime: datetime # datetime: 委托时间 (datetime对象),通常是委托创建或提交到Gateway的时间
reference: str = "" # reference: 委托引用标识 (字符串, 可选)。用户或策略可自定义,用于跟踪。
# 例如,策略名称、信号来源等。
# vt_symbol 是 vn.py 内部使用的唯一合约标识符
vt_symbol: str = field(init=False) # vt_symbol: vn.py合约标识符 (字符串), 不在构造函数中初始化
# vt_orderid 是 vn.py 内部生成的唯一委托ID,格式通常是 gateway_name.orderid
vt_orderid: str = field(init=False) # vt_orderid: vn.py全局委托ID (字符串), 不在构造函数中初始化
def __post_init__(self) -> None: # 在__init__之后自动调用的方法
"""在初始化之后,自动生成vt_symbol和vt_orderid。"""
self.vt_symbol = f"{
self.symbol}.{
self.exchange.value}" # 拼接 symbol 和 exchange 的值作为 vt_symbol
self.vt_orderid = f"{
self.gateway_name}.{
self.orderid}" # 拼接 gateway_name 和 orderid 作为 vt_orderid
def is_active(self) -> bool: # 判断委托是否处于活动状态 (即可能继续成交或被撤销)
"""
检查委托是否仍处于活动状态 (即尚未最终完成)。
活动状态通常包括:正在提交、未成交、部分成交。
"""
return self.status in [Status.SUBMITTING, Status.NOTTRADED, Status.PARTTRADED] # 返回是否为活动状态
def __repr__(self) -> str: # 自定义对象的字符串表示形式
"""自定义对象的字符串表示,方便打印和调试。"""
return (
f"OrderData(vt_orderid='{
self.vt_orderid}', "
f"vt_symbol='{
self.vt_symbol}', "
f"direction='{
self.direction.value}', offset='{
self.offset.value}', "
f"price={
self.price}, volume={
self.volume}, traded={
self.traded}, "
f"status='{
self.status.value}', "
f"datetime='{
self.datetime.strftime('%Y-%m-%d %H:%M:%S')}')"
)
代码解释:
gateway_name: str
,symbol: str
,exchange: Exchange
: 与TickData
和BarData
中的含义类似,指明了委托相关的合约和来源接口。orderid: str
: 字符串,这是此条委托在 对应 Gateway 或交易所层面 的唯一标识符。- 对于CTP接口,这通常是交易所返回的
OrderSysID
(如果已报到交易所) 或由 CTP API 在本地生成的FrontID_SessionID_OrderRef
组合(在报单到交易所前,OrderRef
是关键)。vn.py 的 CTP Gateway 会处理这些细节,并提供一个统一的orderid
给上层。 - 对于其他接口,其格式和生成方式各有不同。
OrderData
的orderid
字段旨在存储那个可以被用来向 Gateway 或交易所查询此订单状态或发起撤单操作的ID。
- 对于CTP接口,这通常是交易所返回的
type: OrderType
:OrderType
枚举,表示委托的类型,如限价单 (LIMIT
)、市价单 (MARKET
)、FAK、FOK等。不同的市场和接口支持的委托类型不同。direction: Direction
:Direction
枚举,表示买卖方向。Direction.LONG
通常指“买入”动作。Direction.SHORT
通常指“卖出”动作。- 结合
offset
字段才能完整确定是开仓还是平仓。例如:Direction.LONG
,Offset.OPEN
= 买入开多仓Direction.SHORT
,Offset.OPEN
= 卖出开空仓Direction.SHORT
,Offset.CLOSE
= 卖出平多仓 (或Offset.CLOSETODAY
,Offset.CLOSEYESTERDAY
)Direction.LONG
,Offset.CLOSE
= 买入平空仓 (或Offset.CLOSETODAY
,Offset.CLOSEYESTERDAY
)
offset: Offset
:Offset
枚举,表示开平标志。对于期货和期权这类有“开仓”和“平仓”概念的市场,此字段非常重要。对于股票等现货市场,此字段可能为Offset.NONE
。Offset.OPEN
: 开仓。Offset.CLOSE
: 平仓。在某些交易所(如国内期货),这通常优先平昨仓。Offset.CLOSETODAY
: 平今仓。明确指示平掉今日开的仓位。Offset.CLOSEYESTERDAY
: 平昨仓。明确指示平掉昨日持有的仓位。
price: float
: 浮点数,委托的价格。对于限价单,这是期望的成交价格。对于市价单,此字段的含义可能因接口而异(可能为0,或者交易所接受的特定指示价格,如涨跌停板价)。volume: float
: 浮点数,委托的总数量(单位是合约的交易单位,如“手”或“股”)。traded: float = 0
: 浮点数,已成交的数量。初始为0,当收到该委托的部分成交或全部成交回报时,此字段会被更新。status: Status = Status.SUBMITTING
:Status
枚举,表示委托的当前状态。新创建的OrderData
对象(例如,由策略发出下单请求后,在MainEngine
或 Gateway 层面生成的)其初始状态可能是Status.SUBMITTING
,或者如果 Gateway 能立即确认已报到交易所,则可能是Status.NOTTRADED
。datetime: datetime
:datetime
对象,表示委托相关的时间。这通常是:- 当策略发出委托请求时,
MainEngine
或 App (如CtaEngine
) 创建OrderData
对象时记录的本地时间。 - 或者是 Gateway 将委托成功发送到柜台/交易所后,从回报中获取的时间戳。
- 当委托状态更新时,此时间戳也可能随之更新为最新回报的时间。
- 当策略发出委托请求时,
reference: str = ""
: 字符串,一个可选的引用字段。策略开发者可以用它来存储与此委托相关的自定义信息,例如是哪个策略实例发出的、基于什么信号触发的、或者是为了实现特定的订单管理逻辑(如止损单、跟踪止损单的父订单ID等)。这个字段对 vn.py 核心逻辑通常是透明的,主要供用户使用。vt_symbol: str = field(init=False)
:symbol.exchange
,与之前一致。vt_orderid: str = field(init=False)
: vn.py 全局唯一委托ID。格式通常是gateway_name.orderid
。- 由于不同 Gateway 返回的
orderid
可能在全局不唯一(例如,两个不同期货公司的CTP接口可能产生相同的内部OrderRef
),vt_orderid
通过加上gateway_name
前缀来确保在整个 vn.py 系统中的唯一性。这对于MainEngine
或上层应用统一管理所有接口的委托非常重要。
- 由于不同 Gateway 返回的
is_active(self) -> bool
: 一个辅助方法,用于判断该委托是否仍处于“活动”状态,即它还没有达到最终状态(如全部成交、已撤销、拒单)。活动状态的委托是策略或用户可能需要继续关注或操作(如撤单)的对象。
OrderData
的创建与流转:
-
下单 (Send Order):
- 策略或用户通过
MainEngine
或某个 App (如CtaEngine
) 提供的下单接口(例如,send_order(vt_symbol, direction, offset, price, volume, order_type, gateway_name, reference)
)发起一个交易请求。 MainEngine
或 App 会根据这些参数创建一个OrderRequest
对象 (这是一个内部数据结构,包含了下单所需的所有信息)。MainEngine
将OrderRequest
传递给指定的gateway_name
对应的BaseGateway
实例的send_order(req: OrderRequest)
方法。BaseGateway
在其send_order
方法内部:- 首先,可能会进行一些本地校验(如参数有效性)。
- 然后,它会为这个委托生成一个本地的、在当前 Gateway 内唯一的
orderid
(例如,CTP接口中可能是递增的OrderRef
)。 - 接着,它会创建一个初始的
OrderData
对象,状态通常设为Status.SUBMITTING
,并填充好所有已知信息。vt_orderid
也会在此刻基于gateway_name
和这个本地orderid
生成。 - 这个初始的
OrderData
对象会被包装成EVENT_ORDER
事件并 立即推送一次。这样做是为了让策略和UI能够立刻知道有一个委托正在被提交,并获取到其vt_orderid
,即使此时交易所还没有任何回报。 - 同时,Gateway 会将下单请求转换为其所对接的交易柜台API所需的格式,并通过API发送出去。
- Gateway 会将这个本地生成的
orderid
(或OrderRef
) 与未来交易所可能返回的OrderSysID
(交易所系统号) 建立映射关系,以便后续能将交易所回报正确关联到这个委托上。
- 策略或用户通过
-
交易所回报处理 (Exchange Acknowledgment/Update):
- 当交易柜台API收到来自交易所的关于此委托的回报时(例如,报单已接收、部分成交、全部成交、已撤单、拒单等),Gateway 的回调函数会被触发。
- Gateway 解析这些回报,根据回报中的信息(如
OrderSysID
,OrderRef
, 成交数量,状态等)找到之前创建或已缓存的对应的OrderData
对象 (通常通过vt_orderid
或内部映射表查找)。 - Gateway 更新这个
OrderData
对象的字段,如status
,traded
,orderid
(如果之前是本地ID,现在可以用交易所ID覆盖或补充),以及可能更新datetime
。 - 更新后的
OrderData
对象再次被包装成EVENT_ORDER
事件,并通过事件引擎推送出去。
-
事件处理:
MainEngine
通常会持有一个所有活动委托的字典(键为vt_orderid
,值为OrderData
对象),并在收到EVENT_ORDER
时更新这个字典。- CTA策略引擎 (
CtaEngine
) 会监听EVENT_ORDER
。当收到事件时,它会根据OrderData
的vt_orderid
或reference
找到对应的策略实例,并调用策略的on_order(order: OrderData)
方法。 - GUI 模块也会监听
EVENT_ORDER
,以实时更新界面上显示的委托列表和状态。
撤单 (Cancel Order):
- 策略或用户可以通过
MainEngine
或 App 提供的撤单接口(例如,cancel_order(vt_orderid: str)
)发起撤单请求。 - 请求中需要提供要撤销委托的
vt_orderid
。 MainEngine
找到vt_orderid
对应的 Gateway,并调用其cancel_order(req: CancelRequest)
方法 (其中CancelRequest
也是一个内部数据结构,包含了orderid
和exchange
等信息)。- Gateway 将撤单指令发送到交易所。
- 交易所对撤单操作的确认或拒绝也会通过与上述类似的委托回报流程,更新对应
OrderData
的状态 (例如,变为Status.CANCELLED
或因其他原因如已成交而撤单失败) 并推送EVENT_ORDER
事件。
关键作用:
- 状态跟踪:
OrderData
使得 vn.py 能够实时跟踪每一笔委托从提交到最终完成的整个过程。 - 策略逻辑: 策略在其
on_order
方法中,可以根据委托状态的变化执行相应的逻辑,例如:- 如果委托长时间未成交,可以尝试撤单并重新以更有利的价格下单。
- 如果委托被拒绝,可以记录原因并采取补救措施。
- 如果委托部分成交,可以更新策略的持仓状态和剩余目标。
- 风险管理: 风控模块可以监控所有活动的
OrderData
,进行额度控制、撤销异常委托等。 - 用户界面: GUI 通过
OrderData
向用户展示所有委托的详细信息和实时状态。 - 持久化:
OrderData
对象可以被序列化并存储到数据库中,用于交易记录查询和事后分析。
理解 OrderData
的所有字段含义、状态变迁以及它在 vn.py 系统中的产生和流转方式,对于开发可靠的交易策略和应用至关重要。
2.4 TradeData
(成交数据)
TradeData
对象封装了某一笔委托实际发生的成交明细。当委托在交易所部分成交或全部成交时,交易柜台会返回成交回报,Gateway 接收到这些回报后,会为每一笔独立的成交创建一个 TradeData
对象,并通过 EVENT_TRADE
事件在系统中传递。
一条委托(OrderData
)可能会产生多条成交记录(TradeData
),尤其是当委托数量较大,在不同价格或不同时间点被逐步撮合时。
# 文件路径: vnpy/trader/object.py (或类似的核心定义处)
from dataclasses import dataclass, field # 使用dataclasses
from datetime import datetime # 时间戳
from vnpy.trader.constant import Exchange, Direction, Offset # 导入相关枚举
# 假设Exchange, Direction, Offset枚举已定义 (同OrderData中的定义)
@dataclass
class TradeData: # 定义TradeData数据类
"""
成交数据对象。
"""
gateway_name: str # gateway_name: 产生此成交的Gateway名称 (字符串)
symbol: str # symbol: 合约代码 (字符串)
exchange: Exchange # exchange: 交易所对象 (Exchange枚举实例)
orderid: str # orderid: 对应的委托号 (字符串)。这应该与产生此成交的OrderData的orderid一致。
tradeid: str # tradeid: 成交号 (字符串)。这是由交易所为每笔成交生成的唯一ID。
direction: Direction # direction: 成交方向 (Direction枚举实例)。通常与对应OrderData的direction一致。
offset: Offset # offset: 开平标志 (Offset枚举实例)。通常与对应OrderData的offset一致。
price: float # price: 成交价格 (浮点数)。
volume: float # volume: 本次成交数量 (浮点数)。
datetime: datetime # datetime: 成交时间 (datetime对象),由交易所回报提供。
# vt_symbol 是 vn.py 内部使用的唯一合约标识符
vt_symbol: str = field(init=False) # vt_symbol: vn.py合约标识符 (字符串), 不在构造函数中初始化
# vt_orderid 是 vn.py 内部生成的唯一委托ID
vt_orderid: str = field(init=False) # vt_orderid: vn.py全局委托ID (字符串), 不在构造函数中初始化
# vt_tradeid 是 vn.py 内部生成的唯一成交ID,格式通常是 gateway_name.tradeid
vt_tradeid: str = field(init=False) # vt_tradeid: vn.py全局成交ID (字符串), 不在构造函数中初始化
def __post_init__(self) -> None: # 在__init__之后自动调用的方法
"""在初始化之后,自动生成相关vt_开头的ID。"""
self.vt_symbol = f"{
self.symbol}.{
self.exchange.value}" # 拼接 symbol 和 exchange 的值作为 vt_symbol
self.vt_orderid = f"{
self.gateway_name}.{
self.orderid}" # 拼接 gateway_name 和 orderid 作为 vt_orderid
self.vt_tradeid = f"{
self.gateway_name}.{
self.tradeid}" # 拼接 gateway_name 和 tradeid 作为 vt_tradeid
def __repr__(self) -> str: # 自定义对象的字符串表示形式
"""自定义对象的字符串表示,方便打印和调试。"""
return (
f"TradeData(vt_tradeid='{
self.vt_tradeid}', "
f"vt_orderid='{
self.vt_orderid}', "
f"vt_symbol='{
self.vt_symbol}', "
f"direction='{
self.direction.value}', offset='{
self.offset.value}', "
f"price={
self.price}, volume={
self.volume}, "
f"datetime='{
self.datetime.strftime('%Y-%m-%d %H:%M:%S.%f')}')" # 注意成交时间可能带毫秒
)
代码解释:
gateway_name: str
,symbol: str
,exchange: Exchange
: 与OrderData
中含义一致。orderid: str
: 字符串,这条成交所属的委托的orderid
。通过这个字段,可以将TradeData
与其父OrderData
关联起来。tradeid: str
: 字符串,交易所为这笔具体成交分配的唯一成交编号。这个 ID 在交易所层面是唯一的。direction: Direction
,offset: Offset
:Direction
和Offset
枚举,表示这笔成交的实际方向和开平属性。它们通常会与触发这笔成交的OrderData
的相应字段保持一致。price: float
: 浮点数,这笔成交的实际成交价格。volume: float
: 浮点数,这笔具体成交的数量。一条委托可能被拆分成多笔成交,每笔成交都有自己的volume
。对应OrderData
的traded
字段是所有相关TradeData
的volume
之和。datetime: datetime
:datetime
对象,这笔成交在交易所发生的精确时间,通常由交易所成交回报提供,可能精确到毫秒或更高精度。vt_symbol: str = field(init=False)
:symbol.exchange
。vt_orderid: str = field(init=False)
:gateway_name.orderid
,用于关联到 vn.py 全局唯一的委托。vt_tradeid: str = field(init=False)
: vn.py 全局唯一成交ID,格式通常是gateway_name.tradeid
。与vt_orderid
类似,通过加上gateway_name
前缀来确保在整个 vn.py 系统中的唯一性。__repr__
: 提供了易读的字符串表示,成交时间这里用了%Y-%m-%d %H:%M:%S.%f
格式,表示可能包含微秒。
TradeData
的产生与流转:
-
交易所成交回报:
- 当一条在交易所挂出的委托(
OrderData
)与对手方的委托撮合成功,产生一笔或多笔成交时,交易所会向交易柜台发送成交回报。 - Gateway 的交易接口回调函数(如 CTP 的
OnRtnTrade
)会接收到这些原始成交数据。
- 当一条在交易所挂出的委托(
-
Gateway 处理:
- Gateway 解析收到的原始成交数据。
- 对于每一笔独立的成交回报,Gateway 会创建一个新的
TradeData
对象,并填充所有字段(gateway_name
,symbol
,exchange
,orderid
(从回报中获取,应与原始委托关联),tradeid
(交易所返回的唯一成交号),direction
,offset
,price
,volume
,datetime
)。 - Gateway 将创建的
TradeData
对象包装成EVENT_TRADE
事件 (通常值为字符串 “eTrade.”),并通过事件引擎推送出去。
-
事件处理:
MainEngine
通常会监听EVENT_TRADE
,并将收到的TradeData
对象存储起来(例如,存入一个以vt_tradeid
为键的字典或一个列表),同时也可能用它来更新对应OrderData
的traded
(已成交数量) 字段,以及PositionData
(持仓数据,后续会讲到)。- CTA策略引擎 (
CtaEngine
) 会监听EVENT_TRADE
。当收到事件时,它会根据TradeData
的vt_orderid
或reference
(如果TradeData
中也填充了这个字段,或者通过其关联的OrderData
获取) 找到对应的策略实例,并调用策略的on_trade(trade: TradeData)
方法。 - GUI 模块也会监听
EVENT_TRADE
,以实时更新界面上显示的成交列表。 - 数据记录模块(如
DataManager
App)可能会监听EVENT_TRADE
并将成交记录持久化到数据库。
TradeData
与 OrderData
的关系:
- 一个
OrderData
对象代表一个交易意图(委托)。 - 一个
OrderData
对象可能产生零个、一个或多个TradeData
对象。- 零个:委托未成交(如被撤销或拒单)。
- 一个:委托一次性全部成交,或者只成交了一部分后其余被撤销。
- 多个:委托被逐步部分成交。例如,一个10手的买单可能先成交3手,再成交5手,最后成交2手,这将产生三条不同的
TradeData
记录,但这三条TradeData
的vt_orderid
(或orderid
) 都是相同的,指向同一个父委托。
TradeData
中的price
和volume
是这笔具体成交的实际价格和数量。- 对应
OrderData
的traded
字段应该是所有关联的TradeData
的volume
之和。 - 当
OrderData.traded == OrderData.volume
且OrderData.status
变为Status.ALLTRADED
时,表示该委托已完全执行。
关键作用:
- 精确执行记录:
TradeData
提供了每一笔实际成交的精确细节(价格、数量、时间),这是计算交易成本、盈亏、滑点等关键绩效指标的基础。 - 更新持仓:
TradeData
是更新策略或账户实际持仓 (PositionData
) 的直接依据。每当一笔成交发生,都需要根据其方向、开平标志、价格和数量来调整相应的持仓信息。 - 策略逻辑: 策略的
on_trade
方法可以根据成交回报执行相应逻辑:- 确认入场/出场点位和成本。
- 更新止损、止盈价格。
- 管理部分成交后的剩余委托。
- 触发后续的交易行为(例如,金字塔加仓的一部分成交后,准备下一部分)。
- 交易分析: 详细的成交记录对于分析交易行为、评估策略执行效果、识别潜在的滑点问题等非常重要。
- 合规与审计: 成交记录是交易合规性检查和审计追踪的重要凭证。
理解 OrderData
和 TradeData
的区别与联系,以及它们各自的产生和处理流程,是掌握 vn.py 交易执行核心机制的关键一步。这两个数据对象构成了从“意图”到“执行”再到“结果”的完整信息链条。
2.5 PositionData
(持仓数据)
PositionData
对象封装了某一特定合约在特定方向(多头或空头)上的持仓详情。它通常包含了持仓数量、持仓均价、持仓盈亏等关键信息。
PositionData
的来源与更新:
- 初始查询:
- 当 Gateway 成功连接到交易柜台后,通常会主动查询一次当前账户的所有持仓情况。
- Gateway 接收到柜台返回的持仓信息后,会为每个合约的每个方向(如果该合约同时存在多头和空头持仓,例如某些对锁或组合场景,虽然在vn.py中通常合并为净持仓或按多空分别表示)创建一个或更新
PositionData
对象。 - 这些初始的
PositionData
对象会通过EVENT_POSITION
事件推送到事件引擎。
- 实时更新 (基于成交):
- 当系统收到
TradeData
(成交数据) 时,MainEngine
或 Gateway 自身会根据成交的方向 (Direction
)、开平标志 (Offset
)、价格和数量来更新对应的PositionData
。- 例如,一笔买入开多仓的成交会增加该合约多头方向的
PositionData
的数量,并可能影响其持仓均价。 - 一笔卖出平多仓的成交会减少该合约多头方向的
PositionData
的数量。
- 例如,一笔买入开多仓的成交会增加该合约多头方向的
- 更新后的
PositionData
也会通过EVENT_POSITION
事件推送。
- 当系统收到
- 盘中推送 (某些接口):
- 某些交易接口(尤其是一些做市商接口或特定柜台)可能会在盘中主动推送持仓变动信息,而不仅仅是基于本地的成交回报来更新。Gateway 需要处理这些推送并相应更新
PositionData
。
- 某些交易接口(尤其是一些做市商接口或特定柜台)可能会在盘中主动推送持仓变动信息,而不仅仅是基于本地的成交回报来更新。Gateway 需要处理这些推送并相应更新
持仓方向 (Direction
) 与 PositionData
的关系:
在 vn.py 中,一个合约的持仓通常会按多头 (Direction.LONG
) 和空头 (Direction.SHORT
) 分别用一个 PositionData
对象来表示。
- 例如,如果账户持有 rb2401 合约 10 手多单,则会有一个
PositionData
对象,其vt_symbol
为 “rb2401.SHFE”,direction
为Direction.LONG
,volume
为 10。 - 如果同时持有该合约 5 手空单(例如对锁),则会有另一个
PositionData
对象,其vt_symbol
相同,direction
为Direction.SHORT
,volume
为 5。 - 如果一个合约只有净持仓的概念(如股票),通常也会用
Direction.LONG
来表示持有的正数量,volume
为负数在PositionData
中不常见,一般是通过多空两个PositionData
对象来体现净头寸。
MainEngine
通常会维护一个持仓字典,键可能是 vt_positionid
(通常是 vt_symbol.direction.value
的组合),值为 PositionData
对象。
# 文件路径: vnpy/trader/object.py (或类似的核心定义处)
from dataclasses import dataclass, field # 使用dataclasses
from vnpy.trader.constant import Exchange, Direction, Offset # 导入相关枚举
# 假设Exchange, Direction枚举已定义
@dataclass
class PositionData: # 定义PositionData数据类
"""
持仓数据对象。
"""
gateway_name: str # gateway_name: 持仓所在的Gateway名称 (字符串)
symbol: str