Skip to content

Learn

Stock Backtest Research

Test frozen stock parameters on an untouched stock group

A parameter selected on one stock group has not been tested on every other stock simply because the code accepts another symbol. Reserve a target group before tuning, freeze the chosen parameters and run that group without further adjustment. Keep stocks with no trades, missing inputs and failed executions visible so the reported result describes the reserved group rather than selected survivors.

This guide constructs a development-to-target transfer ledger for a single market and currency. Its miniature standard-library experiment uses invented session returns, not historical securities. The method is an external research workflow; CoTrading support is planned or unverified, and a transfer test cannot establish universal applicability.

Define what is being transferred

Freeze the strategy formula, parameter values, input transformations, scoring interval, costs, execution timing and account assumptions. Transfer those together. Keeping a lookback number while changing the data adjustment or missing-value rule does not isolate whether that parameter generalizes to different stocks.

Write the research question narrowly: does a candidate chosen on this development group retain acceptable behavior on this prespecified target group under the same protocol? Do not turn the result into a claim about another market, currency or liquidity regime that was not tested. A failed transfer is informative about this candidate and domain, not automatic proof that its entire strategy family is impossible.

Distinguish cross-stock transfer from a later-time holdout. Two groups tested over the same historical dates share market conditions and may have correlated returns. A stock-group split does not by itself provide future-period evidence. If both forms of separation matter, design stock and time boundaries explicitly rather than treating one as a substitute for the other.

Reserve securities before seeing target performance

Save the development and target lists with stable security identifiers, membership dates and the selection rationale. Check that their intersection is empty at the intended grouping level. Different tickers or share classes may still represent the same issuer or economic exposure; symbol-string inequality alone is not enough for every research purpose.

The GroupShuffleSplit documentation describes group-based separation and notes that different randomized folds need not have mutually exclusive test sets. Our example uses one fixed reserved group, not repeated random splits. The documentation supports the grouping distinction; it does not certify financial independence between stocks.

Compare data coverage, listing history, missing quotes and adjustment policy across groups before interpreting performance. A target stock with insufficient history should remain an explicit input failure. Replacing it after seeing poor output with an easier stock would change the reserved group. If replacement rules are necessary, freeze them and report every original exclusion.

Select on development data only

The synthetic rule observes the previous session’s completed open-to-close return. If that feature exceeds a threshold, it takes one unit of exposure for the next session, opening and closing within that session. Otherwise it stays flat. Costs are zero and proportional exposure is a mathematical simplification, so trade counts below mean one-session round trips, not raw fill rows.

Candidates are thresholds zero and 0.02. Development stock DA has returns [0.03,0.04,-0.02,0.01,-0.03]; DB has [0.03,0.02,-0.01,0.01,-0.02]. Score each threshold on both development series, sum those descriptive scores and choose the higher result, with smaller threshold as the tie-break. That operation selects 0.02.

The scikit-learn evaluation guide explains why test results used for tuning undermine evaluation. In our workflow the target results are never passed to the selector. The selection function’s input is the development dictionary only; the target runner receives the already chosen threshold as a fixed value.

Execute every reserved target stock

Target TA has returns [0.03,-0.02,0.01,0.03,-0.01]. TB has five zeros. TC represents a missing dataset. Keep all three in the original target manifest and apply the same threshold 0.02 to each available series.

TargetResult statusOne-session round tripsCompounded strategy return
TAcompleted2−2.98%
TBno_trades00%
TCmissingunavailableunavailable

TA trades the −2% second session and −1% fifth session because their preceding features exceed the threshold. Its compound result is 0.98 × 0.99 − 1 = −2.98%. TB never produces an eligible feature. Its zero result is a valid flat-account outcome under this toy zero-cost policy. TC has no computed result at all.

Do not rerun thresholds on TA and adopt whichever works best there. That would convert the target into another development stock. A second-phase study can be useful, but must preserve the first transfer output and seek new evidence for any revised candidate.

Implement status before aggregation

The key runner distinguishes absent input from an empty set of triggered trades:

if series is None:
    return {'status': 'missing', 'trades': None, 'score': None}
selected = [series[t] for t in range(1, len(series))
            if series[t-1] > threshold]
status = 'no_trades' if not selected else 'completed'

The full sample compounds selected returns and preserves the status. A production version must also distinguish execution failure, insufficient lookback, unsupported security and rejected orders according to actual observable fields. Do not invent precise error categories that an engine does not record.

If summarizing results, state the denominator. Here two of three target datasets can be evaluated, while only one target produces trades. An equal-stock average over available results would include TA and TB and exclude TC with a visible coverage statement. It is a descriptive statistic, not automatically the return of an executable combined portfolio with shared cash.

Test overlap and selection leakage

The first acceptance check is that development and target identifiers are disjoint. Stop before selection if overlap exists. For issuer-level separation, run the check on the chosen issuer mapping as well. Mapping gaps should be unresolved records, not assumed different issuers simply because labels differ.

The different-mechanism counterexample is missing data being reported as zero return. That would make TC look like another successfully tested flat stock and increase apparent coverage from two to three. The script asserts that TC’s score remains unavailable while TB’s legitimate no-trade result remains zero.

Also inspect the selector’s call boundary. Saving frozen parameters after the fact is insufficient if target scores were already used to choose them. Keep the selection timestamp, development score table and candidate reference before target execution. If an investigator has already explored the target group, disclose that history and narrow the evaluation claim.

Run the transfer and decide what evidence remains

Download the complete example. Use Python 3.12 or later and run python R612.py. The standard-library program selects 0.02 on DA and DB, then reports TA’s two round trips and approximately −0.0298 return, TB’s no-trade zero and TC’s missing status. Assertions check disjoint groups, unchanged target parameters and distinct missing/no-trade handling.

Accept the research package when every originally reserved stock has a result or reason, data and execution assumptions match, and the target was not retuned. Export both membership lists, per-stock coverage, chosen configuration, run references and all failed outputs. Do not hide TA’s loss or remove TB because it contributes no active trades.

Inspect two triggered TA sessions and one valid untriggered TB session. Those local traces establish both active and flat paths, while a summary score alone cannot reveal a reversed threshold comparison or an incorrectly omitted observation.

Reevaluate when membership, available history, corporate-action handling or strategy inputs change. Preserve the earlier transfer as a separate experiment. A wider target group or a new period can extend evidence, but each addition has its own selection history. This synthetic example verifies the mechanics of zero-retuning transfer and status accounting; it does not prove profitability on any real stock group.

FAQ

Is an untouched stock group also a future-period test?

No. Cross-stock and chronological separation answer different questions and can share the same market conditions.

Should stocks without trades disappear from the table?

No. Retain their valid no-trade status and distinguish it from missing data or failed execution.