Compare stock backtests with different coverage periods
Two completed stock backtests with different date ranges answer different historical questions. Preserve those original reports, then create a separate common-period experiment with shared input coverage and initial account policy. Cropping their equity curves and rebasing both to one does not necessarily remove inherited holdings, costs or capital differences.
This guide produces full-period and common-period columns, an excluded-session list and a restart-state record. Its standard-library example uses synthetic session indices and one-share targets. It is an external comparison method, not verified CoTrading functionality, and it keeps one market and currency rather than combining unrelated trading calendars.
Establish actual coverage rather than nominal labels
Obtain each run’s consumed session keys, signal eligibility, first executable fill, last valuation and unresolved orders. A report labeled January through March may have fewer usable observations because of missing quotes, warm-up or suspension. Compare the actual covered opportunities before assuming its dates overlap another run.
Keep both original contexts, including strategy, configuration, data, costs and starting state. Different strategies may legitimately be compared, but common controls must be stated. Do not quietly replace an older run’s universe with today’s surviving stocks when constructing the shared history.
The backtesting.py API describes initial cash, indicator warm-up and its execution timing. Those controls illustrate why a date label alone is insufficient. Our rerunner implements its own simplified state policy; the source does not establish that a product can restart arbitrary strategies from any desired snapshot.
Compute the shared schedule and exclusions
Intersect actual eligible session sets. Save dates present only in A, only in B and in both. If the intersection is empty, reject a common-period ranking. Annualizing disjoint results cannot create observations of the same market conditions that were never jointly tested.
Specify how gaps are handled. The example’s intersection is contiguous synthetic indices two through five. A real market may have irregular shared sessions; iterate the validated shared calendar or reject unsupported gaps, rather than looping over the numeric minimum and maximum and silently including excluded dates.
Preserve earlier history required for indicators. Excluding a day from scoring does not mean deleting it from the information available before the new start. Conversely, retaining history must not automatically carry its trades into the restarted account. Information history and account history need separate policies.
Freeze the common initial account
This comparison resets cash to 1000, holdings to zero and pending orders to none immediately before the common first executable session. Both candidates use zero costs and one-share targets. A real study should also align fee schedules, sizing, lots, execution assumptions and terminal valuation.
If the research question instead requires inherited holdings, define one common snapshot and its provenance, then apply both strategies consistently. Do not let each original run bring its own accumulated capital and call the resulting curves equal-start experiments. Changing that policy creates a different question and should receive a separate label.
Document boundary orders. A signal known before the common start may be permitted to execute at the start under the frozen rules. An already filled old order must not become a new fill unless the reset experiment genuinely requires it. Keep signal identity and new execution identity distinct.
Work through different full-period runs
Synthetic prices at indices zero through five are [100,110,90,95,105,100]. At each execution index t, a strategy targets one share if the preceding price meets its threshold, otherwise zero. It trades the difference from current holdings at price t and marks there. The prior price is known before the later execution.
Strategy A uses threshold 100 and runs sessions one through five. It initially buys at 110, later exits at 95 and ends with equity 985. Strategy B uses threshold 105 and begins at session two; it buys at 90, exits at 95 and ends with 1005 under its full-period schedule.
| Experiment | Scored sessions | Opening capital | Ending equity |
|---|---|---|---|
| Original A | 1–5 | 1000 | 985 |
| Original B | 2–5 | 1000 | 1005 |
| Common-period A | 2–5 | 1000 | 1005 |
| Common-period B | 2–5 | 1000 | 1005 |
A’s original full-period loss and B’s gain remain valid descriptions of those different experiments. They are not deleted when the common-period comparison is added. The exclusion ledger shows A-only session one and no B-only session in this fixture.
Rerun the shared period from the declared state
Start each common account flat before session two. Both rules use preceding price 110 and buy at 90, then exit at 95 after the next signal. Their common ending equities are both 1005, or +0.5%. Later targets can differ in principle; on this particular path they produce equal common outcomes.
shared = sorted(set(a_sessions) & set(b_sessions))
if not shared:
raise ValueError('no common period')
common_a = run(shared[0], shared[-1], threshold_a)
common_b = run(shared[0], shared[-1], threshold_b)
That code relies on the example’s contiguous validated schedule. The account runner explicitly resets cash and shares, computes targets from known prior prices and rebuilds every fill. It does not borrow the old curve or subtract earlier profit afterward.
The final marks include any remaining shares consistently. Both strategies can buy again near the end without changing marked equity at that instant. A real terminal-liquidation rule and its costs must be shared; dropping open exposure from one report would reintroduce an input mismatch.
Demonstrate why rebasing is a different operation
At session two, original A already holds a share purchased at 110. Marked at 90, its equity is 980. Cropping from that point and dividing by 980 gives 985/980 - 1, approximately 0.51020408%. The reset experiment starts with 1000 and buys a new one-share position at 90, yielding exactly 0.5%.
The amounts are close but unequal. Rebased old equity scales an inherited account path; it does not execute the new initial state. With costs, leverage, integer lots or larger prior exposure, differences can be more substantial. The principle does not depend on the size of this toy discrepancy.
The sample asserts the exact full-period and rerun ending values and explicitly rejects equality between the cropped ratio and 0.005 under the chosen tolerance. This is a state-boundary counterexample, separate from the no-intersection case, which blocks comparison before any rerun begins.
Publish two columns with distinct interpretations
Report original full-period performance and shared-period rerun performance together, each with its own dates and run reference. Do not replace the original values with the newer ones or describe the common result as a correction to the old loss. The experiments answer different coverage questions.
Annualized metrics, trade counts and drawdowns should be recomputed from each declared period, with sample size and frequency visible. A longer original run does not become equally informative by multiplying a short result by a time factor. Common-period agreement here does not prove the strategies are equivalent on other inputs.
Because the shared interval was constructed from already observed reports, label this comparison as historical analysis. It is not automatically a fresh holdout. If choosing a strategy after seeing the common comparison, retain that selection history when planning independent future evaluation.
Execute and accept the comparison
Download the complete example. Use Python 3.12 or later and run python R621.py. Standard-library assertions verify original endings 985 and 1005, shared sessions [2,3,4,5], reset endings 1005 and 1005, cropped A’s 0.51020408% and rejection of disjoint coverage.
Accept only when shared inputs, initial state, cost and terminal policies match the declared question and excluded sessions are retained. Save both old reports and new execution ledgers. Rebuild when coverage, data, strategy or restart policy changes, preserving earlier comparisons rather than mixing their scores.
This method verifies synthetic coverage and account reconstruction. It does not validate real historical universe membership, data availability or an untested engine’s restart behavior, and it supplies no claim about future stock returns.
FAQ
Does rebasing cropped equity create an equal-start rerun?
No. It can retain inherited positions and capital effects that the new account policy excludes.
What if the two runs have no common sessions?
Reject the common-period ranking. Separate historical summaries remain possible with clear coverage labels.