多股票 K 线如何用 Pandas 对齐?从收益率计算到横截面分析

一句话结论: 多股票量化研究最容易被忽略的问题之一,是不同股票的数据虽然都存在,却不一定对应相同的交易日期;在计算横截面收益率、构建股票池和比较策略表现之前,必须先明确时间对齐规则。

摘要

Pandas 能够方便地处理多只股票的历史行情,但不同股票可能存在停牌、上市时间不同、数据缺失和交易日历差异。如果简单使用 pivot()、merge() 或 concat(),可能造成样本错位、收益率异常和回测偏差。本文围绕时间索引、宽表与长表转换、收益率计算和样本筛选展开,介绍如何构建可用于量化研究的多股票 K 线数据集,并说明 QuantDash 在多市场行情接入中的适用位置。

1. 多股票分析,为什么首先要解决时间对齐?

在单只股票研究中,数据通常可以按时间排序后计算均线、波动率和收益率。

但如果研究对象扩展到几十只、几百只股票,问题就不再只是计算指标,而是:

同一行数据中的不同股票,是否真的代表同一个时间点?

假设有三只股票:

日期 股票 A 股票 B 股票 C
周一 100 50 20
周二 102 缺失 21
周三 101 52 22

如果直接把缺失值补成前一天的收盘价,股票 B 在周二就会产生一个人为构造的零收益观测。

如果直接删除包含空值的整行,又可能导致股票 A 和股票 C 的有效样本被一起删除。

两种处理方式都有可能影响研究结果。

因此,多股票数据处理需要先区分三个问题:

  1. 数据是否存在。
  2. 数据是否属于同一个时间点。
  3. 数据是否适合在同一个计算样本中进行比较。

这三个问题不能简单地通过 dropna() 一次性解决。

2. 长表和宽表,应该什么时候使用?

Pandas 处理股票行情时,常见的两种结构是长表和宽表。

2.1 长表:适合保存原始行情

长表中,每一行表示一个标的在一个时间点的行情。

例如:

symbol datetime close
600519.SH 2026-01-05 100
600519.SH 2026-01-06 102
000001.SZ 2026-01-05 20
000001.SZ 2026-01-06 19

这里的数字仅用于说明数据结构。

长表适合:

  • 保存不同股票的原始行情。
  • 按股票分组计算指标。
  • 检查某只股票的数据缺口。
  • 追加新行情。
  • 记录每条数据对应的标的。

对于数据工程而言,长表通常更容易保留原始数据的完整性。

2.2 宽表:适合横向比较股票

宽表将股票代码转换为列名。

例如:

datetime 600519.SH 000001.SZ
2026-01-05 100 20
2026-01-06 102 19

这种结构适合:

  • 比较不同股票的收益率。
  • 构建横截面因子矩阵。
  • 计算股票之间的相关性。
  • 分析组合收益。
  • 为矩阵运算准备输入数据。

两者没有绝对的优劣。

一个实用的原则是:原始行情尽量保留为长表,需要横向计算时再转换为宽表。

3. 使用 Pandas 构建多股票收盘价矩阵

假设已经获取并整理好多只股票的行情,数据保存在 all_klines 中,至少包含 symbol、datetime 和 close 字段。

可以使用 pivot() 构建收盘价矩阵:

python 复制代码
import pandas as pd

prices = all_klines.pivot(
    index="datetime",
    columns="symbol",
    values="close",
)

prices = prices.sort_index()

print(prices.head())

这里需要注意,pivot() 要求每个索引与列名的组合具有唯一值。

如果同一股票、同一时间出现重复记录,转换可能失败。

不要为了让 pivot() 正常执行,就直接改用:

python 复制代码
prices = all_klines.pivot_table(
    index="datetime",
    columns="symbol",
    values="close",
    aggfunc="last",
)

虽然这种写法可以在聚合时保留最后一个值,但它也可能掩盖重复数据问题。

如果重复记录来自错误的任务重跑或数据冲突,应该先排查,而不是直接聚合。

建议先检查:

python 复制代码
duplicates = all_klines[
    all_klines.duplicated(
        subset=["symbol", "datetime"],
        keep=False,
    )
]

if not duplicates.empty:
    print(duplicates)

确认数据不存在未经处理的重复记录后,再构建宽表。

4. 计算收益率之前,先确认价格序列的含义

构建价格矩阵后,通常会计算每只股票的日收益率。

python 复制代码
returns = prices.pct_change(
    fill_method=None
)

pct_change() 计算的是相邻观测值之间的比例变化。

但是,计算结果能否解释为某个交易日的收益率,取决于时间索引的定义和缺失值处理方式。

例如,股票 A 的价格连续覆盖周一和周二,而股票 B 在周二缺少数据。

如果股票 B 在周三重新出现有效价格,那么它的周三收益率可能对应的是跨越多个交易日的价格变化,而不是严格意义上的单日收益率。

这意味着:

  • 对单只股票计算历史收益时,应保留实际观测之间的时间间隔信息。
  • 对横截面策略计算每日收益时,应明确当天哪些股票具有有效观测。
  • 对组合回测而言,不能把不同时间跨度的收益率直接当成相同期间的收益。

