2. 金融时间序列基础
在把 TCN 用于金融长序列预测之前,必须先回答一个更基础的问题:我们手里的“序列”到底是什么。金融数据不是实验室里等间隔采样的理想信号,它同时受到交易制度、市场微观结构、信息披露节奏和参与者行为的影响。理解数据类型、频率、长序列特性、非平稳性以及噪声与缺失值,是后续特征提取和模型设计能否成立的前提。
一、金融数据类型与频率:先分清对象,再谈建模
金融数据按形态大致可分为四类:行情类(价格、成交量、买卖盘)、基本面类(财报、估值、宏观指标)、文本类(公告、新闻、研报、社交媒体)和衍生类(技术指标、波动率、资金流)。它们的采样机制完全不同:行情是事件驱动的高频流,基本面是低频离散快照,文本是不规则到达的异步事件。
频率决定了序列的“信息密度”和“噪声结构”。常见频率包括:
- Tick 级:逐笔成交与委托,毫秒至秒级,含大量微观结构噪声;
- 分钟级:1min、5min、15min,适合日内策略,但存在日内周期;
- 日频:最常用的建模频率,样本量适中,噪声相对可控;
- 周频/月频:宏观与基本面常用,样本少、长序列依赖强;
- 混合频率:如日频价格配月频宏观,需要对齐或分层建模。
频率选择不是越高越好。高频数据信噪比低、存储与计算成本高,且非同步交易会引入伪相关。对 TCN 而言,日频与分钟级重采样通常是长序列建模的平衡点。
二、长序列特性:TCN 的用武之地
金融序列的“长”体现在两个层面:一是样本长度长(数年日频可达数千点),二是依赖跨度长(宏观周期、政策滞后、情绪传导可能跨越数百个时间步)。传统 RNN 在长依赖上易梯度消失,而 TCN 通过膨胀因果卷积以指数级扩大感受野,天然适合捕捉长程模式。
但长序列也带来三个现实问题:
- 分布漂移:市场制度变化使早期样本与近期样本分布不一致;
- 记忆衰减:并非所有历史都同等重要,需要注意力或门控机制筛选;
- 计算与显存:长输入会显著增加卷积层显存占用,需要分块或降采样。
三、非平稳性:金融序列的“默认状态”
平稳性要求均值、方差和自协方差不随时间变化。金融价格几乎从不满足这一条件:存在趋势、波动率聚集、结构突变。直接对原始价格建模,模型容易学到“时间位置”而非“可泛化模式”。
处理非平稳性的常用手段包括:
- 差分:一阶差分消除趋势,得到收益率序列;
- 对数变换:稳定方差,使乘法关系线性化;
- 滚动标准化:用滚动窗口的均值方差做 z-score,避免未来信息泄露;
- 波动率建模:GARCH 类模型或滚动波动率作为特征;
- 结构突变检测:识别制度切换点,分段建模或加入虚拟变量。
需要强调:差分和标准化必须只用历史信息。若用全样本均值方差做标准化,会造成严重的数据穿越,离线指标虚高、实盘失效。
四、噪声与缺失值:清洗决定上限
金融噪声来源多样:买卖价差跳动、非同步交易、错误报价、停牌、涨跌停、节假日。缺失值则常见于:非交易日、停牌、数据源断档、字段未披露。处理方式需区分“真缺失”与“假缺失”。
| 问题类型 | 典型场景 | 推荐处理 | 风险提示 |
|---|---|---|---|
| 随机缺失 | 数据源偶发断点 | 线性插值 / 前向填充 | 连续长段缺失不宜插值 |
| 结构性缺失 | 停牌、非交易日 | 按交易日历对齐,保留掩码 | 直接填充会制造虚假连续 |
| 异常值 | 错误报价、闪崩 | MAD / 分位数截断 / 中位数替换 | 勿用全局均值,易受极值污染 |
| 高频噪声 | Tick 级跳动 | 重采样、移动平均、小波去噪 | 过度平滑会抹掉有效信号 |
一个稳健的清洗流程可以概括为:对齐交易日历 → 标记缺失与异常 → 分类型填充 → 生成缺失掩码 → 滚动标准化。掩码本身可作为特征输入 TCN,让模型知道“这里原本没有数据”。
# 示例:滚动标准化 + 缺失掩码(避免未来信息泄露)
import numpy as np
import pandas as pd
def rolling_zscore(series, window=60):
mean = series.rolling(window, min_periods=1).mean()
std = series.rolling(window, min_periods=1).std()
return (series - mean) / (std + 1e-8)
df = pd.read_csv("prices.csv", parse_dates=["date"]).set_index("date")
df["ret"] = np.log(df["close"]).diff()
df["ret_z"] = rolling_zscore(df["ret"], window=60)
df["mask"] = df["ret"].notna().astype(int)
df["ret_filled"] = df["ret"].fillna(0.0)
print(df[["ret", "ret_z", "mask", "ret_filled"]].tail())
五、案例复盘:从原始行情到可建模序列
假设我们拿到某股票 2018—2024 年的日频行情,目标是预测未来 5 日收益率。第一步,按交易日历对齐,剔除停牌日并记录掩码;第二步,用对数差分得到收益率,用 MAD 截断极端值;第三步,用 60 日滚动窗口做 z-score;第四步,构造 TCN 输入张量,形状为 [batch, channels, seq_len],其中 channels 包含收益率、成交量变化、波动率和掩码。
复盘发现:若跳过掩码,模型在停牌前后会出现异常预测;若用全样本标准化,验证集指标明显优于实盘。可见,数据基础工作直接决定 TCN 的上限,模型结构只是在这个上限内做优化。
把上述基础工作做扎实,TCN 才能专注于学习真正可泛化的时序模式。数据类型决定输入通道,频率决定序列长度与噪声水平,非平稳性决定预处理方式,噪声与缺失决定清洗策略——四者共同构成金融长序列预测的地基。