AlphaKit 数据研究工作台

Python 合并行情与 PE/PB:先对齐日期

用 AlphaKit SDK 获取日线与每日估值,以标的和交易日做一对一外连接,保留缺失、识别负 PE,并理解原始价格和估值的不同口径。

研究实践 · 更新于

本页内容

一张价格表,一张估值表,怎样放在一起?

行情和估值是两张不同的表。即使查询相同日期,返回的行数、顺序和缺失情况也可能不同。直接按行号拼接,可能把某天价格放到另一日的 PE 旁边。

本例固定查询 2026 年 6 月 1 日至 6 月 30 日的 600519.SH,用“股票代码 + 交易日”连接,并保留两张表中所有日期,暴露未匹配的数据。股票代码仅作方法示例。

获取两组数据

先完成 SDK 初始化,再执行:

quotes = api.daily(
    ts_code="600519.SH", start_date="20260601", end_date="20260630",
)
valuation = api.daily_basic(
    ts_code="600519.SH", start_date="20260601", end_date="20260630",
)

真实结果取决于数据覆盖和权限。两张表都应先检查空返回、标的和日期;估值字段缺失不能用价格补出来。

一对一连接,并留下缺失

以下片段检查连接键,并将所需数值转换为数值类型。价格数据必须完整、为正数;估值缺失和负值保留,供后续解释。

import numpy as np
import pandas as pd

def prepare(frame, numeric_columns, name):
    if frame.empty:
        raise ValueError(f"{name}没有返回数据,请先核对权限和覆盖")
    required = {"ts_code", "trade_date", *numeric_columns}
    missing = required - set(frame.columns)
    if missing:
        raise ValueError(f"{name}缺少字段:{sorted(missing)}")
    result = frame[list(required)].copy()
    result["ts_code"] = result["ts_code"].astype("string").str.strip()
    date_text = result["trade_date"].astype("string").str.strip()
    if not date_text.str.fullmatch(r"[0-9]{8}").fillna(False).all():
        raise ValueError(f"{name}交易日期应为八位 YYYYMMDD")
    result["trade_date"] = pd.to_datetime(
        date_text,
        format="%Y%m%d", errors="raise",
    )
    if result[["ts_code", "trade_date"]].isna().any().any():
        raise ValueError(f"{name}连接键有缺失")
    if not result["ts_code"].eq("600519.SH").all():
        raise ValueError(f"{name}包含请求标的以外的数据")
    if not result["trade_date"].between("2026-06-01", "2026-06-30").all():
        raise ValueError(f"{name}日期超出请求区间")
    if result.duplicated(["ts_code", "trade_date"]).any():
        raise ValueError(f"{name}同一标的同一天有重复行")
    for column in numeric_columns:
        result[column] = pd.to_numeric(result[column], errors="raise")
        values = result[column].dropna()
        if not np.isfinite(values).all():
            raise ValueError(f"{name}的{column}存在非有限数值")
    return result

left = prepare(quotes, ["close"], "日线")
right = prepare(valuation, ["pe", "pe_ttm", "pb"], "估值")
if left["close"].isna().any() or not left["close"].gt(0).all():
    raise ValueError("日线收盘价存在缺失或非正数")

aligned = left.merge(
    right,
    on=["ts_code", "trade_date"],
    how="outer",
    validate="one_to_one",
    indicator=True,
).sort_values(["ts_code", "trade_date"]).reset_index(drop=True)

columns = ["ts_code", "trade_date", "close", "pe", "pe_ttm", "pb", "_merge"]
aligned = aligned[columns]
print(aligned.to_string(index=False))
print("连接情况:", aligned["_merge"].value_counts().to_dict())
print("估值缺失:", aligned[["pe", "pe_ttm", "pb"]].isna().sum().to_dict())
aligned.to_csv(
    "valuation_alignment.csv", index=False,
    encoding="utf-8-sig", date_format="%Y-%m-%d",
)

validate="one_to_one" 防止重复键把一行变成多行。how="outer" 保留两表日期的并集;indicator=True 增加 _merge 列,显示每行来自哪里。

这里没有使用前向填充,也没有删掉未匹配日期。输出 CSV 会保存到运行时的当前目录,运行前请确认没有同名文件需要保留。

_merge 和空白是什么意思?

标记 含义 下一步
both 两张表都有这一标的、这一日期 继续检查具体字段是否仍有缺失
left_only 有行情,没有匹配的估值行 核对估值覆盖、权限和日期
right_only 有估值,没有匹配的行情行 核对行情覆盖和对应日期

空白或 NaN 表示缺失,不是零倍 PE,也不是“极便宜”。两边有同一天的行,也不保证每个估值字段都有数值。

不前向填充,是为了让观察者看到原始缺口。若某项研究决定沿用前一天数值,应另外标出原值日期和沿用规则,不能把它伪装成当天取得的新数值。

PE、PE(TTM) 和 PB 不能混着读

PE、PE(TTM) 都是市盈率,但盈利比较口径可能不同;TTM 表示滚动近十二个月口径。PB 是市净率。使用前应核对服务字段定义、单位和数据日期,并选定统一口径再比较。

负 PE 不能解释为比正 PE 更便宜。 它可能涉及亏损等盈利口径变化。负值、缺失和异常值需要单独理解;不要简单排序后挑出最小值作为“低估”。PB 的非正值也需要核对净资产与数据口径。

本例只对齐历史日期,没有构建买卖规则。原始 close 也不是已经复权、扣除成本的收益序列。

没有 Token:先看缺失怎样保留

可以下载完整示例。解压后进入含 valuation_alignment.py 的目录,运行合成样本演示:

python -m pip install -r requirements.txt
python valuation_alignment.py --demo --csv

演示模式不访问服务、不需要 Token。样本特意保留未匹配日期和缺失值,用于解释连接行为;它们不是某只股票的真实估值或产品可用数据的证明。

完整脚本在项目中的位置是 examples/sdk/valuation_alignment.py,连接状态列名为 alignment,重点检查 pe 和 pb;本页核心片段额外展示了 pe_ttm。完整脚本默认只打印结果,加入 --csv 后才导出文件,默认目录为运行时的 outputs/sdk-examples/。重复运行会覆盖同名输出,需要保留时请指定新的 --output-dir。

获得配置后,真实模式使用:

python valuation_alignment.py --ts-code 600519.SH --start-date 20260601 --end-date 20260630 --csv

需要绘图时先安装 matplotlib,再加 --plot。价格、PE、PB 会分成独立子图,保留空值造成的断点;参数说明见 python valuation_alignment.py --help。

从估值数值到历史位置

某天 PE 数值较小,与“它在自身历史中较低”是两个问题。历史位置还需要明确比较窗口、有效样本和同一口径。继续理解历史分位。