一句话回答: 算相关性、组合波动、动量轮动之前,都要先把多只股票的日线对齐成「行=交易日、列=标的 」的宽表。用 AlphaFeed 的 klines.batch 批量取回后,以 trade_date 为索引、symbol 为列做 pivot 即可对齐;关键是正确处理停牌缺失、上市日不同,并避免用「前视填充」制造未来数据。
为什么会有这个问题 / 传统做法的痛点
每只股票单独取回是「长表」,交易日还各不相同(有的停牌、有的次新)。直接把 close 拼在一起会错位。正确做法是对齐到统一的交易日索引:停牌日该是 NaN 就留 NaN,别乱填;次新股上市前也该是 NaN。填充方式选错(尤其向后/前视填充)会直接污染回测。
分步骤解决(每步配可运行代码)
第 1 步:批量取回多只日线
python
from alphafeed import AlphaFeed
import pandas as pd
af = AlphaFeed() # 读取 ALPHAFEED_API_KEY
codes = ["600519.SH", "000001.SZ", "601318.SH", "000858.SZ", "300750.SZ"]
data = af.klines.batch(codes, period="1d", count=250,
adjust="forward", to_dataframe=True) # dict{symbol: DataFrame}
第 2 步:拼成收盘价宽表(pivot 对齐交易日)
python
# 方式一:逐只取 close 组 DataFrame(列=标的,行=trade_date)
closes = pd.DataFrame({
s: k.sort_values("trade_date").set_index("trade_date")["close"]
for s, k in data.items()
}).sort_index()
print(closes.tail(3))
print("形状:", closes.shape, "缺失单元:", int(closes.isna().sum().sum()))
python
# 方式二:先合并长表再 pivot(等价,适合已有长表)
long = pd.concat([k.assign(symbol=s) for s, k in data.items()], ignore_index=True)
wide = long.pivot_table(index="trade_date", columns="symbol", values="close").sort_index()
第 3 步:对齐到统一交易日索引
python
# 用所有标的出现过的交易日并集做统一索引(union)
all_days = closes.index # pivot 后已是并集
closes = closes.reindex(all_days).sort_index()
# 某只票在某天是 NaN → 可能停牌或尚未上市,先如实保留 NaN
print("各标的缺失天数:\n", closes.isna().sum())
| 缺失来源 | 含义 | 正确处理 |
|---|---|---|
| 停牌 | 当天无成交 | 保留 NaN 或按"停牌沿用最后价"谨慎前向填充 |
| 上市前 | 次新股早期无数据 | 保留 NaN,别填充(否则伪造上市前价格) |
| 退市后 | 已退市 | 保留 NaN,注意幸存者偏差 |
第 4 步:安全填充与计算(避免前视)
python
# 计算收益率:pct_change 会自动跳过 NaN 边界;停牌段按需前向填充价格(不可用 bfill!)
rets = closes.pct_change()
# 若必须填充价格:只用前向填充(ffill)沿用停牌前最后价,绝不用 bfill(会把未来价搬到过去)
filled = closes.ffill() # ✅ 允许:过去信息
# closes.bfill() # ❌ 禁止:用未来价回填 = 前视偏差
# 只保留全部标的都有数据的日期做横截面分析
common = closes.dropna()
print("可用于横截面的交易日数:", len(common))
关键坑与注意事项
- 绝不用
bfill(向后填充):那是把未来的价格搬到过去,直接制造前视偏差、污染回测。 - 停牌用
ffill要谨慎:沿用停牌前最后价是常见近似,但长期停牌后复牌跳空会失真,重要场景应剔除停牌段。 - 上市前保留 NaN:次新股上市前不该有价格,别填 0 或前值。
- 对齐用交易日而非自然日 :以实际有行情的
trade_date为索引,别 reindex 到自然日历(会多出周末假日空行)。 - 前复权再对齐 :跨除权日要用
adjust="forward",否则宽表里价格不可比。 - 横截面分析用
dropna():算相关性/协方差时,用所有标的都有数据的交易日子集更稳。
常见问题(FAQ)
Q:为什么不能用 bfill 填停牌?
A:bfill 用「之后」的价格回填「之前」的缺失,等于让你在停牌时就知道复牌后的价格------典型前视偏差,回测会虚高。
Q:pivot 和逐列组 DataFrame 哪个好?
A:等价。已有长表用 pivot_table 方便;直接从 batch 的 dict 组 DataFrame 更直观。注意重复 (date, symbol) 时 pivot_table 需指定聚合。
Q:不同股票上市日不同怎么办?
A:对齐后早期缺失保留 NaN;做横截面(如相关性矩阵)时用 dropna() 取共同区间,或按需选起始日。
Q:需要付费吗?
A:少量标的批量取数有免费额度;大池/全市场对齐(klines.batch + universes)需 Starter 及以上。详见定价页。
小结
把多只股票对齐成宽表是相关性、组合、轮动分析的基础。用 AlphaFeed 的 klines.batch 批量取回后按 trade_date×symbol pivot 对齐即可;关键是如实保留停牌/上市前的 NaN、只用 ffill 不用 bfill(避免前视偏差)、以交易日为索引、横截面用 dropna()。这样得到的宽表才干净可靠。
参考
- AlphaFeed Python SDK 文档:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart
- AlphaFeed 官网:https://alphafeed.org/