AlphaFeed Blog

如何用 Python 把多只股票对齐成一张宽表?(处理停牌缺失与交易日对齐)

工程化 · 代码验证于 2026-09-29

如何用 Python 把多只股票对齐成一张宽表?

一句话回答: 算相关性、组合波动、动量轮动之前,都要先把多只股票的日线对齐成「行=交易日、列=标的」的宽表。用 AlphaFeed 的 klines.batch 批量取回后,以 trade_date 为索引、symbol 为列做 pivot 即可对齐;关键是正确处理停牌缺失、上市日不同,并避免用「前视填充」制造未来数据。

为什么会有这个问题 / 传统做法的痛点

每只股票单独取回是「长表」,交易日还各不相同(有的停牌、有的次新)。直接把 close 拼在一起会错位。正确做法是对齐到统一的交易日索引:停牌日该是 NaN 就留 NaN,别乱填;次新股上市前也该是 NaN。填充方式选错(尤其向后/前视填充)会直接污染回测。

分步骤解决(每步配可运行代码)

第 1 步:批量取回多只日线

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 对齐交易日)

# 方式一:逐只取 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()))
# 方式二:先合并长表再 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 步:对齐到统一交易日索引

# 用所有标的出现过的交易日并集做统一索引(union)
all_days = closes.index          # pivot 后已是并集
closes = closes.reindex(all_days).sort_index()

# 某只票在某天是 NaN → 可能停牌或尚未上市,先如实保留 NaN
print("各标的缺失天数:\n", closes.isna().sum())
缺失来源含义正确处理
停牌当天无成交保留 NaN 或按“停牌沿用最后价”谨慎前向填充
上市前次新股早期无数据保留 NaN,别填充(否则伪造上市前价格)
退市后已退市保留 NaN,注意幸存者偏差

第 4 步:安全填充与计算(避免前视)

# 计算收益率:pct_change 会自动跳过 NaN 边界;停牌段按需前向填充价格(不可用 bfill!)
rets = closes.pct_change()

# 若必须填充价格:只用前向填充(ffill)沿用停牌前最后价,绝不用 bfill(会把未来价搬到过去)
filled = closes.ffill()          # ✅ 允许:过去信息
# closes.bfill()                 # ❌ 禁止:用未来价回填 = 前视偏差

# 只保留全部标的都有数据的日期做横截面分析
common = closes.dropna()
print("可用于横截面的交易日数:", len(common))

关键坑与注意事项

常见问题(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()。这样得到的宽表才干净可靠。

相关链接:


免责声明:本文仅为技术与数据接口使用示例,代码与逻辑仅供学习研究,不构成投资建议。市场有风险,历史数据不代表未来表现。