第一章:金融数据获取与预处理

做量化投资,第一步就是搞数据。没有数据,再牛的模型也是空中楼阁。我个人习惯用 yfinance 来拿股票历史数据,这玩意儿免费、方便,对A股和美股都支持得不错。今天咱们就聊聊怎么把数据拿到手,洗干净,然后算出日收益率。

1.1 为什么数据清洗这么重要?

你想想看,从交易所拿到的原始数据,那叫一个「脏」。缺失值、停牌、除权除息……各种坑。我曾经在做一个波动率模型时,因为没处理停牌日的数据,结果回测曲线漂亮得不像话,实盘直接翻车。嗯,从那以后,我每次拿到数据第一件事就是检查有没有坑。

数据清洗说白了就三件事:

  • 处理缺失值:停牌日、节假日的数据是NaN,得填上或者删掉
  • 对齐时间轴:多只股票的交易日期必须一致
  • 检查异常值:比如某天涨了1000%,那肯定是数据出错了

核心原则:宁可少用数据,也不要用错数据。一个错误的数据点,可能让你的整个模型崩盘。

1.2 用yfinance获取数据

先装包:

pip install yfinance pandas numpy

然后写代码。我习惯把获取和清洗封装成一个函数,这样后面30个章节都能复用。

import yfinance as yf
import pandas as pd
import numpy as np

def fetch_stock_data(tickers, start_date, end_date):
    """
    获取多只股票的历史数据
    tickers: 股票代码列表,比如 ['AAPL', 'MSFT', 'GOOGL']
    start_date, end_date: 'YYYY-MM-DD' 格式
    """
    data = yf.download(tickers, start=start_date, end=end_date, auto_adjust=True)
    # auto_adjust=True 会自动处理除权除息,省心不少
    return data['Close']  # 我们只需要收盘价

小技巧auto_adjust=True 这个参数我强烈建议打开。它会把历史价格统一调整为复权价,避免除权除息造成的价格跳空。我在做回测时吃过这个亏,后来就一直开着。

1.3 数据清洗实战

拿到数据后,第一件事就是看有没有缺失值。我一般这么干:

def clean_data(df):
    """
    清洗数据:处理缺失值、对齐时间轴
    """
    # 检查缺失比例
    missing_ratio = df.isnull().sum() / len(df)
    print("各股票缺失比例:")
    print(missing_ratio)
    
    # 删除缺失超过5%的股票
    valid_cols = missing_ratio[missing_ratio < 0.05].index
    df = df[valid_cols]
    
    # 前向填充(用前一天的价格填充停牌日)
    df = df.fillna(method='ffill')
    
    # 删除仍然有缺失的行(比如最开始的几天)
    df = df.dropna()
    
    return df

为什么会用前向填充?因为停牌日虽然没交易,但你的持仓还在,用前一天的价格是合理的。我曾经试过用插值法,结果把停牌期间的波动给平滑掉了,模型完全失真。

1.4 计算日收益率

收益率是量化分析的基石。我们通常用对数收益率,因为它有可加性,而且更符合正态分布假设。

def calculate_returns(df):
    """
    计算日收益率(对数收益率)
    """
    # 对数收益率:ln(P_t / P_{t-1})
    returns = np.log(df / df.shift(1))
    
    # 删除第一行(NaN)
    returns = returns.dropna()
    
    return returns

注意:千万别用简单收益率(P_t / P_{t-1} - 1)做时间序列分析。对数收益率在时间上可加,而简单收益率不行。比如两天各涨10%,简单收益率算出来是21%,但对数收益率是19.5%,后者才是正确的复合收益率。

1.5 完整流程与可视化

把上面三步串起来,就是咱们第一章的核心流程。我画了个图,方便你理解:

第一章:数据获取与预处理流程 1. 获取数据 yfinance.download() 2. 数据清洗 处理缺失值、对齐 3. 计算收益率 对数收益率 选择股票代码 AAPL, MSFT, 000001.SZ 设置时间范围 2020-01-01 至 2024-12-31 检查缺失值 缺失率 < 5% 前向填充 ffill 处理停牌 输出:干净的日收益率时间序列 DataFrame 示例输出: 日期 | AAPL | MSFT | GOOGL 2024-01-02 | 0.0023 | -0.0015 | 0.0031

1.6 完整代码示例

把上面所有函数整合起来,就是咱们第一章的完整代码:

import yfinance as yf
import pandas as pd
import numpy as np

def get_clean_returns(tickers, start, end):
    """
    一站式获取并清洗数据,返回日收益率
    """
    # 1. 获取数据
    raw = yf.download(tickers, start=start, end=end, auto_adjust=True)
    prices = raw['Close']
    
    # 2. 清洗
    prices = prices.dropna(axis=1, thresh=len(prices)*0.95)  # 删除缺失过多的股票
    prices = prices.fillna(method='ffill').dropna()
    
    # 3. 计算收益率
    returns = np.log(prices / prices.shift(1)).dropna()
    
    return returns

# 使用示例
tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META']
returns = get_clean_returns(tickers, '2020-01-01', '2024-12-31')
print(returns.head())

避坑指南:我曾经在获取A股数据时,发现 yfinance 对某些股票代码格式有要求。比如平安银行是 000001.SZ,不是 000001。多了一个后缀,数据就全乱了。所以,用之前先确认代码格式。

1.7 本章小结

咱们这一章干了三件事:拿数据、洗数据、算收益率。听起来简单,但每一步都有坑。数据清洗占了我整个量化项目至少30%的时间,真的不夸张。你想想看,如果数据是错的,后面那些漂亮的波动率模型、优化算法,全都是在垃圾上跳舞。

下一章咱们会基于这些干净的收益率数据,开始构建波动率控制组合。但在此之前,我建议你把今天这章代码跑一遍,看看自己拿到的数据长什么样。动手试试,比看十遍都管用。


专注资料整理