Feature engineering for trading models
You gave the model the close, the open, the high, the low, twelve indicators and the volume. It trained beautifully. Out of sample it is a coin flip, and you're now wondering whether to try a bigger model.
Don't. A recurring theme on r/algotrading is a model that was handed raw price columns and an indicator zoo and asked to find the pattern, and the thread that follows is always the same: someone explains that the model learned the price level, not the behaviour, and that no amount of model tuning fixes a feature set that carries no information. In our experience the feature set decides more of the result than the model family does — which is why the model comparison guide ends with the line "what matters more than the model".
This guide is that line, expanded. It covers why raw prices fail, the handful of transforms that turn a price series into something a model can generalise from, the leaks that live inside features rather than in the split, how many features you need, and how to tell a feature that works from one that merely looks busy.
Why raw prices are the worst feature
A price is a level. Bitcoin at 30,000 and Bitcoin at 60,000 are the same instrument behaving in the same ways, but to a model they are two different regions of the input space, and the region it was trained on will never come back. Statisticians call this non-stationarity: the distribution of the feature drifts, so the mapping the model learned from feature to label is anchored to a range of values that stops existing. Tree models are especially exposed. A tree splits on thresholds ("close > 41,250"), so a tree trained on one price range has literally no branch for the next one.
The symptom is a model that is excellent in sample and useless the moment prices leave the training range — which, for a trending instrument, is immediately. People read that as overfitting. It is closer to asking the model a question it was never taught the vocabulary for. The overfitting guide covers the genuine article; this is a different failure with a similar face.
The transforms that make a feature usable
Nearly every good feature is a raw series passed through one of six transforms. The point of each is the same: make the feature's distribution roughly the same in 2016 as in 2026, so a pattern learned in one is still a pattern in the other.
Returns, not prices
The percentage or log change over a window. A 3% five-bar return means the same thing at any price level. This is the first transform and the one most tutorials skip, because the tutorial plots look better with the raw close.
Ratios to a rolling reference
Close divided by its 50-bar average; volume divided by its 20-bar median; range divided by the average range. The ratio strips the level and keeps the relationship. Most classic indicators are already a ratio in disguise, which is why the ones that work tend to be the ones that were normalised at birth.
Ranks and percentiles
Where does today's value sit among the last 250? A percentile rank is bounded between 0 and 1 by construction, is immune to outliers and to slow drift, and tells the model "this is unusual" without telling it "this is 41,250". Trees like ranks; nearest-neighbour models like them even more because the distance between two ranks means the same thing everywhere.
Volatility-normalised distances
How far is price from its 20-bar mean, in units of the 20-bar standard deviation? A move of two sigmas is the same event in a quiet market and a wild one, while a move of 400 dollars is not. Perry Kaufman's work on adaptive indicators is the long-form argument for this; the short form is that any distance feature should be divided by a volatility estimate before a model sees it. It is also the reason triple-barrier labels are set in volatility units.
Time since an event
Bars since the last 20-day high, bars since the last cross, bars since the last three-sigma move. Duration features carry information that no price ratio does, and they are naturally stationary — 14 bars is 14 bars in any decade.
Regime flags
A binary or three-state feature: is the 200-bar slope positive; is realised volatility above its yearly median; is the instrument above or below a long reference. Flags let a tree model learn different rules for different conditions instead of one rule that is wrong in both. The regime guide explains why a strategy without them tends to die at the first change of weather.
| Raw feature | Problem | Honest transform |
|---|---|---|
| Close price | Level drifts; model learns a range that never recurs | n-bar log return; close / rolling mean |
| Volume | Grows over years; spikes dominate | Volume / rolling median; percentile rank over 250 bars |
| Moving average | Same drift as price | Price / MA − 1, divided by volatility |
| RSI, stochastic | Already bounded — but window fitted by eye | Keep; add the same measure at two other windows and let importance decide |
| High − low range | Scales with price | Range / average range (a ratio), or range / close |
| Days since a high | None — already stationary | Keep as is; cap at a maximum |
| Calendar (day of week) | Fine, but weak | Keep only if importance survives out of sample |
Fractional differencing: the trade-off underneath
Taking a return throws away memory. A one-bar return knows nothing about the last month; the raw price knows everything about it but drifts. Marcos López de Prado, in Advances in Financial Machine Learning (2018), proposes fractional differentiation as the middle: difference the series just enough to make it stationary while keeping as much long memory as the test allows. It's a genuine idea and worth reading in the original. In our experience the simpler transforms above get most of the benefit for most retail timeframes, and fractional differencing is the thing to try when a model needs long-range context it can't get from a stack of returns at several windows — not a first move.
The leak that hides inside a feature
The leakage guide covers the six ways a backtest sees the future. Three of them live in the feature pipeline specifically, and they are the ones a careful split does not catch:
- Scaling fitted on everything. If you standardise a feature using the mean and spread of the whole history, every training row knows the future's average. Fit the scaler on the training window only; apply it forward. The LSTM guide shows what this one leak does to a curve.
- Centred windows. A smoothing that uses bars on both sides of the current one — some filters, some "zero-lag" tricks — has read tomorrow. Every window must end at the current bar. This is repainting by another route.
- Features that overlap the label. If the label is "did price rise over the next 10 bars" and a feature is a 10-bar forward-looking anything, the feature is the label. Less obviously: a feature computed at bar i from data that arrives after bar i closes — a daily volume total used at the open, an adjusted price that was adjusted later.
The test for all three is the same: shift every feature forward by one bar and rerun. A real feature set gets slightly worse. A leaking one collapses.
How many features
Fewer than you think. The instinct is to give the model everything and let it choose, and tree models are genuinely good at ignoring junk — but they are not free. Two effects cost you:
- Correlated features split importance. Five versions of momentum share the credit five ways, so each looks weak and none looks worth keeping. The information is real; the accounting hides it. López de Prado's clustered importance is the formal fix; the informal one is to keep one representative per idea.
- Noise features find patterns. Given enough random columns, a model will find one that happened to line up with the label in sample. Each junk feature is a lottery ticket for the optimiser, and the number of tickets is a multiple-testing problem you didn't record.
A workable range for a single-instrument model on hourly or daily bars is somewhere between five and thirty features, chosen because each describes a different thing — trend, distance, volatility, volume behaviour, duration, regime — rather than the same thing at six windows. Grinsztajn, Oyallon and Varoquaux (2022) found tree models beat deep learning on tabular data partly because trees handle uninformative features better; that is a reason to prefer trees, not a licence to add noise.
Judging a feature honestly
In-sample importance — the number most libraries print — tells you which features the model used, not which ones helped. A feature the model used to memorise noise scores high. Three checks that actually mean something:
- Permutation importance, out of sample. Shuffle one feature's column in the test window and measure how much the out-of-sample score drops. López de Prado calls this mean decrease accuracy (MDA) and argues for it over the in-sample impurity-based number (MDI) for exactly this reason. A feature whose shuffle changes nothing was decoration.
- Stability across walk-forward windows. Run walk-forward and look at importance window by window. A real feature is near the top most of the time. A feature that is first in one window and last in the next fitted that window's noise.
- Removal. Drop the feature, retrain, compare out-of-sample. Slow, decisive, and the only test that accounts for the other features covering for it.
A working order
- Define the label first, in volatility units, with a horizon you can defend.
- Write down the five or six behaviours you believe matter for that label. One feature each, transformed to be stationary.
- Fit every scaler and reference on the training window only. Shift-test the whole set once.
- Train something simple. Read out-of-sample permutation importance. Remove what didn't move the score.
- Only now add a second window or a second version of the ideas that survived, and repeat step 4.
- Confirm the survivors are stable across walk-forward windows before you trust any of them.
That order puts the model last on purpose. If the label and the features are honest, most of the six model families in the comparison guide land within a few points of each other. If they are not, the best model in the world finds the leak faster.
How this looks in Wise Apple
Wise Apple's feature bench is fed by WiseApple Script — a Pine-like language with 26 built-ins — and any series a script outputs can be used as a model feature, so the transforms above are one line each rather than a pipeline. Scaling and Train-Only PCA are fitted on the training window only by default, the look-ahead check catches a feature that reads a later bar, and the out-of-sample metrics are reported per Walk-Forward Window rather than once in sample — so a feature set that only works in one stretch of history shows up as a window that fails, which is the stability check in the list above run for you. It tests one instrument at a time, in the browser on your own machine, and is early software from one builder.
Questions traders ask about feature engineering
What are good features for a stock prediction model?
Features that describe behaviour rather than level: returns over several windows, price relative to a rolling reference, percentile ranks, distance from a mean in volatility units, bars since a recent high or low, and regime flags such as the sign of a long-term slope. Raw prices, raw volume and raw moving averages drift with the price level and do not generalise to ranges the model never saw.
Why does my machine learning model fail on new data even though it trained well?
Often because the features were non-stationary. A model trained on raw prices learns rules tied to a price range, and once the market leaves that range those rules have no branch to fall into. Transform every feature into something whose distribution is similar across years — returns, ratios, ranks and volatility-normalised distances — and then check the result out of sample with a one-bar shift test to rule out leakage.
How many features should a trading model have?
Fewer than most people use. For a single instrument on hourly or daily bars, five to thirty features chosen to cover different ideas — trend, distance, volatility, volume behaviour, duration, regime — usually beats a zoo of forty indicators that restate six ideas at seven windows. Correlated features split importance and hide what works; noise features give the optimiser lottery tickets. Keep one clean feature per idea and let out-of-sample permutation importance decide on extras.
How do I know if a feature is actually useful?
Do not trust in-sample importance; it rewards features the model used to memorise noise. Use permutation importance on the out-of-sample window (shuffle the feature, measure the drop in score), check that the feature stays near the top across successive walk-forward windows, and confirm by removing it and retraining. A feature that survives all three carried information; one that fails any of them was decoration.