一句话回答
个人量化项目选择第一套行情接口,重点不是接口数量有多少,而是能否稳定地获取所需数据,并让不同策略复用同一套数据接入逻辑。建议优先检查标的覆盖、K 线周期、复权方式、返回结构和异常处理,再将数据获取与策略计算分离。QuantDash Python SDK 可以承担行情获取环节,开发者则负责字段校验、数据标准化和本地管理,为后续因子研究与回测建立统一的数据基础。
为什么会有这个问题 / 传统做法有什么问题?
个人量化项目刚开始时,行情接口往往是最容易被低估的基础模块。
例如,准备开发一个简单的股票动量策略,第一版可能只有几十行代码:
- 获取股票最近 100 个交易日的收盘价。
- 计算过去 20 个交易日的价格变化。
- 选择涨幅较高的股票。
- 根据历史数据评估策略表现。
如果只研究一只股票,随便写一个数据请求函数似乎就够用了。
但当项目增加到多只股票、多种策略时,问题就逐渐出现。
假设有两个策略:
- 策略 A 使用日 K 线计算 20 日动量。
- 策略 B 使用日 K 线计算 60 日均线。
如果两个策略分别实现行情获取,就可能出现以下差异:
| 对比项目 | 策略 A | 策略 B |
|---|---|---|
| 数据来源 | 接口 A | 接口 B |
| 股票代码 | 600519 | 600519.SH |
| 复权方式 | 前复权 | 不复权 |
| 缺失数据 | 直接删除 | 前值填充 |
| 时间排序 | 升序 | 降序 |
这种情况下,即使两个策略研究同一只股票,底层数据也未必一致。
尤其是复权方式不同,会直接影响收益率和技术指标的计算结果。
例如,假设某只股票在公司行动前后的原始收盘价分别为 100 元和 50 元,仅根据这两个价格计算:
[
R=\frac{50}{100}-1=-50%
]
这个结果只是两个原始价格之间的变化,不能直接解释为投资者实际损失了 50%。
如果涉及拆股、送股或者现金分红,还需要结合公司行动和相应复权规则分析。
这说明一个问题:
行情接口的价值不仅在于获取价格,更在于让数据口径能够被明确记录、验证和复用。
传统做法通常存在三个局限。
第一,策略代码与数据获取代码耦合。更换接口时,需要同时修改多个策略。
第二,数据字段缺少统一管理。不同接口可能使用不同的日期字段、价格字段和数值类型。
第三,历史数据缺少来源记录。几个月后重新运行策略时,可能无法判断当时使用的是哪种数据口径。
对于个人开发者,更合适的方式是先建立一个简单的数据接入层,而不是一开始就建设复杂的数据平台。
分步骤解决:使用 QuantDash 进行实践
下面以 A 股日线研究为例,构建第一套可复用的行情获取流程。
目标不是编写完整交易系统,而是实现四件事:
- 统一初始化行情客户端。
- 使用固定参数获取历史 K 线。
- 将接口返回转换为研究层使用的数据结构。
- 对数据质量进行基础检查。
第 1 步:安装 SDK 并初始化客户端
首先安装 QuantDash Python SDK。
# 在 Python 环境中执行安装命令# pip install quantdash实际安装时,在终端执行pip install quantdash。
API Key 不建议直接写入策略代码,可以通过环境变量配置。
importosfromquantdashimportQuantDash api_key=os.getenv("QUANTDASH_API_KEY")ifnotapi_key:raiseRuntimeError("请先设置 QUANTDASH_API_KEY 环境变量")qd=QuantDash(api_key=api_key)这里使用os.getenv()读取环境变量QUANTDASH_API_KEY。
这样可以将认证配置与业务逻辑分离,避免在代码仓库中保存明文密钥。
需要注意,这段代码只完成客户端初始化,不代表 API Key 已经过服务端验证。实际认证情况仍需通过请求确认。
第 2 步:获取与主题相关的数据
第一套行情接口不必追求覆盖所有数据类型。
对于以日线因子和选股为主的个人项目,可以先验证历史 K 线是否满足需求。
例如获取贵州茅台最近 100 条日 K 线:
importpandasaspd symbol="600519.SH"try:raw_df=qd.klines.get(symbol,period="1d",count=100,adjust="forward",to_dataframe=True,)exceptExceptionasexc:raiseRuntimeError(f"获取{symbol}历史行情失败")fromexcifnotisinstance(raw_df,pd.DataFrame):raiseTypeError("行情接口未返回 DataFrame")ifraw_df.empty:raiseValueError(f"{symbol}行情数据为空")print("返回字段:",raw_df.columns.tolist())print("数据行数:",len(raw_df))print(raw_df.head())几个关键参数需要明确。
period=“1d”表示获取日 K 线。
count=100表示请求最近 100 条 K 线记录,实际返回数量需要检查。
adjust=“forward”表示选择前复权比例调整。
to_dataframe=True表示要求返回 Pandas DataFrame,便于后续处理。
需要特别注意,前复权价格适合某些连续价格序列分析,但并不等于实际成交价格,也不能直接代表完整投资收益。
第一次接入时,不建议立即进入策略计算。
先检查实际返回字段、字段类型和记录数量,才能判断数据是否满足后续业务要求。
第 3 步:处理数据并展示核心逻辑
第一套可复用接口应当尽量向策略层提供统一结构。
例如,策略层只关心日期、收盘价,以及经过验证的数据排序。
由于这里不能预先确认具体 SDK 版本返回的日期列名,下面将字段映射作为显式配置,开发者需要根据上一步输出的实际字段调整。
importpandasaspd# 根据实际返回字段修改DATE_COLUMN="date"CLOSE_COLUMN="close"defnormalize_daily_klines(raw_df:pd.DataFrame,date_column:str,close_column:str,)->pd.DataFrame:ifraw_dfisNoneorraw_df.empty:raiseValueError("输入行情为空")required={date_column,close_column}missing=required-set(raw_df.columns)ifmissing:raiseValueError(f"缺少必要字段:{sorted(missing)}")result=raw_df[[date_column,close_column]].copy()result.columns=["trade_date","close"]result["trade_date"]=pd.to_datetime(result["trade_date"],errors="coerce",)result["close"]=pd.to_numeric(result["close"],errors="coerce",)ifresult.isna().any().any():raiseValueError("日期或收盘价存在无效值")if(result["close"]<=0).any():raiseValueError("发现非正收盘价")ifresult["trade_date"].duplicated().any():raiseValueError("存在重复交易日期")returnresult.sort_values("trade_date").reset_index(drop=True)daily_df=normalize_daily_klines(raw_df,date_column=DATE_COLUMN,close_column=CLOSE_COLUMN,)print(daily_df.tail())代码中DATE_COLUMN和CLOSE_COLUMN是研究层的字段映射配置,并不是对 QuantDash 返回字段的无条件保证。
如果实际日期字段是毫秒时间戳,需要明确使用相应的时间单位解析;不能直接依靠默认日期推断。
这里最重要的设计,是让数据获取层与数据标准化层相互独立。
未来即使增加新的行情来源,也可以让不同来源的数据经过标准化后,再交给同一套因子计算代码。
第 4 步:展示完整业务示例或进阶处理
接下来,用统一后的日线数据计算一个简单的 20 日价格动量指标。
定义为:
[
M_{20}(t)=\frac{P_t}{P_{t-20}}-1
]
其中,(P_t) 为当前交易日收盘价,(P_{t-20}) 为 20 个交易记录之前的收盘价。
这个指标衡量价格变化,不代表包含分红再投资、交易费用等因素的完整持仓收益。
defcalculate_momentum(daily_df:pd.DataFrame,window:int=20,)->pd.DataFrame:ifwindow<1:raiseValueError("计算窗口必须大于 0")required={"trade_date","close"}ifnotrequired.issubset(daily_df.columns):raiseValueError("缺少计算所需字段")iflen(daily_df)<=window:raiseValueError("历史数据长度不足")result=daily_df.copy()result["momentum"]=(result["close"]/result["close"].shift(window)-1)valid_values=result["momentum"].dropna()ifvalid_values.empty:raiseValueError("没有有效的动量结果")returnresult momentum_df=calculate_momentum(daily_df,window=20,)print(momentum_df[["trade_date","close","momentum"]].tail())这里的shift(window)是按已有记录移动,并不自动保证跨越的是完整、连续的交易日集合。
如果行情缺失了某些交易日,所谓的 20 日动量可能实际上跨越了更长时间。
因此,真正用于选股前,还需要结合交易日历检查数据完整性。
完成以上步骤后,个人项目就拥有了一个基本的数据处理流程:
行情接口 → 字段验证 → 标准化 → 因子计算。
以后增加均线、波动率或其他日线因子时,可以复用前面的数据获取与校验逻辑。
关键坑与注意事项
不要只比较接口调用是否方便。首先确认所需市场、历史周期、复权方式和数据范围是否满足策略需求,调用简洁不能代替数据适用性验证。
不要默认返回字段始终不变。不同 SDK 版本或数据类型可能存在结构差异,建议为研究层建立明确的字段映射,并在关键字段缺失时终止计算。
不要把返回数量等同于完整交易日数量。count=100表示请求记录数量,是否覆盖预期交易区间还需要结合实际交易日期检查。
不要混用复权口径。计算动量、均线或其他价格类因子时,应记录adjust参数,避免将前复权与不复权数据直接拼接。
接口返回为空要有明确处理。不能把空数据自动当作停牌或无交易,应该区分请求错误、无数据、时间范围不匹配等不同情况。
缓存数据需要记录来源和参数。本地保存行情时,至少记录标的、周期、复权方式、获取时间和数据版本标识,以支持后续排查与研究复现。
常见问题(FAQ)
Q:个人量化项目一开始需要同时接入多个行情接口吗?
A:通常不需要。可以先确定一种主要研究场景,验证单一接口是否满足行情覆盖、数据口径和质量要求。只有存在明确缺口时,再考虑增加其他来源。
Q:为什么要把行情获取和因子计算分开?
A:两者职责不同。行情获取负责提供数据,因子计算负责解释和加工数据。分层后更容易修改接口、复用指标以及排查数据问题。
Q:日线研究一定要使用前复权数据吗?
A:不一定。前复权适合部分连续价格分析场景,但涉及真实成交价格、订单模拟或持仓核算时,需要根据业务目标选择正确口径,并额外处理公司行动。
Q:QuantDash SDK 能直接提供完整的量化回测系统吗?
A:本文只使用其已知的历史行情获取能力。交易费用、持仓管理、订单撮合、公司行动核算和回测可复现性,需要业务系统自行设计,不能默认由行情 SDK 自动完成。
Q:什么时候应该把行情数据保存到本地?
A:当同一份数据需要被多个策略重复读取,或者研究结果需要复现时,就值得考虑本地缓存。保存时应同时记录请求参数与数据来源,避免后续无法识别数据口径。
小结
个人量化项目选择第一套行情接口,真正需要解决的是数据能否长期复用,而不仅是能否成功发起请求。标的代码、交易日期、复权方式、字段类型和异常处理,都应该在最初阶段建立明确规则。
QuantDash Python SDK 可以用于统一获取历史 K 线,减少策略代码直接处理底层接口的重复工作。但字段校验、数据完整性检查、缓存管理和业务计算仍然需要开发者自行实现。先把这条基础数据链路做好,后续扩展因子研究和策略回测才更容易保持一致性与可复现性。
参考
QuantDash Python SDK 文档:https://docs.quantdash.net/zh-Hans/sdk/python-quickstart