3、北向资金数据清洗:缺失值处理、异常值检测、数据标准化与对齐

数据清洗这步,说实话,是整套策略里最枯燥但最要命的环节。我见过太多人拿着漂亮模型往脏数据上一跑,结果亏得底裤都不剩。北向资金数据尤其如此——交易所接口偶尔抽风、节假日导致断档、不同数据源时间戳对不上……这些问题你不提前处理,后面所有分析都是白搭。

今天咱们就把这块硬骨头啃下来。我会把我在实战中踩过的坑、试过的好方法,一股脑倒给你。

3.1 缺失值处理:别让空值毁了你的策略

北向资金数据最常见的缺失场景有三种:

  • 交易日缺失:A股休市但港股通正常,或者反过来
  • 单日数据缺失:接口超时、网络波动导致某天没拉到数据
  • 字段缺失:比如只拿到了净买入额,没拿到成交额

我个人习惯先做一步「数据完整性检查」。写个脚本看看哪些日期是空的:

import pandas as pd
import numpy as np

# 读取北向资金日频数据
df = pd.read_csv('northbound_flow.csv', parse_dates=['date'])
df.set_index('date', inplace=True)

# 检查缺失日期
full_calendar = pd.date_range(start=df.index.min(), end=df.index.max(), freq='B')
missing_dates = full_calendar.difference(df.index)
print(f"缺失交易日数量: {len(missing_dates)}")
print(missing_dates[:10])  # 看看前10个缺失日期

嗯,这里要注意:freq='B' 只排除周末,但A股还有春节、国庆等法定假日。更稳妥的做法是用交易所公布的交易日历。

我的小技巧: 从上交所或深交所官网下载当年的交易日历CSV,直接用它做日期索引。我在项目中吃过亏——用'B'频率导致把非交易日当缺失值填充了,回测结果虚高不少。

处理缺失值,我一般按优先级来:

  1. 前向填充(ffill):适合北向资金这种「存量数据」。比如昨天买了100亿,今天数据缺失,用昨天的值填充——逻辑上说得通,因为北向资金持仓变化是渐进的。
  2. 线性插值:如果连续缺失超过3天,前向填充误差会累积。这时候用插值更合理。
  3. 直接删除:缺失比例低于1%的,删掉也没影响。但别超过5%,否则样本偏差会让你后悔。
# 前向填充(默认策略)
df.fillna(method='ffill', inplace=True)

# 如果连续缺失超过3天,改用线性插值
df.interpolate(method='linear', limit_direction='both', inplace=True)

# 检查还有没有漏网之鱼
print(f"处理后缺失值数量: {df.isnull().sum().sum()}")
避坑指南: 我曾经直接用均值填充北向资金数据,结果策略信号频繁开仓——因为均值把波动抹平了,导致模型误判趋势。记住:金融时序数据,千万别用全局均值填充!

3.2 异常值检测:揪出那些离谱的数字

北向资金数据里,异常值通常长这样:

  • 单日净买入突然飙到500亿(正常也就几十亿)
  • 成交额突然变成0(可能是接口返回了空值)
  • 连续5天净流入后突然巨量流出(可能是数据错误,也可能是真的大资金调仓)

怎么区分真假异常?我常用的方法是「3σ原则 + 滚动窗口」:

def detect_outliers(series, window=20, n_std=3):
    rolling_mean = series.rolling(window=window).mean()
    rolling_std = series.rolling(window=window).std()
    
    upper_bound = rolling_mean + n_std * rolling_std
    lower_bound = rolling_mean - n_std * rolling_std
    
    outliers = (series > upper_bound) | (series < lower_bound)
    return outliers

# 检测北向资金净买入的异常值
df['outlier'] = detect_outliers(df['net_buy'], window=20, n_std=3)
outlier_dates = df[df['outlier']].index
print(f"检测到 {len(outlier_dates)} 个异常日期")
print(outlier_dates[:5])

为什么用滚动窗口?因为北向资金的波动率本身就在变——2019年日均几十亿,2021年可能上百亿。固定阈值根本没法用。滚动窗口相当于给每个时间段「量身定制」了正常范围。

关键判断逻辑:

  • 如果异常值出现在财报发布日、政策公布日——保留,这是真实的市场反应
  • 如果异常值出现在普通交易日,且前后数据正常——标记为可疑,人工复核
  • 如果异常值导致策略信号反转——我会回看原始数据源,确认是不是接口问题

处理异常值,我推荐「缩尾处理」而不是直接删除:

