第二章 数据获取与清洗:从公开API获取北向资金日频数据

做量化分析的人都知道一句话:垃圾进,垃圾出。北向资金分析也不例外。你拿到的数据如果是脏的,后面再漂亮的模型也是白搭。这一章,我就带你手把手搞定数据获取和清洗这两个最基础、也最关键的环节。

核心要点:北向资金日频数据的获取并不复杂,但清洗对齐才是真正的技术活。节假日处理、缺失值填充、数据对齐,这三件事做不好,你的分析结果可能全是错的。

2.1 数据源选择:从哪里拿数据?

我个人习惯用三个数据源:东方财富、新浪财经、以及港交所官方。为什么是三个?因为单一数据源偶尔会抽风,多一个备份心里踏实。

东方财富的接口最稳定,我用了三年基本没出过问题。新浪财经的响应速度最快,适合做实时监控。港交所官方数据最权威,但接口调用限制比较多。

这里我直接给出常用的API地址:

# 东方财富北向资金日频数据接口
url = "https://push2.eastmoney.com/api/qt/kamt.kline/get"
params = {
    "fields1": "f1,f2,f3,f4",
    "fields2": "f51,f52,f53,f54,f55",
    "klt": "101",  # 日频
    "lmt": "1000", # 最近1000个交易日
    "secid": "1.000001"  # 沪股通
}

# 深股通类似,secid改为"0.159900"

小技巧:我建议你同时拉取沪股通和深股通的数据,然后合并计算。因为有些时候沪股通和深股通的资金流向方向是相反的,单独看一个会失真。

2.2 数据获取实战:写一个稳定的爬虫

直接上代码。这是我用了很久的一个模板,稳定可靠:

import requests
import pandas as pd
import time
from datetime import datetime, timedelta

def fetch_north_flow(start_date, end_date):
    """
    获取北向资金日频数据
    start_date: '2024-01-01'
    end_date: '2024-12-31'
    """
    # 沪股通
    sh_url = "https://push2.eastmoney.com/api/qt/kamt.kline/get"
    sh_params = {
        "fields1": "f1,f2,f3,f4",
        "fields2": "f51,f52,f53,f54,f55",
        "klt": "101",
        "lmt": "1000",
        "secid": "1.000001"
    }
    
    # 深股通
    sz_url = "https://push2.eastmoney.com/api/qt/kamt.kline/get"
    sz_params = {
        "fields1": "f1,f2,f3,f4",
        "fields2": "f51,f52,f53,f54,f55",
        "klt": "101",
        "lmt": "1000",
        "secid": "0.159900"
    }
    
    try:
        sh_resp = requests.get(sh_url, params=sh_params, timeout=10)
        sz_resp = requests.get(sz_url, params=sz_params, timeout=10)
        
        # 解析数据
        sh_data = sh_resp.json()['data']['klines']
        sz_data = sz_resp.json()['data']['klines']
        
        # 合并处理
        df_sh = parse_kline(sh_data, '沪股通')
        df_sz = parse_kline(sz_data, '深股通')
        
        # 合并
        df = pd.merge(df_sh, df_sz, on='date', how='outer')
        df['total_net'] = df['sh_net'] + df['sz_net']
        
        return df
        
    except Exception as e:
        print(f"数据获取失败: {e}")
        return None

def parse_kline(kline_data, name):
    """解析K线数据"""
    records = []
    for item in kline_data:
        parts = item.split(',')
        records.append({
            'date': parts[0],
            f'{name}_net': float(parts[1]),  # 净买入额
            f'{name}_buy': float(parts[2]),  # 买入额
            f'{name}_sell': float(parts[3])  # 卖出额
        })
    return pd.DataFrame(records)

注意:我曾经遇到过一个问题——API返回的数据顺序偶尔会乱。所以拿到数据后,一定要按日期排序。另外,建议每次请求之间加0.5秒的延迟,别把人家服务器搞崩了。

2.3 数据清洗:剔除节假日

A股市场有它自己的交易日历。北向资金只在A股交易日才有数据。你想想看,如果直接把所有日期都拿来分析,周末和节假日的数据全是空的,那分析结果能对吗?

我的做法是维护一个交易日历表:

# A股交易日历(示例)
trading_days = [
    '2024-01-02', '2024-01-03', '2024-01-04',  # 元旦后
    '2024-02-19', '2024-02-20', '2024-02-21',  # 春节后
    # ... 实际需要完整列表
]

