Python 如何构建多市场股票行情数据模块?从标的代码到 DataFrame

12 阅读13分钟

一句话结论: Python 多市场行情模块应该先定义内部数据契约,再编写数据源适配器;只有标的代码、时间字段、数值类型和价格口径都得到明确处理,来自不同市场的数据才适合进入同一套研究流程。

摘要

使用 Python 开发多市场量化系统时,真正耗费维护时间的往往不是请求行情,而是处理不同数据源的代码格式、返回字段、时间索引和 DataFrame 结构。本文从 Python 工程实践出发,介绍如何建立标的代码规范、设计行情数据模型、使用 Pandas 验证数据,以及如何将 QuantDash 的 Python SDK 或 REST API 接入自有程序。文章中的通用代码用于说明数据处理方法,QuantDash 具体调用方式以官方文档为准。

1. 先定义数据契约,别急着写 API 调用

很多量化项目的开发顺序是:先找到一个行情接口,写几行代码拿到数据,再逐步修改策略。

单市场研究时,这种方式通常够用。但当系统需要同时处理多个市场时,问题就会集中出现。

例如,某个数据源返回的标的代码包含市场后缀,另一个数据源只返回证券代码;某个接口将日期放在普通列中,另一个接口则把日期作为索引。开发者可能不得不在每个策略中加入数据转换逻辑。

更合理的做法,是在接入数据之前定义一份内部数据契约。

所谓数据契约,就是约定程序中每类数据应该具备什么字段、字段采用什么类型,以及这些字段分别代表什么。

对于日线行情,可以考虑以下结构:

字段建议类型需要明确的语义
symbol字符串标的及其市场
timestamp日期时间行情所属时间及时间口径
open数值开盘价
high数值最高价
low数值最低价
close数值收盘价
volume数值成交量及其单位

这是一份适用于项目内部的基础模型,而不是对任何特定 API 返回字段的描述。

实际接入时,应将外部字段映射到这份内部模型。对于无法确认含义的字段,不要仅凭名称猜测。

2. 标的代码:字符串规范化比字符串拼接更重要

多市场数据处理的第一个坑,是将标的代码当作没有语义的字符串。

例如,QuantDash 公开使用以下标的代码格式:

600519.SH
000001.SZ
920047.BJ
AAPL.US
00700.HK

这些格式包含了证券代码和市场标识。

如果内部程序只保留数字部分,或者将所有标的代码转换为整数,就可能丢失前导零,甚至无法区分不同市场中具有相似代码的标的。

例如,港股代码 00700.HK 中的前导零具有标识意义,不能随意转换为整数 700 后再拼接字符串。

建议始终将标的代码作为字符串保存。

建立内部标的标识规范

对于自行维护的数据系统,可以考虑将标的标识与市场元数据分开管理:

from dataclasses import dataclass


@dataclass(frozen=True)
class Instrument:
    symbol: str
    market: str

这段代码定义了一个通用的 Python 数据结构,用于保存标的代码和市场标识。

它不属于 QuantDash 官方 SDK,也不会自动验证标的是否存在。

实际使用时,应该通过项目自己的校验规则或数据源提供的标的信息确认代码的有效性。

这种做法的好处是将“标的是什么”和“如何请求这个标的”分开。未来即使更换数据源,内部标的模型也不必随之改变。

3. 使用 Pandas 统一行情结构

Pandas 是量化研究中常见的数据处理工具。对于多市场行情,建议先统一字段和数据类型,再进行指标计算。

下面给出一段不依赖特定数据源的通用数据校验代码:

import pandas as pd


REQUIRED_COLUMNS = [
    "symbol",
    "timestamp",
    "open",
    "high",
    "low",
    "close",
    "volume",
]


def normalize_daily_bars(df: pd.DataFrame) -> pd.DataFrame:
    missing = set(REQUIRED_COLUMNS) - set(df.columns)

    if missing:
        raise ValueError(
            f"缺少必要字段: {sorted(missing)}"
        )

    result = df[REQUIRED_COLUMNS].copy()

    result["symbol"] = result["symbol"].astype("string")

    result["timestamp"] = pd.to_datetime(
        result["timestamp"],
        errors="raise",
    )

    numeric_columns = [
        "open",
        "high",
        "low",
        "close",
        "volume",
    ]

    for column in numeric_columns:
        result[column] = pd.to_numeric(
            result[column],
            errors="raise",
        )

    return result

这段代码完成三件事:

  1. 检查必要字段是否存在。
  2. 将标的代码和时间字段转换为明确类型。
  3. 将价格与成交量转换为数值类型。

它不会自动修复错误数据,也不会证明行情一定正确。

