交易记录数据结构:从原始数据到可用信号

做量化交易这些年,我处理过的交易记录少说也有几亿条。说实话,很多人一上来就急着找Alpha信号,却忽略了最基础的东西——你连数据都没搞清楚,怎么可能挖出靠谱的信号?

这一章,我们就来聊聊交易记录的数据结构。说白了,就是搞清楚你的数据长什么样,怎么把它收拾干净,让它能为你所用。

一、常见字段:交易记录的“五官”

一条完整的交易记录,通常包含以下几个核心字段。我习惯把它们称为“五官”——少了哪一个,你都看不清市场的全貌。

字段名 类型 说明 典型示例
时间戳 datetime 交易发生的精确时间 2024-01-15 09:30:00.123
价格 float 成交价格 15.68
成交量 int/float 成交数量 1000
买卖方向 str/int B/S 或 1/-1 B 或 1
成交金额 float 价格 × 成交量 15680.0
交易代码 str 股票/合约代码 600519.SH

嗯,这里要注意一点。很多新手拿到数据,看到“时间戳”就以为万事大吉了。其实不然。我在项目中遇到过好几次,同一个交易所的数据,时间精度居然不一样——有的精确到毫秒,有的只到秒。你想想看,高频交易里差个几毫秒,信号就完全变味了。

核心原则:时间精度必须统一。如果数据源混用,建议全部对齐到最低精度,或者用插值法补齐。

二、数据清洗与预处理:别让脏数据毁了你的策略

数据清洗这事儿,我怎么说都不为过。曾经有一次,我跑了一个看起来完美的回测,收益曲线漂亮得不行。结果一查,原来是某天的数据里多了一个零——成交量被放大了10倍。嗯,从那以后,我每次拿到数据都要先过一遍清洗流程。

2.1 常见的数据“脏”问题

  • 缺失值:某段时间没有交易记录,或者字段为空
  • 异常值:价格突然跳空、成交量暴增(可能是数据录入错误)
  • 重复记录:同一条交易被记录了两次
  • 时间错乱:时间戳顺序颠倒,或者出现未来时间
  • 方向标识不一致:有的用B/S,有的用1/0,有的用Buy/Sell
避坑指南:我曾经遇到过一个数据集,买卖方向字段里居然出现了“B”、“S”、“1”、“0”、“Buy”、“Sell”六种写法。如果不统一处理,后续计算净买卖量时直接崩掉。

2.2 清洗流程(我常用的套路)

import pandas as pd
import numpy as np

# 1. 加载数据
df = pd.read_csv('trade_records.csv')

# 2. 统一时间格式
df['time'] = pd.to_datetime(df['time'], format='%Y-%m-%d %H:%M:%S.%f')

# 3. 排序并去重
df = df.sort_values('time').drop_duplicates(subset=['time', 'price', 'volume'])

# 4. 处理缺失值
df = df.dropna(subset=['price', 'volume'])

# 5. 统一买卖方向
direction_map = {'B': 1, 'S': -1, 'Buy': 1, 'Sell': -1, '1': 1, '0': -1}
df['direction'] = df['direction'].map(direction_map)

# 6. 过滤异常值(3倍标准差法)
price_mean = df['price'].mean()
price_std = df['price'].std()
df = df[(df['price'] > price_mean - 3*price_std) & 
        (df['price'] < price_mean + 3*price_std)]

print(f"清洗后剩余记录数: {len(df)}")

你看,代码其实不复杂。但每一步都有它的道理。比如去重这一步,我见过有人直接drop_duplicates()一把梭,结果把不同时间点的正常交易也给删了。所以一定要指定subset,只针对真正重复的字段做判断。

三、时间序列对齐:让不同频率的数据“对上话”

做量化交易,你很少只用单一频率的数据。你可能同时有1分钟K线、逐笔交易、还有日线数据。这些数据的时间戳不一样,怎么对齐?

说白了,时间序列对齐就是让不同数据源在同一个时间轴上“站好队”。我习惯用重采样(resample)和向前填充(forward fill)来处理。

3.1 对齐的两种场景

  1. 高频→低频:比如把逐笔交易聚合成1分钟K线
  2. 低频→高频:比如把日线数据填充到分钟级别(注意:这会有信息损失)

3.2 实战代码:逐笔交易对齐到1分钟K线

# 假设df是逐笔交易数据,包含time, price, volume, direction
df['time'] = pd.to_datetime(df['time'])
df = df.set_index('time')

# 重采样到1分钟
minute_data = df.resample('1min').agg({
    'price': 'ohlc',          # 生成开盘、最高、最低、收盘
    'volume': 'sum',          # 成交量求和
    'direction': lambda x: (x == 1).sum() - (x == -1).sum()  # 净买卖量
})

# 重命名列
minute_data.columns = ['open', 'high', 'low', 'close', 'volume', 'net_buy']

# 填充缺失的分钟(比如没有交易的分钟)
minute_data = minute_data.fillna(method='ffill')

print(minute_data.head())
个人经验:对齐的时候,我建议先检查一下数据的时间跨度。如果某只股票一天只交易了3个小时,但你用24小时去重采样,中间会多出一大堆空值。这时候用ffill填充要小心——它会把收盘价一直带到第二天开盘,这显然不合理。

四、知识体系总览

下面这张图,是我自己整理的数据处理流程。每次拿到新数据,我都会按这个框架走一遍。你可以把它当作一个检查清单。

交易记录数据处理流程 原始交易记录 字段解析:时间、价格、成交量、方向 数据清洗与预处理 缺失值处理 | 异常值过滤 | 去重 | 方向统一 时间排序 | 格式标准化 时间序列对齐 重采样(高频→低频 / 低频→高频) 向前填充 | 插值法 | 时间轴统一 可用信号数据

这张图其实就概括了本章的核心。从原始数据到可用信号,中间必须经过字段解析、数据清洗、时间对齐这三步。少一步,你的Alpha信号就可能建立在沙子上。

五、一些碎碎念

做数据处理,其实没什么高深的技巧。更多的是耐心和细心。我见过太多人,花了几周时间写策略,结果数据预处理只用了半天。嗯,结果可想而知——回测漂亮,实盘拉胯。

我个人习惯是,拿到任何新数据,先花70%的时间做清洗和对齐,剩下30%才用来挖信号。你可能会觉得这太慢了。但相信我,数据干净了,信号自然就浮出来了。

最后说一句:别怕脏数据。每个脏数据背后,都可能藏着一个别人没发现的Alpha。关键是你得先把它洗干净。