def filter_trading_days(df, trading_days):
    """只保留交易日数据"""
    df['date'] = pd.to_datetime(df['date'])
    df = df[df['date'].isin(trading_days)]
    return df.sort_values('date')

避坑指南:我曾经直接用pd.bdate_range()生成交易日,结果发现它把调休上班的周末也算进去了,而真正的交易日反而漏了。后来我改用上交所和深交所官方发布的交易日历,这才彻底解决。

2.4 缺失值处理:别让NaN毁了你的模型

数据拿到手,你会发现有些日期是缺失的。原因很多:API超时、网络波动、或者那天确实没有交易数据。怎么处理?

我总结了三种常用方法:

方法 适用场景 我的建议
前向填充 短期缺失(1-2天) 用前一天的数据填充,简单有效
线性插值 中期缺失(3-5天) 用前后数据做线性拟合,更平滑
直接删除 长期缺失(超过5天) 别犹豫,删掉最安全

具体实现:

def handle_missing_values(df, method='ffill'):
    """
    处理缺失值
    method: 'ffill' 前向填充, 'linear' 线性插值, 'drop' 删除
    """
    if method == 'ffill':
        df = df.fillna(method='ffill')
    elif method == 'linear':
        df = df.interpolate(method='linear')
    elif method == 'drop':
        df = df.dropna()
    
    return df

我的经验:对于北向资金数据,我一般先用前向填充。因为资金流向有惯性,今天和昨天的变化不会太大。但如果连续缺失超过3天,我会改用线性插值。记住,没有一种方法能通吃所有情况,你得根据数据特点灵活选择。

2.5 数据对齐:让北向资金和A股指数同步

这是最容易出错的一步。北向资金数据和A股指数数据,它们的日期可能不完全对齐。比如北向资金在某个交易日没有交易,但A股指数有数据。或者反过来。

我的对齐逻辑是这样的:

def align_data(north_flow, index_data):
    """
    对齐北向资金和A股指数数据
    以交易日历为准,两边都对齐
    """
    # 合并
    merged = pd.merge(north_flow, index_data, on='date', how='inner')
    
    # 检查对齐后的数据量
    print(f"对齐前北向数据: {len(north_flow)} 天")
    print(f"对齐前指数数据: {len(index_data)} 天")
    print(f"对齐后数据: {len(merged)} 天")
    
    return merged

重要提醒:对齐时一定要用inner join,别用outer。为什么?因为outer join会产生很多NaN,你后面还得花时间处理。我刚开始做的时候用了outer,结果数据量翻了一倍,但大部分都是无效数据,白白浪费时间。

2.6 本章知识体系

下面这张图是我自己画的,把整个数据获取和清洗的流程串起来了:

北向资金数据获取与清洗流程 数据源 东方财富 / 新浪 / 港交所 数据获取 API请求 / 解析 / 合并 数据清洗 剔除节假日 缺失值处理 前向填充 / 插值 数据对齐 与A股指数对齐 清洗对齐后的数据 可直接用于分析和建模 关键步骤 ① 多数据源备份 ② 交易日历过滤 ③ 缺失值灵活处理 ④ 严格对齐

2.7 完整代码示例

最后,我把整个流程封装成一个完整的函数。你直接复制就能用:

def get_clean_north_flow(start_date, end_date):
    """
    一站式获取并清洗北向资金数据
    """
    # 1. 获取数据
    df = fetch_north_flow(start_date, end_date)
    if df is None:
        return None
    
    # 2. 加载交易日历
    trading_days = load_trading_calendar()
    
    # 3. 过滤交易日
    df = filter_trading_days(df, trading_days)
    
    # 4. 处理缺失值
    df = handle_missing_values(df, method='ffill')
    
    # 5. 对齐指数数据
    index_data = fetch_index_data(start_date, end_date)
    df = align_data(df, index_data)
    
    return df

# 使用示例
data = get_clean_north_flow('2024-01-01', '2024-12-31')
print(data.head())

最后说一句:数据清洗这件事,看起来枯燥,但它是整个量化分析的地基。我见过太多人急着跑模型,结果数据都没对齐,分析出来的结论全是错的。嗯,别走那条弯路。

专注资料整理