Stock_Analysis_For_Quant / A 股适配

A 股回测要补的六件事:复权、T+1、涨跌停、停牌、成本与字段

Stock_Analysis_For_Quant 里的示例是美股口径:Python 侧用 yfinance 取 AAPL 这类 ticker,R 侧用 quantmod::getSymbols('AAPL'),Power BI 目录里附带的是 ^GSPC.csv(标普 500)。这套口径可以直接照抄的是代码结构,不能照抄的是交易制度。

A 股多出六件事必须自己补:复权口径、T+1 交收、涨跌停与停牌造成的不可成交、显式的交易成本、交易日历,以及字段与单位差异。本站没有在本机运行过任何示例(仓库无依赖清单),所以这一页给的是改造清单与骨架,不是运行结果——官方仓库也没有任何 A 股说明。

核验提交 df4bd3c58d71数据源:yfinance / quantmod制度与费率以官方为准示例未在本站运行

从美股口径到 A 股研究

仓库:yfinance 美股Python 侧取 AAPL 这类美股 ticker
仓库:quantmod 美股R 侧 getSymbols('AAPL'),Power BI 附 ^GSPC.csv
复权与前复权差异三种复权方式对收益与均线的影响不同
T+1 与涨跌停可成交性信号日与成交日要分离,涨跌停可能买不进卖不出
停牌与交易日历停牌日无行情,交易日历不等于自然日
印花税、佣金与滑点成本要显式建模,费率以交易所与券商为准
美股口径到 A 股改造的差异示意(依据仓库实际使用的数据源与 A 股公开交易制度整理);示意非官方图表,具体费率与规则以交易所、券商与你使用的数据源为准。
Delta

美股口径与 A 股口径差在哪:Stock_Analysis_For_Quant 示例里会出问题的七处

下表左列是维度,中间两列是「仓库怎么做的」与「A 股要额外做什么」,右两列告诉你出问题时去哪一层查。

维度仓库示例里的做法A 股要额外处理什么在仓库里哪一层会出问题怎么查
数据接口Python 侧 yfinance、R 侧 quantmod,取的是美股 ticker换成 A 股可用的数据源,并确认复权参数与字段名取数脚本与 notebook 的开头几格打开 YFinance_Tutorial.ipynb 或 BasicStockAnalysis.R 看取数那几行
复权口径示例分散在多个语言与文件里,没有统一说明用的是哪种复权价先定死一种复权方式,并让指标、收益、回撤都吃同一份价格序列指标计算与收益计算之间最容易口径不一致看同一标的的收盘价序列在不同文件里是否同源
交收制度不涉及:美股可当日买卖信号日与成交日必须分离,买入当日不可卖出(T+1)所有策略类 notebook 与 Excel 策略工作簿看信号序列是如何映射到成交价格的
涨跌停不涉及:美股无涨跌停(有熔断机制,但个股层面不同)触及涨跌停的当日可能无法成交,需要可成交性过滤策略回测的成交假设看成交环节有没有对涨停/跌停做条件判断
停牌与交易日历不涉及:按取到的交易日序列走停牌日没有行情;交易日历不等于自然日,滚动窗口要按交易日数时间序列类 notebook 与滚动指标看日期索引是否连续、缺失日如何处理
交易成本各示例的假设不一致,多数只给收益曲线佣金、印花税、过户费、滑点都要显式加进成交价或现金流组合与策略样例的收益计算段看有没有扣成本的步骤,还是一条净值直接算收益
字段与单位美股字段(close / volume,美元计价,股数)A 股字段名与单位不同(手与股、元与分),成交量口径要看数据源定义数据加载与列名映射段核对读进来的列名与单位,尤其是成交量
一句话:这七处里有六处是「制度差异」,只有一处(复权)是「数据口径差异」。制度差异无法靠换数据源解决,必须自己在回测逻辑里加条件;而 Stock_Analysis_For_Quant 的美股示例不会替你做这件事。
为什么这些差异在示例里看不出来:美股市场没有涨跌停,T+0 交收,除权除息的处理也相对简单,所以示例作者不需要在代码里写这些分支——不是他写漏了,而是那个市场里没有这件事。因此,照抄代码结构后再补制度分支,是唯一可行的路径:先让链路跑通,再逐条把制度条件塞进成交环节。反过来做(先纠结制度细节再动手)通常更慢,因为你还不知道自己会踩到哪一条。
Steps

