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