3. 数据准备与清洗:获取Level-2行情数据、处理缺失值与异常值、数据对齐与重采样

做TWAP策略,最怕什么?

不是策略逻辑写错了,而是数据本身就有问题。我见过太多人,策略回测跑得漂漂亮亮,一上实盘就崩。为什么?数据没洗干净。

这一章,咱们就聊聊数据准备这件事。说白了,就是给你的策略喂一口干净的饭。

3.1 获取Level-2行情数据

TWAP策略对数据精度要求很高。普通的分时线,一分钟才一个价,根本不够用。你需要的是Level-2行情——也就是逐笔成交和十档盘口。

我个人习惯用Python的akshare库来拉数据。它免费,接口也稳定。当然,如果你有券商的数据源,那更好。

import akshare as ak
import pandas as pd
import numpy as np

# 获取某只股票的Level-2逐笔成交数据
# 注意:akshare的接口可能会变,建议用前先查文档
def fetch_level2_tick(symbol, date):
    """
    获取Level-2逐笔成交数据
    :param symbol: 股票代码,如 '600519'
    :param date: 日期,如 '2024-01-15'
    :return: DataFrame
    """
    try:
        df = ak.stock_zh_a_tick_tx(symbol=symbol, trade_date=date)
        print(f"成功获取 {symbol} 在 {date} 的Level-2数据,共 {len(df)} 条")
        return df
    except Exception as e:
        print(f"获取数据失败: {e}")
        return None

# 示例调用
df_tick = fetch_level2_tick('600519', '2024-01-15')
print(df_tick.head())

这里有个坑。Level-2数据量很大,一天可能有几万甚至十几万笔。你想想看,如果直接全量加载到内存,机器很容易卡死。我建议分批读取,或者用dtype指定列类型来节省内存。

小技巧: 拉数据时,只保留你需要的列。比如TWAP只需要时间、价格、成交量,别把买卖方向、成交编号都拉进来。省内存,也省时间。

3.2 处理缺失值与异常值

数据到手了,下一步就是清洗。为什么?因为交易所的数据也不是完美的。网络抖动、服务器延迟、撮合异常,都会导致数据出问题。

缺失值处理

最常见的缺失情况是:某几秒没有成交记录。TWAP是按时间切片的,如果某段时间没数据,你的算法就不知道该怎么下单。

我的做法是:先检查缺失比例。如果缺失少于5%,用前向填充;如果超过10%,那这段数据可能有问题,建议直接丢弃。

def handle_missing(df, time_col='time', price_col='price', vol_col='volume'):
    """
    处理缺失值
    1. 检查时间序列是否连续
    2. 对缺失的价格用前向填充
    3. 对缺失的成交量填0
    """
    # 生成完整的时间序列(假设是1秒间隔)
    full_time = pd.date_range(start=df[time_col].min(), 
                              end=df[time_col].max(), freq='1S')
    
    # 对齐
    df = df.set_index(time_col).reindex(full_time)
    
    # 前向填充价格
    df[price_col] = df[price_col].ffill()
    
    # 成交量缺失填0
    df[vol_col] = df[vol_col].fillna(0)
    
    missing_rate = df[price_col].isna().sum() / len(df)
    if missing_rate > 0.05:
        print(f"警告:缺失率 {missing_rate:.2%},建议检查数据源")
    
    return df.reset_index().rename(columns={'index': time_col})

异常值处理

异常值比缺失值更隐蔽。比如某笔成交价格突然比前一笔高了10%,这可能是错单,也可能是真实的大单。怎么判断?

我一般用两种方法:

  • 价格突变检测: 如果单笔价格变动超过前20笔均值的3倍标准差,标记为异常。
  • 成交量异常检测: 如果单笔成交量超过过去100笔均值的5倍,标记为异常。

嗯,这里要注意:不要直接删除异常值。我曾经吃过这个亏——删掉了一个看似异常的跳空,结果那其实是开盘集合竞价的真实数据。后来我改成「标记+人工复核」的方式。

