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

一句话回答: 算相关性、组合波动、动量轮动之前,都要先把多只股票的日线对齐成「行=交易日、列=标的 」的宽表。用 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()。这样得到的宽表才干净可靠。

参考

相关推荐
蹊黎4 小时前
C++中vector类的部分简单实现
开发语言·c++·visual studio
IT_Octopus9 小时前
【零基础入门 LLM 开发 · Day 13】:LangChain 的对话记忆——Message 对象版
人工智能·python·langchain
2603_965148119 小时前
社区与学习资源:从入门到精通的API选品学习路径
数据库·人工智能·python·学习·数据挖掘
程序猿追10 小时前
kotlin-retry 接入鸿蒙后,怎样确认它真的会停止重试
开发语言·kotlin·harmonyos
我是人✓10 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
智能直播10 小时前
网络RTMP拉流不卡顿、声音不同步,一文讲透缓冲区、时间戳
开发语言·ffmpeg·php·音视频·视频编解码
CCblandy10 小时前
令牌桶在 0.9 倍负载超发 19%、固定窗口边界放行 20 个:四种限流算法同构实测
后端·python·算法·性能优化·架构
墨云安全11 小时前
内网资产无法暴露到公网,怎么做安全检测?2026 年内网渗透测试的落地路径
开发语言·安全·网络安全·渗透测试·php
摇滚侠11 小时前
《计算机是如何工作的:人人都能懂的计算机软硬件工作原理》第 9 章 机器码与汇编语言 阅读笔记 9
java·开发语言·笔记