第二章:数据准备——获取纳斯达克主要行业ETF数据
做量化投资,数据就是你的弹药库。没有数据,再牛的策略也是纸上谈兵。
这一章,我们来搞定纳斯达克主要行业ETF的数据获取。说白了,就是要把QQQ、XLK、XLV这些家伙的历史数据拉下来,存好,等着后续分析用。
2.1 为什么选这些ETF?
我刚开始做行业轮动策略时,也纠结过到底选哪些标的。后来发现,纳斯达克这几个行业ETF覆盖面广、流动性好,而且数据源稳定。
你想想看,QQQ代表整个纳斯达克100,XLK是科技,XLV是医疗,XLI是工业,XLP是必需消费,XLY是可选消费,XLF是金融,XLU是公用事业,XLB是材料,XLRE是房地产。这十个基本把美股主要行业都覆盖了。
核心思路:行业轮动的本质就是资金在不同板块间流动。我们跟踪这些ETF,就能捕捉到资金流向的变化。
2.2 数据获取方案
我个人习惯用Python的yfinance库来拉数据。为什么选它?因为免费、简单、数据质量还行。
安装命令很简单:
pip install yfinance pandas
嗯,这里要注意:yfinance的数据源是雅虎财经,国内访问有时候会慢。我建议你配个代理,或者用国内的tushare替代。不过为了教学方便,咱们先用yfinance。
2.3 代码实战:批量获取ETF数据
直接上代码。这段代码我用了好几年,基本没出过问题。
import yfinance as yf
import pandas as pd
from datetime import datetime, timedelta
# 定义ETF列表
etf_list = ['QQQ', 'XLK', 'XLV', 'XLI', 'XLP',
'XLY', 'XLF', 'XLU', 'XLB', 'XLRE']
# 设置时间范围:过去5年
end_date = datetime.now()
start_date = end_date - timedelta(days=5*365)
# 批量下载数据
def fetch_etf_data(tickers, start, end):
data = {}
for ticker in tickers:
print(f"正在获取 {ticker} 数据...")
try:
df = yf.download(ticker, start=start, end=end,
progress=False)
data[ticker] = df
except Exception as e:
print(f"获取 {ticker} 失败: {e}")
return data
# 执行下载
etf_data = fetch_etf_data(etf_list, start_date, end_date)
# 查看数据样例
print(etf_data['QQQ'].head())
避坑指南:我曾经一次性下载10个ETF的5年数据,结果网络超时了。后来我加了重试机制,每次下载间隔1秒。代码里加个time.sleep(1)就能解决。
2.4 数据清洗与存储
数据拉下来后,你会发现有些ETF在某些日期没有交易数据。比如节假日、停牌日。这些空值需要处理。
我的做法是:
- 用
ffill()向前填充缺失值 - 删除仍然为空的行
- 统一索引为日期格式
# 数据清洗函数
def clean_etf_data(data_dict):
cleaned = {}
for ticker, df in data_dict.items():
# 只保留收盘价
df_clean = df[['Close']].copy()
df_clean.columns = [f'{ticker}_Close']
# 填充缺失值
df_clean = df_clean.ffill().dropna()
cleaned[ticker] = df_clean
return cleaned
# 合并所有ETF数据
def merge_etf_data(cleaned_dict):
merged = pd.DataFrame()
for ticker, df in cleaned_dict.items():
if merged.empty:
merged = df
else:
merged = merged.join(df, how='outer')
return merged.dropna()
# 执行清洗和合并
cleaned_data = clean_etf_data(etf_data)
merged_data = merge_etf_data(cleaned_data)
# 保存到CSV
merged_data.to_csv('nasdaq_etf_data.csv')
print(f"数据保存完成,共 {len(merged_data)} 个交易日")
2.5 数据质量检查
数据拿到手,别急着用。先做个快速检查。
| 检查项 | 方法 | 预期结果 |
|---|---|---|
| 缺失值比例 | merged_data.isnull().sum() |
应为0 |
| 日期连续性 | 检查日期是否连续 | 无跳空(节假日除外) |
| 价格合理性 | 检查是否有负值或异常值 | 所有价格>0 |
| 数据长度 | 各ETF行数是否一致 | 应完全一致 |
注意:XLRE(房地产ETF)是2015年才推出的,如果你拉的数据早于2015年,这部分数据会是空的。我建议统一从2016年开始拉数据,这样所有ETF都有完整记录。
2.6 数据可视化预览
数据准备好了,咱们画个图看看整体走势。这里用matplotlib简单画一下。
import matplotlib.pyplot as plt
# 归一化处理,方便对比
normalized = merged_data / merged_data.iloc[0] * 100
# 绘制走势图
plt.figure(figsize=(14, 8))
for col in normalized.columns:
plt.plot(normalized.index, normalized[col],
label=col.replace('_Close', ''), linewidth=1)
plt.title('纳斯达克行业ETF走势对比(归一化)', fontsize=14)
plt.xlabel('日期')
plt.ylabel('价格(基准=100)')
plt.legend(loc='best')
plt.grid(True, alpha=0.3)
plt.show()
你会发现,不同行业的走势差异很大。比如2020年疫情初期,XLK(科技)跌得少、涨得快,而XLI(工业)跌得惨。这就是行业轮动的机会所在。
2.7 数据更新机制
策略不是做一次就完事的。你需要定期更新数据。
我建议:
- 每天收盘后自动拉取最新数据
- 用增量更新的方式,只拉取缺失的日期
- 保留原始数据,不要覆盖
# 增量更新函数
def update_data(existing_file, tickers):
# 读取已有数据
old_data = pd.read_csv(existing_file, index_col=0, parse_dates=True)
last_date = old_data.index[-1]
# 只拉取新数据
new_data = fetch_etf_data(tickers,
start=last_date + timedelta(days=1),
end=datetime.now())
# 合并新旧数据
updated = pd.concat([old_data, new_data])
updated.to_csv(existing_file)
print(f"数据已更新至 {updated.index[-1].date()}")
核心要点:数据准备是整个策略的基石。数据质量直接决定策略效果。我见过太多人花90%时间调参数,却不愿意花10%时间检查数据。结果策略回测漂亮,实盘一塌糊涂。
好了,数据已经准备好了。接下来我们就可以用这些数据做行业轮动分析了。
数据准备这块,其实没什么高深的技术。关键是要细心、规范。我见过有人因为日期格式不统一,回测时数据对不上,白白浪费了两天时间。所以,从一开始就养成良好的数据管理习惯,后面会省很多事。
个人经验:我习惯在数据文件里加一个版本号,比如nasdaq_etf_data_v1.csv。这样万一数据出问题,可以快速回滚到上一个版本。别问我为什么知道这个坑...
数据准备好了,接下来就可以开始真正的行业轮动分析工作了。