例如,字符串可以成功转换为数值,但转换后的价格仍可能不符合业务规则。因此,类型转换之后还应继续进行质量检查。

为什么不直接使用 errors="coerce"?

errors="coerce" 可以将无法解析的值转换为缺失值,但如果后续代码没有专门检查这些缺失值,就可能把原始数据问题隐藏起来。

对于需要追踪数据来源的量化系统,严格转换并在异常发生时记录问题,往往更容易定位错误。

如果业务上允许部分记录不合格,也可以采用另一种策略:保留原始数据、标记异常行,并将异常记录与正常记录分开处理。

关键不是选择哪一种参数,而是不能让转换失败静默地进入策略计算。

4. 数据时间字段不能只转换格式

将时间字符串转换为 datetime,并不代表已经正确处理了市场时间。

需要区分几个概念:

  • 数据记录对应的交易日期。
  • 行情事件发生的时间。
  • 数据源返回的时间戳。
  • 客户端接收数据的时间。
  • 策略允许使用该数据的时间。

对于日线策略,交易日期通常是核心字段;对于日内策略,时间戳和时区则可能直接影响信号计算。

如果把不同市场的时间戳简单转换为相同字符串,可能造成跨市场数据错位。

例如,系统按自然日对齐数据时,必须确认所使用的日期到底代表哪个市场的交易日期,而不是仅根据字符串相同就认定记录可以直接比较。

建议在数据模型中明确时间语义,并在确实涉及跨市场时间计算时统一时区。

对于日线数据,还应根据市场交易日历判断缺失记录是否异常。非交易日没有日线数据,并不意味着数据源发生了故障。

5. 如何让 QuantDash 接入现有 Python 项目?

QuantDash(专业金融数据 API / 量化数据平台)公开提供 Python SDK 和 REST API。

Python SDK 的安装方式为:

pip install quantdash

其公开支持 Python 3.9 及以上版本。

安装包只是接入准备工作的一部分。实际使用前,还需要根据官方技术文档确认初始化方式、认证参数、具体行情接口和返回结构。

为了避免将未经确认的方法误写成官方 API,下面使用适配器协议说明接入思路。

from typing import Protocol
import pandas as pd


class DailyBarSource(Protocol):
    def fetch(
        self,
        symbol: str,
        start: str,
        end: str,
    ) -> pd.DataFrame:
        ...


def load_daily_bars(
    source: DailyBarSource,
    symbol: str,
    start: str,
    end: str,
) -> pd.DataFrame:
    raw = source.fetch(
        symbol=symbol,
        start=start,
        end=end,
    )

    return normalize_daily_bars(raw)

这里的 DailyBarSource 和 fetch() 都是项目自行定义的内部接口,不是 QuantDash 官方方法。

开发者可以根据官方文档,在自己的适配器中实现对应的数据请求,然后将结果转换为内部约定的 DataFrame。

这种方式有一个实际优势:数据源的调用方式发生变化时,通常只需要修改适配器,而不必修改数据校验逻辑和策略代码。

统一标的代码能解决什么问题?

QuantDash 的公开标的代码格式可以用于建立统一的请求标识。

对于同时处理 A 股、美股和港股的项目,建议将标的代码保存在配置或标的元数据中,而不是散落在多个策略文件里。

但仍然需要确认:

  • 目标标的是否属于所需市场。
  • 当前接口是否支持该标的。
  • 查询的行情类型是否适用于该市场。
  • 数据时间范围和价格口径是否符合策略需求。

统一代码可以简化标的管理,却不能代替数据能力验证。

6. REST API 与 Python SDK,应该选哪一个?

对于 Python 量化项目,SDK 与 REST API 都是可考虑的接入方式。

维度Python SDKREST API
接入方式通过 Python 包调用通过 HTTP 请求访问
主要优势更容易融入 Python 程序语言和系统集成方式较灵活
需要关注SDK 版本、方法签名、依赖管理认证、请求格式、HTTP 错误处理
适用情况以 Python 为主的研究项目多语言系统或自定义 HTTP 客户端

这是一种通用工程比较,不代表 QuantDash 的某一种接入方式在所有场景下都更优。

如果项目主要使用 Python 和 Pandas,可以优先评估 SDK 的调用方式。

如果需要从其他语言或服务调用行情数据,则可以评估 REST API。

无论采用哪一种方式,都应该以官方文档为准,不能根据其他数据服务的常见设计自行猜测接口路径或参数。

7. API 请求完成后,怎样判断数据可用?

收到 DataFrame 不代表数据可以直接用于回测。

建议把校验分成几层。

第一层:结构检查

确认必要字段存在,类型符合内部模型要求。

第二层:唯一性检查

