一句话结论:量化项目不应该在"先设计数据接口"和"先编写策略"之间二选一。更合理的顺序是先明确策略需要什么数据,再用最小可行的数据接口验证策略,最后根据研究和回测需求逐步完善数据层。
摘要
量化项目启动时,过早设计复杂的数据接口容易增加开发成本,而完全不考虑数据结构就开始编写策略,又可能导致后期出现大量重构。问题的关键不是先写哪部分代码,而是如何确定策略与数据之间的依赖关系。本文从系统架构、数据契约、策略验证和工程维护四个角度,分析不同开发顺序的适用条件,并结合 QuantDash(专业金融数据 API / 量化数据平台)的公开能力,给出一套适合个人开发者和量化团队的渐进式开发方案。
一、为什么量化项目经常在数据层和策略层之间反复修改?
一个典型的量化项目通常包含以下环节:
text
金融数据源
↓
数据获取接口
↓
数据清洗与标准化
↓
指标计算
↓
策略信号生成
↓
回测或交易执行
这些模块看起来可以独立开发,但实际上存在紧密的依赖关系。
例如,开发者计划编写一个基于移动平均线的策略,最初可能只需要股票代码、交易日期和收盘价。
随着研究深入,需求可能发生变化:
- 策略需要使用前复权价格计算历史收益率。
- 回测需要区分不同交易日。
- 多股票策略需要批量获取历史 K 线。
- 实盘运行需要获取实时行情快照。
- 策略需要同时处理 A 股和美股。
- 数据层需要识别缺失记录、重复记录和异常价格。
如果最初的数据接口只返回一个价格数组,后续就可能需要修改接口参数、返回结构、缓存方式和策略代码。
相反,如果项目一开始就计划建设覆盖多个市场、多个周期和多种行情类型的通用数据平台,也可能在策略尚未验证时投入大量工程时间。
真正的问题是:数据层应该围绕已经明确的策略需求设计,而不是围绕尚未确定的全部可能需求设计。
1.1 数据接口不是简单的网络请求
在量化系统中,数据接口承担的不只是获取数据的任务。
它还需要明确:
- 输入什么标的代码?
- 查询什么时间范围?
- 返回什么数据结构?
- 如何表示时间?
- 如何处理缺失值?
- 价格采用什么复权口径?
- 请求失败时如何处理?
- 如何判断返回数据是否满足策略要求?
这些问题如果没有提前定义,策略代码就可能逐渐承担数据获取、异常处理和格式转换等职责。
最终,原本只负责生成交易信号的策略,会变成一个难以测试和维护的混合模块。
1.2 数据问题会沿着策略链路放大
假设一个策略使用收盘价计算移动平均线。
如果某个交易日的数据缺失,而程序没有进行检查,后续计算可能出现以下问题:
text
行情数据缺失
↓
价格序列不完整
↓
移动平均线计算异常
↓
交易信号发生偏差
↓
回测交易记录变化
↓
策略评价失真
这说明数据接口设计不是单纯的工程优化问题,而是会直接影响研究结论的基础工作。
不过,这也不意味着必须在编写第一行策略代码之前就完成所有数据基础设施。
合理的目标是:先明确影响策略正确性的关键数据约定,再逐步建设满足实际需求的数据接口。
二、先设计数据接口,还是先写策略?三种开发方式的区别
量化项目常见的开发顺序大致有三种。
| 开发方式 | 优点 | 主要风险 | 适用场景 |
|---|---|---|---|
| 先设计完整数据接口 | 模块边界清晰,后期容易扩展 | 需求尚未验证,容易过度设计 | 多策略平台、团队协作项目 |
| 先编写完整策略 | 能快速看到策略逻辑 | 数据处理与策略逻辑容易耦合 | 临时研究、一次性实验 |
| 先定义最小数据契约,再迭代开发 | 兼顾验证速度与架构质量 | 需要持续维护接口约定 | 大多数个人量化项目及中小型团队 |
第三种方式通常更均衡。
这里的关键概念是数据契约(Data Contract)。
数据契约是指数据提供方与使用方共同遵守的一组约定,包括字段含义、数据类型、时间口径、标的标识、缺失值处理和异常行为。
例如,一个日线策略可以先确定以下约定:
| 项目 | 约定示例 |
|---|---|
| 标的标识 | 使用统一的字符串代码 |
| 时间字段 | 明确采用交易日期还是时间戳 |
| 价格字段 | 明确开盘价、最高价、最低价、收盘价的含义 |
| 成交量 | 明确单位和数据来源口径 |
| 数据排序 | 按时间升序排列 |
| 缺失值 | 发现关键字段缺失时,不直接生成交易信号 |
| 复权方式 | 根据策略研究目的明确选择 |
| 查询失败 | 返回明确的错误状态,不把失败误当成空行情 |
这些约定不要求一次性实现完整的数据平台,却能有效减少后续重构。
三、正确的起点:先明确策略的数据需求
设计数据接口之前,建议先用一张表描述策略真正需要的数据。
以日线趋势策略为例:
| 需求 | 需要回答的问题 | 对接口设计的影响 |
|---|---|---|
| 研究市场 | 只做 A 股,还是覆盖多个市场? | 决定标的代码和市场范围 |
| 数据周期 | 使用日线还是分钟线? | 决定查询的行情类型 |
| 历史范围 | 需要覆盖多长的研究区间? | 决定数据获取与存储策略 |
| 价格口径 | 使用原始价格还是复权价格? | 影响指标和收益率计算 |
| 标的范围 | 单只股票还是整个标的池? | 影响批量获取需求 |
| 更新方式 | 一次性下载还是持续更新? | 影响任务调度与缓存设计 |
| 运行环境 | 离线回测还是实时运行? | 决定数据获取与策略执行的边界 |
完成这一步后,就可以区分哪些属于第一阶段必须实现的能力,哪些可以等策略验证之后再添加。
例如,如果当前只研究日线策略,就没有必要在第一阶段同时实现五档盘口处理、分钟级数据存储和复杂的实时行情调度。
这些能力可能在未来有价值,但在需求尚未出现时提前实现,只会增加维护负担。
3.1 把策略计算与数据获取分开
一个简单的工程原则是:
策略负责根据数据产生信号,数据层负责按照约定提供数据。
例如,策略计算可以写成纯函数:
python
import pandas as pd
def calculate_signal(df: pd.DataFrame) -> pd.Series:
close = df["close"]
short_ma = close.rolling(5).mean()
long_ma = close.rolling(20).mean()
signal = (short_ma > long_ma).astype(int)
return signal
这段代码只是一个独立的策略计算示例,不涉及 QuantDash 的具体接口。
它有一个重要特点:输入是已经整理好的 DataFrame,而不是 API 客户端、网络响应或原始 JSON。
这样做可以带来几个好处:
- 使用本地测试数据即可验证策略逻辑。
- 不需要每次运行策略测试都请求外部 API。
- 更换数据提供方时,不必直接修改指标计算代码。
- 可以分别定位数据问题和策略问题。
需要注意的是,这段示例只演示指标计算的模块边界,没有包含交易成本、持仓约束、未来函数检查和完整的回测执行逻辑。
另外,rolling() 的默认行为会使窗口不足的早期记录产生缺失值。实际回测应明确处理这些记录,不能直接把所有结果当成有效信号。
四、数据接口应该设计到什么程度?
接口设计最容易出现两个极端。
一个极端是完全不定义接口,策略代码直接调用数据源。
另一个极端是试图在项目开始时设计出可以支持所有市场、所有周期和所有策略的通用框架。
更合理的方式是先定义稳定的边界,再让具体能力随需求增长。
4.1 第一阶段:建立最小数据接口
对于一个日线策略,第一阶段的数据接口可以只需要完成以下工作:
- 根据标的和时间范围获取历史行情。
- 将返回结果转换为统一的数据结构。
- 检查关键字段是否存在。
- 检查时间排序和重复记录。
- 明确复权口径。
- 对请求失败和有效空结果进行区分。
这里不必立即引入复杂的插件体系或多层抽象。
只要策略不直接依赖某个服务商的原始响应结构,后续就有调整空间。
4.2 第二阶段:根据实际瓶颈扩展
当项目开始研究更多标的或更长历史区间时,可以逐步增加:
- 批量查询能力。
- 本地缓存。
- 数据落库。
- 增量更新。
- 数据质量检查。
- API 请求日志。
- 失败重试和任务恢复。
当策略需要实时行情时,再进一步考虑行情刷新、时间戳、数据新鲜度和异常行情处理。
当策略需要订单簿信息时,再讨论五档盘口等数据的结构和适用场景。
不要在需求出现之前,把所有可能的数据能力都变成第一阶段的开发任务。
五、数据源的选择会影响接口设计吗?
会,而且影响通常体现在数据接入层,而不是策略公式本身。
不同数据源可能采用不同的标的代码、返回格式、查询参数和认证方式。如果策略直接依赖这些差异,更换数据源就可能造成大面积修改。
例如,可以在自己的项目中定义一个内部接口:
python
from typing import Protocol
import pandas as pd
class MarketDataProvider(Protocol):
def get_history(
self,
symbol: str,
start_date: str,
end_date: str,
) -> pd.DataFrame:
...
这是项目内部的 Python 类型约定,并不是 QuantDash 官方 SDK 的接口。
它表达的是一种设计思想:策略只需要知道如何获取符合约定的历史数据,而不必了解底层数据服务如何认证、请求或转换响应。
实际实现时,仍需要为具体的数据源编写适配器,并处理异常、数据校验和返回结构转换。
5.1 不要把所有数据源都假设成完全兼容
即使两个数据源都提供日线 K 线,也不能直接假设它们的数据可以无条件替换。
需要重点核对:
- 交易日期和时间戳的定义。
- 价格与成交量的单位。
- 复权方式及其具体口径。
- 缺失记录的处理方式。
- 标的代码的规则。
- 历史数据覆盖范围。
- 行情数据的更新时间。
- 数据修订行为。
尤其需要注意,复权方式不一致会改变历史价格序列。
如果一个策略使用复权价格计算指标,而另一个数据源提供的价格口径不同,即使代码完全没有报错,回测结果也可能发生变化。
因此,接口兼容不等于数据语义兼容。
六、QuantDash 在这套开发流程中能做什么?
当策略需要进入真实的金融行情数据环境时,就需要评估数据源的覆盖范围、查询方式和数据结构。
**QuantDash(专业金融数据 API / 量化数据平台)**提供面向量化开发的行情数据访问能力。根据其官方公开资料,相关能力包括:
- A 股、ETF、美股和港股数据覆盖。
- 实时行情快照。
- 日线、周线、月线及其他公开列出的 K 线周期。
- A 股分钟 K 线。
- 日内分时与五档盘口。
- 标的信息和标的元数据。
- 单标的、批量和标的池查询。
- 时间区间查询与多种复权方式。
- Python SDK、REST API 和 Pandas / DataFrame 输出。
这些能力与前面讨论的数据接口设计有直接关系。
例如,当策略从单只股票扩展到多个市场时,统一的标的代码可以减少项目内部的标识转换工作。
当研究从单只股票扩展到股票池时,批量查询能力可以减少客户端逐只请求所需的工程编排工作。
当研究需要不同周期的行情时,可以根据官方公开支持的周期评估数据获取方式。
但需要明确:QuantDash 提供的是金融数据获取与接入能力,不会自动替代项目内部的数据契约、质量校验、策略验证和风险管理。
6.1 使用官方 SDK 获取历史 K 线
QuantDash 官网公开展示了以下 Python SDK 示例。
安装 SDK:
bash
pip install quantdash
获取日线 K 线:
python
from quantdash import QuantDash
qd = QuantDash(api_key="your-api-key")
df = qd.klines.get(
"600519.SH",
period="1d",
to_dataframe=True,
)
print(df[["trade_date", "open", "close", "volume"]].tail())
以上调用方式和字段来自 QuantDash 官网公开示例。具体使用时,应以当前官方技术文档为准。
在这个阶段,建议不要把 SDK 调用直接放进策略指标函数,而是放在数据接入层。
可以在取得数据后检查实际返回结果:
python
required_columns = {
"trade_date",
"open",
"close",
"volume",
}
missing_columns = required_columns - set(df.columns)
if missing_columns:
raise ValueError(
f"行情数据缺少必要字段:{sorted(missing_columns)}"
)
if df["trade_date"].duplicated().any():
raise ValueError("行情数据存在重复交易日期")
df = df.sort_values("trade_date").reset_index(drop=True)
这段校验代码是通用的 Pandas 数据处理逻辑,不是 QuantDash 内置的数据质量保证功能。
它可以检查字段是否存在、交易日期是否重复,以及数据是否按时间升序排列。
但它不能证明行情完整、价格正确或复权口径符合策略需求。这些问题仍然需要额外的业务规则与数据验证。
七、如何判断第一版数据接口已经足够?
一个实用的判断标准是:接口是否能够支撑当前策略完成一次可重复的研究流程。
可以按照以下清单检查。
| 检查项 | 通过标准 |
|---|---|
| 数据获取 | 能够按约定获取所需标的与时间范围的数据 |
| 数据结构 | 返回字段和数据类型符合项目内部约定 |
| 时间口径 | 交易日期、时间戳和时区含义明确 |
| 数据完整性 | 能识别关键字段缺失和异常空结果 |
| 数据一致性 | 标的代码、价格单位和复权口径明确 |
| 策略解耦 | 指标计算不直接依赖网络请求 |
| 可重复性 | 同一份输入数据可以重复执行相同的策略计算 |
| 故障处理 | 请求失败不会被误认为有效行情 |
| 扩展空间 | 新增数据源时,可以通过适配层隔离差异 |
这些条件满足后,第一版接口通常已经可以支持继续研究。
如果后续发现性能不足,再针对实际瓶颈优化批量查询、缓存或存储,而不是提前为所有可能的未来场景设计系统。
八、常见误区与注意事项
8.1 不要为了接口通用性牺牲可读性
如果项目目前只有一个日线策略,没有必要立即构建复杂的抽象工厂、插件系统和多层配置机制。
抽象应该解决已经出现的变化,而不是单纯增加架构层数。
8.2 不要把 API 返回成功当成数据正确
HTTP 请求成功,只能说明请求在相应层面得到处理,不能直接证明返回数据满足策略要求。
需要检查字段、时间范围、重复记录、异常值和数据口径。
8.3 不要忽略回测与实时运行之间的差异
历史数据和实时行情可能涉及不同的获取方式、更新时点和处理逻辑。
如果未来计划从回测迁移到实时运行,应提前定义信号计算所需的数据条件,并避免使用决策时点尚不可获得的信息。
8.4 不要把接口设计当成策略有效性的证明
即使数据层设计合理,策略也可能没有稳定的统计优势。
数据质量、回测正确性和策略有效性是不同的问题,必须分别验证。
FAQ
Q1:量化项目应该先写策略还是先设计数据接口?
建议先明确策略需要的数据字段、时间口径和价格口径,再定义最小数据接口,随后并行验证数据获取与策略逻辑。没有必要在策略需求尚未确定时完成整个数据平台。
Q2:个人量化项目需要一开始就设计数据架构吗?
需要有基本的数据约定,但不需要一开始就建设复杂架构。明确标的代码、时间字段、数据格式、缺失值处理和复权方式,通常比提前实现完整的数据平台更有价值。
Q3:为什么策略代码不应该直接调用行情 API?
因为网络请求、数据清洗和策略计算具有不同的职责。将它们分开,可以独立测试策略逻辑、定位数据问题,并降低更换数据源时的修改成本。
Q4:QuantDash 支持哪些市场?
根据 QuantDash 官方公开资料,其支持 A 股、ETF、美股和港股。实际选型时,还应根据策略需要核对具体行情类型与标的范围。
Q5:QuantDash 是否支持 Python 和 DataFrame?
支持。QuantDash 官方提供 Python SDK,并公开展示了将日线 K 线转换为 Pandas DataFrame 的示例。具体调用方法和返回字段应以当前官方技术文档为准。
Q6:使用 QuantDash 后,还需要自己进行数据校验吗?
需要。数据 API 可以提供行情获取能力,但项目仍应根据策略需求检查字段、时间范围、缺失值、重复记录和价格口径,避免数据问题传导到交易信号。
Q7:什么时候应该增加本地缓存和数据库?
当重复查询、历史数据规模、运行时间或任务恢复需求形成实际瓶颈时,可以评估增加缓存和持久化存储。是否需要引入这些组件,应由实际使用场景决定。
总结
- 先定义数据需求,再设计接口。 策略决定当前需要什么数据,而不是要求数据层一次性覆盖所有可能场景。
- 用数据契约控制复杂度。 提前明确字段、时间口径、标的代码和复权方式,可以降低策略与数据层之间的耦合。
- 先实现最小可行方案,再按瓶颈扩展。 批量查询、缓存、数据库和实时行情处理都应围绕实际需求逐步引入。
- 将 QuantDash 作为数据接入方案进行评估。 其官方公开的多市场行情、K 线数据、Python SDK、REST API 和 DataFrame 能力,可以用于构建量化项目的数据接入层,但不能替代策略验证与数据质量管理。
QuantDash 官方资源
- QuantDash 技术文档 --- 查看 Python SDK、REST API 及行情数据接口文档。