实时行情接口返回的数据,不一定能直接用于策略计算。
一个接口可能返回多只股票的最新价格、成交量和行情时间,但策略真正需要的,往往是一张结构明确的价格观察表:每行代表一个标的,每列代表一种价格或状态,所有字段都有清晰的含义。
如果后续需要计算价格偏离、横截面排名或交易信号,就更需要在行情接入之后增加一层数据整理逻辑。
本文以 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 官方文档
- QuantDash 技术文档 --- 查看 Python SDK、REST API 和数据接口说明。
结语
将实时行情转换成价格观察表,关键不在于把接口响应简单转换成 DataFrame,而在于建立稳定的数据契约。
一个可用于策略计算的观察表,至少应该明确:
- 每行记录代表什么。
- 每个价格字段具有什么含义。
- 数据对应哪个观测时点。
- 如何识别无效数据和重复记录。
- 哪些条件满足后才允许进入策略计算。
当这些规则被固定下来,行情接入、因子计算和交易决策之间的边界就会更加清楚,也更容易定位策略结果异常的真正原因。