改造六步:把 Stock_Analysis_For_Quant 的示例换成 A 股数据

每一步都给一段可运行骨架与预期输出。骨架是方法示例,不含任何收益结论,本站未在真实 A 股数据上运行。

  1. 先换数据源,再谈指标

    把取数那几行换掉,其余逻辑先不动。这样出问题时能确定是数据层还是计算层。

    import akshare as ak
    df = ak.stock_zh_a_hist(symbol="600519", period="daily", adjust="qfq")
    df = df.rename(columns={"日期": "date", "开盘": "open", "收盘": "close",
                            "最高": "high", "最低": "low", "成交量": "volume"})
    df["date"] = df.index

    预期输出:一张带 date 与 close 的日频表,列名与你后续代码一致。若数据源换成了 tushare-finance,字段名与频率参数不同,需要重做这一段映射。

  2. 定死复权口径

    复权方式一经确定,整条链路都必须用它:算指标、算收益、算回撤、做对比,全用同一份价格。混用是不一致的最常见来源。

    # 只用一份复权价,不改口径
    price = df["close"]                      # 已经是前复权价
    ma20 = price.rolling(20).mean()          # 均线也用它
    ret = price.pct_change()                 # 收益也用它

    预期输出:指标曲线与收益序列来自同源价格。检查方法:随便取一天,用 price 手算一次日收益,与 ret 对得上,说明口径没串。

  3. 加可成交性过滤(涨跌停与停牌)

    A 股回测最容易高估收益的地方就在这里:信号出现,但那天根本买不进或卖不出。

    # 需要数据源提供涨停价/跌停价或涨跌幅
    can_buy  = signal_buy  & (df["close"] < df["limit_up"])   # 涨停当日跳过
    can_sell = signal_sell & (df["close"] > df["limit_down"]) # 跌停当日跳过
    tradable = df["volume"] > 0                            # 停牌日为 0 成交量

    预期输出:成交日的数量少于信号日。若两者数量完全相同,几乎可以肯定过滤没生效——先确认数据源是否给出涨跌停价。

  4. 把信号日与成交日分开

    T+1 意味着买入后当日不可卖出;更稳妥的做法是统一用「次日开盘或次日收盘」成交,并给滑点留位置。

    # T+1:T 日出信号,T+1 成交
    df["exec_price"] = df["open"].shift(-1)
    # 卖出还要满足至少持有 1 个交易日
    hold_days = signal_sell.index - entry_index >= 1

    预期输出:成交价格序列相对信号序列错开一根。用 head() 对齐看两列日期即可确认错开是否正确。

  5. 显式建模交易成本

    成本必须落到成交价或现金流上,而不是事后在净值上乘一个系数。四项成本的口径见下一节。

    def trade_cost(amount_buy, amount_sell, rate):
        commission = (amount_buy + amount_sell) * rate["commission"]
        stamp      = amount_sell * rate["stamp"]        # 卖出方计
        transfer   = (amount_buy + amount_sell) * rate["transfer"]
        return commission + stamp + transfer

    预期输出:每笔交易有一条成本记录,净值曲线比不加成本时低,且交易频率越高差距越大。差值是否合理,取决于你填的费率。

  6. 交易日历对齐与样本基准

    把交易日历与标的序列对齐,避免自然日算平均;再定死样本区间与基准,后续所有比较都在同一区间内。

    # 以交易日历为基准重采样,避免自然日空转
    calendar = pd.DatetimeIndex(df["date"])
    aligned = series.reindex(calendar).ffill()
    # 样本与基准写进配置,而不是散在代码里
    sample = ("2018-01-01", "2025-12-31")
    benchmark = "000300"   # 基准代码,按你的数据源能力选择

    预期输出:一张与实际交易日数量一致的时间索引(无周末空行),以及一份可复制的样本配置。样本区间一旦写死,任何时候重跑都得到同一范围。

Costs

交易成本怎么建模:四项成本与它们的公式形状

本站不给具体费率数字——费率会随政策与券商合同变化。下表给的是公式形状与量级性质,具体数值请以交易所公告、券商合同与你使用的数据源为准。

