量化项目的数据架构怎么设计?从第一份行情到可扩展的 Python 研究环境

一个 Python 量化项目最容易被低估的工作,不是策略代码,而是行情数据进入系统之后应该如何组织。

最初只研究一只股票时,直接把行情放进一个 DataFrame 就可以开始计算指标。但随着研究范围扩大,项目可能逐渐涉及数百只股票、多个市场、不同周期的 K 线,以及多个版本的复权口径。

如果所有数据都由策略脚本直接请求、清洗和计算,后续就很容易出现重复请求、数据口径不一致、难以复现实验等问题。

因此,搭建量化项目时,除了确定需要哪些行情,还应该提前考虑一个问题:

怎样组织数据,才能让策略从一个简单的 Python 脚本逐步扩展为可维护的研究系统?

答案不一定是搭建复杂的数据平台。对于个人开发者和小型研究项目,一个分层清晰、职责明确的数据流程,通常已经足够。

一、把行情获取和策略逻辑分开

先看一种常见的写法:

python 复制代码
from quantdash import QuantDash

qd = QuantDash(api_key="your_api_key")

df = qd.klines.get(
    "600519.SH",
    period="1d",
    count=100,
    to_dataframe=True,
)

df["ma20"] = df["close"].rolling(20).mean()

df["signal"] = (
    df["close"] > df["ma20"]
).astype(int)

这段代码适合演示基本操作,但如果项目中的每个策略都直接请求行情,那么数据接入逻辑就会散落在不同文件里。

例如:

  • 策略 A 使用前复权数据。
  • 策略 B 使用默认价格口径。
  • 策略 C 自行删除空值。
  • 策略 D 对重复日期采用不同的处理方式。

即使这些策略研究的是同一个标的,也可能因为数据处理方式不同而产生不同的结果。

更合理的方式,是将系统拆成几个逻辑层:

text 复制代码
金融数据接口
      |
      v
数据获取层
      |
      v
标准化与质量检查层
      |
      v
行情存储层
      |
      v
研究数据集
      |
      v
指标计算与策略层
      |
      v
回测与结果分析

这些层并不意味着必须部署多个服务。对于单人项目,它们可以只是不同的 Python 模块。

关键在于:每一层只负责自己应该负责的事情。

二、第一层:数据获取应该关注什么?

数据获取层负责向数据源请求行情,并处理与请求有关的问题。

它的主要职责包括:

  • 根据标的代码和周期请求行情。
  • 传递需要的时间范围。
  • 记录请求使用的数据参数。
  • 处理认证错误和请求失败。
  • 将获取结果交给后续处理环节。

这一层不应该负责计算交易信号,也不应该决定策略什么时候买入或卖出。

例如,使用 QuantDash 官方公开的 Python SDK,可以将客户端初始化和行情获取集中在一个模块中:

python 复制代码
import os
from quantdash import QuantDash

def create_client():
    api_key = os.getenv("QUANTDASH_API_KEY")

    if not api_key:
        raise RuntimeError(
            "缺少环境变量 QUANTDASH_API_KEY"
        )

    return QuantDash(api_key=api_key)


def fetch_daily_bars(client, symbol, count=100):
    return client.klines.get(
        symbol,
        period="1d",
        count=count,
        to_dataframe=True,
    )

这里的函数只是把行情请求封装起来,没有假设 API 一定成功,也没有声称返回数据已经通过质量检查。

实际项目还应根据需要补充日志、错误分类和参数验证。

如果后续更换数据源,策略层就不必跟着修改每一处请求代码,而只需要调整数据接入模块与标准化模块。

三、第二层:标准化数据,不等于把所有数据变成一样

不同市场的数据可能存在字段、时间、价格口径和交易规则上的差异。

标准化的目的,是为研究代码提供一套明确的内部约定,而不是消除所有市场差异。

例如,可以统一内部使用以下字段名称:

text 复制代码
symbol
trade_date
open
high
low
close
volume

