qfq vs hfq: which adjusted price should an A-share backtest use?

Use backward-adjusted (hfq) prices for A-share return calculations, raw prices for price-limit and tradability checks, and forward-adjusted (qfq) only for reading charts. qfq history is recomputed after every dividend or share distribution, so a qfq backtest cannot be reproduced later.

Updated 2026-08-01

Short answer: compute returns on backward-adjusted (houfuquan, 后复权) prices, run tradability and price-limit checks on raw (bufuquan, 不复权) prices, and treat forward-adjusted (qianfuquan, 前复权) series as a chart-reading convenience rather than a backtest input. This is not a style preference. Forward-adjusted history gets recomputed every time a company distributes, so the value stored at a given date changes under you, and a backtest you ran in March will not reproduce in September.

What the three modes actually do

On the Shanghai and Shenzhen exchanges, the printed price is mechanically cut on the ex-dividend / ex-rights date (chuquan chuxi, 除权除息). A cash dividend takes a fraction of a percent off. A share distribution of the common 10-for-10 kind (10送10) doubles the share count and halves the quote overnight. Nothing happened to the holder's wealth; the quote simply changed units. Price adjustment is the correction that turns that quote back into a series you can difference.

Forward adjustment anchors the most recent bar to the live market price and scales every earlier bar down by the accumulated factor. Backward adjustment anchors the first listed bar and scales later bars up, which is why a long-listed name quoted around 30 on a broker screen can show several hundred in an hfq series. Raw is whatever the exchange printed on the day, gaps and all.

Modeakshare adjust=Anchored toWhen a new distribution landsMatches broker screenUse it for
Raw (不复权, bufuquan)"" (empty string, the default)nothing — exchange printnothing is rewrittenyes, across all historyprice-limit tests, tradability filters, order-price realism
Forward-adjusted (前复权, qianfuquan)"qfq"today's pricethe entire history is recomputedyes, but only the latest barreading charts, eyeballing trend shape
Backward-adjusted (后复权, houfuquan)"hfq"the first listed priceonly bars after the event changeno — levels drift far above marketreturn series and any backtest you need to reproduce
The three adjustment modes in China A-share data, and what each one is safe for.

Raw prices manufacture losses that never happened

Take two raw closes across a 10-for-10 distribution and difference them. You have just booked a 50 percent single-day loss on a position that lost nothing. Any pipeline with a stop-loss, a drawdown filter, or a "drop names with a bad recent tape" rule will act on that number. The failure is loud enough to catch, which is the good case.

The quiet case is worse. Cash dividends in A-shares typically knock somewhere between a fraction of a percent and a couple of percent off the print. That is small enough to hide inside daily noise, so nothing crashes and no chart looks wrong. It just bleeds a little off every measured return, on every name, for as long as the dataset runs. You find it by reconciling against a corporate-action calendar, not by staring at the equity curve.

Forward-adjusted history is not a fixed object

The forward factor is computed backward from the current date. Every new distribution changes the accumulated factor, which changes every historical bar. Pull three years of qfq daily data today, pull the same three years six months from now, and the numbers will differ — same ticker, same dates, different closes. On a name with a long history of share distributions, the arithmetic pushed far enough back can even produce a negative price, which is a clear signal that the series has stopped meaning anything at that depth.

  1. 1Results stop being reproducible. A colleague re-running your notebook gets different numbers and neither of you is wrong.
  2. 2Stored runs cannot be compared. Last quarter's result file and this quarter's are built on different inputs, so a diff tells you nothing about your code changes.
  3. 3Any rule written in price levels drifts. A filter like "skip names printing under a certain price" selects a different set of bars each time the history is rebuilt.

China-specific mechanics you cannot skip

Three market rules interact with the adjustment choice, and none of them have a clean equivalent in US equities.

Daily price limits (zhangdieting, 涨跌停) cap how far a stock can move in one session — ±10 percent on the main boards, ±20 percent on the STAR Market and ChiNext, ±30 percent on the Beijing Stock Exchange. Newly listed shares have no limit for their first five sessions. Main-board names under special treatment (ST) used to carry a tighter ±5 percent band; that was abolished on 2026-07-06 and they now move with the rest of the board at ±10 percent, so any backtest spanning that date has to switch rules mid-sample. The limit is computed off the previous close **as the exchange publishes it** — normally the prior session's raw close, but on an ex-rights date it is the ex-rights reference price, not the raw close. Compute it off an adjusted close and your limit detection will be wrong on exactly the days that matter. When a stock opens at its limit and never leaves, the bar has open, high, low and close all equal (yiziban, 一字板) and there is effectively nothing to fill against; a backtest that assumes a fill there is inventing liquidity.