# 缩尾处理:将异常值替换为边界值
def winsorize(series, lower=0.01, upper=0.99):
    q_low = series.quantile(lower)
    q_high = series.quantile(upper)
    return series.clip(lower=q_low, upper=q_high)

df['net_buy_clean'] = winsorize(df['net_buy'], lower=0.01, upper=0.99)

你想想看,直接删除异常值会导致时间序列出现「断点」,后续计算滚动指标时全乱套。缩尾处理既保留了数据量,又限制了极端值的影响。

3.3 数据标准化:让北向资金和指数站在同一起跑线

北向资金净买入是「绝对数值」,上证指数是「点位」,两者量纲完全不同。直接拿原始数据做回归?那结果肯定被指数点位主导。

标准化说白了就是「去量纲化」。我常用的方法有三种:

方法 公式 适用场景
Z-score 标准化 (x - μ) / σ 数据近似正态分布时
Min-Max 归一化 (x - min) / (max - min) 数据有明确上下界时
分位数标准化 映射到 [0,1] 均匀分布 数据分布偏态严重时

我个人偏好 Z-score,原因很简单——它保留了数据的相对大小关系。北向资金净买入从100亿降到50亿,Z-score能反映出「偏离均值多少倍标准差」,这对策略信号生成特别有用。

from sklearn.preprocessing import StandardScaler

# 对北向资金和指数分别做Z-score标准化
scaler = StandardScaler()
df[['net_buy_std', 'index_std']] = scaler.fit_transform(df[['net_buy_clean', 'index_close']])

# 标准化后,两者均值0,标准差1,可以直接比较
print(f"北向资金标准化后均值: {df['net_buy_std'].mean():.4f}")
print(f"指数标准化后均值: {df['index_std'].mean():.4f}")
注意: 标准化时一定要用「训练集」的均值和标准差去转换「测试集」。我在做回测时犯过这个错——用全量数据算均值和标准差,导致未来信息泄露,回测曲线漂亮得不像真的。后来改成滚动标准化,才得到靠谱的结果。

3.4 数据对齐:让时间戳严丝合缝

北向资金数据和指数数据的时间戳,经常对不上。原因包括:

  • 北向资金数据是「收盘后」发布的,指数数据是「盘中实时」的
  • 港股通交易日和A股交易日不完全一致
  • 不同数据源的时间格式不同(有的用'2024-01-15',有的用'2024/01/15')

对齐的核心原则就一条:以指数数据的时间戳为基准。因为我们的策略是「根据北向资金信号交易指数」,指数的时间轴才是主线。

# 读取指数数据
index_df = pd.read_csv('sh_index.csv', parse_dates=['date'])
index_df.set_index('date', inplace=True)

# 读取北向资金数据
north_df = pd.read_csv('northbound.csv', parse_dates=['date'])
north_df.set_index('date', inplace=True)

# 以指数日期为基准,左连接北向资金数据
aligned_df = index_df.join(north_df[['net_buy_clean']], how='left')

# 检查对齐后的缺失情况
print(f"对齐后缺失值数量: {aligned_df['net_buy_clean'].isnull().sum()}")

这里用 how='left' 的意思是:保留指数所有交易日,北向资金数据有就填,没有就空着。然后我们再走一遍缺失值处理流程。

我曾经踩过的坑: 有次我用 how='inner' 做对齐,结果发现回测区间少了将近20个交易日——因为港股通休市日被全删了。这些日子指数明明在交易,策略却「空仓」,回测结果严重失真。记住:永远以指数为基准!

3.5 本章知识体系

下面这张图,把数据清洗的完整流程串起来了。我建议你保存下来,每次做数据预处理时对照着走一遍:

北向资金数据清洗流程 原始北向资金数据 缺失值处理 前向填充 → 线性插值 → 删除(<1%) 异常值检测 3σ滚动窗口 → 人工复核 → 缩尾处理 数据标准化 Z-score / Min-Max / 分位数标准化 数据对齐(以指数为基准) 关键原则 1. 保留交易日历 2. 滚动窗口检测 3. 缩尾不删除 4. 训练集标准化 5. 指数为基准

数据清洗这步,说白了就是「垃圾进,垃圾出」的防火墙。你花80%的时间把数据洗干净,后面20%的建模工作才能出真东西。我见过太多人急着跑模型,结果被脏数据坑得怀疑人生——别走那条路。

下一章咱们聊特征工程,到时候你会感谢今天认真清洗数据的自己。


专注资料整理