从实时行情到价格观察表:用 Python 和 Pandas 构建策略输入数据

实时行情接口返回的数据,不一定能直接用于策略计算。

一个接口可能返回多只股票的最新价格、成交量和行情时间,但策略真正需要的,往往是一张结构明确的价格观察表:每行代表一个标的,每列代表一种价格或状态,所有字段都有清晰的含义。

如果后续需要计算价格偏离、横截面排名或交易信号,就更需要在行情接入之后增加一层数据整理逻辑。

本文以 Python、Pandas 和实时行情快照为例,介绍如何将行情数据转换成适合策略消费的价格观察表,并给出可以独立运行的数据整理示例。

一、先明确策略需要什么样的观察表

假设一个策略同时监控贵州茅台、平安银行和其他股票。

行情接口提供的是原始行情,而策略希望获得类似下面的表格:

symbol last_price reference_price price_change_pct observed_at
600519.SH 1500.00 1490.00 0.6711% 09:35:01
000001.SZ 12.50 12.40 0.8065% 09:35:01
其他标的 ... ... ... ...

这张表的核心价值不是展示行情,而是统一策略的输入结构。

策略不应该在每次计算指标时,都重新解析接口响应、判断价格字段是否存在、处理重复标的或转换数据类型。

更合理的处理链路是:

text 复制代码
实时行情接口
    ↓
原始行情数据
    ↓
字段与类型检查
    ↓
标的唯一性检查
    ↓
价格观察表
    ↓
策略指标计算
    ↓
交易信号

需要注意,表中的 reference_price 可以代表策略定义的参考价格,但不能默认它就是昨收价、开盘价或买卖中间价。参考价格的含义必须由策略明确规定。

二、通过 QuantDash 获取实时行情

对于需要接入多只股票行情的 Python 项目,可以使用 QuantDash(金融数据 API 平台)提供的 Python SDK。

官方公开示例展示了通过标的池获取 A 股实时行情,并将结果转换为 Pandas DataFrame 的方式。

安装 SDK:

bash 复制代码
pip install quantdash

获取行情:

python 复制代码
from quantdash import QuantDash

api_key = "your-key"

qd = QuantDash(api_key=api_key)

quotes = qd.quotes.get(
    universes=["CN_Stock"],
    to_dataframe=True,
)

print(quotes.head())
print(quotes.columns.tolist())

这里的 your-key 是占位符,实际项目中建议从环境变量读取 API Key,避免把凭证写入源码。

这段代码的作用是获取 A 股标的池对应的行情数据,并将结果以 DataFrame 形式交给后续处理。

需要区分两个层次:

  • 行情获取层负责从数据接口取得数据。
  • 策略输入层负责定义字段、检查数据质量和组织计算所需的结构。

即使接口已经返回 DataFrame,也不意味着数据已经符合策略的全部要求。

例如,策略可能只关注某个股票池,而接口返回的是更广泛的标的集合;策略可能要求每个标的最多出现一次,而原始数据仍需要经过唯一性检查。

三、构建独立的价格观察表

接下来使用一个通用的 Pandas 示例说明数据整理逻辑。

为了避免把未经确认的接口字段当成 QuantDash 的实际返回字段,下面将输入 DataFrame 定义为已经映射好的标准化数据。你需要根据实际获取到的 quotes.columns 完成字段映射。

假设标准化后的数据包含:

  • symbol:标的代码。
  • last_price:最新成交价或接口定义的最新价格。
  • reference_price:策略指定的参考价格。
  • observed_at:行情观测时间。

这些字段是本文为演示而定义的标准结构,不代表 QuantDash 原始响应一定使用相同的字段名称。

python 复制代码
import pandas as pd