T+1 settlement means shares bought today cannot be sold until the next session. Any strategy whose measured return comes from an intraday exit on the entry day is not describing an executable trade. That constrains what "next-day return" can even mean, which brings us to the measurement problem sitting underneath all of this.

Evidence: one batch of signals, two conventions, opposite signs

On 2026-07-30 our own system produced a batch of signals. Every one of them gapped down at the next open. Measured as next-day open to next-day close, the batch averaged +0.56 percent. Measured as signal-day close to the average of the next day's high and close, the same batch averaged −0.28 percent. That is 0.84 percentage points of spread and a sign flip, on identical signals, with no parameter changed. The first convention silently throws away the overnight gap; the second keeps it. Price adjustment sits underneath both, and if any of those closes had come from a raw series crossing a distribution, the spread would have been larger still.

A second case from the same system: a model quoted at a 61.6 percent hit rate on its tuning sample dropped to 56.6 percent when re-run across the full market on forward-adjusted data over two and a half years. Applying real fees and the live take-profit and stop rules turned the expectation negative. Swapping the benchmark to mid- and small-cap indices turned out-of-sample excess return to somewhere between −0.3 and −0.5 percent, which says the surviving positive number was mostly small-cap beta. Data handling and benchmark choice each moved the result more than any parameter sweep did. That is one model in one market rather than a general law, but it sets the order of operations: settle the price series first, tune second.

Vendor notes: akshare, tushare, and where they disagree

In akshare the adjust parameter differs by function. stock_zh_a_hist (Eastmoney) takes an empty string, "qfq" or "hfq". stock_zh_a_daily (Sina) additionally accepts "qfq-factor" and "hfq-factor", which return the adjustment factors themselves as columns. In both cases the empty string means raw and is the default, so the shortest possible call quietly hands back unadjusted prices. Those two factor modes are worth knowing about: they make "store raw plus factors" a one-argument change rather than a reason to switch vendors. Tushare takes the other approach — raw bars plus a separate adjustment factor you apply yourself. More work, and easier to audit, because the factor is a column you can inspect instead of a transformation someone else already baked in.

When two vendors are compared bar by bar, they usually agree everywhere except a handful of dates. Those dates are corporate actions, not bad data, and rights issues (peigu, 配股), where the shareholder has to put in new money, are where implementations differ most. One practical cost worth recording: pulling daily history concurrently from Eastmoney's push2his endpoint got our IP rate-limited and then blocked outright, and the fix was switching to Sina's endpoint rather than tuning the retry logic. That cost about a day. For building a research dataset, rate limits and coverage differences matter more than how pleasant the API surface is.

SymptomLikely causeHow to check
A single-day loss of −30 percent or worse on an otherwise healthy nameraw closes differenced across an ex-rights datere-pull the window with hfq and see whether the gap disappears
Two data sources agree on almost every session and diverge on a handful of datesdifferent adjustment factor handling, usually around rights issuesline the mismatched dates up against the corporate-action calendar
Last quarter's backtest no longer reproducesqfq history was recomputed after a new distributionre-pull the same window, diff old against new, then move to hfq or store raw plus factors
Limit-up filter stopped firing after a data changethe limit test is reading adjusted pricescompare against the previous close the exchange published for that date, which on an ex-rights day is the ex-rights reference price
Four symptoms that usually trace back to the adjustment setting.

A three-step check before trusting any A-share backtest

  1. 1Pick one known ex-dividend date, print the raw and adjusted closes on both sides of it, and confirm the gap is present in raw and gone in adjusted. If it survives adjustment, the factor is not being applied.
  2. 2Pull the same historical window twice, weeks apart, and diff the files. Identical means your series is stable. Different means you are on qfq and your results have a shelf life.
  3. 3Trace which series each rule reads. Returns and performance statistics should come off adjusted data; limit checks, suspension checks and order prices should come off raw. A pipeline that uses one series for everything is wrong in one direction or the other.

Where this stops helping

Adjustment fixes the price series and nothing else. It does not model dividend withholding, which in A-shares varies with how long the position was held. It does not handle suspension days, delistings, or the survivorship problem in whatever universe file you started from. It says nothing about fills or costs. And hfq is not fully unambiguous either — two vendors can hand you two different hfq series for the same name if they treat rights issues differently.

The useful test is a swap. Change the data vendor, keep everything else fixed, and re-run. If the result moves materially, what you measured was the data pipeline rather than the market.

Related notes