避坑指南: 我曾经在回测中直接删除了所有异常值,结果策略表现异常好。后来发现,那些「异常值」其实是真实的市场冲击成本。删掉它们,等于忽略了交易成本,回测结果完全失真。
def detect_outliers(df, price_col='price', vol_col='volume', window=20, z_thresh=3):
    """
    检测异常值
    返回异常标记列
    """
    # 价格异常
    df['price_ma'] = df[price_col].rolling(window=window).mean()
    df['price_std'] = df[price_col].rolling(window=window).std()
    df['price_z'] = (df[price_col] - df['price_ma']) / df['price_std']
    df['price_outlier'] = df['price_z'].abs() > z_thresh
    
    # 成交量异常
    df['vol_ma'] = df[vol_col].rolling(window=100).mean()
    df['vol_std'] = df[vol_col].rolling(window=100).std()
    df['vol_z'] = (df[vol_col] - df['vol_ma']) / df['vol_std']
    df['vol_outlier'] = df['vol_z'].abs() > 5
    
    # 综合标记
    df['is_outlier'] = df['price_outlier'] | df['vol_outlier']
    
    return df

3.3 数据对齐与重采样

TWAP策略的核心逻辑是:把总订单量均匀分配到每个时间切片。所以,数据必须对齐到统一的时间轴上。

为什么需要对齐?

因为Level-2数据是事件驱动的——有成交才记录。但TWAP需要的是固定间隔的快照。比如每5秒看一眼,当前价格是多少,累计成交了多少。

重采样怎么做?

我一般用pandasresample方法。把逐笔数据聚合成固定间隔的K线。

def resample_to_twap(df, freq='5S', price_col='price', vol_col='volume'):
    """
    将逐笔数据重采样为TWAP所需的时间切片数据
    :param freq: 时间切片长度,如 '5S' 表示5秒
    :return: 包含 open, high, low, close, volume 的DataFrame
    """
    # 确保时间列是datetime类型
    df['time'] = pd.to_datetime(df['time'])
    df = df.set_index('time')
    
    # 重采样
    resampled = df.resample(freq).agg({
        price_col: ['first', 'max', 'min', 'last'],
        vol_col: 'sum'
    })
    
    # 展平列名
    resampled.columns = ['open', 'high', 'low', 'close', 'volume']
    
    # 去掉没有成交的时间段
    resampled = resampled[resampled['volume'] > 0]
    
    return resampled

# 示例
df_5s = resample_to_twap(df_tick, freq='5S')
print(df_5s.head())

你想想看,如果切片太短(比如1秒),很多切片里没有成交,TWAP就没法执行。如果切片太长(比如1分钟),又失去了精细化的优势。我个人习惯用5秒或10秒,这个频率既能捕捉到微观波动,又不会太稀疏。

核心逻辑: TWAP的数据对齐,本质上是在「时间均匀」和「数据可用」之间找平衡。切片内必须有成交数据,否则算法会卡住。

3.4 本章知识体系

下面这张图,帮你把数据准备的全流程串起来:

数据准备与清洗流程 1. 获取Level-2数据 逐笔成交 + 十档盘口 2. 缺失值处理 前向填充 / 丢弃 3. 异常值检测 Z-score / 标记复核 4. 数据对齐 统一时间轴 5. 重采样 5秒/10秒切片 TWAP可用数据 OHLCV + 时间戳 数据质量决定策略上限,清洗环节不可跳过

3.5 实战中的几个建议

最后,分享几个我在实战中踩过的坑:

  • 不要迷信Level-2数据: 它也有延迟。我对比过券商终端和API数据,有时差几百毫秒。做TWAP时,建议在算法里加一个「容忍窗口」。
  • 数据存储要规划好: 一天的数据可能几百MB。我习惯用Parquet格式存储,比CSV小5倍,读取也快。
  • 回测和实盘的数据源要一致: 我曾经回测用A数据源,实盘用B数据源,结果策略表现完全不同。后来统一了数据源,问题才解决。
我的习惯: 每次清洗完数据,我都会画一张价格曲线图,肉眼扫一遍。如果看到奇怪的尖峰或断层,就回去查原始数据。这步虽然土,但很管用。

数据准备这块,说白了就是「慢工出细活」。你花在清洗上的每一分钟,都会在策略表现上得到回报。别急着跑策略,先把数据伺候好。

专注资料整理