一句话回答
在批量量化研究中,统一不同股票的计算起止日期,不能只给所有股票设置相同的查询时间,还需要根据实际返回的交易日期进行二次对齐。比较稳妥的方式是先确定研究窗口,再获取各股票的历史 K 线,最后提取共同有效交易日。使用 QuantDash Python SDK 可以统一获取指定时间范围内的行情,日期交集计算、缺失检查和样本筛选则由 Python 研究代码完成。
为什么会有这个问题 / 传统做法有什么问题?
批量计算股票因子时,经常遇到一个容易被忽略的问题:明明给所有股票设置了相同的开始日期和结束日期,最后参与计算的数据却不在同一个时间范围内。
假设我们需要研究两只股票:
- 股票 A:研究区间内每天都有有效行情。
- 股票 B:研究区间内存在停牌日期,或者某些日期的数据没有成功获取。
如果直接按各自数据的第一行和最后一行计算区间收益,就可能出现:
股票 A 使用 20 个有效收盘价,股票 B 只有 17 个。
这时,即使两只股票的查询时间相同,实际价格观测区间仍然可能不同。
对于区间价格收益率,常用公式为:
[
R_i=\frac{P_{i,T}}{P_{i,0}}-1
]
其中,(P_{i,0}) 和 (P_{i,T}) 分别代表股票 (i) 在计算起点与终点的收盘价。
如果两只股票的实际起止日期不同,得到的收益率就不能直接视为相同观察区间内的结果。
传统做法通常有两个问题。
第一,直接使用 count 参数获取最近 N 根 K 线。虽然每只股票都返回相同数量的记录,但停牌、上市时间和数据缺失可能导致这些记录对应不同的日历区间。
第二,简单截取某个日期范围,然后直接计算指标。这种方式没有验证每只股票是否真的拥有相同的有效日期。
对于横截面选股、因子排名和批量收益比较,更合理的处理方式是建立统一研究日历。
分步骤解决:使用 QuantDash 进行实践
第 1 步:安装 SDK 并初始化客户端
首先安装 QuantDash Python SDK:
# 在终端执行
# pip install quantdash
示例还需要 pandas,实际环境中应确认已经安装。
API Key 不建议直接写入代码,应通过环境变量读取。
import os
import pandas as pd
from quantdash import QuantDash
api_key = os.getenv("QUANTDASH_API_KEY")
if not api_key:
raise RuntimeError(
"请先设置 QUANTDASH_API_KEY 环境变量"
)
qd = QuantDash(api_key=api_key)
后面的代码默认继续使用这里初始化的 qd 客户端。
第 2 步:批量获取指定时间范围的 K 线
这里选择两只 A 股作为示例标的:
- 600519.SH
- 000001.SZ
为了保证查询时间的一致性,使用 start_time 和 end_time 明确指定研究窗口。
from datetime import datetime
from zoneinfo import ZoneInfo
symbols = ["600519.SH", "000001.SZ"]
tz = ZoneInfo("Asia/Shanghai")
start_time = int(
datetime(2026, 5, 1, tzinfo=tz).timestamp() * 1000
)
end_time = int(
datetime(2026, 6, 1, tzinfo=tz).timestamp() * 1000
) - 1
try:
raw_data = qd.klines.batch(
symbols,
period="1d",
start_time=start_time,
end_time=end_time,
adjust="forward",
to_dataframe=True,
show_progress=True,
)
except Exception as exc:
raise RuntimeError(
"批量获取历史行情失败"
) from exc
if not isinstance(raw_data, dict) or not raw_data:
raise ValueError("批量行情返回为空或结构异常")
这里有几个关键参数:
- period=“1d”:获取日 K 线。
- start_time:查询起始时间,使用毫秒时间戳。
- end_time:查询结束时间。
- adjust=“forward”:使用前复权比例调整价格。
- to_dataframe=True:以 DataFrame 形式获取单只股票的数据。
根据 SDK 文档,批量查询的返回结果可按股票代码访问对应的 DataFrame。实际开发仍需检查批量结果是否包含所有请求标的。
日期参数限定查询窗口,但不保证每个标的都拥有窗口内全部交易日。
第 3 步:提取共同有效交易日
对于日 K 线,当前 SDK 文档示例使用 trade_date 表示交易日期。
下面先标准化日期和收盘价字段,再计算不同股票的交易日期交集。
cleaned_data = {}
date_sets = []
for symbol in symbols:
if symbol not in raw_data:
raise ValueError(f"{symbol} 未返回行情")
df = raw_data[symbol]
if not isinstance(df, pd.DataFrame) or df.empty:
raise ValueError(f"{symbol} 行情为空")
required = {"trade_date", "close"}
if not required.issubset(df.columns):
raise ValueError(
f"{symbol} 缺少字段: "
f"{required - set(df.columns)}"
)
df = df.copy()
df["trade_date"] = pd.to_datetime(
df["trade_date"], errors="coerce"
).dt.normalize()
df["close"] = pd.to_numeric(
df["close"], errors="coerce"
)
df = df.dropna(subset=["trade_date", "close"])
df = df[df["close"] > 0]
if df["trade_date"].duplicated().any():
raise ValueError(f"{symbol} 存在重复交易日期")
df = df.sort_values("trade_date")
if df.empty:
raise ValueError(f"{symbol} 没有有效行情")
cleaned_data[symbol] = df
date_sets.append(set(df["trade_date"]))
common_dates = sorted(
set.intersection(*date_sets)
)
if len(common_dates) < 2:
raise ValueError("共同交易日不足,无法计算区间收益")
print("共同计算起点:", common_dates[0].date())
print("共同计算终点:", common_dates[-1].date())
print("共同有效交易日数:", len(common_dates))
这里的核心是 set.intersection()。
只有所有股票均包含的有效交易日期,才会进入共同日期集合。
与简单比较最早日期和最晚日期不同,这种方法还能排除研究区间内部不一致的交易日。
需要注意,当前实现采用严格交集规则。它能够保证样本在日期上可比,但可能显著减少参与计算的日期数量。
第 4 步:在共同日期上计算区间收益
有了统一的日期集合,就可以进一步构建收盘价矩阵。
price_series = []
for symbol, df in cleaned_data.items():
aligned = df[
df["trade_date"].isin(common_dates)
].copy()
series = aligned.set_index(
"trade_date"
)["close"]
series.name = symbol
price_series.append(series)
price_matrix = pd.concat(
price_series, axis=1
).sort_index()
price_matrix = price_matrix.reindex(common_dates)
if price_matrix.isna().any().any():
raise ValueError("对齐后仍存在缺失价格")
start_prices = price_matrix.iloc[0]
end_prices = price_matrix.iloc[-1]
if (start_prices <= 0).any():
raise ValueError("计算起点存在非正价格")
returns = end_prices.div(start_prices) - 1
result = pd.DataFrame({
"symbol": returns.index,
"start_date": price_matrix.index[0].date(),
"end_date": price_matrix.index[-1].date(),
"price_return": returns.values,
})
print(result.to_string(index=False))
这里计算的是共同区间内的复权价格变化率,不等同于包含交易费用、税费、实际分红到账和持仓变化的完整投资收益。
上述代码得到的所有股票都使用相同的计算起点和终点。
但还有一个重要区别:统一共同有效交易日,并不意味着所有股票在整个原始研究区间内都有完整行情。
如果策略要求每个交易日都能够形成信号,还需要结合交易日历和数据完整性检查进一步处理。
关键坑与注意事项
- 查询日期相同不代表实际交易日期相同。 应当以返回的 trade_date 为准,不能仅使用请求参数判断样本是否对齐。
- 相同的 K 线数量不代表相同的观察区间。 使用 count 查询时,需要额外检查第一条和最后一条记录的日期。
- 停牌和数据缺失不能简单等同。 某日没有 K 线可能有多种原因,应结合交易状态或其他可靠数据判断。
- 日期交集可能导致样本缩减。 股票数量较多时,一个标的的异常日期可能导致所有股票失去该日期,必要时应考虑覆盖率规则。
- 前复权价格需要保持相同处理口径。 不应将前复权、后复权和原始收盘价混合放入同一收益比较。
- 历史数据可能发生修订。 对需要复现的研究任务,应保存查询窗口、复权口径、数据获取时间和处理代码版本。
常见问题(FAQ)
Q:直接给所有股票传入相同的 start_time 和 end_time,不就统一日期了吗?
A:这只是统一查询窗口,并没有保证每只股票都返回相同的有效交易日,需要继续根据实际日期字段对齐。
Q:为什么不直接使用 count=20?
A:count=20 适合获取指定数量的 K 线,但对于存在停牌或数据缺失的股票,20 根 K 线对应的时间跨度可能不同。
Q:日期交集适用于所有批量研究吗?
A:不一定。它适合要求严格同步观察日期的研究。对于大规模股票池,可能需要使用基准交易日历和覆盖率阈值,而不是让全部股票决定共同日期。
Q:QuantDash 会自动完成不同股票的日期对齐吗?
A:不能仅根据批量接口推断存在这样的自动处理能力。QuantDash 用于获取行情,本文的日期标准化和交集运算由 Python 代码实现。
Q:前复权收盘价可以直接用于回测账户收益吗?
A:不能直接等同。复权价格可以辅助研究价格变化,但完整账户收益还涉及成交价格、实际持仓、公司行动及交易成本。
小结
批量研究真正需要统一的,不只是 API 查询时传入的开始日期和结束日期,还包括参与计算的实际交易日。对交易日期进行标准化,再构建共同有效日期集合,是提高横截面研究可比性的一种直接方法。
QuantDash Python SDK 可以减少多股票历史行情的数据接入工作,但交易日交集、异常价格过滤、缺失处理和结果验证仍应由研究系统明确实现。将这些规则固定下来,才能让不同批次的研究结果具有更好的可解释性和可复现性。
参考
QuantDash Python SDK 文档:https://docs.quantdash.net/zh-Hans/sdk/python-quickstart
402



被折叠的 条评论
为什么被折叠?



