ashareapi 复权口径怎么用:为什么你的均线在除权日会歪掉

写策略回测的人迟早会撞上这件事:某只票在分红送股那天,K 线图上突然掉一个缺口,均线跟着拐了一下,你以为是行情变了,其实只是除权。

这篇讲清楚 ashareapi 的复权口径,以及「二次复权」这个容易踩的坑。代码用标准库,直接能跑。文中每个参数行为和数值都是我在线上真实调出来的(核对于 2026-10-05),不是抄文档。

先给结论:

你可能以为实际是
要传 adjust=qfq 才拿前复权没这个参数,返回的固定就是前复权
传了 adjust 没报错 = 生效了参数被静默忽略,返回和没传完全一样
历史 K 线取一次可以长期缓存前复权会随新除权除息重算,旧缓存会过期

先看现象:不复权会出的那个「台阶」

先讲清楚不复权数据长什么样(下面用文字描述,因为我要给的都是实测数据 —— 见下一段):

某只票分红送股那天,价格会按比例下调:你手里多了股票或现金,但每股价格掉了。在 K 线上就是一个向下跳的台阶。如果直接把这段序列拿去算均线、算涨跌幅、做形态识别,这个台阶会被当成一次暴跌 —— 指标就错了。

这就是为什么面向量化的数据源基本都要做复权。

⚠️ 这里有个反直觉的点,是我实测才注意到的:在 ashareapi 的前复权数据里,用「找缺口」的办法是找不到除权日的 —— 因为缺口已经被抹平了。

我拿 8 只票 60 个交易日,逐个交易日算「个股跌幅 vs 同行中位跌幅」,想筛出「单只票单独大跌」的日子:

缺口 9.3pp   sz000063   2026-07-20   当日跌 6.38%   同行中位 -2.90%
缺口 7.8pp   sz000063   2026-07-17   当日跌 10.10%  同行中位 +2.29%
缺口 7.3pp   sz300750   2026-07-10   当日跌 7.15%   同行中位 -0.12%

筛出来的这三天全是个股真实的暴跌行情(sz000063 在 07-17 单日跌 10.10%,是跌停级别的消息面),没有一个是除权。前复权口径下,除权不会在价格序列上留下痕迹 —— 这正是它该有的表现。

再看一段真实的连续序列(工商银行 sh601398,2026 年 6 月底这一段,实测数据):

2026-06-30   open 7.18   last 7.07
2026-07-01   open 7.05   last 7.06
2026-07-02   open 7.03   last 7.05
2026-07-03   open 7.03   last 7.04

相邻交易日之间没有台阶,开收盘也都在正常波动范围内。

下一节验证口径本身。

ashareapi 的口径:固定前复权,没有参数

ashareapi 的 /v1/kline 返回的价格固定是前复权,除权除息日不跳空。

import json, urllib.request

API = "https://api.ashareapi.com"
url = f"{API}/v1/kline?code=sh600519&count=5"
data = json.loads(urllib.request.urlopen(url, timeout=15).read())
for row in data["data"]:
    print(row["date"], row["last"])

返回是连续的日线,除权日不会出现台阶。不用选、也没得选 —— /v1/kline 的参数只有 code、period、count 三个。

前复权是以最新价格为基准,把历史价格按除权除息比例回推。好处是价格序列连续,均线、形态、技术指标都不被缺口干扰;代价是历史价格会随时间变化 —— 每发生一次新的除权除息,之前所有价格都要重算。

一个容易被忽略的后果:别长期缓存

既然历史价格会重算,那同一只票的历史 K 线,今天取和上个月取,数值可能不一样。

做回测请每次重新取数,不要把一个季度前缓存的价格序列拿来复用。这条比复权本身更容易让人吃亏 —— 因为缓存了旧数据不会有任何报错,只会默默算错。

坑:不要自己做「二次复权」

这个坑最隐蔽。取数正确,但加工的时候错了。

最常见的两种写法:

① 传了 adjust 参数,以为生效了

# 以为这样能选复权方式
url = f"{API}/v1/kline?code=sh600519&count=5&adjust=qfq"