如果策略要求每个交易日都存在有效数据,应建立明确的样本筛选规则,而不是依赖 Pandas 的默认行为。

5. 如何区分缺失行情与正常的交易日差异?

多股票数据缺失的原因并不相同。

情况一:非交易日

周末或市场休市日没有日 K 线,不应自动被视为数据异常。

情况二:股票尚未上市

如果研究区间早于某只股票的上市时间,该股票没有行情可能是正常现象。

情况三:停牌或其他交易状态

此时应根据策略设计决定如何处理,而不是直接假设股票存在可交易价格。

情况四:数据获取失败

如果某个交易日应该有行情,却没有获得有效记录,则需要进一步排查数据获取、权限、查询参数或数据源覆盖问题。

情况五:不同市场的交易日历不同

如果研究涉及 A 股、港股和美股,不同市场的休市安排可能不同。不能直接假设所有市场的日 K 都能按同一套自然日规则对齐。

因此,建立多股票价格矩阵前,最好先定义研究所使用的交易日历。

对于跨市场策略,还需要决定统一采用哪个时间基准,以及不同市场在某个时间点没有有效价格时应该如何处理。

6. dropna() 什么时候有用,什么时候会造成问题?

Pandas 提供了简单的缺失值处理方式。

例如:

python 复制代码
complete_prices = prices.dropna(
    axis=0,
    how="any",
)

这会删除至少有一只股票缺少收盘价的日期。

这种方法适合某些要求完整横截面样本的研究,但可能删除大量有效数据。

例如,一次横截面分析涉及大量股票,只要其中一只股票缺少数据,整行就会被删除。

另一种方法是:

python 复制代码
available_prices = prices.dropna(
    axis=1,
    how="all",
)

它会删除所有价格都缺失的股票列。

这可以清理完全没有有效数据的列,但无法解决某只股票只在部分日期缺失的问题。

因此,应该根据策略目标选择缺失值处理方法。

处理方式 优点 代价 适用场景
删除不完整日期 保证保留日期上的横截面完整 可能损失大量样本 必须使用完整股票池的分析
保留缺失值 不轻易丢弃有效数据 后续计算需要明确处理规则 探索性研究和质量分析
按股票筛选 避免低覆盖股票影响研究 股票池可能随时间变化 具有最低数据覆盖要求的策略
前向填充 在特定业务场景下维持某些状态值 可能制造虚假价格观测 明确允许使用延续值的非交易用途

特别要注意:如果填充后的价格进入收益率计算,可能产生人为的零收益,甚至掩盖真实的数据缺口。

7. 用分组计算避免股票之间相互污染

如果数据仍然保持长表格式,可以通过 groupby() 按股票分组计算收益率。

python 复制代码
all_klines = all_klines.sort_values(
    ["symbol", "datetime"]
).copy()

all_klines["return"] = (
    all_klines.groupby("symbol")["close"]
    .pct_change(fill_method=None)
)

这种方法的优势在于:

  • 不必先把全部股票转换为宽表。
  • 每只股票单独计算收益率。
  • 可以保留长表结构。
  • 更容易按标的检查数据质量。

但这里仍然存在一个重要边界:分组计算只保证不同股票之间不会直接混算,并不保证每两条相邻观测对应相邻交易日。

如果某只股票缺少中间的 K 线,pct_change() 仍可能计算两条有效观测之间的比例变化。

因此,如果策略需要严格的单日收益率,应该进一步结合交易日历和缺失记录进行校验。

8. 数据对齐如何影响横截面因子?

假设策略每天根据过去一段时间的收益率对股票进行排名,然后选择排名靠前的股票。

此时,数据对齐问题会影响股票排名。

例如:

text 复制代码
股票 A:有效的单日收益率
股票 B:跨多个交易日计算的收益率
股票 C:由填充价格计算出的零收益

如果直接将三者放进同一个横截面排名中,排名就可能失去可比性。

工程上应该至少明确:

  1. 收益率对应的时间区间。
  2. 当天哪些股票具有有效观测。
  3. 缺失数据是否影响因子计算。
  4. 股票池是否包含当时尚不可交易的股票。
  5. 排名使用的数据是否在决策时点已经可获得。

最后一点涉及 Look-ahead Bias(未来函数偏差),即回测使用了实际决策时点尚不可获得的信息。

即使价格矩阵本身没有明显异常,只要因子使用了未来数据,回测结果仍可能失真。

因此,时间对齐不仅是 Pandas 的数据处理问题,也是策略研究的正确性问题。

9. QuantDash 在多股票行情接入中能做什么?

当研究需要覆盖不同市场或多个标的时,数据获取接口的组织方式会影响后续工程设计。

QuantDash(专业金融数据 API / 量化数据平台)官方公开支持:

  • A 股(沪深京)。
  • ETF。
  • 美股。
  • 港股。
  • 日线、周线、月线等 K 线周期。
  • A 股分钟 K 线及日内相关行情。
  • 多种复权方式。
  • Python SDK 与 REST API。
  • 批量 K 线等查询能力。

