4. 数据获取与清洗:从CSV/Excel读取交易数据、处理缺失值、数据类型转换、时间序列索引设置
做量化交易,有一句话我特别认同:垃圾进,垃圾出。你策略再牛,模型再花哨,数据源要是脏的,结果全是扯淡。今天咱们就聊聊数据获取与清洗这第一步,也是我踩坑最多的一步。
核心要点:数据清洗占整个量化分析工作量的 60%-80%。别嫌烦,这是基本功。
4.1 从CSV/Excel读取交易数据
我个人习惯用 pandas 来读数据。为啥?因为它快,而且对金融数据支持好。你想想看,每天几千只股票的 tick 数据,用 Excel 打开直接卡死,pandas 几秒钟搞定。
先看最常用的 CSV 读取:
import pandas as pd
# 读取CSV文件
df = pd.read_csv('trade_data.csv')
# 看一眼数据长啥样
print(df.head())
print(df.info())
print(df.describe())
嗯,这里要注意:read_csv() 有很多参数可以调。我在项目中遇到过最坑的一次,是数据文件里混了中文编码,直接报错。后来加了个 encoding='utf-8' 才搞定。
Excel 文件稍微麻烦点,但也不复杂:
# 读取Excel文件
df_excel = pd.read_excel('trade_data.xlsx', sheet_name='Sheet1')
# 如果Excel里有多个sheet,可以指定
df_sheet2 = pd.read_excel('trade_data.xlsx', sheet_name='Sheet2')
小技巧:如果数据文件很大,可以用 nrows=1000 先读前1000行看看结构,确认没问题再全量读取。别一上来就全量读,万一格式不对,白等半天。
4.2 处理缺失值
真实交易数据里,缺失值太常见了。比如某天股票停牌,或者交易所数据推送延迟,都会产生空值。怎么处理?
先看看数据里有没有缺失值:
# 检查缺失值
print(df.isnull().sum())
# 缺失值占比
print(df.isnull().mean() * 100)
处理方式主要有三种,我按推荐程度排个序:
- 删除缺失行:如果缺失比例很小(比如 < 5%),直接删掉最省事。
- 填充缺失值:用前一个值、后一个值、或者均值填充。
- 插值法:用线性插值或时间插值,适合时间序列数据。
# 方式1:删除缺失行
df_clean = df.dropna()
# 方式2:向前填充(用上一个非空值填充)
df_filled = df.fillna(method='ffill')
# 方式3:线性插值
df_interpolated = df.interpolate(method='linear')
避坑指南:我曾经在回测时用了 fillna(method='ffill') 处理价格数据,结果某只股票停牌一周,复牌后直接用了停牌前的价格。回测曲线漂亮得不行,实盘一跑就崩。后来我改成用 limit=5 限制连续填充的天数,超过5天就标记为异常。
4.3 数据类型转换
读进来的数据,类型经常不对。比如日期列是字符串,价格列是 object 类型。不转换的话,后面计算全错。
我一般这样检查类型:
# 查看每列的数据类型
print(df.dtypes)
# 常见的类型问题
# - 日期列是 object 类型 → 转 datetime
# - 价格列是 object 类型 → 转 float
# - 成交量列是 object 类型 → 转 int
转换代码很简单:
# 日期列转换
df['date'] = pd.to_datetime(df['date'])
# 价格列转换(注意:可能有逗号、百分号等)
df['price'] = pd.to_numeric(df['price'], errors='coerce')
# 成交量转换
df['volume'] = df['volume'].astype(int)
小技巧:errors='coerce' 这个参数很实用。遇到无法转换的值,它会变成 NaN,而不是直接报错。这样你可以后续统一处理异常值。
4.4 时间序列索引设置
交易数据天然是时间序列。把时间列设为索引,后面做切片、重采样、滚动计算都方便得多。
# 设置日期列为索引
df = df.set_index('date')
# 确保索引是datetime类型
df.index = pd.to_datetime(df.index)
# 排序索引(重要!)
df = df.sort_index()
设置好索引后,你可以这样操作:
# 按日期范围切片
df_2023 = df['2023-01-01':'2023-12-31']
# 按月份重采样
df_monthly = df.resample('M').last()
# 滚动计算(比如20日均线)
df['ma20'] = df['close'].rolling(window=20).mean()
核心逻辑:时间序列索引是量化分析的基石。没有它,你没法做回测、没法对齐数据、没法计算各种指标。所以这一步千万别省。
4.5 本章知识体系
下面这张图,是我自己总结的数据清洗流程。每次做新项目,我都按这个步骤来,基本不会漏东西。
4.6 完整示例:从CSV到清洗完毕
最后,给你一个完整的代码示例。这是我做套利分析时常用的模板:
import pandas as pd
import numpy as np
# 1. 读取数据
df = pd.read_csv('arbitrage_data.csv', encoding='utf-8')
# 2. 初步检查
print(f"原始数据形状: {df.shape}")
print(f"缺失值统计:\n{df.isnull().sum()}")
# 3. 处理缺失值(价格列用前向填充,成交量列用0填充)
df['price'] = df['price'].fillna(method='ffill', limit=3)
df['volume'] = df['volume'].fillna(0)
# 4. 类型转换
df['date'] = pd.to_datetime(df['date'])
df['price'] = pd.to_numeric(df['price'], errors='coerce')
df['volume'] = df['volume'].astype(int)
# 5. 设置时间索引
df = df.set_index('date').sort_index()
# 6. 最终检查
print(f"清洗后数据形状: {df.shape}")
print(f"数据时间范围: {df.index.min()} ~ {df.index.max()}")
print(df.head())
个人经验:我建议你把数据清洗写成函数,每次读数据都调用。这样既规范,又不容易漏步骤。我自己的项目里,这个函数已经迭代了十几个版本,现在基本能处理 90% 的脏数据问题。
数据清洗这事儿,说白了就是「磨刀不误砍柴工」。你花 10 分钟把数据洗干净,后面回测、分析、实盘都能省下大把时间。别嫌麻烦,这是每个量化交易者的必修课。