结果参数被静默忽略,返回的还是同样的前复权数据。不会有 400,不会有警告 —— 请求成功,数据也对,只是你以为你选了个选项,其实没选。

这不算错误,但会让你误判:比如你以为自己在对比「不复权 vs 前复权」两套数据,实际上拿到的是同一套,对比出来的结论自然没意义。

② 把别处的不复权数据拼进来

# ❌ 前半段来自数据源 A(不复权),后半段来自 ashareapi(前复权)
combined = a_unadjusted + b_qfq

两边口径不同,拼在一起算出来的收益率、均线全是错的。这叫二次复权 —— 数据本身就带了一次调整,你又用另一套基准去对齐它。

正确做法:同一段序列用一个数据源从头取到尾。需要换源就整段重取,不要拼接。用 api.ashareapi.com 取数时,它返回的已经是复权后的价格,直接拿去算就行。

怎么自查口径对不对

最直接的一步:取最近 180 个交易日的收盘序列,找最大单日变动。

import json, urllib.request

API = "https://api.ashareapi.com"
rows = json.loads(urllib.request.urlopen(
    f"{API}/v1/kline?code=sh600519&count=180", timeout=15).read())["data"]

mx, day = 0, None
for i in range(1, len(rows)):
    a, b = float(rows[i-1]["last"]), float(rows[i]["last"])
    if abs(a - b) / a > mx:
        mx, day = abs(a - b) / a, (rows[i-1]["date"], rows[i]["date"], rows[i-1]["last"], rows[i]["last"])
print(f"最大单日变动 {mx*100:.2f}%  {day[0]} -> {day[1]}  ({day[2]} -> {day[3]})")

我实测的结果是这样的(核对于 2026-10-05):

代码最大单日变动出现在
sh6005198.09%2026-01-28
sz0008589.31%2026-01-28
sh6013983.92%2026-08-03

三个关键读数:

  • 没有出现 10% 以上的断崖 —— 如果口径里有未复权的除权缺口,个股会出现远超涨跌停限制的落差。
  • 两只白酒在同一交易日(01-28)同时大跌 —— 这是市场行情,不是某只票除权。个股除权不会让不同股票在同一天同步。
  • 所以上面这些数字是真实的行情波动,不是数据问题。

反过来看:如果你的序列里出现某只票单独的、幅度远超涨跌停的单日落差,而当天别的票都正常 —— 那才要怀疑复权口径。

这个方法的好处是不需要你事先知道除权日 —— 除权日不好查,但"最大单日变动"一行代码就能算。

补充一个更简单的验证:同一段代码连着跑两次,参数一个字都不改,看返回是否一致。

def row5():
    u = f"{API}/v1/kline?code=sh600519&count=5"
    d = json.loads(urllib.request.urlopen(u, timeout=15).read())
    return [r["last"] for r in d["data"]]

a, b = row5(), row5()
print("一致" if a == b else f"不一致:{a} vs {b}")

如果给请求加了 adjust 之类的参数后,结果和加之前逐字节相同,那基本可以确定这个参数没被识别 —— 这是个通用的判别手法,不只适用于复权参数。这套口径在 ashareapi.com 的端点描述里是逐字写明的,可以对着核。

常见误用清单

写法结果怎么办
传 adjust=qfq参数被忽略,返回前复权不用传,口径固定
不复权数据 + 前复权数据拼接二次复权,序列错整段用同一数据源
长期缓存历史 K 线新除权后旧缓存过期,静默算错每次重新取数
自己再套一层复权计算二次复权数据已经是前复权,直接用

前复权的定义、除权日不跳空的说明,以及一段自查方法,文档里写的是 https://ashareapi.com/wiki/adjustment/。除权日前后怎么对公告,那份页面也提了。

一句话总结

ashareapi 的 K 线已经是前复权,你要做的只有两件事:别传 adjust(没这个参数)、别拼接不同口径的数据。剩下的坑基本都出在缓存上 —— 前复权会随新除权重算,旧数据不会报错,只会算错。


文中 adjust 参数被忽略、返回逐字节相同这两条,是我在线上真实调出来的(核对于 2026-10-05,数据日 2026-09-30)。

0
0
0
0
评论
未登录
暂无评论