第四章:数据获取与清洗——用Tushare/AkShare搞定基金历史净值

做量化投资,有一句老话叫「垃圾进,垃圾出」。你模型再牛,策略再花哨,数据源有问题,结果全是扯淡。我这些年踩过的坑,有一半都跟数据质量有关。今天咱们就聊聊,怎么用Python把基金的历史净值数据搞到手,再把它洗得干干净净。

4.1 数据获取:Tushare vs AkShare,我选哪个?

国内做金融数据,绕不开两个库:Tushare和AkShare。很多人问我哪个好,我的回答是——看场景。

Tushare是老牌选手,数据质量高,但需要注册获取token,部分接口有积分限制。我个人习惯用它做深度研究,因为数据字段全,历史数据完整。

AkShare是后起之秀,完全免费,接口丰富,覆盖了股票、基金、期货、宏观数据。说白了,它把各大财经网站的数据爬下来封装好了,用起来很方便。我建议做快速原型验证时用AkShare,省去注册的麻烦。

我的经验:两个库我都用。生产环境用Tushare,因为稳定;研究阶段用AkShare,因为灵活。你想想看,万一Tushare的token过期了,你还有AkShare兜底,多好。

4.2 用Tushare获取基金净值数据

先上代码。假设你要获取某只基金的历史净值,比如「易方达蓝筹精选」(代码:005827)。

import tushare as ts
import pandas as pd

# 设置token(你需要去tushare官网注册获取)
ts.set_token('你的token')
pro = ts.pro_api()

# 获取基金净值
df = pro.fund_nav(ts_code='005827.OF', start_date='20200101', end_date='20231231')
print(df.head())

返回的数据长这样:

ts_code nav_date unit_nav accum_nav adj_nav
005827.OF 2020-01-02 1.2345 1.2345 1.2345
005827.OF 2020-01-03 1.2401 1.2401 1.2401

这里要注意:unit_nav是单位净值,accum_nav是累计净值,adj_nav是复权净值。做回测时,我建议用复权净值,因为它考虑了分红和拆分,更真实反映基金的实际收益。

避坑指南:我曾经遇到过Tushare返回的数据中,某些日期缺失。比如2020年春节后第一个交易日,数据就是空的。这不是bug,是因为那天确实没有交易。但如果你不做处理,后面计算收益率时会出问题。

4.3 用AkShare获取基金净值

如果你不想注册,AkShare是更好的选择。看代码:

import akshare as ak

# 获取公募基金净值
df = ak.fund_open_fund_info_em(symbol="005827", indicator="单位净值走势")
print(df.head())

AkShare返回的数据格式跟Tushare略有不同,但核心字段都有。我个人觉得AkShare的字段命名更直观,比如日期列直接叫净值日期,省得猜。

4.4 数据清洗:缺失值处理

数据拿到手,第一件事就是检查缺失值。我一般用isnull().sum()快速扫一眼。

# 检查缺失值
print(df.isnull().sum())

# 处理缺失值:向前填充(用上一个交易日的数据填充)
df['unit_nav'].fillna(method='ffill', inplace=True)

# 或者用插值法
df['unit_nav'].interpolate(method='linear', inplace=True)

为什么用向前填充?因为基金净值是时间序列数据,缺失通常发生在非交易日。用上一个交易日的净值填充,逻辑上说得通。插值法适合数据点之间缺失较少的情况,如果连续缺失超过3天,我建议用向前填充。

小技巧:我习惯先看缺失比例。如果某只基金某段时间缺失超过20%,我会直接放弃那段时间的数据,或者换一只基金。数据质量太差,模型再牛也白搭。

4.5 数据标准化:让不同基金可比

不同基金的净值绝对值差异很大,比如有的基金净值1块,有的10块。直接比较没意义。标准化就是解决这个问题的。

常用的方法有两种:

  • Z-score标准化:减去均值,除以标准差。适合数据分布接近正态的情况。
  • Min-Max标准化:缩放到[0,1]区间。适合数据有明确边界的情况。
from sklearn.preprocessing import StandardScaler, MinMaxScaler

# Z-score标准化
scaler = StandardScaler()
df['nav_standardized'] = scaler.fit_transform(df[['unit_nav']])

# Min-Max标准化
scaler = MinMaxScaler()
df['nav_normalized'] = scaler.fit_transform(df[['unit_nav']])

我个人习惯用Z-score,因为它保留了数据的分布特征。但如果你要做聚类或者神经网络,Min-Max可能更合适。

4.6 数据重采样:从日频到周频、月频

做量化策略时,日频数据太细,噪音多。我经常把数据重采样到周频或月频,减少噪音,提高策略稳定性。

# 设置日期为索引
df.set_index('nav_date', inplace=True)

# 重采样到周频,取每周最后一个交易日的净值
df_weekly = df.resample('W').last()

# 重采样到月频,取每月最后一个交易日的净值
df_monthly = df.resample('M').last()

为什么取最后一个交易日?因为基金净值是时点数据,不是区间数据。取最后一个值,代表该周期结束时的净值水平。如果你取平均值,反而会模糊掉趋势。

我的经验:做趋势跟踪策略时,我常用周频数据。做资产配置时,月频就够了。频率越高,交易成本越高,你想想看,是不是这个道理?

4.7 本章知识体系

下面这张图,是我自己总结的数据获取与清洗流程。你照着这个流程走,基本不会出大问题。

数据获取与清洗流程 数据获取 Tushare / AkShare 缺失值处理 向前填充 / 插值法 数据标准化 Z-score / Min-Max 数据重采样 日频 → 周频 / 月频 数据质量检查 缺失比例 / 异常值 输出清洗后数据 保存为CSV / 存入数据库 数据不合格,返回重处理 虚线表示数据质量不合格时,需要返回上一步重新处理

4.8 完整代码示例

最后,给你一个完整的流程代码。你直接复制就能用。

import tushare as ts
import pandas as pd
from sklearn.preprocessing import StandardScaler

# 1. 获取数据
ts.set_token('你的token')
pro = ts.pro_api()
df = pro.fund_nav(ts_code='005827.OF', start_date='20200101', end_date='20231231')

# 2. 缺失值处理
df['unit_nav'].fillna(method='ffill', inplace=True)

# 3. 数据标准化
scaler = StandardScaler()
df['nav_std'] = scaler.fit_transform(df[['unit_nav']])

# 4. 数据重采样
df.set_index('nav_date', inplace=True)
df_weekly = df.resample('W').last()
df_monthly = df.resample('M').last()

# 5. 保存结果
df_weekly.to_csv('fund_weekly.csv')
print('数据清洗完成,已保存为 fund_weekly.csv')

最后提醒一句:数据清洗没有银弹。每只基金、每个数据源都有自己的脾气。我的建议是,先跑一遍流程,看看数据长什么样,再决定怎么处理。别一上来就套模板,容易翻车。

专注资料整理