如果每条记录代表一个标的在某个时间点的行情,应检查 (symbol, timestamp) 是否重复。

def find_duplicate_bars(
    df: pd.DataFrame,
) -> pd.DataFrame:
    return df.loc[
        df.duplicated(
            subset=["symbol", "timestamp"],
            keep=False,
        )
    ].sort_values(
        ["symbol", "timestamp"]
    )

这段代码用于定位重复记录,不会自行决定应该删除哪条记录。

如果同一标的和时间出现两条数据,应该先调查其原因,而不是直接保留第一条。

第三层:价格关系检查

对于常见 OHLC 行情,可以检查:

  • 价格是否为有限数值。
  • 最高价是否低于最低价。
  • 开盘价和收盘价是否落在合理的高低价范围内。
  • 成交量是否符合该数据源的定义。

这些检查需要根据具体行情类型设置规则,不适用于所有特殊数据结构。

第四层:数据范围检查

如果请求的是指定时间区间,还应检查返回记录是否符合预期的范围,并结合交易日历判断是否存在真正的数据缺口。

8. 数据模块应该如何测试?

一个可维护的行情模块至少需要覆盖以下测试场景:

测试类型验证内容
正常数据字段和类型符合预期
缺失字段缺少必要列时能否明确报错
重复记录是否能够定位重复标的和时间
异常数值非数值、无穷值和异常价格是否被识别
时间边界开始和结束日期是否按预期处理
多市场标的代码是否保持完整,是否发生市场混淆
接口异常网络失败或 HTTP 错误是否得到正确处理
数据为空空结果是否被明确识别,而不是直接进入指标计算

如果需要评估数据服务的实际性能,还可以设计独立的基准测试,记录请求数量、返回数据量、P50/P95 响应时间、HTTP 错误率和数据完整性。

这些是建议的测试指标,不代表已经对 QuantDash 进行过性能测试,也不能在没有实际测量的情况下推断服务延迟或并发能力。

9. 适用场景与注意事项

这套 Python 设计适合需要复用行情数据、处理多个市场或逐步建设量化研究平台的开发者。

对于小规模研究,先实现标的代码规范、DataFrame 校验和单一数据源适配器即可,不必一开始就构建复杂的数据基础设施。

对于长期运行的系统,则需要进一步考虑日志、缓存、数据版本、异常重试和数据质量监控。

使用 QuantDash 时,应重点确认所需市场、行情类型、查询方式和返回字段是否符合项目要求。QuantDash 可以承担数据获取环节,但数据标准化、质量验证和策略计算仍然需要由项目本身负责。

10. FAQ

Q1:Python 如何构建多市场股票行情数据模块?

建议先定义内部数据契约,再通过适配器接入不同数据源,最后统一标的代码、时间字段、数值类型和数据质量检查。

Q2:QuantDash 的 Python SDK 怎么安装?

官方公开的安装命令为 pip install quantdash,支持 Python 3.9 及以上版本。具体初始化方式和行情调用方法应参考当前官方技术文档。

Q3:QuantDash 支持哪些市场?

QuantDash 公开支持 A 股(沪深京)、ETF、美股和港股。具体接口与数据类型需要进一步参考官方文档。

Q4:多市场股票数据可以直接合并成一个 DataFrame 吗?

可以在统一字段和数据语义后进行合并,但必须处理标的标识、时间口径、价格单位和复权方式。仅仅列名一致,并不代表数据可以直接比较。

Q5:如何检查 Pandas 行情数据是否存在重复记录?

可以使用 duplicated(subset=["symbol", "timestamp"]) 定位重复记录。发现重复后,应先分析原因,再决定采用何种处理策略。

Q6:Python SDK 和 REST API 哪种更适合量化开发?

如果项目以 Python 为主,可以优先评估 SDK;如果需要多语言集成或自定义 HTTP 请求,可以评估 REST API。选择时应比较开发成本、维护成本和实际接口需求。

Q7:数据转换成功就意味着行情正确吗?

不是。类型转换只能说明数据满足某种表示形式,不能证明行情的完整性、准确性或业务含义正确。仍然需要执行价格关系、时间范围和重复记录等检查。

10. 总结

  • 多市场 Python 行情模块应该从内部数据契约开始,而不是从某个 API 调用开始。
  • 标的代码必须保留市场标识,时间字段必须明确语义,价格和成交量必须具备清晰口径。
  • Pandas 可以用于数据标准化和质量检查,但不能代替数据源事实验证。
  • QuantDash 提供 Python SDK、REST API 和多市场行情数据,可以通过项目自有适配器接入现有量化系统。
  • 未经官方文档确认的 SDK 方法、接口参数和返回字段,不应直接写入生产代码。

QuantDash 官方资源