4. 时间序列切分策略:滑动窗口法、扩展窗口法、时间点分割法在交易数据中的应用与代码实现

在金融交易数据中,样本的切分不能采用随机打乱的方式,因为交易数据具有严格的时间依赖性和序列自相关性。错误的切分会导致未来信息泄露,使模型在回测中表现优异,但在实盘交易中失效。本章详细讲解三种主流的时间序列切分策略,并给出其在异常交易检测场景下的具体实现。

核心原则: 任何切分策略都必须保证训练集的时间戳全部早于验证集/测试集的时间戳,杜绝“用未来预测过去”的数据泄露。

4.1 三种切分策略对比

策略名称 核心思想 训练集规模 适用场景 主要风险
滑动窗口法 固定窗口长度,按步长滑动 恒定 高频交易、短期模式识别 窗口长度选择敏感
扩展窗口法 起始点固定,终点逐步后移 递增 趋势跟踪、长期策略回测 早期样本权重过大
时间点分割法 按固定时间点(如月末、季末)切分 阶梯式增长 监管报告、定期模型重训 切分点可能错过市场突变

4.2 滑动窗口法(Sliding Window)

滑动窗口法维护一个固定长度的训练窗口,每次按步长向前滑动,丢弃最旧的数据,加入最新的数据。该方法适用于概念漂移较快的交易场景,如日内高频异常交易检测。

关键参数: 窗口长度(window_size)决定模型记忆的历史深度;步长(step_size)控制模型更新频率。步长越小,模型更新越频繁,但计算开销越大。
滑动窗口法切分示意图 t₀ t₁ t₂ t₃ t₄ t₅ 窗口 1(训练) 验证 窗口 2(训练) 验证 步长

代码实现:滑动窗口切分

import pandas as pd
import numpy as np
from typing import List, Tuple

def sliding_window_split(
    df: pd.DataFrame,
    window_size: int = 1000,
    step_size: int = 200,
    test_size: int = 200
) -> List[Tuple[pd.DataFrame, pd.DataFrame]]:
    """
    滑动窗口法切分交易数据
    
    Parameters:
    -----------
    df : pd.DataFrame
        按时间排序的交易数据,必须包含 'timestamp' 列
    window_size : int
        训练窗口长度(样本数)
    step_size : int
        滑动步长(样本数)
    test_size : int
        验证集长度(样本数)
    
    Returns:
    --------
    splits : List[Tuple[train_df, test_df]]
        训练集与验证集的列表
    """
    df = df.sort_values('timestamp').reset_index(drop=True)
    total_len = len(df)
    splits = []
    
    start = 0
    while start + window_size + test_size <= total_len:
        train_end = start + window_size
        test_end = train_end + test_size
        
        train_df = df.iloc[start:train_end].copy()
        test_df = df.iloc[train_end:test_end].copy()
        
        splits.append((train_df, test_df))
        start += step_size
    
    return splits

# 使用示例
# df = pd.read_parquet('trade_data.parquet')
# splits = sliding_window_split(df, window_size=5000, step_size=1000, test_size=500)
# print(f"生成 {len(splits)} 个训练/验证对")

4.3 扩展窗口法(Expanding Window)

扩展窗口法的训练集起始点固定,终点不断向后扩展。该方法保留了全部历史信息,适合市场结构相对稳定、需要长期记忆的场景,如信用风险模型或低频异常交易检测。

扩展窗口法切分示意图 t₀ t₁ t₂ t₃ t₄ t₅ 训练 1 验证 训练 2(扩展) 验证 训练 3(进一步扩展) 验证 扩展 扩展

代码实现:扩展窗口切分

def expanding_window_split(
    df: pd.DataFrame,
    min_train_size: int = 1000,
    step_size: int = 500,
    test_size: int = 200
) -> List[Tuple[pd.DataFrame, pd.DataFrame]]:
    """
    扩展窗口法切分交易数据
    
    Parameters:
    -----------
    df : pd.DataFrame
        按时间排序的交易数据
    min_train_size : int
        初始训练集最小样本数
    step_size : int
        每次扩展的样本数
    test_size : int
        验证集长度
    
    Returns:
    --------
    splits : List[Tuple[train_df, test_df]]
    """
    df = df.sort_values('timestamp').reset_index(drop=True)
    total_len = len(df)
    splits = []
    
    train_end = min_train_size
    while train_end + test_size <= total_len:
        train_df = df.iloc[:train_end].copy()
        test_df = df.iloc[train_end:train_end + test_size].copy()
        
        splits.append((train_df, test_df))
        train_end += step_size
    
    return splits

# 使用示例
# splits = expanding_window_split(df, min_train_size=3000, step_size=1000, test_size=500)
# print(f"生成 {len(splits)} 个训练/验证对,训练集规模从 {len(splits[0][0])} 到 {len(splits[-1][0])}")

4.4 时间点分割法(Time Point Split)

时间点分割法根据预定义的日历时间点(如每月1日、每季度初)进行切分。该方法与业务周期高度对齐,常用于监管报送、定期模型验证等场景。

注意: 时间点分割法要求切分点之间的数据量不均衡。例如,2月交易天数少,训练集可能明显小于3月。建议在切分后检查各分区的样本量,避免出现极小的验证集。

代码实现:时间点分割

def time_point_split(
    df: pd.DataFrame,
    split_dates: List[str],
    date_column: str = 'timestamp'
) -> List[Tuple[pd.DataFrame, pd.DataFrame]]:
    """
    按指定时间点分割交易数据
    
    Parameters:
    -----------
    df : pd.DataFrame
        交易数据
    split_dates : List[str]
        分割日期列表,格式 'YYYY-MM-DD'
    date_column : str
        日期列名
    
    Returns:
    --------
    splits : List[Tuple[train_df, test_df]]
        每个元素为 (训练集, 验证集)
    """
    df = df.sort_values(date_column).reset_index(drop=True)
    split_dates = sorted(pd.to_datetime(split_dates))
    splits = []
    
    prev_date = df[date_column].min()
    for split_date in split_dates:
        train_mask = df[date_column] < split_date
        test_mask = (df[date_column] >= split_date) & \
                    (df[date_column] < split_date + pd.Timedelta(days=30))
        
        train_df = df[train_mask].copy()
        test_df = df[test_mask].copy()
        
        if len(train_df) > 0 and len(test_df) > 0:
            splits.append((train_df, test_df))
    
    return splits

# 使用示例
# split_dates = ['2023-04-01', '2023-07-01', '2023-10-01', '2024-01-01']
# splits = time_point_split(df, split_dates)
# for i, (train, test) in enumerate(splits):
#     print(f"切分 {i+1}: 训练 {len(train)} 条, 验证 {len(test)} 条")

4.5 策略选择指南

  • 高频异常交易检测(毫秒/秒级):优先使用滑动窗口法,窗口长度设为 5000~10000 笔交易,步长设为窗口的 20%~50%。
  • 日频/周频策略回测:使用扩展窗口法,初始训练集至少包含 2 年数据,每次扩展 1 个季度。
  • 监管合规模型验证:使用时间点分割法,切分点对齐监管报告截止日期(如每月末、每季末)。
  • 市场结构突变期:避免使用扩展窗口法,改用滑动窗口法并缩短窗口长度,减少历史陈旧数据的影响。
实战建议: 在异常交易检测项目中,建议同时使用滑动窗口法和时间点分割法进行交叉验证。滑动窗口法评估模型对近期模式的捕捉能力,时间点分割法评估模型在固定时间周期上的稳定性。两种策略的验证结果差异较大时,说明模型存在过拟合或概念漂移问题。