Skip to content

Learn

Stock Backtest Research

Stress-test stock candidates with extra signal delays

A signal-delay stress test asks what happens when an already frozen candidate acts later than the baseline execution policy. Keep the baseline lag and the added delay in separate fields. Replacing the engine’s original next-session rule with a zero-delay setting can accidentally make the supposed stress case trade earlier, invalidating the comparison.

This guide replays fixed synthetic buy and sell signals under three added delays, then traces each leg through the data boundary. It is an external research extension, not a promise of real execution speed or a verified CoTrading feature. All prices are invented, costs are zero and the experiment stays within one market and currency.

Capture the actual baseline timing

Record when information is available, when the strategy emits the signal and when the existing engine permits the order to fill. Distinguish signal formation, order release and market execution. A label such as “one-day delay” is ambiguous unless those timestamps and the relevant market-session calendar are visible.

The backtesting.py API documentation describes its own default next-bar market fills and the alternative closing-price setting. That is a reference behavior to inspect, not evidence that CoTrading or every other engine has the same scheduling. Verify the baseline of the engine actually used by the research.

Our baseline is one synthetic session after the signal. Added delays are zero, one and two sessions, so total lags are one, two and three. The zero-added-delay column reproduces the baseline; it does not remove the original lag. Save both components alongside every generated execution event.

Freeze candidates and the stress policy

Use parameter candidates selected before seeing these stress results. Keep their signal opportunities, data snapshot, sizing, costs and terminal policy fixed. Decide whether both entry and exit signals are shifted, whether orders expire and what happens if an opposing signal arrives while an earlier order is pending.

The miniature example shifts both legs of one-share round trips by the same added delay. It has no partial fills, competing orders, cash shortages or order expiry. Those simplifications make timing arithmetic inspectable. A production candidate with portfolio interactions needs a complete rerun of order and account state, not only a shifted table of realized trade profits.

Predefine what constitutes degradation. Possibilities include breaching a minimum net result, losing a required ranking or producing too many unmatched opportunities. Keep these separate. A rank change does not automatically mean a negative return, and a candidate cannot pass a completion requirement merely because its available closed trades look attractive.

Build the synthetic opportunity ledger

Execution and marking prices at indices zero through seven are [100,101,103,100,106,104,108,107]. Candidate A emits a buy at zero and a sell at three. B emits a buy at one and a sell at four. Candidate C, included to exercise the terminal boundary, emits a buy at five and a sell at six.

For each leg calculate fill_index = signal_index + baseline_lag + extra_lag. Preserve the original signal key so the same opportunity can be matched across scenarios. The sample asserts that every permissible fill occurs strictly after its signal. It never substitutes the signal price when a later execution price is missing.

Extra delayA fill indicesA closed profitB fill indicesB closed profit
01, 452, 51
12, 513, 68
23, 684, 71

A leads B at baseline, loses the lead with one extra session and leads again with two. Delay effects are not monotonic in this invented path. Do not force the result into a story that later execution must always worsen profit. The purpose is to measure sensitivity under specified scenarios.

Trace each leg instead of dropping incomplete trades

The core event construction records both lag components and an explicit out-of-range state:

fill_t = signal_t + baseline_lag + extra_lag
leg = {
    'signal_t': signal_t, 'fill_t': fill_t,
    'status': 'filled' if fill_t < len(prices) else 'out_of_range',
}

The complete example also retains side, price and scenario. Pairing the resulting legs yields a closed round trip, an open position at the end or no entry. These are different account states. A missing exit must not erase the buy that already consumed cash and created exposure.

This matching uses frozen candidate and signal identifiers, not a nearest-profitable-trade rule. When multiple events share a date, preserve stable sequence and order references. Otherwise a scenario can accidentally pair an old entry with a different exit and report timing sensitivity that is really a matching error.

Work through the terminal counterexample

C at zero added delay buys at index six for 108 and sells at seven for 107, closing at −1. With one added session it buys at seven for 107, but its planned sale moves to eight, outside the data. The account has one open share, terminal unrealized profit zero and no completed-round-trip profit.

With two added sessions, entry moves to eight and exit to nine. Both legs are out of range; no position is opened. This is not the same as the previous scenario’s open share. The script records closed, open_at_end and no_entry, with closed profit unavailable in the latter two cases.

Do not rank C’s missing closed result as zero against A and B. If comparing complete portfolio equity, use a declared terminal valuation for open positions and show the account return separately from round-trip profit. If complete opportunity follow-up is required, extend data under a frozen update policy or report insufficient coverage. Never invent a future exit.

Interpret degradation without changing the question

A’s closed profit falls from five to one under one extra session, while B rises from one to eight. An original requirement to retain baseline leadership would flag A in that scenario. A requirement merely to remain nonnegative would not. Report the actual rule and the corresponding status rather than calling every change a failure.

Percentage deterioration can mislead when the baseline is zero or negative. Prefer profit amounts, exposure, completion counts and explicit thresholds where ratios have no stable denominator. Retain scenarios that improve performance as well as those that worsen it; selecting only the harshest or most flattering lag after inspection changes the research claim.

Shifted signals do not remove all look-ahead. An indicator computed with future prices is still contaminated even if its resulting order is delayed. Keep point-in-time input checks separate from timing stress. Likewise a one-session synthetic shift does not model intraday network latency, spread uncertainty or guaranteed order fills.

Run and accept the stress matrix

Download the complete example. Use Python 3.12 or later and run python R613.py. The standalone standard-library script reports A’s profits [5,1,8], B’s [1,8,1] and C’s three distinct states. Assertions verify the ranking reversal, total timing rule, one open share at extra delay one and two out-of-range legs at extra delay two.

Archive the frozen candidate references, signal opportunities, baseline lag, added lag, per-leg events, terminal positions and comparison criteria. Reconcile every original opportunity to an explicit scenario state. An incomplete trade must remain visible even if the summary chart displays only completed profit.

First verify that the zero-added-delay leg ledger reproduces the original baseline exactly. If it does not, stop before interpreting stress rankings: the implementation has changed more than the declared delay and the later columns cannot isolate the intended effect.

Recompute when the data tail extends, the engine baseline changes or the stress policy changes. Label whether a previously open C trade now closes because new observations arrived; retain the earlier censored result. This synthetic method verifies timing and boundary accounting only, without establishing historical performance, product support or practical execution guarantees.

FAQ

Does zero added delay mean same-session execution?

No. It retains the baseline lag, which is one session in this example.

Can an unfinished trade be assigned zero profit?

Not as completed-round-trip profit. Preserve its open or unentered state and report any terminal mark separately.