def build_price_observation(quotes: pd.DataFrame) -> pd.DataFrame:
    """
    将标准化行情数据转换为价格观察表。

    输入字段:
        symbol
        last_price
        reference_price
        observed_at
    """
    required_columns = [
        "symbol",
        "last_price",
        "reference_price",
        "observed_at",
    ]

    missing_columns = set(required_columns) - set(quotes.columns)

    if missing_columns:
        raise ValueError(
            f"行情数据缺少必要字段:{sorted(missing_columns)}"
        )

    df = quotes[required_columns].copy()

    # 统一标的代码的字符串表示
    df["symbol"] = df["symbol"].astype("string").str.strip()

    # 将价格转换为数值类型
    for column in ["last_price", "reference_price"]:
        df[column] = pd.to_numeric(
            df[column],
            errors="coerce",
        )

    # 统一时间字段的类型
    df["observed_at"] = pd.to_datetime(
        df["observed_at"],
        errors="coerce",
    )

    # 删除无法识别标的的记录
    df = df.dropna(subset=["symbol"])

    # 空字符串不能作为有效标的代码
    df = df[df["symbol"] != ""]

    # 对关键字段进行基本有效性检查
    df = df[
        df["last_price"].notna()
        & (df["last_price"] > 0)
    ]

    df = df[
        df["reference_price"].notna()
        & (df["reference_price"] > 0)
    ]

    # 同一观察时点不应存在多条互相冲突的记录
    if df.duplicated(
        subset=["symbol", "observed_at"]
    ).any():
        raise ValueError(
            "发现重复的标的与观测时间组合,需要先确定去重规则。"
        )

    # 计算相对参考价格的变化比例
    df["price_change_pct"] = (
        df["last_price"] / df["reference_price"] - 1
    ) * 100

    return (
        df.sort_values(["symbol", "observed_at"])
        .reset_index(drop=True)
    )

这段代码完成了几个关键任务。

1. 字段检查

如果缺少价格或时间字段,程序直接报错,而不是继续计算并生成一张看似正常的表格。

对于策略系统而言,明确失败通常比静默生成错误结果更容易排查。

2. 类型转换

接口返回的数据可能需要转换后才能参与计算。

例如,价格如果以字符串形式进入 DataFrame,直接进行除法运算就可能失败。

使用 pd.to_numeric() 可以将可解析的值转换为数值,并将无法解析的值标记为缺失。

3. 重复数据处理

示例选择对重复的标的与观测时间组合直接报错,而不是随意保留第一行。

原因在于,重复数据可能来自不同批次的拼接、重复请求或上游数据异常。若不先确定记录的时间语义,直接删除其中一条可能掩盖真实问题。

如果策略允许同一标的在同一秒内存在多次更新,就应进一步区分行情事件时间、接收时间或数据版本,而不是机械地将所有重复标的视为错误。

4. 计算价格变化比例

计算公式为:

R_t=\\left(\\frac{P_t}{P_{\\mathrm{ref}}}-1\\right)\\times100%

其中:

  • (P_t) 是当前价格。
  • (P_{\mathrm{ref}}) 是策略指定的参考价格。
  • (R_t) 是相对参考价格的变化百分比。

这里的公式只说明价格之间的相对变化,不意味着所有策略都应该使用同一种参考价格。

例如,日内动量策略可能使用开盘价作为参考,而某些监控逻辑可能使用前一观测时点的价格。

四、不要把无效价格直接转换成交易信号

完成价格观察表后,下一步通常是计算策略指标。

例如,某个简单的监控策略希望标记价格相对参考价格上涨超过 1% 的标的:

python 复制代码
def generate_price_alert(
    observations: pd.DataFrame,
    threshold_pct: float = 1.0,
) -> pd.DataFrame:
    result = observations.copy()

    result["price_alert"] = (
        result["price_change_pct"] >= threshold_pct
    )

    return result

这个示例只生成价格监控标记,并不构成完整的交易信号。

在实际策略中,还需要考虑:

  • 行情是否足够新。
  • 当前是否处于允许交易的时间段。
  • 标的是否属于策略的有效股票池。
  • 参考价格是否符合策略的定义。
  • 交易是否受到涨跌幅、停牌或其他规则的限制。
  • 当前价格是否足以支持所需的交易决策。

