如何用 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()。这样得到的宽表才干净可靠。

参考

相关推荐
xiaoye-duck8 分钟前
《C++ 项目:手写大模型接入 SDK》DeepSeek接口设计(下):流式响应实现和测试
开发语言·网络·c++·语言模型
后台模板学习16 分钟前
大模型 Prompt 工程在电商订单系统中的踩坑记录
java·python·prompt
2601_9679873730 分钟前
如何运行 Python 代码
python·最佳实践·脚本文件·代码运行·交互式环境
WiKiLeaks_successor32 分钟前
重构Time库的抽象设计
python·重构
AI源码41 分钟前
基于 Python 的短视频用户行为分析系统
开发语言·python
第九序言T1 小时前
数据分析岗 Pandas 面试题 20 道(上):概念与读代码
python·数据分析·pandas·数据处理
Dmz丶1 小时前
前端转 AI 100 天 Day 37:Few-shot 与 CoT——让模型学会“推理”
python
孟健1 小时前
我用Grok Bot本地剪口播视频,3段提示词搞定气口切除、字幕与封面
人工智能·python·aigc
旖旎夜光1 小时前
【C++ 基础入门】类和对象(上):封装、对象模型与 this 指针
开发语言·c++·笔记·学习
揽星河..2 小时前
【C语言】结构体
c语言·开发语言