Backtesting a linear-regression support strategy across markets
- Type
- In-house R&D / team-training build
- Method
- Linear-regression dynamic support (pattern trading)
- Markets
- US and Indian index futures, adaptable to any instrument
- Language
- Python
- Focus
- Look-ahead-free backtesting and execution modelling
What this is
An in-house build, not a client project and not something we sell or trade. We researched a pattern-trading idea ourselves and then built the whole engine to test it honestly: a backtesting system, the trade logic around it, and the reporting that tells us whether any of it is real. We chose to automate it as a sample project for our own team, the same way we did with our 0DTE options build. What we show here is the engineering. There is no product, no signal service, and no performance figure on this page.
The idea, in one line
The strategy looks for dynamic support. It fits support lines to the lowest lows in a recent window using linear regression, keeps a line alive for as long as price keeps respecting it, re-fits the line as new lows touch it, and buys when price comes back down to a line that is still live. That is the shape of it. The exact windows, thresholds and ratios stay in-house.
Why it is a harder build than it sounds
On a chart, a support line looks obvious in hindsight. Turning that into software a machine runs bar by bar, without cheating, is where the real work is. Three things make it genuinely hard:
- Lines have a life. A line is born in one window but can stay relevant for years, so the system has to carry a growing set of them and keep every one correct.
- The hindsight trap. It is very easy to write a backtest that quietly looks into the future. Avoiding that took real care in how support is found and how signals are timed.
- One codebase, many markets. The same engine has to work on a US index, an Indian index and a commodity, which behave nothing alike.
Finding support without looking into the future
The single most important decision in the whole system is how it finds a support point, because that is exactly where most backtests cheat. We deliberately do not use classic swing-low detection, which needs future bars to confirm a low and so bakes a look-ahead straight into the test. Instead the recent window is cut into blocks, and within each closed block the system simply takes the lowest lows. Because it only ever looks at bars that have already closed, there is no peeking ahead and no confirmation lag. A block whose support is falling rather than rising is thrown away before it can do any harm.
Building the lines, and a subtle bug worth naming
From those support points, the system builds candidate lines by pairing points up, fitting each pair by least squares, and projecting the line forward. As price prints new lows near a line, the line absorbs them and is re-fit through all of its touch points, so a good line tightens over time.
That re-fit created a subtle problem worth calling out, because it is the kind of thing that separates a demo from a real system. A least-squares re-fit through later points can quietly rotate a rising support line into a falling one, which is no longer support at all. We added a guard that checks a line's own slope after every re-fit and drops it the moment it has rotated the wrong way. We also merge near-identical lines using both their angle and their price, with the angle normalised so the same rule behaves the same on a low-priced index and a high-priced one.
Persistence, and a trade-off we chose to keep
Most trendline systems throw a line away as soon as it scrolls off the screen. Ours does the opposite: a line lives until price closes through it, however long that takes. That is what lets the system act on support established long before. The honest cost is that the set of live lines grows over a long test. We measured how far it grows, decided it was acceptable for what we gain, and wrote it down rather than hiding it. Being clear about a trade-off like that is part of the job.
Entry, and being pessimistic on purpose
A trade is only considered when a line's projected support actually falls inside the bar's range, meaning price genuinely traded down to it, not merely near it. A guard makes sure the touch is a real return to a line that already existed, not a circular touch of a line just drawn through the current bar, which removed a chunk of flattering false signals. Only one position is ever open at a time.
Where a single bar could have triggered several things at once, the engine always resolves to the worst case for the trade: a stop is assumed to hit before a target, and exits that depend on the close are only taken at the close. Fills are gap-aware, so a bar that gaps through a level fills at the open, not at the tidy price. None of this makes the backtest look better, and that is the point. A backtest is only worth anything if it is harder on you than the live market will be.
The exit, in two phases
The exit is built to give a trade room to work while cutting the ones that do not. One lot scales out at a first target and the rest runs, protected by a trailing stop once that target is hit. The first phase is a confirmation gate: within a short window after entry the trade has to show both momentum and trend agreement, and if it has not by the deadline the position is closed. Once it passes, the second phase simply holds and follows the trend, stepping out when the trend rolls over. We describe the structure here, not the indicators, periods or targets behind it.
One engine, many markets
The system is modular by design: separate parts for data, support detection, line management, indicators, entry, and risk and exits, with an orchestrator that runs the backtest and a reporting layer on top. Adding a new instrument takes a single command and a lot size, with no change to the tested logic. The data layer is hardened for the mess of real files: date or datetime headers in any case, mixed date formats inside one file, comma-formatted numbers, duplicate timestamps and unreliable volume, all handled so the time series stays clean and gap-free. It runs today on US and Indian index futures and adapts to commodities and equities the same way.
How we prove it to ourselves
A strategy is only as trustworthy as the way it is tested. Every run produces a full set of records: the trade log, a daily ledger, annual summaries, and detailed audit files for the support points and for every line event, so any signal can be traced back to exactly what the system saw. A separate report groups signals by their features after the fact, which is how we learn which conditions actually carry weight. And nothing is adopted on the strength of a good in-sample result. A change is only kept if it is chosen on one span of history and then still holds up on a later span the tuning never touched. That walk-forward discipline, in-sample first and untouched out-of-sample after, is the whole difference between a strategy and a story. We report the method. We do not publish results.
What it demonstrates
This is a compact demonstration of the engineering that honest strategy research actually needs: a look-ahead-free detection method, careful state management for objects that live and die over time, execution modelling that is pessimistic on purpose, and a validation discipline that refuses to fool itself. It is the same standard we bring to the research and validation work we do for clients, and to backtesting a client's own strategy on their broker.
This is an in-house engineering exercise. It is not a product, not a signal or advisory service, and not investment advice. We make no claim about how the strategy performs, and we show no returns. The specific parameters and thresholds are ours and stay in-house.