这些能力与多股票研究中的数据获取需求有关。

例如,统一标的代码可以减少应用侧对不同市场代码格式的特殊处理:

text 复制代码
600519.SH
000001.SZ
920047.BJ
AAPL.US
00700.HK

不过,统一代码格式不代表不同市场的数据天然拥有相同交易日历、时区、成交量单位或价格口径。

这些差异仍需要在应用侧进行处理。

Python SDK 示例

QuantDash 官方 GitHub README 展示了以下日 K 查询方式:

python 复制代码
from quantdash import QuantDash

qd = QuantDash()

kline = qd.klines.get(
    "600519.SH",
    period="1d",
    count=5,
    adjust="forward",
    to_dataframe=True,
)

这里展示的是官方公开示例中的单标的查询方式。

本文不将其改写成未经确认的批量 SDK 调用。实际批量获取时,应按照官方技术文档确认支持的接口、参数和返回结构。

无论采用哪种合法的数据获取方式,返回的数据都可以按照统一的内部结构进入 Pandas 处理流程。

10. 一套可复用的多股票处理顺序

建议将多股票研究的数据处理拆成以下阶段:

text 复制代码
获取行情
    ↓
映射统一字段
    ↓
检查重复记录
    ↓
校验价格和时间字段
    ↓
对照交易日历
    ↓
按股票分组或转换宽表
    ↓
计算收益率与因子
    ↓
筛选有效样本
    ↓
执行回测

这种设计有一个好处:每一步的输入和输出都相对明确。

当回测结果异常时,可以判断问题究竟出现在数据获取、时间对齐、收益率计算还是策略逻辑,而不是反复修改策略参数。

FAQ

Q1:Pandas 如何将多只股票的 K 线转换为宽表?

A:可以使用 pivot(index="datetime", columns="symbol", values="close") 将长表转换为收盘价矩阵。转换前必须确认每个股票与时间的组合唯一,否则应先处理重复记录。

Q2:股票缺少某个交易日的行情,应该怎么处理?

A:先确认缺失是否来自正常休市、停牌、上市时间差异或数据获取失败。只有明确原因后,才能决定保留缺失值、筛选样本或采用其他处理规则。

Q3:为什么多股票收益率不能直接使用 dropna()?

A:dropna() 可能删除包含缺失值的整行,从而损失大量有效样本。更重要的是,删除缺失值并不能自动保证剩余收益率对应相同的时间区间。

Q4:groupby("symbol").pct_change() 可以保证收益率正确吗?

A:不能完全保证。它能避免跨股票计算,但如果同一股票的中间 K 线缺失,相邻有效观测仍可能跨越多个交易日。严格的单日收益率需要结合交易日历校验。

Q5:QuantDash 支持哪些市场的行情数据?

A:根据 QuantDash 官方公开能力,其市场覆盖包括 A 股、ETF、美股和港股。具体接口、周期和数据权限应以官方技术文档为准。

Q6:QuantDash 的统一标的代码能否解决跨市场数据对齐问题?

A:统一代码格式有助于标的识别,但不能自动解决交易日历、时区、价格口径和缺失值问题。跨市场量化研究仍需要在应用侧建立明确的数据标准。

总结

  • 长表适合保存行情,宽表适合横向分析。 应根据研究目的选择结构,而不是在获取数据后立即转换。
  • 时间对齐比简单合并更重要。 同一行的股票数据应具有可比较的时间含义。
  • 收益率计算必须考虑缺失数据。 pct_change() 不会自动判断两条记录之间是否跨越了多个交易日。
  • QuantDash 可用于多市场行情接入。 Python SDK、REST API、统一标的代码和 K 线相关查询能力,可以作为构建多股票研究数据集的基础;数据对齐和回测正确性仍需要应用侧负责。

QuantDash 官方资源

相关推荐
架构技术专栏9 小时前
Agent 工具调用:从工具声明到执行反馈
前端·人工智能·后端
码事漫谈10 小时前
你让AI干的活,它可能只干了十分之一——而你根本看不出来
后端
Wx-bishekaifayuan10 小时前
express皖美特色农产品网售系统53118-计算机课程设计、毕业设计
人工智能·spring boot·后端·python·课程设计·express·旅游
Wx-bishekaifayuan11 小时前
springboot汉中旅游服务平台41442-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游
WeiXin_DZbishe11 小时前
基于springboot城市公交实时运营与调度系统-计算机毕设 附源码【课程设计】66173
java·vue.js·spring boot·后端·python·php·课程设计
WeiXin_DZbishe11 小时前
基于Springboot+AI的kuall音乐广场的设计与实现-计算机毕设 附源码【课程设计】12715
vue.js·人工智能·spring boot·后端·python·php·课程设计
用户2022522150612 小时前
我在 DSH 做了个 AI 交付验收台:没有证据,就不算通过
github
A接拉起00712 小时前
2026 年 JavaScript 开发者需要了解的知识
前端·javascript·后端