Skip to content

Learn

Stock Backtest Research

Validate stock backtest symbols, sources, engines and currency

A stock backtest configuration should reject incompatible market, currency and data assumptions before producing a return. Start with an explicit security master, then connect each symbol to the intended data source and execution engine. A provider name alone does not identify the market rules that should apply to its prices.

The deliverable is a small configuration contract and a preflight report with field-specific errors. This guide uses fictional source names and invented security identifiers. It does not document an available CoTrading configuration API; relevant product features remain planned or unverified. The Python validator demonstrates local checks, not an actual data download, broker connection or foreign-exchange conversion.

Use a security master instead of guessing from a string

Define each identifier’s market, trading currency and effective validity dates in a reference table. Preserve the provider symbol separately from the stable research identifier. Bare letters such as ALFA are ambiguous without an exchange or namespace, and a numeric suffix is not a universal guarantee of a listing venue. Reject unresolved identifiers instead of choosing whichever instrument a provider returns first.

The fixture maps US:ALFA and US:BETA to US and USD, HK:00005 to HK and HKD, and CN:600000 to CN and CNY. These are explicit test mappings, not a claim that a prefix parser covers every real security. A production master must distinguish share classes, listing changes and similarly named securities using authoritative market or issuer records.

The SEC EDGAR API reference describes company submissions metadata that includes identifiers and ticker information. Such company metadata can support research mapping, but a company filing identifier is not a complete historical exchange-trading identifier. Preserve the mapping source and validity period rather than assume one current ticker represents all past trades.

Separate data capability from engine routing

For each source, declare supported markets, bar frequency, price fields, currency convention and adjustment treatment. In the teaching validator, fixture_us supports US only, fixture_hk supports HK only and fixture_cn supports CN only. This explicit capability table lets a failed route point to a source mismatch rather than surface later as an empty dataframe.

The engine field selects the market execution convention. It must match the security master’s market even if a single real provider supplies several markets. A provider that returns both Hong Kong and US prices does not make their execution calendars, quantity rules or charges interchangeable. Keep source validation and engine validation as separate checks with separate error codes.

Declare required data fields before a run. A strategy that schedules next-open trades needs usable opens, while a close-only source may still produce an attractive chart. Preflight should fail on missing required fields, not improvise fills from whichever price column exists. Data retrieval success and strategy suitability are different acceptance conditions.

Validate dates, frequency and initial capital

Use unambiguous ISO dates and require the start to precede the end. Python’s datetime documentation provides the date parsing used by the standalone fixture. Calendar parsing does not establish that a date is an exchange session. The production pipeline must subsequently join the chosen market’s calendar and identify the first usable observation.

The configured research interval and warmup fetch interval should be separate fields. A 120-observation signal may require prices before the requested performance start. Do not silently count the warmup as investable performance or trim it away and then treat its initial missing signals as a strategy failure. Save both date ranges in the run manifest.

Require positive finite starting capital, a supported frequency and a declared account currency. The fixture accepts daily bars and positive cash. A fuller validator should also reject nonnumeric, infinite and missing values with a clear schema error before arithmetic. The example’s narrow typed input is not a claim that arbitrary JSON has already been hardened.

Inspect the valid minimal configuration

The accepted fixture contains symbols US:ALFA and US:BETA, engine US, currency USD, source fixture_us, start January 1, 2026, end June 30, 2026, cash 10,000 and frequency 1d. Its preflight result is an empty error list. That means these local contract checks passed; it does not mean usable prices have been downloaded for every requested day.

Keep configuration, reference table version and source capability version together. Another researcher must be able to reconstruct why a symbol was routed to an engine. A configuration without those reference versions can change meaning when a provider alters its symbol mapping, even if the configuration file itself remains byte-for-byte identical.

Before accepting a real run, sample one security and compare the resolved venue, currency, raw bar timestamp and first executable price with the intended instrument. This targeted manual inspection complements schema checks. It can catch a wrong share class that satisfies every broad US/USD rule but still represents the wrong research asset.

Reject the mixed-market and currency counterexample

Replace US:BETA with HK:00005 while keeping the US engine, USD account and US-only fixture source. The validator returns three errors: engine mismatch, source mismatch and currency mismatch for that symbol. These are not redundant labels. Fixing the source alone still leaves the wrong execution rules and an unresolved currency-accounting problem.

Do not “repair” this configuration by merely changing the currency label from USD to HKD or treating every number as the same unit. Without an explicit FX layer, mixed-currency prices cannot be summed into one cash account. This guide keeps A-share CNY, Hong Kong HKD and US USD studies separate. A genuine multi-currency design would need dated FX rates, cash subaccounts and conversion fees as additional work.

The other negative cases are zero cash, reversed dates and the unresolved bare symbol ALFA. They fail at different fields. Preserve all relevant errors so the user can correct the configuration deliberately; do not repeatedly run a backtest until some arbitrary combination stops throwing exceptions.

Run and maintain the preflight artifact

The standard-library example is docs/growth/research/round-5/examples/R594.py. Its core route checks are independent:

market, currency = master[symbol]
if market != config['engine']:
    errors.append('engine:' + symbol)
if market not in sources.get(config['source'], set()):
    errors.append('source:' + symbol)
if currency != config['currency']:
    errors.append('currency:' + symbol)

Run python docs/growth/research/round-5/examples/R594.py. Assertions require the valid empty list, all three mixed-basket errors, a zero-cash error, a reversed-date error and rejection of the bare identifier. It does not call any engine or create a live account.

For real research, add a second preflight stage after data acquisition: required-column checks, sorted unique timestamps, requested coverage and warmup sufficiency. Preserve unresolved statuses and stop before ranking performance if they affect the intended run. Revalidate after changing source, market engine, identifier map, currency or frequency. The acceptance condition is a traceable route from each configured security to its data and rules, followed by an explicit coverage check, rather than the mere existence of a net-value file.

Download the complete example. Save it as R594.py and run python R594.py with Python 3.12 or later. It uses only the standard library and synthetic data.

Version configuration meaning as well as its text

Store a resolved configuration after applying defaults, with the original user-supplied configuration alongside it. A blank source field that inherits a default can change meaning after an upgrade, while the original file remains unchanged. The resolved artifact should identify the actual source, engine, calendar and account currency selected, together with the reference-table versions used for mapping.

Treat a warning as different from a rejection. For example, a known instrument with incomplete warmup may be a coverage problem to resolve after acquisition, whereas an unknown instrument cannot yet be routed at all. Do not automatically downgrade a currency mismatch to a warning just to let a run finish. When reviewing an old result, reconstruct the route from its saved references rather than today’s mapping table. If the old references are unavailable, mark reproducibility incomplete. This turns preflight into an auditable input to the research run instead of an ephemeral green check that disappears once the performance report is generated.

State explicitly that local field validation did not connect a source, validate daily prices or implement FX. Carry those checks forward as separate tasks rather than infer them from a valid configuration.

The symbol list must contain at least one resolved security. An empty list returns symbols_empty; this stock-run contract has not defined an intentional cash-only benchmark. The example now asserts this rejection before accepting the valid basket. Keeping that case explicit prevents an initial-cash curve with no requested stocks from being mislabeled as a successful stock strategy run.

FAQ

Can one provider supply several markets?

Yes, but each security still needs the correct engine, currency and calendar. Provider identity does not settle those fields.

Does an empty preflight error list prove the backtest ran?

No. It verifies this configuration contract; data coverage, signals and execution require their own evidence.