第4章 数据预处理与特征工程:Tick级数据清洗、特征构建与标准化

做高频策略,圈里有个共识:数据决定上限,模型只是逼近这个上限。我见过太多人把精力全花在调参上,结果数据本身就有问题——那真是白费功夫。今天咱们就聊聊Tick级数据怎么洗、特征怎么造、标准化怎么做。

4.1 Tick级数据清洗:别让脏数据毁了你的策略

先说说Tick数据长什么样。交易所每笔成交都会推一条记录,包含时间、价格、成交量、买卖方向。但实际拿到的数据,嗯,问题不少。

4.1.1 常见脏数据类型

问题类型 典型表现 处理方式
时间戳错乱 后一笔时间早于前一笔 按时间排序后剔除异常
价格异常 价格超出当日涨跌停板 用前一笔价格替换或剔除
成交量异常 单笔成交量超过持仓量 标记为可疑,人工复核
重复数据 完全相同的记录出现多次 去重保留第一条

我个人习惯,拿到原始数据后先做三件事:排序、去重、范围检查。排序用时间戳,去重按时间+价格+成交量联合判断,范围检查就是看价格是否在合理区间内。

核心原则:宁可少一条数据,也不要一条脏数据。高频策略对噪声极其敏感,一条错误的价格就能让信号完全跑偏。

4.1.2 实战清洗代码

下面是我常用的清洗流程,用Python实现,效率还不错:

import pandas as pd
import numpy as np

def clean_tick_data(df):
    """
    Tick数据清洗函数
    df: 包含['time', 'price', 'volume', 'direction']的DataFrame
    """
    # 1. 按时间排序
    df = df.sort_values('time').reset_index(drop=True)
    
    # 2. 去重
    df = df.drop_duplicates(subset=['time', 'price', 'volume'])
    
    # 3. 价格范围检查(假设是螺纹钢,价格在3000-5000之间)
    price_low, price_high = 3000, 5000
    df = df[(df['price'] >= price_low) & (df['price'] <= price_high)]
    
    # 4. 成交量检查(单笔不超过1000手)
    df = df[df['volume'] <= 1000]
    
    # 5. 时间连续性检查(相邻时间差不超过5秒)
    df['time_diff'] = df['time'].diff().dt.total_seconds()
    df = df[df['time_diff'] <= 5.0]
    
    return df.drop('time_diff', axis=1)

避坑指南:我曾经在清洗股指期货数据时,没注意夜盘和日盘的时间连续性,结果把夜盘最后一笔和日盘第一笔之间的时间差当成了异常,直接删掉了开盘数据。后来才发现,夜盘收盘到日盘开盘本来就有间隔。所以,一定要区分交易时段

4.2 特征构建:从Tick数据中挖出有用信息

数据洗干净了,接下来就是造特征。高频策略的特征,说白了就是从微观结构里找规律。我重点讲三个:价差、订单簿斜率、成交量分布。

4.2.1 价差特征

价差是最基础的特征。但别小看它,不同市场、不同品种的价差模式差异很大。比如股指期货的价差通常很窄,而某些商品期货的价差可能波动剧烈。

常用的价差特征包括:

  • 买卖价差:ask_price - bid_price,反映流动性
  • 相对价差:(ask - bid) / mid_price,标准化后的价差
  • 价差变化率:当前价差 / 前N笔平均价差 - 1,捕捉价差突变

代码实现很简单:

def compute_spread_features(df):
    """
    计算价差相关特征
    df: 包含bid_price, ask_price的DataFrame
    """
    df['spread'] = df['ask_price'] - df['bid_price']
    df['mid_price'] = (df['ask_price'] + df['bid_price']) / 2
    df['rel_spread'] = df['spread'] / df['mid_price']
    
    # 价差变化率(相对前10笔的均值)
    df['spread_ma10'] = df['spread'].rolling(10).mean()
    df['spread_change'] = df['spread'] / df['spread_ma10'] - 1
    
    return df

4.2.2 订单簿斜率

订单簿斜率,说白了就是看买卖双方的挂单力量对比。斜率越大,说明某一方的挂单越集中,价格越容易朝那个方向移动。

