4. 市场数据事件:Tick数据与Bar数据的结构定义、数据事件的数据模型、数据源适配器接口设计
在事件驱动回测与灰度发布系统中,市场数据是驱动所有策略逻辑运转的原始燃料。本章将严格定义Tick与Bar两种核心数据结构的字段规范,构建统一的数据事件模型,并设计可插拔的数据源适配器接口,确保系统能够无缝对接不同来源的行情数据。
4.1 Tick数据结构定义
Tick数据是逐笔成交或逐笔报价的原始记录,代表市场在某一瞬间的微观快照。在低延迟回测与高频策略灰度验证中,Tick数据的精度直接影响订单簿重建与滑点模拟的准确性。
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
symbol |
string | 交易标的代码 | "BTC-USDT" |
exchange |
string | 交易所标识 | "BINANCE" |
timestamp |
int64 | Unix毫秒时间戳 | 1718000000123 |
last_price |
float64 | 最新成交价 | 67500.50 |
last_volume |
float64 | 最新成交量(基础币种) | 0.023 |
bid_price |
float64 | 买一价 | 67499.00 |
bid_volume |
float64 | 买一量 | 1.500 |
ask_price |
float64 | 卖一价 | 67501.00 |
ask_volume |
float64 | 卖一量 | 0.800 |
tick_direction |
int8 | 成交方向:1=主动买,-1=主动卖,0=未知 | 1 |
设计要点:Tick数据必须包含完整的买卖盘口(Bid/Ask)信息,而非仅成交价。灰度发布中,灰度实例需要根据盘口深度模拟限价单的成交概率,缺少盘口的Tick数据会导致滑点模型失真。
4.2 Bar数据结构定义
Bar数据(K线)是Tick数据在固定时间窗口内的聚合结果。Bar数据在回测中用于降低计算负载,在灰度发布中用于趋势型策略的快速验证。
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
symbol |
string | 交易标的代码 | "ETH-USDT" |
exchange |
string | 交易所标识 | "OKX" |
timestamp |
int64 | Bar起始时间(毫秒) | 1718000100000 |
open |
float64 | 开盘价 | 3450.00 |
high |
float64 | 最高价 | 3480.50 |
low |
float64 | 最低价 | 3440.20 |
close |
float64 | 收盘价 | 3475.80 |
volume |
float64 | 成交量(基础币种) | 12500.000 |
turnover |
float64 | 成交额(计价币种) | 43250000.00 |
count |
int32 | 该Bar内的成交笔数 | 892 |
灰度发布注意:Bar数据的时间戳必须严格对齐到时间窗口的起始边界。灰度实例与生产实例使用不同时间窗口(如1min vs 5min)时,需在数据源适配器层完成对齐,否则策略信号会产生相位偏差。
4.3 数据事件的数据模型
为了统一处理Tick和Bar两种数据,我们设计一个泛型数据事件模型。该模型将原始数据封装为事件对象,携带元数据(如事件类型、生成时间、数据源标识),便于事件总线进行路由和分发。
// MarketDataEvent 泛型数据事件模型
type MarketDataEvent struct {
EventID string // 全局唯一事件ID
EventType EventType // 事件类型:TickEvent / BarEvent
SourceID string // 数据源适配器ID
Symbol string // 交易标的
Exchange string // 交易所
EventTime int64 // 事件生成时间(纳秒)
ReceiveTime int64 // 事件到达系统时间(纳秒)
Data interface{} // 具体数据:*TickData 或 *BarData
Metadata map[string]string // 扩展元数据
}
// EventType 事件类型枚举
type EventType int8
const (
TickEvent EventType = iota + 1
BarEvent
)
延迟测量:
EventTime与ReceiveTime的差值即为数据源到事件总线的传输延迟。在灰度发布中,该延迟用于评估灰度实例与生产实例之间的数据同步质量,延迟超过阈值时应触发告警。
4.4 数据源适配器接口设计
数据源适配器是连接外部行情源与事件驱动引擎的桥梁。我们定义一套标准接口,使得CSV文件、数据库、WebSocket实时流等不同来源的数据都能以统一的方式注入系统。
// DataSourceAdapter 数据源适配器接口
type DataSourceAdapter interface {
// Init 初始化适配器,传入配置参数
Init(config map[string]interface{}) error
// Connect 建立与数据源的连接
Connect() error
// Subscribe 订阅指定标的的数据
Subscribe(symbols []string, exchange string) error
// Start 启动数据流,将数据事件推送到事件通道
Start(eventChan chan<- MarketDataEvent) error
// Stop 停止数据流并释放资源
Stop() error
// Status 返回适配器当前状态
Status() AdapterStatus
}
// AdapterStatus 适配器状态
type AdapterStatus struct {
Connected bool
SubscribedNum int
LastEventTime int64
ErrorCount int
}
接口设计遵循以下原则:
- 生命周期管理:Init → Connect → Subscribe → Start → Stop,每个阶段可独立重试。
- 背压感知:
Start方法接收带缓冲的eventChan,当通道满时适配器应主动降速或丢弃旧数据。 - 状态可观测:
Status()返回实时状态,供灰度发布控制面监控数据源健康度。
4.5 数据流架构总览
下图展示了从原始数据源到事件驱动引擎的完整数据流,以及适配器层在其中的核心位置。
4.6 适配器实现对比
不同数据源适配器在实现细节上存在显著差异,下表总结了三种典型适配器的关键特性:
| 特性 | CSV适配器 | DB适配器 | WebSocket适配器 |
|---|---|---|---|
| 数据速率 | 可控(按行读取) | 受限于查询性能 | 实时最高速率 |
| 时间控制 | 支持回放加速/暂停 | 按时间范围查询 | 实时时间戳 |
| 重连机制 | 无需重连 | 连接池自动重连 | 指数退避重连 |
| 数据完整性 | 全量回放 | 依赖数据库记录 | 可能丢包需补订 |
| 典型延迟 | 微秒级(内存读取) | 毫秒级(网络+IO) | 亚毫秒级(本地) |
灰度发布最佳实践:灰度实例建议使用CSV适配器进行历史数据回放,以验证策略逻辑的正确性;生产实例使用WebSocket适配器接入实时流。两者通过事件总线隔离,互不影响。
4.7 数据事件路由规则
事件总线根据MarketDataEvent中的元数据将事件路由到对应的策略实例。路由规则支持多级匹配:
- 按标的路由:同一策略可能订阅多个标的,事件根据
Symbol字段分发。 - 按事件类型路由:高频策略订阅Tick事件,趋势策略订阅Bar事件。
- 按灰度标签路由:灰度实例携带
Metadata["gray_tag"],事件总线根据标签将数据副本发送到灰度通道。
// 事件路由示例:根据灰度标签分发
func (bus *EventBus) Route(event MarketDataEvent) {
// 生产通道:所有实例
bus.producerChan <- event
// 灰度通道:仅当事件携带灰度标签
if tag, ok := event.Metadata["gray_tag"]; ok {
if grayChan, exists := bus.grayChannels[tag]; exists {
select {
case grayChan <- event:
default:
// 灰度通道满时丢弃,避免阻塞生产通道
bus.metrics.DiscardEvent(tag)
}
}
}
}
关键约束:灰度通道必须使用非阻塞发送(select + default),防止灰度实例处理缓慢时反压生产通道。灰度发布期间,生产通道的稳定性优先级高于灰度通道的完整性。