第一章:数据准备与预处理——打好地基才能盖高楼

各位同学,咱们直接进入正题。做量化交易,尤其是研究羊群效应这种偏行为金融的策略,数据就是你的眼睛。眼睛要是花的,后面再牛的模型也白搭。这一章,我带你手把手把数据这关过了。

1. 数据源选择:Level2行情 vs 逐笔成交

先说说数据从哪来。我个人习惯把数据源分成两类:Level2行情逐笔成交数据。很多人一开始搞混,我当年也踩过坑。

数据源 特点 适用场景
Level2行情 每3秒快照一次,包含十档买卖盘口、逐笔成交汇总 监测资金流向、大单异动、盘口挂单变化
逐笔成交数据 每一笔真实成交的明细,精确到毫秒 识别主力对倒、拆单行为、计算真实资金流向

说白了,Level2行情像看“快照”,逐笔成交像看“录像”。研究羊群效应时,我建议两者都拿。为什么?因为羊群行为往往体现在“小单跟随大单”的逐笔细节里,而Level2的盘口变化能帮你判断情绪拐点。

我的经验:如果预算有限,先上逐笔成交数据。我在项目中遇到过,光靠Level2做出来的信号,回测漂亮,实盘一跑就崩——就是因为缺失了逐笔的微观结构信息。

2. 数据清洗:别让脏数据毁了你的策略

数据拿到手,第一件事不是分析,是洗数据。我见过太多人上来就建模,结果发现异常值全是交易所测试数据。嗯,这里要注意。

2.1 异常值处理

常见的异常值有几种:

  • 价格异常:比如某只股票突然出现0.01元的成交价。这通常是交易所测试或数据源错误。
  • 成交量异常:单笔成交量超过该股票流通盘的1%,大概率是数据合并错误。
  • 时间戳异常:比如下午3点之后还有成交记录,那是盘后数据混进来了。

我的处理方式很简单:

# 价格异常:剔除涨跌停板之外的极端值
df = df[(df['price'] >= df['price'].quantile(0.001)) & 
        (df['price'] <= df['price'].quantile(0.999))]

# 成交量异常:单笔成交超过流通股本0.5%的标记为异常
df = df[df['volume'] <= df['circulating_shares'] * 0.005]

# 时间戳异常:只保留交易时段数据
df = df[(df['time'] >= '09:30:00') & (df['time'] <= '15:00:00')]
避坑指南:我曾经因为没处理“集合竞价”阶段的数据,导致开盘前几分钟的信号全部失真。后来我强制剔除了9:25之前的逐笔数据,问题才解决。

2.2 缺失值填充

逐笔数据一般不会缺,但Level2行情偶尔会丢包。遇到缺失值,我的原则是:能不填就不填,非要填就谨慎

  • 连续缺失不超过3个周期:用前向填充(ffill),因为行情变化有惯性。
  • 连续缺失超过3个周期:直接删除该时间段,避免引入噪声。
  • 盘口数据缺失:用前后各5个周期的中位数填充,别用均值——盘口数据分布很偏。
# 前向填充连续缺失不超过3个
df['bid_price'].fillna(method='ffill', limit=3, inplace=True)

# 超过3个缺失的直接删除
df = df[df['bid_price'].notna()]

3. 数据对齐与重采样:统一时间尺度

不同数据源的时间戳精度不一样。逐笔数据精确到毫秒,Level2行情每3秒一次。你要把它们对齐到统一的K线周期上,才能做特征计算。

我个人习惯用1分钟K线做日内策略,用5分钟K线做波段策略。为什么?1分钟能捕捉到羊群效应的启动瞬间,5分钟能过滤掉部分噪音。

3.1 重采样到1分钟K线

# 将逐笔成交数据重采样为1分钟K线
df_1min = df.resample('1T', on='time').agg({
    'price': 'ohlc',      # 开盘价、最高价、最低价、收盘价
    'volume': 'sum',      # 成交量求和
    'amount': 'sum',      # 成交额求和
    'buy_volume': 'sum',  # 主动买入量
    'sell_volume': 'sum'  # 主动卖出量
})

这里有个细节:OHLC(开盘、最高、最低、收盘)的计算要小心。我见过有人直接用pandas的ohlc函数,结果开盘价取的是该分钟的第一笔成交价——但如果第一笔是异常值呢?所以我建议先清洗,再重采样。

3.2 对齐不同数据源

Level2行情和逐笔数据的时间戳可能差几毫秒。对齐时,我通常以逐笔数据的时间戳为准,把Level2的盘口数据向前对齐到最近的整秒。

# 将Level2数据对齐到逐笔数据的时间轴
df_level2['time_aligned'] = df_level2['time'].dt.floor('1S')
df_merged = pd.merge_asof(df_tick, df_level2, 
                          left_on='time', 
                          right_on='time_aligned', 
                          direction='backward')
小技巧:对齐时用pd.merge_asofdirection='backward',意思是拿Level2最近一次的快照数据来匹配逐笔成交。这样能最大程度保留盘口信息的时效性。

4. 特征工程基础:从原始数据到有效信号

数据准备好了,接下来就是造特征。羊群效应相关的特征,我总结了三类:

  • 资金流向类:主力净流入、散户净流入、大单占比。这些能直接反映“谁在买、谁在卖”。
  • 盘口情绪类:买卖盘口厚度差、委买委卖比例、挂单撤单频率。这些能看出市场情绪是贪婪还是恐惧。
  • 微观结构类:成交间隔时间、每笔成交量分布、价格冲击系数。这些能识别出“跟风盘”的痕迹。

举个例子,计算“散户跟风强度”这个特征:

# 定义:小单(散户)跟风强度 = 小单买入量 / 总成交量
df['small_buy_ratio'] = df['small_buy_volume'] / df['volume']

# 定义:大单异动系数 = 大单成交额 / 过去5分钟平均大单成交额
df['large_order_anomaly'] = df['large_amount'] / df['large_amount'].rolling(5).mean()

这些特征看起来简单,但组合起来就能刻画羊群效应的典型场景:大单先动,小单后跟,盘口挂单迅速堆积,然后价格加速上涨或下跌。

核心思路:特征不是越多越好。我见过有人一口气造了200个特征,结果过拟合得一塌糊涂。我的建议是,先围绕“羊群效应”这个核心逻辑造10-15个特征,跑通之后再慢慢加。

知识体系总览

下面这张图,是我做数据预处理时的完整流程。你可以把它当成一个检查清单,每次处理数据前过一遍。

数据准备与预处理流程 数据源选择 Level2行情 / 逐笔成交 数据清洗 异常值处理 / 缺失值填充 对齐与重采样 1分钟 / 5分钟K线 特征工程基础 资金流向 / 盘口情绪 / 微观结构 ✅ 干净、对齐、有特征的数据集 ⚠️ 常见坑点 • 集合竞价数据未剔除 • 缺失值填充方式错误 • 不同数据源时间戳未对齐 • 特征造太多导致过拟合 • 回测时用了未来数据 📊 输出示例 时间 | 价格 | 成交量 09:31 | 10.2 | 1500 09:32 | 10.3 | 2300 09:33 | 10.4 | 1800 ... 共240根1分钟K线

你看,整个流程其实不复杂,但每一步都马虎不得。数据源选对了,清洗干净了,对齐准确了,特征造合理了——后面建模就是水到渠成的事。

好了,这一章就到这里。数据准备是苦活累活,但也是最有价值的活。你想想看,如果连数据都是错的,那策略再漂亮又有什么用?


专注资料整理