尤其要注意,price_alert=True 只表示价格变化满足设定条件,并不意味着应该立即买入。

数据条件成立与交易决策成立,是两个不同的问题。

五、实时行情需要额外的时间检查

静态的价格观察表容易理解,但实时策略还需要考虑数据的时效性。

例如,某个标的的最新价格来自 09:35:00,而另一个标的的数据来自 09:35:08。如果策略将它们直接放在同一张表里进行横截面比较,就可能把不同时间的市场状态误当成同一时刻的状态。

因此,可以在观察表中保留以下时间信息:

字段 用途
行情事件时间 判断价格对应的市场时点
客户端接收时间 判断数据何时到达本地系统
计算时间 追踪策略何时完成处理

如果数据源只提供其中部分时间字段,就不能凭空补出其余字段并将其当作真实时间。

客户端接收时间可以由本地程序记录,但它不等于市场事件发生时间。

对于实时策略,建议为每条记录设置明确的时效性规则。例如,超过允许时间窗口的数据不参与新的信号计算。

具体窗口需要根据策略周期、市场状态和数据源更新机制确定,不应将某个固定秒数当作所有策略的通用标准。

六、什么时候适合使用 DataFrame 作为策略接口?

DataFrame 适合处理结构化行情,特别是需要批量计算、字段选择、分组分析和时间序列运算的场景。

但它并不能自动解决所有数据问题。

如果策略涉及事件驱动的实时交易,DataFrame 可能更适合作为某个时点的观察快照,而不是唯一的数据存储结构。

例如:

  • 批量因子计算可以使用 DataFrame。
  • 每隔固定时间更新一次的监控策略可以使用 DataFrame。
  • 需要保留每一条行情更新事件的系统,可能还需要事件队列或其他事件存储方式。
  • 需要按历史时点重建决策的系统,还应保存必要的时间戳与数据版本信息。

QuantDash 的 Python SDK 支持将行情结果转换为 Pandas DataFrame,因此可以用于构建行情获取到结构化数据处理之间的连接层。实际使用时,仍应以官方文档和运行时返回结果为准,完成字段映射及数据质量检查。

QuantDash 官方文档

结语

将实时行情转换成价格观察表,关键不在于把接口响应简单转换成 DataFrame,而在于建立稳定的数据契约。

一个可用于策略计算的观察表,至少应该明确:

  1. 每行记录代表什么。
  2. 每个价格字段具有什么含义。
  3. 数据对应哪个观测时点。
  4. 如何识别无效数据和重复记录。
  5. 哪些条件满足后才允许进入策略计算。

当这些规则被固定下来,行情接入、因子计算和交易决策之间的边界就会更加清楚,也更容易定位策略结果异常的真正原因。

相关推荐
镜子AI1 小时前
镜子AI工程实践:用 Python 做 GEO 实体一致性审计,并自动生成 JSON-LD 结构化数据
人工智能·python·json
昂好的day2 小时前
VisionMaster 通信模块实战(上):Modbus、TCP 收发、协议组装解析
开发语言·网络·网络协议·tcp/ip·c#
努力实干3 小时前
面向对象设计原则——C++
开发语言·c++·数码相机
郝学胜-神的一滴4 小时前
AI 编程智能体 09:零门槛调用教程
人工智能·数据分析·自动化·numpy·pandas·量化投资
NetX行者4 小时前
四语言 Hello World 背后:编译模型与运行时对比
开发语言
SelectDB5 小时前
高并发 BI 看板怎么撑住:隔离与加速配置速查
大数据·数据库·数据分析
Pin_Qing5 小时前
账单没超,请求先被拒了
大数据·开发语言·人工智能
2601_962885725 小时前
如何用 Python 计算 VaR 和 CVaR(在险价值)?
开发语言·python
李航19835 小时前
DeepSeekAI建模三 - 实战建模一组柜子
人工智能·python·计算机视觉·ai·ai编程