Skip to content

Learn

Stock Backtest Research

Trace stock signal timing into next-open backtest fills

To diagnose a next-open stock fill, trace one decision through three separate arrays: the raw close-based condition, the strategy’s returned output and the engine’s executable target. A shifted strategy output can look safe while the engine adds another delay. Conversely, removing every shift can let a close-based decision trade at an opening that occurred before the information existed.

This guide uses five synthetic bars and two complete cash accounts to expose the difference. The code is an authored alignment reproduction, not a test of a running Vibe or CoTrading engine. The actual product’s signal meaning, shift location and fill implementation remain unverified until inspected and exercised with its own recorded inputs and outputs.

Write an information-time contract first

The raw signal is a target long state: one when the completed close is strictly greater than 9.5, otherwise zero. One means hold forty shares, not buy forty additional shares every day. Zero means flat, not sell forty shares regardless of current inventory. The first opening starts flat because no earlier close has been supplied.

For raw condition at index i, the earliest permitted fill under this contract is opening i plus one. The example engine always delays the strategy output by one bar. If the strategy returns the raw condition unchanged, there is one total delay. If it already delays the condition internally, the same engine produces two total delays. Both variants avoid using the current close at the same opening, but they implement different timing.

Do not infer the contract from a column name such as signal or position. Inspect whether the function returns desired state, entry events, signed orders or already executable targets. Also check whether a later layer aligns by timestamp labels, array positions or a shifted index. A safe-looking local operation cannot prove the end-to-end chain is correct when the surrounding semantics are unknown.

The five opens are 10, 11, 15, 9 and 8. Their completed closes are 10, 12, 8, 9 and 8. Raw conditions are therefore one, one, zero, zero, zero. The differing opening and closing values are intentional: using a signal’s closing price as its fill would obscure the cost of a timing error.

Trace the single-delay account

With no internal strategy shift, the engine’s executable targets are zero, one, one, zero, zero. At opening zero no order occurs. The target at opening one comes from close zero, so forty shares are bought at 11. The account starts with 1,000, spends 440 principal and one fee, and has cash 559 with forty shares and cumulative fees one.

At opening two the target remains one, sourced from close one. Because the account already holds forty shares, no new purchase occurs. This is where a state-based signal differs from an entry-event stream. Treating every one as another buy would create eighty shares, use additional cash and implement pyramiding that the research never requested.

Close two is 8, so its raw condition is zero. It reaches opening three after the engine’s one delay. Selling forty at 9 receives 359 net after one fee. Cash finishes at 918, inventory zero and total fees two. The bridge is 1,000 minus 441 plus 359. The forty-share price loss is eighty and the net loss is eighty-two after fees.

The ledger includes source_close_index for every executable target. It is unknown for opening zero and then equals zero, one, two and three. That field is more informative than saying that signals were shifted: it identifies the exact observation responsible for each potential action. Preserve it or an equivalent decision identifier when debugging an actual engine.

Reproduce the accidental second delay

Now delay the same raw conditions by one inside the strategy and keep the engine delay unchanged. Executable targets become zero, zero, one, one, zero. The purchase moves to opening two at 15, costs 601 including a fee and leaves 399 cash. The account still holds at opening three because the zero from close two has not yet reached the executable layer.

At opening four, sell forty at 8 and receive 319 net. Ending cash is 718, holdings zero and fees two. Compared with the single-delay account, the two-delay account loses another 200 on these synthetic openings. The difference follows from entering at 15 rather than 11 and exiting at 8 rather than 9; it is not an additional commission or a different raw condition.

The lower result is not proof that one delay always produces better returns. Another price path could reverse the comparison. Correctness follows from the declared information-time and execution contract, not from selecting whichever lag wins after looking at outcomes. If a strategy intentionally waits two sessions, document that rule and test it as such rather than calling it an accidental bug.

Test current-close leakage independently

Change close two from 8 to 1,000 while holding every opening unchanged. The raw condition at two changes immediately, but actual orders, cash and quantity through opening two must remain identical. That opening occurred before close two was available. A same-opening response would expose an information-time violation in this close-based contract.

At opening three the changed condition is allowed to matter: the single-delay account now remains long instead of selling. This second assertion matters because a test that only checks unchanged earlier rows could also pass if the signal were accidentally ignored forever. Together, the unchanged past and changed eligible future establish both the boundary and actual signal use.

Run only the first three bars and require the same ledger prefix as the full run. The shortened account remains open rather than being force-liquidated at the last provided row. Otherwise a dataset-end exit would alter the very prefix used to diagnose alignment. Synthetic price perturbations and prefix checks target different routes by which later information can contaminate earlier execution.

Distinguish alignment from fill realism

Even a correctly aligned order does not prove a real opening fill at the supplied price. Investor.gov’s order reference explains the difference between order instructions and guaranteed prices. The independent fixture assumes executable finite positive openings and charges one unit per completed order; it does not model opening auctions, limits, partial fills or market impact.

The script rejects an invalid opening and insufficient purchase cash. Inputs must have equal lengths. Its close array is a controlled numeric fixture rather than a general missing-data ingestion layer. In a real integration, first validate price fields and timestamps, then apply the timing trace. A successful shift check cannot justify accepting a NaN close or a misaligned exchange timezone.

For product debugging, save the effective strategy settings, raw data identity, raw conditions, returned outputs, engine targets and actual orders for the same short window. Locate the first index where the expected and observed chain diverge. Changing a lag in the strategy before locating that boundary may hide the problem in one example while doubling or removing a necessary delay elsewhere.

Run the complete example

Download the complete example and execute python R596.py with Python 3.12 or newer. It uses only the standard library and synthetic bars. Output prints every row of both accounts, including conditions, targets, source close indices, orders, cash, inventory, cumulative fees and equity valued at each supplied opening.

The exact order assertions require buys at indices one and two respectively, and sales at three and four. Final cash must be 918 and 718 with flat inventory and two fees in each account. These are reproducible mechanics, not historical performance claims. Product support remains pending until the real runner produces its own equivalent trace under a verified contract.

Keep equity valuation out of the signal trace

The ledger values inventory at each supplied opening after processing that opening’s orders. It does not use that valuation to generate the current target. At opening two, the single-delay account can show a larger marked position value at price fifteen while retaining the same forty shares. A change in marked equity is therefore not another purchase. Separate those columns when comparing engine output, especially if a chart plots portfolio value alongside a signal that uses different timestamps.

FAQ

Is shifting inside the strategy always wrong?

No. It depends on what the downstream engine expects. This fixture already supplies one engine delay, so adding another changes a next-opening rule into a two-opening rule.

Can a correct lag guarantee a real opening price?

No. Timing eligibility and actual execution are separate. The example assumes supplied executable openings and does not establish auction or broker behavior.