ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

如何判断自己写的行情获取脚本是否已经不适合继续扩展?

如何判断自己写的行情获取脚本是否已经不适合继续扩展?

行情获取脚本最容易出现的问题,不是最初写得不够好,而是它在不断增加功能之后,逐渐变成一个谁都不敢修改的程序。

最开始,脚本可能只需要获取一只股票的日 K 线。后来增加了多只股票、分钟行情、定时更新、数据落盘和异常重试。每增加一个需求,都能通过修改几行代码解决。直到某一天,新增一个标的需要修改三个函数,调整请求参数又影响了数据保存逻辑,偶尔出现的数据缺失更是很难定位。

这时,问题已经不只是代码风格,而是现有结构是否还适合承载新的需求。

判断行情获取脚本是否需要重构,不能只看代码行数,也不能简单地以是否使用类、是否采用异步请求作为标准。更值得关注的是:新增需求的成本是否持续上升,以及一个局部故障是否会影响整个数据流程。

一、先看三个最明显的信号

1. 新增数据类型,总要修改原来的核心逻辑

假设最初的脚本只负责获取股票日 K 线:

deffetch_data(symbol):# 请求行情# 解析数据# 保存到本地pass

当需求扩展到分钟 K 线时,可能有人直接增加条件:

deffetch_data(symbol,period="1d"):ifperiod=="1d":# 获取日 K 线passelifperiod=="5m":# 获取 5 分钟 K 线pass# 保存数据

这种写法本身并没有错。对于只有少量固定需求的小工具,条件分支甚至可能是最简单、最容易维护的实现。

真正值得警惕的是,随着功能继续增加,fetch_data()开始同时处理以下任务:

  • 判断市场和标的类型。
  • 选择请求参数。
  • 调用数据接口。
  • 转换返回结果。
  • 检查数据质量。
  • 决定保存路径。
  • 处理异常并重试。
  • 更新任务状态。

此时,任何新功能都可能影响多个已有行为。

可以用一个简单的问题判断:如果只想修改数据保存方式,是否需要理解行情接口的全部细节?

如果答案是肯定的,说明模块之间的职责已经开始混杂。

2. 同一个请求失败,需要到多个地方寻找原因

行情脚本常见的失败并不只有网络异常。

例如:

表现可能的原因
返回空数据标的代码不正确、数据不可用或查询条件不匹配
请求失败网络、认证或服务端异常
数据条数减少查询范围、交易日或返回结果存在差异
文件没有更新保存逻辑失败、任务提前退出或数据未通过校验
重复记录增多多次写入缺少明确的去重规则
定时任务偶尔中断异常没有被正确隔离或恢复

如果请求、解析、校验和写入都在一个函数里,那么日志即使显示了异常,也未必能迅速确定问题属于哪个环节。

更合理的做法是让各个环节具有清晰的输入、输出和错误边界。

例如,数据获取函数负责返回数据,校验函数负责检查数据,保存函数负责持久化。这样,即使最终文件没有更新,也可以先确认数据是否已经成功获取。

3. 脚本能够运行,却无法证明数据完整

这是量化开发中尤其容易被忽略的问题。

一次请求没有抛出异常,不代表获得的数据一定符合研究需求。

例如,计划获取某只股票最近 100 根日 K 线,接口正常返回,但结果只有 60 行。程序如果直接保存数据并报告成功,后续的均线计算和策略研究可能仍然可以运行,却已经使用了不完整的输入。

对于行情采集程序,至少应区分三个状态:

  1. 请求成功:数据接口正常返回。
  2. 数据有效:返回结果符合基本结构和质量要求。
  3. 任务完成:数据经过必要检查后,已经成功保存或交付给下游。

这三个状态不能混为一谈。

如果现有脚本只有一个success = True,而没有进一步区分请求结果、数据质量和保存状态,那么在任务规模扩大之前,就有必要重新审视它的执行流程。

二、用 QuantDash 检查现有脚本的数据处理链路

当行情获取任务与金融市场数据直接相关时,可以使用 QuantDash Python SDK 获取真实行情,再通过 Pandas 检查数据结构。

官方公开示例提供了以下调用方式:使用QuantDash初始化客户端,通过qd.klines.get()获取 K 线,并可以将结果转换为 DataFrame。具体用法应以官方文档为准。(GitHub)

先安装 SDK:

pipinstallquantdash pandas

在运行环境中配置 API Key:

exportQUANTDASH_API_KEY="your_api_key_here"

Windows PowerShell 可以使用:

$env:QUANTDASH_API_KEY ="your_api_key_here"

下面以获取贵州茅台的前复权日 K 线为例,演示如何把数据获取和数据检查分开。