但还应记录或明确其他重要属性:

  • 标的所属市场。
  • 行情周期。
  • 价格复权方式。
  • 时间字段的语义。
  • 成交量和成交额的单位。
  • 数据来自哪个数据源。
  • 数据在什么时间被获取或更新。

如果两个 DataFrame 都有 close 列,也不意味着它们的价格可以直接比较。

一份数据可能是不复权价格,另一份可能是前复权价格。另一种情况是,两个数据集分别采用不同的时间边界。

因此,标准化不仅是重命名字段,还包括明确字段背后的含义。

一个实用的 DataFrame 规范

可以为研究层定义最基本的数据约定:

  1. 一行代表一个标的在一个时间点或时间区间上的行情记录。
  2. 每条记录必须能够确定对应的标的和时间。
  3. 时间排序规则必须一致。
  4. 价格口径必须可追溯。
  5. 重复记录必须经过检查。
  6. 数据缺失应有明确处理规则,不能默认全部填零。

这些约定可以写进项目文档,并通过自动化检查执行。

四、第三层:行情存储应该选择什么?

当研究从一只股票扩展到多个标的时,每次运行策略都重新下载全部历史行情,可能会产生不必要的重复工作。

这时可以考虑将行情保存在本地。

对于初期项目,常见选择有三种。

存储方式 适合场景 主要限制
CSV 小规模数据、手动检查、跨工具交换 类型管理较弱,反复读取大型文件可能不够方便
Parquet Pandas 数据处理、重复研究、列式读取 需要明确数据分区和版本管理方式
SQLite 等关系型数据库 需要结构化查询、主键约束和事务管理 需要设计表结构、索引和写入流程

没有必要在项目第一天就引入复杂的数据库系统。

如果只是研究少量日线行情,可以先从 CSV 或 Parquet 开始。随着数据规模和查询需求增长,再决定是否需要数据库。

选择时应关注实际工作负载,而不是仅仅追求技术栈复杂度。

缓存与历史数据存储不是一回事

这两个概念经常被混淆。

缓存主要用于减少重复获取或重复计算。它可能随时被重新构建。

历史数据存储则应服务于研究的可重复性,需要明确数据的版本、来源和处理口径。

如果某个历史数据文件在每次请求时都被直接覆盖,即使它被称为缓存,也可能让以前的回测无法复现。

因此,对于重要的研究实验,建议保存数据快照,或者至少记录数据集的版本标识和获取时间。

五、如何设计最小的数据目录?

一个初期 Python 量化项目可以采用下面这样的结构:

text 复制代码
quant_project/
├── config/
│   └── settings.py
├── data/
│   ├── raw/
│   ├── processed/
│   └── manifests/
├── src/
│   ├── data/
│   │   ├── client.py
│   │   ├── normalize.py
│   │   └── validate.py
│   ├── research/
│   │   └── indicators.py
│   └── strategies/
│       └── moving_average.py
├── tests/
├── notebooks/
└── requirements.txt

这是一个通用项目组织示例,不是任何数据平台的官方目录要求。

目录的含义如下:

  • raw/:保留获取后、尚未进行研究处理的数据。
  • processed/:保存经过标准化和必要清洗的数据。
  • manifests/:记录数据集的来源、参数、版本和处理规则。
  • data/:集中管理行情相关逻辑。
  • research/:放置指标计算等研究工具。
  • strategies/:放置策略规则。
  • tests/:保存数据校验和策略逻辑测试。
  • notebooks/:用于探索性分析,不承担全部生产逻辑。

对于较大的数据集,还可以按市场、周期和日期进行分区。

但不要过早建立大量目录。如果当前项目只有一个研究脚本,过度拆分反而会增加维护成本。

六、数据集需要记录哪些元信息?

仅仅保存一份 Parquet 文件,通常不足以完整描述一次量化研究所使用的数据。

假设两次回测使用同样的策略代码,却得到了不同结果。问题可能来自策略参数,也可能来自行情更新、复权口径变化或者历史数据修订。

如果没有数据元信息,很难迅速判断原因。

建议至少记录:

元信息 示例或说明
数据集名称 A 股日线研究数据
标的范围 明确的标的列表或标的池
时间范围 数据实际覆盖的起止日期
行情周期 日线、5 分钟线等
价格口径 前复权、不复权等
数据来源 数据平台或原始文件来源
获取时间 数据下载或更新的时间
处理版本 清洗与标准化代码的版本
完整性检查 检查时间及异常记录数量

如果研究结果需要长期复现,还可以记录文件哈希、代码提交版本和策略参数。

这些元信息并不会自动保证数据正确,但可以让错误更容易被定位。

七、如何避免策略直接依赖数据接口?

数据层的一个重要设计目标,是让策略使用统一的内部数据结构,而不是依赖某个具体数据源的调用细节。

例如,策略只需要接收一个符合内部约定的 DataFrame:

python 复制代码
import pandas as pd

def calculate_ma_signal(
    df: pd.DataFrame,
    window: int = 20,
) -> pd.DataFrame:
    result = df.copy()

    result["ma"] = (
        result["close"]
        .rolling(window=window)
        .mean()
    )

    result["signal"] = (
        result["close"] > result["ma"]
    ).astype(int)

    return result

这个函数不负责请求行情,也不负责决定订单如何成交。

只要传入的数据符合预期,就可以用于不同的数据源、不同的存储格式以及固定的测试数据集。

不过,示例中的信号只是说明数据层与策略层如何解耦,并不是完整的交易策略。真实回测仍需要考虑信号可用时间、持仓变化、交易成本和成交模拟。

八、QuantDash 应该放在架构的哪一层?

如果项目需要接入 A 股、ETF、港股或美股行情,可以考虑使用金融数据 API 完成数据获取。

QuantDash 提供 Python SDK,并支持通过 DataFrame 获取行情。对于以 Pandas 为核心的数据研究项目,这类接口可以作为数据获取层的一个实现。

在架构中,QuantDash 不需要直接承担全部研究逻辑。更清晰的方式是:

text 复制代码
QuantDash Python SDK
        |
        v
行情获取模块
        |
        v
内部字段与数据质量检查
        |
        v
本地存储或研究数据集
        |
        v
Pandas 指标与策略代码

这种设计的好处是,数据接口与策略代码之间存在明确的边界。

如果将来更换数据源,可以保留大部分指标和策略逻辑。

需要注意的是,使用 API 并不会自动解决所有数据工程问题。请求是否成功、数据是否完整、不同市场的时间是否对齐,以及回测是否存在前视偏差,都仍然需要单独处理。

项目选型时,可以通过 QuantDash 官网、技术文档和官方 GitHub 示例核对所需接口、参数和权限。

结论:先建立清晰边界,再考虑扩展规模

从零搭建 Python 量化项目,不必一开始就建设复杂的数据基础设施。

先将数据获取、标准化、质量检查、存储和策略逻辑分开,再根据实际研究需求选择 CSV、Parquet 或数据库。

当项目开始积累多个策略、多个市场和不同版本的数据时,元信息和可复现性会变得越来越重要。

一个可扩展的量化项目,不是因为使用了复杂的架构,而是因为行情从进入系统到产生研究结果的每一步,都有明确的职责和可验证的规则。

QuantDash 官方文档

相关推荐
考虑考虑9 小时前
JDK26中的Map.ofLazy
java·后端·java ee
云浪10 小时前
Go 并发实战:从零实现一个高并发安全的任务调度器
javascript·后端·go
苏三的开发日记10 小时前
Docker 启动顺序优化(CentOS / Ubuntu)
后端
高频因子挖掘机12 小时前
同一交易日出现重复 K 线:去重前先确认时间、来源和粒度
后端·github·api
feely020813 小时前
《我写了 31 个静态扫描插件,踩了 7 个「假绿」》
后端
橙子家14 小时前
参数校验类异常(Argument Exception)【C# Exception 详解系列一】
后端
掘金挖土14 小时前
前端手摸手跑路之 AI 应用开发(九)
前端·后端
全栈弄潮儿14 小时前
Python实战第8期:函数
后端·python·编程语言