Skip to content

Learn

Stock Backtest Research

Audit A-share price-limit blocks at the simulated open fill

An A-share price-limit audit should evaluate the proposed opening fill using information available at that opening. Use a valid previous close, dated security-rule metadata, a tick convention and the candidate price after declared slippage. Do not use the same day’s eventual close to decide whether an earlier opening order was executable.

This guide implements a conservative opening guard and a complete synthetic round trip. It does not reconstruct an order-book queue or prove that every permitted candidate would fill. CoTrading’s historical price-band metadata, board-specific matching and real brokerage execution remain planned or unverified. The authored script exposes only the explicit guard described below.

Attach rules to a security and an effective interval

The SSE 2026 Trading Rules describe price bands based on the previous closing price, tick rounding and different arrangements including STAR Market provisions. A single ten-percent constant cannot represent every security, historical date, listing stage or special status. Security-code prefixes alone are not a complete historical rule source.

The SSE rule publication notice identifies July 6, 2026 as the revision’s effective date and supplies implementation arrangements. This fixture’s ordinary-main-board metadata is explicitly valid from July 6 through December 31, 2026. Its earlier-date query returns unknown rather than applying a current rule retrospectively. Real historical studies need the appropriate archived versions and security-specific states.

Our synthetic metadata supplies a ten-percent band and a 0.01 price tick for an ordinary case. A separate explicitly supplied STAR fixture uses twenty percent. These are scoped examples, not a complete rule database. New-listing periods, risk-warning categories, corporate-action reference adjustments, unrestricted sessions and instrument exceptions need their own dated metadata and validation before inclusion.

An absent metadata row or a date outside its effective interval yields unknown_rules. A missing previous close yields unknown_previous_close. Unknown is not permission to trade. The guard deliberately refuses to infer a rate from a ticker, fill a missing reference with the current close or choose a default merely to keep a simulation running.

Calculate bounds and then apply slippage

With previous close 10 and a ten-percent rate, the unrounded upper and lower bounds are 11 and 9. The script rounds each to the nearest supplied tick using Decimal and half-up rounding. Dividing by the tick before rounding and multiplying back makes the tick operation explicit, rather than assuming that rounding to a number of decimal places always matches an arbitrary price increment.

The Python Decimal reference documents exact decimal inputs and rounding behavior used in this small account. Prices enter from strings. This avoids binary floating artifacts in the hand-check, while retaining finite and positive checks. It does not establish that a generic Decimal operation implements all exchange-specific edge cases without the relevant market specification.

For buys, candidate fill equals opening times one plus slippage. For sells it equals opening times one minus slippage. Require finite positive opening, previous close and tick, a rate strictly between zero and one, and slippage from zero inclusive to one exclusive. Side must be buy or sell. Invalid numeric inputs raise an error; absent evidence keeps a separate unknown status.

The conservative guard blocks a buy candidate at or above the upper band and a sell candidate at or below the lower band. Candidates inside the selected side’s restriction are labeled permitted_by_guard, not filled. The distinction is deliberate: touching a limit may involve available opposite liquidity in real trading, while an inside-band candidate still needs a real executable market and order model.

Use two opposite close-price controls

First, the opening is 11, exactly at the upper band, but the later close is 10.5. The opening buy remains blocked under this guard. Looking at the later unlocked close would incorrectly release an earlier order using future information. A day that later moves away from its limit does not tell this opening-only fixture what quantity was available at the earlier instant.

Second, the opening is 10.5, inside the band, and the later close is 11. The guard permits the opening candidate. A daily-close test would incorrectly reject it because the stock eventually finished at the upper band. These paired cases show both directions of the same timing error, rather than assuming that use of later information always makes results more optimistic.

The later close is not a parameter of the audit function at all. It appears only in the counterexample records so readers can see why a tempting alternative is wrong for this information boundary. A richer intraday simulator could legitimately use later quotes at later timestamps, but it must not retrospectively alter the state of a completed opening decision.

Test the final candidate, not only the raw open

A raw buy opening of 10.99 is below the upper band of 11. With slippage 0.002, however, its candidate is 11.01198 and the guard blocks it. Checking only 10.99 would accept a simulated price outside the allowed guard. Clipping the candidate down to 11 would introduce a new fill-price policy and still fail the conservative at-limit rule.

The sell control starts at 9.01, just above the lower band. Applying the same slippage gives 8.99198 and blocks the sale. A sell opening exactly 9 is also blocked. The direction matters: testing every order against the upper band would miss the lower-band exit problem and allow a position to disappear into cash when its declared guard says it remains held.

At opening 11.5 with previous close 10, ordinary ten-percent metadata blocks a buy while explicit twenty-percent STAR metadata permits it by this guard. This is a different mechanism from slippage: the applicable rule itself changed. It demonstrates why board and date metadata must travel with the order rather than being inferred from whichever rate produces a desirable outcome.

Reconcile an allowed synthetic account

Start with 2,000 cash and no shares. The candidate buy at 10.5 passes the ordinary fixture’s guard. Buy one hundred shares and charge one unit, spending 1,051 and leaving cash 949, holdings one hundred and cumulative fees one. Opening equity at the purchase price is 1,999, equal to starting capital less the paid fee.

The separately supplied exit opening is 10.8, with previous-close reference 10 and zero slippage. Its candidate passes the sell guard. Sell the hundred shares, receive 1,079 after one fee and finish at cash 2,028, no shares and total fees two. Gross price profit is thirty and net cash profit twenty-eight. The bridge is 2,000 minus 1,051 plus 1,079.

The blocked controls never enter this account. Their statuses explain why an order would be refused, while the round trip shows that a permitted path still needs quantity, commission and cash accounting. The example does not assign the successful account’s profit to a blocked opening or manufacture a historical performance statistic from the two supplied prices.

Run the guard and preserve unresolved execution questions

Download the complete example and run python R598.py with Python 3.12 or newer. Only the standard library is required. The metadata and prices are explicit synthetic fixtures anchored to a cited rule interval. Output includes both close-price counterexamples, buy and sell slippage crossings, the lower-band control and final account totals.

Before using a real backtest, archive security-specific bands, reference-price adjustments, effective dates and execution timestamps. Separately verify T-plus-1 inventory, session validity, cash and order quantity. A conservative limit guard can prevent some impossible simulated fills, but cannot certify queue priority, opening-auction allocation, partial execution or every historical rule. Keep those limitations visible in the result rather than converting a permitted status into a promise of actual fill.

Log guard decisions before cash mutations

An audit record should preserve side, previous-close reference, metadata interval, rounded bounds, slippage candidate and status before changing inventory. For the 10.99 buy, the 11.01198 candidate is the reason for rejection; the later close is irrelevant. Retaining these fields makes a future metadata correction traceable to the first affected order. Do not replace a rejected record with a later permitted result under a different rule version without preserving the original input and reason.

FAQ

Does touching a limit prove there was no possible real trade?

No. This authored model chooses to block that side conservatively. Actual execution requires market and queue evidence beyond a daily opening price.

Can the current close decide an earlier opening fill?

Not under this contract. The audit uses previous close, opening candidate and already effective metadata; later closes are only counterexample annotations.