第二章 金融数据基础:股票数据获取、清洗与指标计算
各位同学,欢迎来到第二章。说实话,这一章是整个课程的地基。你想想看,算法再牛,模型再强,数据要是烂了,一切都白搭。我在量化交易这行摸爬滚打这些年,见过太多人把精力全花在调参上,结果数据源都没搞干净——嗯,这种亏我吃过,所以今天咱们把基础打扎实。
2.1 用 yfinance 获取股票数据
我个人习惯用 yfinance 这个库。为什么?因为它免费、简单,而且数据质量对于入门和策略回测来说完全够用。你不需要去申请什么 API Key,一行代码就能拉数据。
先装库:
pip install yfinance
然后拉取某只股票的历史数据。比如我经常拿贵州茅台(A股代码 600519.SS)做演示:
import yfinance as yf
# 下载贵州茅台近1年数据
df = yf.download('600519.SS', start='2023-01-01', end='2024-01-01')
print(df.head())
这里要注意,A股代码后面要加 .SS(上海)或 .SZ(深圳)。港股是 .HK,美股直接写代码就行。我曾经在项目里因为忘记加后缀,拉了一堆空数据回来,排查了半天才发现——这种低级错误,大家引以为戒。
yfinance 返回的是 Pandas DataFrame,包含 Open、High、Low、Close、Volume、Adj Close 这几列。我们做策略,最常用的是收盘价(Close)和成交量(Volume)。
period='1y' 参数,不用写起止日期。比如 yf.download('AAPL', period='6mo') 拉苹果半年的数据。
2.2 数据清洗与预处理
数据拉下来,别急着算指标。先看看有没有脏数据。我遇到过的情况包括:
- 停牌日导致数据缺失(NaN)
- 除权除息日价格跳空
- 节假日数据为空
- 偶尔有异常值(比如成交量突然少个零)
我的标准流程是这样的:
# 1. 检查缺失值
print(df.isnull().sum())
# 2. 向前填充(用上一个交易日的数据填充)
df.fillna(method='ffill', inplace=True)
# 3. 如果开头还有缺失,向后填充
df.fillna(method='bfill', inplace=True)
# 4. 删除剩余的空值(极少情况)
df.dropna(inplace=True)
为什么用向前填充?因为金融数据有连续性。今天停牌,明天开盘价大概率参考昨天收盘价。这是行业惯例。
df.dropna() 了事。如果连续几天停牌,你会丢掉一大段数据,回测结果会失真。我曾经在回测一只小盘股时,因为没处理好停牌数据,策略信号全乱了,亏了不少模拟资金。
另外,检查一下有没有价格小于等于0的情况。理论上不可能,但数据源偶尔抽风:
# 检查异常值
print(df[df['Close'] <= 0])
如果有,直接删掉那行。别犹豫。
2.3 技术指标计算:SMA、RSI、MACD
技术指标是量化策略的「眼睛」。我个人最常用的三个:SMA(简单移动平均)、RSI(相对强弱指标)、MACD(指数平滑异同移动平均线)。
2.3.1 SMA(简单移动平均)
SMA 就是过去 N 天的收盘价平均值。说白了,它帮你过滤掉短期噪音,看清趋势方向。
# 计算 20 日均线
df['SMA_20'] = df['Close'].rolling(window=20).mean()
# 计算 60 日均线
df['SMA_60'] = df['Close'].rolling(window=60).mean()
这里有个坑:rolling() 计算出来的前 N-1 天是 NaN。我建议用 min_periods 参数控制最少需要多少有效数据:
df['SMA_20'] = df['Close'].rolling(window=20, min_periods=10).mean()
这样前10天也能算出值,虽然精度差一点,但不会让策略在初期无信号。
2.3.2 RSI(相对强弱指标)
RSI 衡量价格变动的速度和幅度,范围0-100。一般认为超过70超买,低于30超卖。但说实话,这个阈值在A股经常失效,我一般会动态调整。
计算公式:
def compute_rsi(data, window=14):
delta = data.diff()
gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
return rsi
df['RSI_14'] = compute_rsi(df['Close'])
注意:当 loss 为0时,RSI 会变成100。这在连续上涨行情中常见。我曾经在牛市中用 RSI 做空信号,结果被连续打脸——因为 RSI 在强势行情中会长期维持在70以上,超买之后还能更超买。
2.3.3 MACD(指数平滑异同移动平均线)
MACD 由三部分组成:快线(DIF)、慢线(DEA)、柱状图(MACD柱)。它本质上是两条指数移动平均线的差值。
# 计算 MACD
exp12 = df['Close'].ewm(span=12, adjust=False).mean()
exp26 = df['Close'].ewm(span=26, adjust=False).mean()
df['MACD_DIF'] = exp12 - exp26
df['MACD_DEA'] = df['MACD_DIF'].ewm(span=9, adjust=False).mean()
df['MACD_HIST'] = 2 * (df['MACD_DIF'] - df['MACD_DEA'])
MACD 的金叉(DIF 上穿 DEA)是买入信号,死叉(DIF 下穿 DEA)是卖出信号。但这里有个细节:金叉的位置很重要。如果在零轴上方金叉,说明多头趋势强劲;如果在零轴下方金叉,可能只是反弹。
我个人习惯把 MACD 和成交量配合使用。金叉当天如果成交量放大,信号可靠性更高。
2.4 本章知识体系总览
下面这张图,是我自己梳理的本章知识结构。你把它存下来,学完这章后对照着检查自己是否都掌握了。
2.5 实战小贴士
最后,分享几个我在实战中总结的经验:
| 指标 | 常用参数 | 我的经验 |
|---|---|---|
| SMA | 20日、60日 | 短线用10/20,中线用60/120 |
| RSI | 14日 | 震荡市用30/70,趋势市放宽到20/80 |
| MACD | 12/26/9 | 零轴上方金叉更可靠,配合成交量确认 |
嗯,这一章的内容就到这里。数据基础打好了,后面讲策略回测、强化学习环境搭建,你才不会手忙脚乱。记住:好的数据,是成功策略的一半。