importpandasaspdfromquantdashimportQuantDashdeffetch_klines(symbol:str)->pd.DataFrame:"""获取指定标的的日 K 线。"""client=QuantDash()data=client.klines.get(symbol,period="1d",count=100,adjust="forward",to_dataframe=True,)ifdataisNone:raiseValueError(f"{symbol}没有返回有效的数据对象")ifnotisinstance(data,pd.DataFrame):raiseTypeError("返回结果不是预期的 DataFrame")returndatadefinspect_klines(data:pd.DataFrame)->None:"""检查行情数据的基本结构。"""ifdata.empty:raiseValueError("行情数据为空")ifdata.columns.duplicated().any():raiseValueError("存在重复列名")print(f"数据行数:{len(data)}")print(f"数据列:{data.columns.tolist()}")print(f"缺失值统计:\n{data.isna().sum()}")if__name__=="__main__":klines=fetch_klines("600519.SH")inspect_klines(klines)print(klines.head())

这段代码的重点不是增加多少检查,而是将两个职责明确分开:

  • fetch_klines()负责从数据接口获取结果。
  • inspect_klines()负责检查返回结果的基本结构。

需要注意,示例没有假定具体的时间列或价格列名称,而是先打印实际返回的字段。进一步检查 OHLC、成交量和时间索引之前,应先根据当前 SDK 返回结构确认字段名称。

另外,count=100表示请求的 K 线数量参数,并不能证明最终一定获得 100 行数据。实际返回数量仍然需要检查。

如果希望判断行情数据是否足以支持研究,还应进一步核实:

  • 数据的时间范围是否满足任务要求。
  • 时间索引或时间字段是否存在重复。
  • 价格和成交量字段是否符合预期。
  • 交易日缺口是否合理。
  • 复权方式是否与研究目标一致。

这些检查属于调用方的数据质量逻辑,不应误认为 SDK 自动完成了全部验证。

三、不要急着重写,先把代码拆成三个边界

当脚本出现上述问题时,最容易走向另一个极端:直接引入异步框架、任务队列、数据库和复杂的插件体系。

但如果当前只有几个行情任务,这些改动未必能够解决实际问题。

更有效的第一步,是建立三个相对独立的部分。

第一层:数据获取

只负责调用接口和接收结果。

这一层不应该决定策略如何计算,也不应该在获取数据时顺便更新研究报告或修改业务状态。

第二层:数据校验与整理

负责检查字段、缺失值、重复记录、时间范围和数据类型。

不同数据频率可以有不同的校验规则。例如,日 K 线需要关注交易日序列,而分钟 K 线还需要关注时间戳和盘中数据范围。

不要把所有校验都写成一个无法区分数据类型的通用函数。

第三层:保存与下游交付

负责将通过检查的数据写入数据库、文件或交给研究模块。

保存逻辑应该能够独立测试。例如,将存储格式从 CSV 改为 Parquet 时,不应该需要修改行情请求参数。

这三个部分不一定需要对应三个 Python 类。对于小型脚本,三个函数就足够;对于多市场、多频率的采集项目,再考虑引入独立模块或服务。

架构设计的目标不是增加抽象层数,而是让变化局限在应该变化的地方。

四、什么时候才值得引入缓存、并发和任务调度?

一个常见误区是将扩展性问题等同于运行速度问题。

实际上,脚本可能很慢,但结构依然清晰;也可能运行得很快,却因为数据写入和请求逻辑耦合而难以维护。

是否引入额外机制,应根据实际瓶颈判断。

现象优先考虑的改进
相同历史数据被反复请求评估本地缓存和增量更新
一个标的失败导致整批任务退出增加任务级异常隔离
无法确定哪些标的更新失败记录每个任务的状态
请求逻辑和保存逻辑相互影响拆分数据获取与持久化
数据规模增大后内存压力明显评估分批处理与流式写入
请求耗时成为明确瓶颈测量实际耗时后,再评估并发
多个脚本重复实现相同规则提取经过验证的公共模块

特别是并发请求,不能只看任务执行时间。还需要考虑服务端限制、请求失败后的重试策略,以及批量任务对外部服务的压力。

同样,缓存也不是越多越好。如果研究任务要求特定时间点的数据快照,缓存更新策略就必须与数据时效性要求保持一致。

五、一个实用的重构判断标准

如果你的脚本已经出现以下情况中的多个,就值得安排一次针对性重构:

  • 新增一种行情类型,就必须修改多个不相关的函数。
  • 无法单独测试数据获取、数据检查和数据保存。
  • 任务显示成功,却无法确认数据是否完整。
  • 一个标的的异常会中断所有标的的处理。
  • 相同的请求逻辑在多个脚本中重复出现。
  • 修改保存方式时,必须同时修改接口调用。
  • 无法追溯某批数据来自哪次请求、经过哪些检查。

反过来,如果脚本只有一个明确任务、依赖很少、异常容易定位,而且新增需求很少,那么保留简单实现完全合理。

判断一个行情脚本是否需要扩展,不在于它现在有多少功能,而在于它还能否以可控的成本增加下一个功能。

先划清数据获取、数据校验和数据交付的边界,再根据实际瓶颈决定是否引入更复杂的工程机制。这样通常比一次性重写整个项目更稳妥。

QuantDash 官方文档

  • QuantDash 技术文档 — 查看 Python SDK、REST API 和数据接口说明。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进