成本项公式形状量级性质建模位置注意点
佣金max(成交金额 × 佣金费率, 最低收费)按成交金额比例收取,且通常有最低收费门槛买卖两侧的现金流门槛项在小额交易上影响明显;费率与门槛以你的券商合同为准
印花税卖出成交金额 × 税率按成交金额比例收取,历史上经历过调整,且只对卖出方计卖出侧的现金流只算单边;现行税率以税法与交易所公告为准,本站不给数值
过户费成交金额(或成交股数)× 费率按成交金额或股数比例收取买卖两侧的现金流沪深两市口径历史上存在差异,以中国结算公布的口径为准
滑点成交价 = 信号价 ± 跳数 × 最小变动价位按最小变动价位的整数倍估算,也可用成交金额的比例近似成交价格本身,而不是事后扣减流动性差的标的需要更大跳数;用固定比例近似时要在报告里写明假设
最小交易单位买入数量 = floor(资金 ÷ 价格 ÷ 100) × 100A 股买入按手(100 股)取整,卖出可以不足一手仓位计算与资金占用取整残差会让实际仓位偏离目标权重,组合回测里要显式处理
资金占用(融资/融券时)占用金额 × 利率 × 占用天数 ÷ 年化基数按日计息,年化基数按产品合同现金流不使用融资时这一项为 0;使用时要单列,不要混进佣金
为什么必须显式建模:Stock_Analysis_For_Quant 的示例大多只给净值或收益曲线,没有统一扣成本,所以「同一策略换成本假设后结论翻转」这件事在示例层面是看不出来的。本站的做法是把成本当成一个可替换的参数对象(上一步骨架里的 rate 字典),改费率不用改策略代码,也便于做成本敏感性对比。
Adjustment

复权方式选错,后面全盘皆错

三种复权方式算出来的收益率往往很接近,但价格水平相差很大;凡是涉及「价格绝对值」的环节都会被影响。

复权方式价格序列长什么样对收益率的影响对均线/指标的影响适用场景与注意点
不复权除权除息日出现向下跳空,历史价保持当时的真实成交价跳空日会被算成一次大幅下跌,长期收益被低估均线被跳空带偏,容易产生假信号只在需要核对当日真实成交价(如撮合、成本核算)时使用
前复权以最新价格为基准向前调整,历史价随之缩水收益序列连续,适合做长期收益率与波动率均线与指标连续,适合做技术面回测注意:每次新增除权,历史前复权价都会变,跨版本对比要对齐采集日
后复权以上市初价格为基准向后调整,最新价被放大同样连续,且历史价不再变化指标连续,且序列稳定适合做长期回测与跨期对比;但价格水平与盘口价差距大,不适合展示「现价」类指标
除权除息日附近除权日前后字段值明显断层断层被平滑后,收益率不再包含分红效应滚动窗口跨过除权日时结果失真要确认你的收益口径是「价格收益」还是「含分红的全收益」,两者不能混用
用复权价算回撤回撤曲线的峰谷都来自复权价不复权价会把除权跳空当成真实回撤回撤区间可能被错误放大回撤、波动率、Sharpe 这一类指标必须与收益同源同口径
跨标的/跨市场对比两只标的的复权方式若不同,价格水平不可直接比对比结果可能完全由口径差异造成相对强弱指标失真做对比前先把所有序列统一到同一复权方式与同一基准日
本站的处理建议:把「复权方式」写成一个配置项,并在结果里打印出来;Stock_Analysis_For_Quant 的示例分散在六种工具里,没有统一的复权口径说明,所以这一步只能自己定规矩。以官方仓库当前状态与你使用的数据源文档为准。
Tradability

信号发出来了却买不到:停牌与涨跌停的六种情形

这一节处理的是「回测里能成交、现实里不能」的落差。落差越大,回测越不可信。

情形表现处理方式建模要点注意点
信号日涨停,次日买入次日开盘一字涨停,挂单排队也未必成交把「收盘价触及涨停价」的当日标为不可买入,顺延到下一个可成交日或放弃需要涨跌停价字段,或按涨跌幅限制反推一字板与盘中涨停的处理可以分开;口径要在报告里写明
信号日跌停,无法卖出持仓被动延续,回撤被低估把跌停当日标为不可卖出,顺延处理卖出条件里加「未跌停」判断连续跌停时顺延会导致持仓天数远超预期,要有上限
停牌期间信号失效停牌日没有行情,但策略仍在算信号停牌日不产生信号也不成交用成交量等于 0 或缺少行情行判定长期停牌会让序列断裂,滚动窗口会跨过停牌段
新股与特殊标的的涨跌幅限制不同用统一的涨跌幅阈值会误判可成交性按标的类型分别设阈值阈值做成标的属性而不是全局常量具体限制以交易所规则与标的当前状态为准
退市与长期停牌样本中期标的消失把标的剔除或标记,并检查是否引入幸存者偏差股票池要按当期可得标的构建只在最后一天用「当前成分股」筛选,就构成典型的幸存者偏差
涨跌停与 T+1 叠加买入当日想跑也跑不掉最短持有的判断要在可成交判断之后先判可成交,再判持有天数顺序写反会让「当日买入当日卖出」漏进来

