做分钟级量化研究时,我们经常需要分析一个明确的时间窗口:例如开盘后前 30 分钟、收盘前最后一小时,或者某个交易日中一段特定的行情。
这类任务看起来只是对时间进行筛选,实际上有三个需要解决的问题:
- 如何获取覆盖研究时间段的分钟 K 线?
- 如何准确地按照起止时间保留数据?
- 如何确保截取后的数据没有遗漏、重复或意外包含研究区间之外的行情?
如果处理不当,即使后续的指标计算和策略逻辑完全正确,研究结果仍可能出现偏差。
本文使用 Python、Pandas 和 QuantDash Python SDK,介绍一套从行情获取到时间窗口截取的实用流程。
一、先明确:研究区间不等于数据查询数量
假设我们需要研究贵州茅台在某个交易日 14:00 至 14:30 的分钟级行情。
如果直接获取最近 30 根一分钟 K 线,并不一定能得到这段时间的数据。
原因很简单:count=30 表示按接口定义获取指定数量的 K 线,而不是指定开始和结束时间。返回的数据可能来自其他时段,甚至跨越不同交易日。
因此,应当区分两个概念:
- 数据获取范围:从数据服务中请求哪些行情。
- 研究时间范围:最终允许哪些行情进入分析。
前者决定能拿到什么数据,后者决定哪些数据参与研究。
对于有明确起止时间的任务,推荐先获取覆盖研究窗口的数据,再使用 Pandas 执行精确筛选。
二、使用 QuantDash 获取分钟级行情
安装 SDK:
bash
pip install quantdash pandas
在使用之前,需要准备有效的 QuantDash API Key,并通过环境变量配置:
bash
# macOS / Linux
export QUANTDASH_API_KEY="your_api_key"
Windows PowerShell:
powershell
$env:QUANTDASH_API_KEY = "your_api_key"
下面先获取一个交易日的分钟行情:
python
import pandas as pd
from quantdash import QuantDash
qd = QuantDash()
symbol = "600519.SH"
# 获取当日分钟级行情
df = qd.klines.intraday(
symbol,
period="1m",
to_dataframe=True,
)
if df.empty:
raise ValueError("未获取到分钟行情,请检查标的代码、API Key 和接口权限")
print(df.head())
print(df.columns.tolist())
这里使用的是 QuantDash 的日内行情接口。
需要注意,intraday() 面向当日分钟级行情。如果研究对象是过去某一天,不能简单地认为该接口会自动返回指定历史日期的数据。
对于历史研究,应根据官方 K 线接口的参数说明,使用支持相应时间范围的查询方式获取数据。
另外,分钟行情的返回字段应以实际接口响应为准。后续示例使用 trade_time 作为时间列,因此在执行前需要确认返回数据中存在该字段。
三、用 Pandas 精确截取研究时间
假设已经获得覆盖研究窗口的分钟行情,接下来就可以使用 Pandas 进行筛选。
例如,研究区间为:
- 开始时间:2026 年 6 月 22 日 14:00:00
- 结束时间:2026 年 6 月 22 日 14:30:00
代码如下:
python
import pandas as pd
# df 为已经获取的分钟 K 线 DataFrame
if "trade_time" not in df.columns:
raise ValueError("行情数据缺少 trade_time 时间字段")
df = df.copy()
# 统一时间字段类型
df["trade_time"] = pd.to_datetime(
df["trade_time"],
errors="raise",
)
# 排序,确保后续分析按照时间顺序进行
df = df.sort_values("trade_time").reset_index(drop=True)
start_time = pd.Timestamp("2026-06-22 14:00:00")
end_time = pd.Timestamp("2026-06-22 14:30:00")
if start_time >= end_time:
raise ValueError("开始时间必须早于结束时间")
# 使用左闭右开区间:[start_time, end_time)
mask = (
(df["trade_time"] >= start_time)
& (df["trade_time"] < end_time)
)
research_df = df.loc[mask].copy()
if research_df.empty:
raise ValueError("指定研究区间内没有行情数据")
print("截取数据条数:", len(research_df))
print("实际开始时间:", research_df["trade_time"].min())
print("实际结束时间:", research_df["trade_time"].max())
这里最重要的不是筛选语法,而是时间区间的定义。
示例使用的是左闭右开区间:
t \\in \[t_{\\text{start}}, t_{\\text{end}})
也就是包含开始时间,但不包含结束时间。
这种方式特别适合相邻时间窗口的拼接。例如:
- 第一段:14:00 至 14:30。
- 第二段:14:30 至 15:00。
第一段不包含 14:30,第二段则从 14:30 开始,因此不会因为边界定义不一致而重复纳入同一个时间戳。
如果研究规则明确要求包含结束时间,可以改为:
python
mask = (
(df["trade_time"] >= start_time)
& (df["trade_time"] <= end_time)
)
但应当注意,时间戳恰好等于结束时间的记录是否存在,以及它代表哪一段行情,都需要结合数据源的 K 线时间定义判断。
不能只因为结束时间写成 14:30,就认定一定存在一根 14:30 的完整分钟 K 线。
四、起止时间需要转换成时间戳吗?
在 Pandas 中筛选时间时,通常直接使用 Timestamp 就可以。
但如果数据接口要求使用 Unix 时间戳,就需要进行转换。
例如,某个接口要求以毫秒表示的时间戳:
python
from datetime import datetime, timezone
start_dt = datetime(
2026, 6, 22, 14, 0,
tzinfo=timezone.utc,
)
end_dt = datetime(
2026, 6, 22, 14, 30,
tzinfo=timezone.utc,
)
start_ms = int(start_dt.timestamp() * 1000)
end_ms = int(end_dt.timestamp() * 1000)
print(start_ms)
print(end_ms)
这段代码演示的是时间戳转换,并不代表所有行情接口都要求毫秒时间戳,也不代表所有市场都应使用 UTC 作为研究时间。
对于中国 A 股,研究者通常会以交易所本地时间定义交易窗口。若接口要求 UTC 时间戳,应先明确时间所属时区,再进行转换。
例如,2026 年 6 月 22 日 14:00 的北京时间,对应的是 UTC 06:00,而不是 UTC 14:00。
因此,不能直接将一个没有时区信息的本地时间转换成时间戳,然后假设得到的结果一定符合服务端的要求。
建议:先确认接口的时间参数单位、时区和边界规则,再构造请求参数。
QuantDash 的具体 K 线查询参数应以官方技术文档为准,不要仅凭其他数据源的调用习惯推断。
五、截取完成后,还要检查什么?
成功返回 DataFrame 并不意味着数据一定适合研究。
至少可以检查以下几个方面。
1. 检查实际时间边界
python
print("研究窗口开始:", start_time)
print("研究窗口结束:", end_time)
print("数据实际开始:", research_df["trade_time"].min())
print("数据实际结束:", research_df["trade_time"].max())
assert research_df["trade_time"].min() >= start_time
assert research_df["trade_time"].max() < end_time
如果断言失败,说明筛选结果不符合预期,应检查时间字段类型、时区或筛选条件。
2. 检查重复时间戳
python
duplicate_count = research_df["trade_time"].duplicated().sum()
print("重复时间戳数量:", duplicate_count)
如果同一标的在同一周期下出现重复时间戳,需要进一步确认是数据重复、合并操作造成的重复,还是接口返回结构中存在其他维度。
不要在没有查明原因之前直接删除重复记录。
3. 检查 OHLC 数据
python
price_columns = ["open", "high", "low", "close"]
missing_columns = [
column
for column in price_columns
if column not in research_df.columns
]
if missing_columns:
raise ValueError(f"缺少价格字段:{missing_columns}")
missing_price_count = (
research_df[price_columns]
.isna()
.any(axis=1)
.sum()
)
print("价格字段存在缺失的记录数:", missing_price_count)
这段代码只检查字段和空值,并不能证明所有行情记录都符合金融数据规则。
如果后续研究涉及成交量、成交额或其他指标,还应根据实际返回字段增加对应检查。
六、几个容易忽略的边界问题
| 问题 | 可能的影响 | 建议 |
|---|---|---|
只用 count 获取数据 |
返回的数据不一定覆盖目标时间 | 先确认查询方式能覆盖研究区间 |
| 直接使用字符串比较时间 | 字段格式不一致时可能出错 | 统一转换为时间类型 |
| 混用本地时间与 UTC | 截取到错误的时段 | 明确时区后再比较 |
| 忽略结束时间边界 | 相邻窗口可能出现重复数据 | 统一采用左闭右开或其他明确规则 |
| 把交易时段之外的缺口当作数据缺失 | 错误地判断行情不完整 | 根据交易所交易日历判断 |
| 直接删除重复记录 | 可能掩盖数据处理问题 | 先查明重复原因 |
还有一个值得强调的问题:分钟 K 线不一定严格按照每分钟连续生成。
午间休市、周末、法定节假日、停牌以及交易所交易规则,都可能影响时间序列的连续性。
例如,A 股上午交易结束后,时间序列不会因为中午休市而自然产生一整段有效成交分钟线。
因此,检查分钟数据完整性时,不应简单地认为相邻记录的时间差必须等于一分钟。
结语
根据研究起止时间截取分钟行情,关键在于把数据获取与研究窗口定义分开处理。
首先获取覆盖目标区间的数据,再统一时间字段、明确时区和边界规则,最后使用 Pandas 精确筛选并检查结果。
如果研究的是历史某一天,应优先确认历史 K 线接口的时间查询能力;如果研究的是当前交易日,则可以根据需要使用日内行情接口。
这样做的价值不只是让代码正确运行,更是确保后续的收益率、技术指标和策略回测建立在符合研究定义的数据之上。
QuantDash 官方文档
- QuantDash 技术文档 --- 查看 Python SDK、REST API 和数据接口说明。