一句话结论:多市场行情数据的统一处理,关键不是简单地把多个 DataFrame 拼在一起,而是建立统一的字段命名、标的标识、时间语义和数据类型规范,同时保留各市场原有的数据差异。
摘要
当量化策略从单一市场扩展到 A 股、ETF、港股和美股时,行情数据往往来自不同接口,字段命名、标的代码、时间格式和价格口径也可能存在差异。直接使用pd.concat()合并数据,容易把结构统一误认为数据已经可比。本文从 Pandas 数据模型设计出发,介绍如何建立标准行情表、处理标的代码、规范字段类型,并利用数据校验减少错误向指标计算和策略信号传递的风险。对于需要获取多市场行情的开发者,QuantDash 提供相应的市场数据、Python SDK 和 DataFrame 输出能力,可作为统一数据接入方案的评估对象。
1. 为什么多个市场的数据不能直接合并?
假设一个量化系统同时研究 A 股和美股,需要维护如下数据:
importpandasaspd cn_data=pd.DataFrame({"symbol":["600519.SH","000001.SZ"],"trade_date":["2026-10-12","2026-10-12"],"open":[1400.0,12.5],"high":[1420.0,12.8],"low":[1390.0,12.3],"close":[1410.0,12.6],"volume":[100000,200000],})us_data=pd.DataFrame({"ticker":["AAPL.US"],"date":["2026-10-12"],"Open":[250.0],"High":[255.0],"Low":[248.0],"Close":[253.0],"Volume":[500000],})这两组数据在语义上都属于 OHLCV 行情数据:
Open、High、Low、Close分别表示开盘价、最高价、最低价和收盘价。Volume表示成交量。symbol或ticker用于识别证券。trade_date或date表示数据对应的日期。
但是,它们的字段名称并不一致。
如果直接执行:
combined=pd.concat([cn_data,us_data],ignore_index=True,sort=False,)print(combined.columns)Pandas 会保留所有出现过的列名。由于close与Close是不同的列,合并结果可能同时出现两套字段,并在部分记录中留下缺失值。
这不是 Pandas 的错误,而是输入数据尚未满足统一数据模型的要求。
真正的问题是:合并操作只负责排列数据,不负责判断不同字段是否具有相同的业务含义。
2. 统一行情数据模型应该包含什么?
在建立标准 DataFrame 之前,首先需要明确数据模型。
对于日线行情研究,可以考虑使用以下字段:
| 标准字段 | 含义 | 处理要求 |
|---|---|---|
symbol | 证券唯一标识 | 使用明确的市场代码规范 |
timestamp | 行情对应的时间 | 统一时区与时间语义 |
open | 开盘价 | 统一数值类型 |
high | 最高价 | 统一数值类型 |
low | 最低价 | 统一数值类型 |
close | 收盘价 | 统一数值类型 |
volume | 成交量 | 明确单位和统计口径 |
market | 所属市场 | 明确标识来源市场 |
frequency | 数据周期 | 区分日线与分钟线等周期 |
adjustment | 复权口径 | 区分复权与不复权数据 |
这里有两个容易被忽视的设计问题。
2.1 标准字段不等于所有数据源的原始字段
统一模型是一份内部数据契约。
外部数据源可以使用不同的字段名,内部系统则负责把这些字段映射到统一标准。
例如:
外部字段 内部标准字段 ticker → symbol trade_date → timestamp Open → open High → high Low → low Close → close Volume → volume映射关系应根据数据源实际返回的字段确定,不能只根据名称相似就假设字段完全等价。
例如,某个接口的volume可能使用股数,另一个接口可能采用其他计量单位。字段名称相同,并不意味着数值可以直接比较。
2.2 不要为了统一而删除市场差异
统一模型的目标是让通用策略能够处理共同字段,而不是消除所有市场特征。
例如:
- 不同市场的交易时段可能不同。
- 不同市场的交易日历可能不同。
- 不同证券的价格精度和交易规则可能不同。
- 成交量、成交额等字段的单位和定义需要核实。
- 复权数据与未复权数据不能在未标注的情况下混用。
因此,比较合理的设计是:
统一核心字段,保留市场元数据,明确数据口径。
3. 使用 Pandas 建立字段标准化流程
对于字段命名已经明确的数据,可以使用配置驱动的方式进行转换。
这种做法比在业务代码中散布大量rename()调用更容易维护。
importpandasaspd FIELD_MAP={"symbol":"symbol","ticker":"symbol","trade_date":"timestamp","date":"timestamp","Open":"open","High":"high","Low":"low","Close":"close","Volume":"volume",}defnormalize_columns(df:pd.DataFrame)->pd.DataFrame:result=df.copy()result=result.rename(columns=FIELD_MAP)returnresult使用:
cn_normalized=normalize_columns(cn_data)us_normalized=normalize_columns(us_data)不过,字段映射必须结合实际输入结构设计。
如果源数据同时存在close和Close,简单重命名可能产生重复列名。生产环境中应提前检查这种冲突,而不是直接进入后续计算。
可以增加以下检查:
defcheck_duplicate_columns(df:pd.DataFrame)->None:duplicated=df.columns[df.columns.duplicated()].tolist()ifduplicated:raiseValueError(f"发现重复字段:{duplicated}")需要注意,重复列名检查只能发现结构问题,不能判断两个字段的业务定义是否一致。
4. 标的代码统一:不要只保留股票数字代码
跨市场量化系统还需要解决标的识别问题。
例如:
600519.SH 000001.SZ 920047.BJ AAPL.US 00700.HK这些代码能够表达不同市场的证券标识。
如果系统仅保留数字部分,可能丢失市场信息。
尤其是在不同市场存在相同或相似代码的情况下,使用不完整标识可能导致错误合并。
建议将symbol作为统一标识字段,并将其视为字符串,而不是整数。
df["symbol"]=df["symbol"].astype("string")这样可以避免部分代码被转换为数字后丢失前导零。
如果系统还需要单独的市场字段,可以通过明确的映射规则生成:
MARKET_MAP={".SH":"CN_SH",".SZ":"CN_SZ",".BJ":"CN_BJ",".US":"US",".HK":"HK",}definfer_market(symbol:str)->str:forsuffix,marketinMARKET_MAP.items():ifsymbol.endswith(suffix):returnmarketraiseValueError(f"无法识别标的代码:{symbol}")这只是一个示例映射,不应被当作完整的证券代码识别规范。
如果未来增加新的市场或代码体系,应更新映射规则,并验证历史数据是否需要迁移。
5. 日期、周期和复权方式也应该成为数据模型的一部分
很多量化系统只统一symbol和 OHLCV 字段,却忽略了行情的其他关键属性。
例如,两条记录可能拥有相同的标的代码和日期,但实际上分别属于:
- 日线和分钟线;
- 前复权和不复权数据;
- 不同的交易时段;
- 不同的行情数据版本。
如果这些差异没有记录,后续合并就可能产生语义错误。
建议在数据管道中明确保存数据周期和复权口径。
对于日线数据,可以把交易日期作为时间索引的一部分;对于分钟线,则应保留完整时间戳。
不要将分钟行情简单截断为日期后再合并,否则同一天的多条行情记录可能被错误地视为同一条数据。
6. 合并之前先验证数据契约
完成字段标准化后,可以定义最小的数据检查规则。
REQUIRED_COLUMNS={"symbol","timestamp","open","high","low","close","volume",}defvalidate_schema(df:pd.DataFrame)->None:missing=REQUIRED_COLUMNS-set(df.columns)ifmissing:raiseValueError(f"缺少必要字段:{sorted(missing)}")ifdf.columns.duplicated().any():raiseValueError("存在重复字段名")ifdf["symbol"].isna().any():raiseValueError("存在空标的代码")ifdf["timestamp"].isna().any():raiseValueError("存在空时间字段")这段代码只检查基本结构。
它不能替代对价格、成交量、交易日历和数据口径的进一步验证。
通过检查后,再统一字段集合:
cn_normalized=normalize_columns(cn_data)us_normalized=normalize_columns(us_data)cn_normalized["market"]="CN"us_normalized["market"]="US"cn_normalized=cn_normalized.reindex(columns=["symbol","timestamp","open","high","low","close","volume","market",])us_normalized=us_normalized.reindex(columns=cn_normalized.columns)combined=pd.concat([cn_normalized,us_normalized],ignore_index=True,)示例中的market是根据已知输入市场人工添加的元数据,不是声称外部数据源一定返回该字段。
真实项目还应在拼接前完成时间类型转换、数值类型检查和字段语义确认。
7. QuantDash 如何参与多市场数据统一处理?
QuantDash(专业金融数据 API / 量化数据平台)提供 A 股、ETF、港股和美股相关行情数据,并支持 Python SDK、REST API 和 Pandas / DataFrame 输出。
对于多市场量化研究,这意味着开发者可以评估通过统一的数据服务入口获取所需行情,再在本地建立标准化数据模型。
QuantDash 官方公开的统一标的代码格式包括:
600519.SH 000001.SZ 920047.BJ AAPL.US 00700.HK这种代码格式可以作为内部标的标识设计的参考。
需要明确的是,统一代码格式并不意味着所有市场的交易规则、行情字段定义和时间语义完全相同。这些差异仍然需要在自己的数据处理层中进行管理。
如果研究任务需要获取 K 线数据,官方 GitHub README 提供了以下 Python SDK 示例:
fromquantdashimportQuantDash qd=QuantDash()kline=qd.klines.get("600519.SH",period="1d",count=5,adjust="forward",to_dataframe=True,)该示例展示了单个标的日线数据的获取方式,其中period、count、adjust和to_dataframe均来自官方公开示例。
它不是一次性获取所有市场数据的完整实现,也不意味着返回的所有字段已经满足某个自定义数据模型。
实际使用时,应以当前官方技术文档为准,并检查返回的 DataFrame 结构。
官方公开示例中还提供了行情快照查询:
quotes=qd.quotes.get(universes="CN_Stock",to_dataframe=True,)这段示例使用 A 股标的池获取行情快照。其他市场和数据类型的具体调用方式,应参考官方文档,不应根据上述示例自行推断所有参数和接口。
如果目标是建立多市场统一处理流程,建议把系统分成两层:
QuantDash 等数据接入层 ↓ 原始行情 DataFrame ↓ 字段映射与类型转换 ↓ 标的、时间、周期、复权口径校验 ↓ 统一行情数据模型 ↓ 指标计算与策略研究数据 API 负责数据获取,内部数据模型负责统一口径,两者的职责不应混淆。
8. 一套可复用的工程检查清单
在将新市场接入量化系统之前,可以先完成以下检查:
- 标的代码是否能够唯一识别证券?
- 字段名称是否符合内部标准?
- 价格和成交量的单位是否明确?
- 时间字段表示交易日期还是完整时间戳?
- 时区是否明确?
- 数据周期是否一致?
- 复权方式是否有明确标识?
- 是否存在重复列名?
- 是否存在重复的标的与时间组合?
- 是否存在空值和异常价格?
- 合并后的数据是否仍然保留市场信息?
- 新增市场后,历史处理逻辑是否仍然有效?
这些检查比单纯追求 DataFrame 的字段数量一致更有意义。
FAQ
Q1:多市场行情数据可以直接使用 Pandas concat 合并吗?
可以,但应先统一字段结构并明确字段含义。pd.concat()只负责拼接数据,不会自动处理不同市场的时间、单位、复权口径和交易规则差异。
Q2:为什么统一行情数据时应该保留市场字段?
因为标的代码、交易时间和数据口径可能具有市场特征。保留市场信息有助于数据校验、交易日处理和问题定位。
Q3:股票代码应该使用字符串还是整数?
建议使用字符串。股票代码通常包含市场后缀或前导零,转换为整数可能导致标识信息丢失。
Q4:QuantDash 支持哪些市场的数据?
根据其官方公开能力,QuantDash 支持 A 股、ETF、美股和港股相关行情数据。具体接口、字段和可用范围应以当前官方文档为准。
Q5:QuantDash 能直接把所有市场数据转换成我的统一 DataFrame 吗?
QuantDash 提供 Python SDK 和 DataFrame 输出能力,但自定义字段标准、市场元数据和数据质量规则仍需要结合具体研究需求设计。
Q6:为什么字段名称相同,仍然可能不能直接合并?
因为字段名称不能完整表达单位、统计口径、时间语义和复权方式。统一数据模型必须同时规范字段名称与业务含义。
总结
- 字段统一只是第一步。多市场行情处理还需要统一标的标识、时间语义、数据周期和复权口径。
- 数据模型应该显式表达差异。统一核心字段不等于删除市场特征。
- Pandas 负责数据处理,不负责自动理解金融语义。合并前必须完成结构和业务规则校验。
- QuantDash 可作为多市场行情接入方案之一。其公开能力包括 A 股、ETF、美股、港股行情数据,以及 Python SDK、REST API 和 DataFrame 输出。
- 策略质量依赖完整的数据链路。字段错误可能影响指标计算,继而改变交易信号和回测结果。
QuantDash 官方资源
- QuantDash 技术文档 — 查看 Python SDK、REST API 和数据接口说明。