原则一:先过滤,再统计

可成交性过滤必须发生在收益统计之前。如果先把收益算完再去掉几笔交易,剩下的收益已经包含了不可成交的那些,统计口径就废了。正确顺序是:信号 → 可成交性 → 成交 → 收益。

原则二:把假设写在结果旁边

顺延成交、放弃入场、按均价成交,这三种处理会给出不同结论。选定一种之后写进报告,并说明它对结果的方向性影响,而不是只贴一条曲线。这样读者才能判断结论的适用范围。

原则三:不做无根据的乐观假设

「涨停也能买到」「停牌也能按前收盘成交」这类假设会让回测好看,但会让结论失效。本站的建议是把假设设成偏保守一侧,再看结论是否依然成立——这正是回测审计页要解决的问题。

为什么这一节值得单独做一张表:Stock_Analysis_For_Quant 的示例大多是「信号出现即可成交」的简化写法,这在美股口径下问题不大,换到 A 股就会系统性高估收益。把六种不可成交情形逐一列出来对照一遍,比事后调参数有效得多。这也解释了为什么很多照着示例改出来的 A 股回测,第一次跑就比预期好看——好看的部分往往来自没被过滤掉的那些交易。
Data routes

中文数据源路线与改造后自检清单

取数这一层可以用中文数据源技能完成,剩下的计算逻辑继续沿用你从 Stock_Analysis_For_Quant 学到的结构。

路线覆盖内容前置条件与本仓库示例的关系注意点与边界
akshare-finance股票、期货、期权、基金、外汇、债券、指数等行情与基本面;A 股日线支持复权参数需本机安装 Python 与 akshare(1.18 及以上)及 pandas替换示例里的 yfinance 取数段,其余计算照旧数据来自公开财经网站,可能限流;不处理实盘交易
tushare-financeA 股、港股、美股、基金、期货、债券等 220 多个接口需要 Tushare Pro 账号与 token适合需要财务与宏观数据的场景接口权限与频次受账号等级约束
a-share-metrics-card单只 A 股的关键指标卡(估值、盈利质量、现金流、负债、分红、活跃度)无需额外 Key;不覆盖港股与美股用于基本面快速核对,替代示例里的基本面段落只做单只标的体检,不做组合与回测
自备 CSV / Excel你已导出的历史行情与财务数据准备好字段规范(日期、开高低收、成交量、复权方式)与 Excel_Stock/ 的工作簿流程直接衔接字段与单位完全靠自己保证,没有校验层
数据商 API机构级行情与财务,含更细的成交与停牌标记账号、授权与费用可替代示例的数据层,计算结构不变授权范围与再分发条款要单独确认
券商/交易所官方渠道成交明细、停复牌公告等按渠道要求申请用于校正第三方数据的涨跌停与停牌标记接入成本较高,通常用于核对而非批量回测
自检项通过标准怎么查
复权口径统一指标、收益、回撤用的是同一份价格序列打印复权方式配置,并抽一天手算日收益与代码结果对比
信号与成交分离成交序列相对信号序列错开且不早于次日把信号日与成交日两列并排 head() 看对齐
可成交性过滤生效成交笔数少于信号笔数比较过滤前后的笔数;相等即视为未生效
成本已显式建模每笔交易都有成本记录,净值随成本下降把费率设为 0 与设为正常值各跑一次,看差异方向
交易日历对齐时间索引与真实交易日数量一致检查索引长度与数据源的交易日数量是否一致
样本与基准固定样本区间与基准写进配置,不在代码里硬编码散落改配置重跑,结果范围随之变化且可复现
字段与单位核对成交量与金额的单位、股数与手数口径一致用「成交量 × 均价」粗算成交额,与数据源给的实际成交额量级比较
不可成交交易有记录被过滤掉的信号有清单,能说清每一笔为什么没成交把过滤原因写成列并统计占比;占比过高说明策略对该标的不可执行
FAQ

