第一章:数据获取与预处理——从交易所/数据商拿到Level-1/Level-2行情

做量化交易,第一道坎就是数据。我见过太多人策略写得漂亮,结果一跑实盘就崩——原因往往是数据源没处理好。今天咱们就聊聊,怎么从交易所和数据商手里,把Level-1和Level-2行情数据拿回来,再洗干净、对齐好。

1.1 Level-1 vs Level-2:到底差在哪?

先搞清楚这两个概念。Level-1行情,说白了就是咱们平时在炒股软件上看到的那一套:开盘价、收盘价、最高价、最低价、成交量、成交额,外加三档买卖盘口。数据频率通常是3秒或5秒快照一次。

Level-2行情就细多了。它提供十档甚至更深的买卖盘口,还有逐笔成交数据。我2018年做高频策略时,发现Level-1的数据根本不够用——你想捕捉盘口瞬间的挂单变化,3秒一次的快照早就把关键信息丢了。

核心区别一句话:Level-1是“结果”,Level-2是“过程”。做高频、做盘口分析,必须上Level-2。

1.2 数据源怎么选?

国内主流的数据源,我列个表给你看:

数据源 Level-1 Level-2 费用 我的评价
万得(Wind) 机构首选,稳定但贵
聚宽(JoinQuant) 回测方便,实盘需注意
通联数据 API友好,文档清晰
交易所直连 极高 延迟最低,但门槛高

我个人习惯是:回测用聚宽或通联,实盘用交易所直连。为什么?回测时数据量巨大,聚宽的历史数据全,省心。实盘时延迟是关键,直连能省掉中间商那几毫秒。

1.3 数据获取实战:用Python拉取Level-2行情

拿通联数据举个例子。他们提供Python SDK,用起来挺顺手。下面这段代码,是我在项目中实际用过的:

import tushare as ts
import pandas as pd
from datetime import datetime

# 设置token(去通联官网申请)
ts.set_token('your_token_here')
pro = ts.pro_api()

# 获取某只股票的Level-2行情
# 注意:Level-2数据需要额外权限
df = pro.stk_factor_pro(
    ts_code='000001.SZ',
    start_date='20240101',
    end_date='20240131',
    fields='ts_code,trade_date,open,high,low,close,vol,amount'
)

print(df.head())

小提示:Level-2的逐笔数据通常按天存储,一天一个文件。我建议你写个定时任务,每天收盘后自动拉取,避免盘中请求被限流。

1.4 数据清洗:缺失值、异常值、复权处理

数据拿到手,别急着用。我踩过的坑太多了——有一次策略回测收益高得离谱,查了半天才发现是某天数据缺失,系统自动用前值填充了,导致信号全错。

1.4.1 缺失值处理

行情数据缺失,常见原因有:停牌、节假日、网络中断。我的处理原则是:

  • 停牌日:直接删除该行,不要填充。因为停牌期间没有真实交易,填充会扭曲指标。
  • 盘中缺失:用前一个有效值填充,但仅限于短时间(比如1分钟内)。
  • 连续缺失:标记为异常,人工核查。
# 缺失值处理示例
df = df.dropna(subset=['close'])  # 删除收盘价缺失的行
df = df.fillna(method='ffill', limit=3)  # 前向填充,最多3个

1.4.2 异常值检测

什么是异常值?比如某只股票突然涨了1000%,或者成交量比前一天翻了100倍。这些多半是数据错误。

我常用的方法是:用滚动窗口计算均值和标准差,超过3倍标准差就标记为异常。

# 异常值检测:3-sigma法则
mean = df['close'].rolling(20).mean()
std = df['close'].rolling(20).std()
df['is_outlier'] = (abs(df['close'] - mean) > 3 * std)

注意:别一发现异常就删除。我曾经删掉了一个“异常”数据,后来发现那是真实发生的乌龙指事件——那天的行情恰恰是策略需要捕捉的极端情况。

1.4.3 复权处理

复权是个大坑。很多新手直接用不复权的价格做回测,结果分红送股后,价格突然跳空,策略信号全乱套。

我的建议是:回测用后复权,实盘用前复权。为什么?后复权能真实反映历史收益,前复权方便当前价格对比。

# 聚宽平台复权示例
from jqdatasdk import *
auth('account', 'password')

# 获取后复权数据
df = get_price('000001.XSHE', 
               start_date='2024-01-01', 
               end_date='2024-01-31',
               frequency='daily',
               fq='post')  # 'post'为后复权,'pre'为前复权

1.5 时间对齐与重采样

不同数据源的时间戳格式可能不一样。有的用Unix时间戳,有的用字符串。更麻烦的是,Level-1和Level-2的采样频率不同——Level-1是3秒一次,Level-2是逐笔。

做流动性指标时,必须把数据对齐到统一的时间轴上。我一般用1分钟或5分钟作为基准。

# 时间对齐:将逐笔数据重采样到1分钟
df['time'] = pd.to_datetime(df['time'], unit='s')
df.set_index('time', inplace=True)

# 重采样:计算每分钟的OHLC
resampled = df.resample('1T').agg({
    'price': 'ohlc',
    'volume': 'sum'
})

核心原则:时间对齐时,用“左闭右开”原则。比如1分钟K线,包含00:00到00:59的数据,不包含01:00的数据。这样能避免数据重复或遗漏。

1.6 本章知识体系总览

下面这张图,把数据获取与预处理的整个流程串起来了。你可以把它当作操作手册:

数据获取与预处理流程 数据源选择 交易所直连 / 数据商API 数据获取 Level-1 / Level-2 行情 数据清洗 缺失值 / 异常值 / 复权 时间对齐与重采样 统一时间轴 / 频率转换 最终输出:干净、对齐的行情数据 用于流动性指标计算与策略回测 注意:每一步都可能出现数据质量问题,建议加入日志记录

1.7 避坑指南

最后,分享几个我踩过的坑:

  • 时间戳时区问题:交易所数据是北京时间,但有些数据商存的是UTC。我曾经没做时区转换,导致策略在开盘前就发出了交易信号——嗯,那笔单子自然没成交。
  • 复权因子更新:公司分红后,复权因子会变。如果你用历史复权数据做回测,记得定期更新复权因子,否则回测结果会漂移。
  • Level-2数据量:一只股票一天的逐笔数据可能有几十万条。如果你做全市场回测,硬盘空间和内存都要提前规划好。

我的习惯:每次拉取数据后,先做一次简单的质量检查——比如看收盘价是否在合理范围内,成交量是否为正。花5分钟检查,能省下后面几小时的调试时间。

数据预处理这一步,看似枯燥,但它是整个量化策略的基石。数据干净了,后面的流动性指标计算才能靠谱。下一章,咱们就聊聊怎么从这些干净数据里,提取出有意义的流动性指标。


公众号:蓝海资料掘金营,微信deep3321