一句话回答
为行情获取模块增加输入参数检查,核心是把标的代码、K 线周期、数据条数和复权方式等参数的校验放在 SDK 调用之前,避免无效请求进入数据获取流程。实际开发中,可以通过 Python 编写统一的参数验证函数,再使用 QuantDash SDK 获取行情,并在返回后补充数据结构检查,形成“请求前校验、获取后验证”的完整处理流程。
为什么会有这个问题 / 传统做法有什么问题?
量化研究程序通常需要反复获取历史行情。例如,一个策略需要读取某只股票最近 100 根日 K 线,另一个策略需要读取最近 200 根 5 分钟 K 线。
如果行情模块只是简单地封装接口调用:
def get_history(qd, symbol, period, count):
return qd.klines.get(
symbol,
period=period,
count=count,
to_dataframe=True,
)
看起来代码很简洁,但调用方可能传入各种不合理的参数。
比如:
- 标的代码写成 600519,缺少交易所后缀。
- period 传入不符合当前接口约定的周期。
- count 被设置为负数。
- count 被传入字符串,例如
"100"。 - adjust 出现拼写错误。
这些问题如果不在入口处理,就可能在 SDK 调用阶段才暴露。
更麻烦的是,不同策略可能使用不同的参数处理方式。有的模块直接报错,有的模块尝试转换类型,还有的模块会悄悄使用默认值。
假设一个策略要求获取 100 根 K 线,但实际请求中 count 被错误设置为 10。如果程序直接使用这 10 根数据计算指标,即使后续代码没有发生异常,研究结果也可能偏离原来的设计。
传统做法通常是依赖接口返回错误,或者通过大量 try/except 捕获异常。
这有两个不足。
第一,错误发现得太晚。原本可以在本地识别的参数错误,却进入了行情请求流程。
第二,错误信息不统一。调用方很难区分到底是自己传错参数,还是网络、权限或数据源出现问题。
更合理的设计是让参数检查成为行情获取模块的一部分。
QuantDash SDK 可以承担行情数据接入工作,业务系统则负责定义参数规则、拒绝不合法输入,并验证实际返回的数据。
分步骤解决:使用 QuantDash 进行实践
第 1 步:安装 SDK 并初始化客户端
安装依赖:
# 在终端执行
# pip install quantdash
实际安装命令为 pip install quantdash。
初始化客户端时,不建议把 API Key 写入源码,而应使用环境变量。
import os
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)
这里的检查属于配置校验。
如果缺少凭证,程序会在初始化阶段明确报告问题,而不是等到实际请求行情时才排查。
需要注意,检查 API Key 是否为空不代表验证了凭证有效性。实际授权是否成功仍需要通过接口调用确认。
第 2 步:获取与主题相关的数据
以获取 A 股历史日 K 线为例,我们需要检查四个参数:
- symbol:标的代码,采用代码加交易所后缀的形式。
- period:K 线周期,这里限制为示例中允许的日线和 5 分钟线。
- count:获取的数据条数,必须为正整数。
- adjust:复权方式,必须属于已知参数集合。
先实现一个校验函数。
import re
ALLOWED_PERIODS = {"1d", "5m"}
ALLOWED_ADJUST = {
"forward",
"backward",
"none",
"forward_additive",
"backward_additive",
}
def validate_history_params(
symbol,
period,
count,
adjust,
):
if not isinstance(symbol, str):
raise TypeError("symbol 必须是字符串")
symbol = symbol.strip().upper()
if not re.fullmatch(
r"\d{6}\.(SH|SZ|BJ)", symbol
):
raise ValueError(
"A 股代码应采用 600519.SH 等格式"
)
if period not in ALLOWED_PERIODS:
raise ValueError(
f"不支持的 K 线周期: {period}"
)
if type(count) is not int:
raise TypeError("count 必须是整数")
if count <= 0 or count > 1000:
raise ValueError(
"本模块要求 count 在 1 到 1000 之间"
)
if adjust not in ALLOWED_ADJUST:
raise ValueError(
f"不支持的复权方式: {adjust}"
)
return {
"symbol": symbol,
"period": period,
"count": count,
"adjust": adjust,
}
这段代码有几个设计细节。
首先,标的格式限制只是当前 A 股模块的本地规则,不意味着 SDK 只支持这些市场。实际项目可以根据支持的市场扩展。
其次,count 的最大值 1000 是这里人为设置的业务限制,并非 QuantDash 接口的已确认上限,部署时需要结合实际接口约束调整。
最后,type(count) is int 可以避免 Python 将布尔值误认为普通整数,因为布尔值实际上属于整数类型的子类。
完成校验后,再调用行情接口。
import pandas as pd
def fetch_history(
qd,
symbol,
period="1d",
count=100,
adjust="forward",
):
params = validate_history_params(
symbol, period, count, adjust
)
try:
df = qd.klines.get(
params["symbol"],
period=params["period"],
count=params["count"],
adjust=params["adjust"],
to_dataframe=True,
)
except Exception as exc:
raise RuntimeError(
f"获取 {params['symbol']} 行情失败"
) from exc
if not isinstance(df, pd.DataFrame):
raise TypeError(
"行情返回结果不是 DataFrame,"
"请检查实际接口返回结构"
)
if df.empty:
raise ValueError("行情返回为空")
return df
这里只使用已知的 qd.klines.get() 接口。
需要说明的是,SDK 的实际返回结构仍应以所安装版本及接口响应为准。这里将非 DataFrame 返回值视为当前封装不满足预期,而不是假设所有接口都会返回同样的数据结构。
第 3 步:处理数据并展示核心逻辑
请求参数正确,不代表返回数据一定符合后续计算要求。
例如,一个简单的日收益率计算至少需要有效的收盘价字段。
这里增加一层数据检查。
以下示例使用 close 作为收盘价列名。该字段名称需要根据实际返回 DataFrame 的列名确认;如果 SDK 返回不同名称,应先建立字段映射。
import numpy as np
import pandas as pd
def validate_close_prices(
df,
close_column="close",
):
if close_column not in df.columns:
raise KeyError(
f"缺少收盘价字段: {close_column},"
f"实际字段: {list(df.columns)}"
)
close = pd.to_numeric(
df[close_column],
errors="coerce",
)
if close.isna().any():
raise ValueError("收盘价存在缺失或非法类型")
if not np.isfinite(close.to_numpy()).all():
raise ValueError("收盘价存在无穷大")
if (close <= 0).any():
raise ValueError("收盘价必须大于 0")
return close
history = fetch_history(
qd,
symbol="600519.SH",
period="1d",
count=100,
adjust="forward",
)
close_prices = validate_close_prices(history)
if len(close_prices) < 2:
raise ValueError("至少需要两条价格记录")
# 计算前必须确认 K 线已经按实际时间字段升序排列。
# 若返回顺序未知,应先按已确认的时间字段排序。
returns = close_prices.pct_change().dropna()
print("收益率序列长度:", len(returns))
这里的 pct_change() 计算的是相邻记录的价格变化率。
当输入为前复权收盘价时,结果是前复权价格序列的相邻变化率,不能直接等同于考虑现金分红、交易费用和税费的真实持仓收益。
此外,代码没有猜测行情时间字段。实际运行时,必须先检查返回结构,并按真实的时间列或时间索引排序,才能保证收益率方向正确。
第 4 步:展示完整业务示例或进阶处理
在一个有多个策略的系统中,可以把参数检查与行情获取统一封装成服务函数。
进一步增加一个很实用的规则:返回的记录数不能超过请求数量,而且不足时要明确提示。
def load_checked_history(
qd,
symbol,
period,
count,
adjust="forward",
):
df = fetch_history(
qd,
symbol=symbol,
period=period,
count=count,
adjust=adjust,
)
if len(df) > count:
raise ValueError(
"返回记录数超过请求数量,"
"请检查返回结构及数据口径"
)
if len(df) < count:
print(
f"提示:请求 {count} 条,"
f"实际获取 {len(df)} 条"
)
validate_close_prices(df)
return df
try:
history = load_checked_history(
qd,
symbol="000001.SZ",
period="1d",
count=100,
adjust="forward",
)
print("行情记录数:", len(history))
except (TypeError, ValueError, KeyError) as exc:
print("输入或数据校验失败:", exc)
except RuntimeError as exc:
print("行情获取失败:", exc)
这里将两类错误分开处理:
一类是参数或数据质量错误,通常需要检查调用逻辑。
另一类是行情获取异常,需要结合 SDK 原始异常进一步定位网络、鉴权或接口响应等问题。
还要注意,len(df) 是否代表 K 线条数,需要先确认 DataFrame 的组织方式。对于单标的、每行代表一根 K 线的结构,上述判断才成立。
关键坑与注意事项
-
不要把格式合法等同于标的有效。 600519.SH 符合代码格式,但仅靠正则表达式无法判断某个标的是否真实存在、是否退市或是否支持请求的周期。需要时可通过 qd.instruments.get() 获取元数据辅助验证,并确认其实际返回结构。
-
不要把本地业务限制误认为 SDK 限制。 例如限制 count 不超过 1000,只是为了控制当前模块的请求规模,不能据此认定接口的实际最大请求条数。
-
不要自动修正所有错误参数。 去掉字符串首尾空格通常可以接受,但将非法周期静默替换成日线可能改变策略逻辑。对影响数据含义的错误,最好直接拒绝。
-
不要忽略空数据。 参数全部合法,也可能因为停牌、标的历史不足、权限或时间范围问题返回空结果。空数据必须和参数错误区分处理。
-
注意复权方式影响价格口径。 forward、backward 和 none 对应不同的数据处理口径,不能在策略执行过程中随意改变默认值。
-
返回记录数不足不一定是接口错误。 股票上市时间较短、数据覆盖范围不足或其他数据条件都可能造成记录数量不足。研究系统应根据策略需求决定提示、拒绝还是继续处理。
-
时间顺序必须单独验证。 输入校验解决不了行情乱序问题。如果需要计算收益率或技术指标,应确认真实时间字段、排序方向和重复记录情况。
常见问题(FAQ)
Q:为什么不直接依赖 SDK 抛出的异常?
A:SDK 异常主要反映接口调用及其自身校验的结果,而业务参数检查还需要覆盖策略约束。提前验证能提供更明确、一致的错误信息。
Q:行情参数检查应该放在策略层还是数据层?
A:通用规则建议放在行情服务入口,策略特有的规则放在策略层。例如代码格式属于通用检查,而某个策略必须使用 200 根 K 线,则属于策略要求。
Q:是否应该自动把字符串形式的 count 转成整数?
A:可以,但需要明确输入协议。如果接口规定只能接受整数,直接拒绝 "100" 更容易发现上游调用错误。
Q:QuantDash SDK 是否会自动完成这些自定义校验?
A:不能假定 SDK 会执行本文定义的正则检查、业务数量上限或自定义数据验证。这些规则由示例中的 Python 函数完成,SDK 主要负责行情数据获取。
Q:生产环境还需要增加哪些检查?
A:通常还需要请求日志、错误分类、参数配置管理、数据时间校验,以及异常情况下的处理策略。对于批量获取,还需要识别部分标的失败,而不是只检查整体结果是否为空。
小结
行情获取模块的输入参数检查,重点不在于多写几个 if,而是建立明确的输入契约。调用前验证参数格式、类型和值域,获取后验证数据结构和关键字段,才能避免错误沿着研究链路继续传播。
通过 QuantDash Python SDK,可以把历史 K 线获取集中到统一接口中,而参数规范、数据质量规则、错误分类和策略适用性判断仍由业务系统负责。把这些边界设计清楚,能够让行情模块更容易维护,也让后续因子计算和策略回测建立在更可靠的数据基础上。
参考
QuantDash Python SDK 文档:https://docs.quantdash.net/zh-Hans/sdk/python-quickstart
390



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