计算方式:

  • 取订单簿前N档的挂单量
  • 对价格和挂单量做线性回归
  • 回归系数就是斜率

我一般取前5档,因为太深的档位流动性差,参考意义不大。

def compute_orderbook_slope(df, levels=5):
    """
    计算订单簿斜率
    df: 包含bid_price_1..5, bid_vol_1..5, ask_price_1..5, ask_vol_1..5
    """
    slopes = []
    for i in range(len(df)):
        # 买方数据
        bid_prices = [df.loc[i, f'bid_price_{j}'] for j in range(1, levels+1)]
        bid_vols = [df.loc[i, f'bid_vol_{j}'] for j in range(1, levels+1)]
        # 卖方数据
        ask_prices = [df.loc[i, f'ask_price_{j}'] for j in range(1, levels+1)]
        ask_vols = [df.loc[i, f'ask_vol_{j}'] for j in range(1, levels+1)]
        
        # 合并所有价格和挂单量
        all_prices = bid_prices + ask_prices
        all_vols = bid_vols + ask_vols
        
        # 线性回归求斜率
        slope = np.polyfit(all_prices, all_vols, 1)[0]
        slopes.append(slope)
    
    df['orderbook_slope'] = slopes
    return df

经验之谈:订单簿斜率在开盘和收盘时段特别有用。我记得有一次做螺纹钢策略,开盘时斜率突然从正变负,我立刻意识到有大单在压盘,果断调整了仓位方向。那笔交易帮我躲过了当天的暴跌。

4.2.3 成交量分布

成交量分布,就是看成交量在价格上的分布情况。如果成交量集中在某个价格区间,说明那里是重要的支撑或阻力位。

我常用的方法:

  • 成交量加权平均价(VWAP):累计成交额 / 累计成交量
  • 成交量分布标准差:衡量成交量在价格上的离散程度
  • 成交量集中度:前N笔成交量占总成交量的比例
def compute_volume_distribution(df, window=100):
    """
    计算成交量分布特征
    df: 包含price, volume的DataFrame
    """
    # VWAP
    df['cum_amount'] = (df['price'] * df['volume']).cumsum()
    df['cum_volume'] = df['volume'].cumsum()
    df['vwap'] = df['cum_amount'] / df['cum_volume']
    
    # 滚动窗口内的成交量集中度
    df['vol_concentration'] = df['volume'].rolling(window).apply(
        lambda x: x.max() / x.sum()
    )
    
    return df

4.3 特征标准化:让模型学得更快

特征造好了,但不同特征的量纲差异很大。比如价差可能是0.5,而成交量可能是5000。如果不做标准化,模型会天然偏向数值大的特征。

常用的标准化方法:

方法 公式 适用场景
Z-score标准化 (x - mean) / std 数据近似正态分布
Min-Max标准化 (x - min) / (max - min) 数据有明确边界
Robust标准化 (x - median) / IQR 数据有较多异常值

我个人偏好Robust标准化,因为Tick数据里异常值太多了。用中位数和四分位距,能有效抵抗异常值的干扰。

from sklearn.preprocessing import RobustScaler

def robust_scale_features(df, feature_cols):
    """
    对指定特征列做Robust标准化
    """
    scaler = RobustScaler()
    df[feature_cols] = scaler.fit_transform(df[feature_cols])
    return df, scaler

注意:标准化参数一定要用训练集计算,然后应用到测试集。千万别在整个数据集上做标准化,否则会造成未来信息泄露。我见过有人犯这个错,回测曲线漂亮得不行,实盘一跑就崩。

4.4 本章知识体系

下面这张图,把数据预处理和特征工程的整个流程串起来了:

Tick级数据预处理与特征工程流程 原始Tick数据 数据清洗 排序 → 去重 → 范围检查 → 时间连续性 特征构建 价差特征 订单簿斜率 成交量分布 特征标准化(Robust)

整个流程走下来,数据从原始状态变成了干净、有信息量、量纲统一的特征矩阵。这时候再扔给模型,效果会好很多。

最后说一句:特征工程没有标准答案。不同品种、不同市场,有效的特征组合可能完全不同。我的建议是:先做一批基础特征,然后通过回测和实盘验证,逐步迭代优化。别指望一次就能找到完美特征集。


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