Skip to content

Learn

Stock Backtest Research

Find the first signal difference between stock code versions

After changing stock strategy code, a different final backtest result does not reveal which branch caused it. Hold data, resolved configuration and execution behavior fixed, then locate the first differing signal by session and security. Trace that local change into target positions, actual fills and the later account path before attributing every subsequent discrepancy to another code defect.

This guide compares two frozen strategy functions with one shared synthetic runner. It is a deterministic code-version experiment, separate from asking a model to generate another strategy. Prices are invented, and CoTrading source-level comparison capabilities remain planned or unverified. Use one market and currency for each real comparison.

Make code the only intended intervention

Save both source versions and record the common dataset, adjustment policy, security identities, parameters, engine, dependencies, costs, calendar and initial account. Compare consumed artifacts, not just filenames or interface labels. A changed price snapshot or a different quantity setting invalidates a pure code-attribution claim.

The sample shares prices [100,100,101,99], initial cash 1000, target quantity one and a next-price execution rule. Only the strategy function changes. A control checker rejects any difference in data, configuration or engine records before the experiment is interpreted. This does not detect unrecorded dependencies, so real programs may require additional instrumentation.

Keep generated code and execution separate. If an external model is called again during either run, record its output as another input and freeze it first. This task investigates two known programs; stochastic code generation or revised instructions would create a different experiment with more than the declared intervention.

Read the source difference without inferring the result

Version one returns current >= previous; version two returns current > previous. The change removes equality from the positive-signal branch. A text diff points to the candidate cause, but only executing the two functions on the same observations shows where it matters.

The Python difflib documentation describes unified text differences. The sample extracts the actual function sources and verifies that its patch includes the changed comparison. This is source evidence, not an automatic causal explanation of account performance.

def signal_v1(current, previous):
    return current >= previous

def signal_v2(current, previous):
    return current > previous

Inspect other source changes too, including defaults, units, imports and side effects. If a large refactor changes many branches, consider smaller controlled comparisons to isolate them. Do not select a convenient line from the diff merely because its explanation sounds plausible while ignoring the actual first divergence.

Align signals by stable event keys

Use session and security identifiers, plus a stable sequence key when several decisions occur at the same time. Join both outputs over the union of their keys. A row that appears in one version only is missing evidence in the other, not automatically a zero or a flat signal.

The example has synthetic security S and signal sessions one and two. It compares dictionary keys, so reversing row insertion order does not produce a false change. By contrast, a simple positional zip can compare different securities or silently stop when one output is shorter. Check coverage before interpreting numeric values.

If timestamps require normalization, preserve the original zone and information time. Rounding or sorting can erase an execution-relevant sequence. The objective is to align the same decision opportunity, not to transform files until they happen to match.

Locate the first branch difference

At signal session one, the current and previous prices are both 100. Version one emits one, version two emits zero. The first divergence is therefore (session=1, security=S), the equality branch. Version one’s target quantity is one share; version two’s is zero.

Against equity 1000 at that decision time, target weights are 0.1 and 0.0. These weights are derived from target quantity times the known signal price, not from a future fill price. Keep target quantity and target weight separate: a one-share target is not automatically a constant ten-percent allocation on every date.

At session two, price 101 exceeds previous price 100, so both functions emit one. Their target quantities again agree. That agreement does not mean their accounts have reconverged, because the earlier branch has already changed holdings and cash.

Follow the different holdings into later fills

The shared execution rule fills at the following synthetic price. Version one buys one share at session two for 101. Version two remains flat there, then buys one share at session three for 99 after the common session-two positive signal.

StageVersion oneVersion two
Session-one signal10
Session-one target weight0.10.0
First buysession 2, price 101session 3, price 99
Terminal marked equity9981000

Version one already owns a share when the later common target is evaluated, so it does not buy another. Version two needs a purchase to reach that same target. The different session-three fill is a position-path consequence of the first divergence, not necessarily a second newly divergent strategy branch.

Both end with one share marked at 99. Version one’s cash is 899 and marked equity 998; version two’s cash is 901 and equity 1000. Costs are zero and the price stream supplies simplified executable marks. This verifies the local account bridge, not real liquidity or trading-unit feasibility.

Separate propagation from new independent differences

Save a table linking code branch, input window, signal, target, fill and account state. After finding the first difference, compare later signals independently of account-dependent orders. If signals remain equal but orders differ because positions differ, label that propagation rather than repeatedly counting new logical failures.

For strategies whose signals themselves use account state, the later signal difference may also be downstream. A diagnostic replay can feed the same account snapshot into both strategy functions to distinguish the direct code effect from feedback. Such a controlled replay is another explicit experiment, not permission to overwrite either original account path.

Avoid requiring final equity equality as the success condition for a code change. Some changes intentionally alter behavior. Acceptance should state whether the observed first divergence matches the intended branch and whether later effects reconcile. A refactor intended to preserve behavior would instead require equivalent outputs under the declared comparison policy.

Exercise missing rows and mixed controls

The first counterexample reverses signal-row insertion order. Keyed comparison correctly reports equality. The second removes a session-two record and requires missing_record; it never fills the absent row with zero. These checks protect the alignment method, independently of the equality-operator change.

A third check changes target quantity from one to two in the control record. The program rejects the pure-code comparison. Otherwise larger orders could be incorrectly attributed to the edited operator. Resolve or separately classify that mixed intervention before claiming a source-level cause.

The complete fixture also asserts the first target weights, exact fill lists and ending balances. It extracts the actual source fragments rather than accepting a manually written patch as proof that those functions ran. These are focused synthetic checks; they do not verify every path a production strategy could take.

Run, accept and update the comparison

Download the complete example. Use Python 3.12 or later and run python R616.py. It uses only the standard library, frozen functions and synthetic prices. Actual output identifies session one/S/equality, weights 0.1 and 0.0, buys at 101 and 99, final equities 998 and 1000, explicit missing-row status and rejection of mixed controls.

Archive source versions, control references, aligned event keys, first input window and the signal-to-account trace. State whether the change was intended and how its effect was validated. Retain later propagated differences rather than deleting them simply because the root branch is understood.

Rebuild the comparison when data, configuration, engine or either code version changes. Preserve the previous package so reviewers can distinguish a new intervention from a correction to the old explanation. The result demonstrates source-to-signal tracing under fixed synthetic conditions; it does not establish real historical profitability or an existing automated CoTrading debugger.

FAQ

Does every later differing fill imply another code bug?

No. A prior signal can change holdings and cash, causing different orders even after signals agree again.

Can missing signal rows be treated as zero?

No. Missing output and a recorded flat decision are different states and must be investigated separately.