关于 A 股回测改造的常见问题

制度与费率类问题一律以交易所公告、券商合同与你使用的数据源文档为准;本页按固定提交 df4bd3c58d71 与 2026-09-30 的仓库结构整理,没有在本机运行过任何示例。

Stock_Analysis_For_Quant 能直接拿来做 A 股吗?

不能直接做。它的数据层是 yfinance(美股 ticker)与 quantmod(同样美股 ticker),Power BI 目录里附带的是标普 500 的 CSV,官方 README 与目录 README 都没有 A 股说明。可以照抄的是代码结构、指标写法与组合流程;数据层与交易制度必须自己替换和补充。以官方仓库当前状态为准。

前复权、后复权、不复权到底该用哪个?

做长期收益与指标回测,用前复权或后复权都可以,关键是整条链路只用一种。前复权的历史价会随最新除权变化,跨时间对比要注明采集日;后复权序列稳定、适合做长期回测,但价格水平与盘口价差距较大。不复权只在需要核对当日真实成交价时使用,直接拿它算长期收益会把除权跳空算成下跌。具体口径请以你使用的数据源文档为准。

T+1 在回测里具体怎么体现?

两个动作:一是把信号日与成交日分离(常见做法是 T 日发信号、T+1 成交),二是限制最短持有时间,买入当日不可卖出。写代码时注意顺序——先判断可成交性,再判断持有天数,否则会漏进「当日买当日卖」这种现实里做不到的交易。另外要注意:T+1 只约束「买入当日不可卖出」,不等于「当日买入当日不能有持仓变动」——例如盘中做加仓是允许的,受限的是卖出方向。把它写成「持有期至少一个交易日」比写成「必须隔夜」更贴近规则本身,也更容易在代码里实现。具体交收规则以现行交易制度为准。

涨跌停买不进、卖不出该怎么处理?

把涨跌停当作成交前置条件,而不是事后修正:触及涨停的当日标为不可买入,触及跌停的当日标为不可卖出,然后选择顺延到下一个可成交日,或者直接放弃这次信号。两种处理给出的曲线不同,选定一种写进报告即可。判断可成交性需要涨跌停价或涨跌幅限制字段,具体限制以交易所规则与标的当前状态为准。

交易成本要加多少?费率从哪里来?

本站不给具体数值,因为费率会随政策和券商合同变化。你需要填四项:佣金(按成交金额比例且通常有最低收费)、印花税(按成交金额比例、只对卖出方计)、过户费(按成交金额或股数比例)、滑点(按最小变动价位的跳数估算)。把这四项做成可替换的参数对象,就能做成本敏感性测试;具体数值以交易所公告、券商合同与结算机构公布口径为准。

免费的中文数据源能用吗?要注意什么?

可以用来做研究。例如 akshare-finance 覆盖股票、期货、期权、基金、外汇、债券与指数的行情与基本面,A 股日线支持复权参数,但数据来自公开财经网站,可能限流,也不处理实盘交易;tushare-finance 覆盖更细的财务数据,但需要账号与 token。替代方案是自己准备 CSV 或 Excel,这时字段与单位完全靠自己保证。数据授权与可用性以各数据源条款为准。

改造完之后,怎么判断结果能不能信?

按顺序查四件事:复权口径是否统一、可成交性过滤是否生效、成本是否显式建模、样本与基准是否固定。这四项都过之后,再去看样本外与参数敏感性——回测审计页把这份检查整理成了十二项表格与四级可信度分级。需要提醒的是,Stock_Analysis_For_Quant 的示例普遍缺少样本外与参数敏感性这两层,所以「照着示例改完之后结论依然站不住」是常见结果,不是你没改对。改造正确不等于结论可信,两者要分开判断。以本站审计清单为方法参考,具体口径以你的数据与实现为准。

下一步:把改造后的回测真正审一遍

制度补上了、成本算进去了,也只是「结果算得对」。要做成研究结论,还要过样本外、参数敏感性与基准比较这几关。回测审计页给了十二项检查表与四级可信度分级,可以直接拿着它逐条核对你跑出来的曲线。