Lesson 10.1: Machine learning for returns and sensible baselines
Learn machine learning for return prediction: honest out-of-sample evaluation, how noise features cause overfitting, and why simple baselines often win.
By the end of this lesson you will be able to set up a return-prediction problem as a panel of stocks and months, measure a model honestly with out-of-sample R-squared, the information coefficient and a long-short spread, explain why the achievable R-squared in monthly returns is a fraction of one per cent, work out how much noise features cost a linear model, compare a hierarchy of models from a naive forecast up to a neural network, and say when a complex model has earned its place. You will show that in a simulated market where the best possible monthly R-squared is 0.42 per cent, a linear model on the three features that matter reaches 0.395 per cent, the same model on all 60 features only 0.14 per cent, a ridge version 0.25 per cent, a forest 0.13 per cent and a small neural network −0.36 per cent, worse than predicting zero. You should have read Lessons 3.3, 3.4 and 4.3. The mathematics is regression, correlation and the idea of bias and variance. This course is education about how markets and research work, not investment advice.
Module 10 asks how artificial intelligence fits into quantitative finance. The honest answer has two halves. Machine learning has genuinely extended what a quant can do: it handles many features, it finds interactions that a human would not write down, and it reads text and images. But financial data has a property that makes the usual machine-learning story a poor guide: it is overwhelmingly noise. The signal in a stock's next-month return is a tiny fraction of its variance, there is only one history, and the world changes. In such a setting, a model's flexibility is as often a liability as an asset. This lesson shows how to use machine learning on returns without fooling yourself, and why the simplest model that works is usually the right place to start.
The prediction problem
The standard setup is a panel: many stocks observed over many months. For stock i at month t you have a vector of features x(i, t), such as the momentum, value, volatility and size measures of Lesson 3.3, all known at the end of month t. The target is the return of the stock over the next month, r(i, t+1). The goal is a function f such that f(x) is a useful forecast of the next return. A model is trained on past months and used to predict the cross-section of stocks in the next month, and the forecasts are turned into a portfolio.
Variations are common. The target can be the raw return, the return in excess of the market, a rank among peers (which is robust to outliers), or the sign. The features are standardised each month across stocks so that they are comparable over time, using only information from that month.
How little there is to find
Suppose a stock's monthly return has a standard deviation of about 10 per cent. A forecast that explains a good part of that would be astonishing. Take a predictable component with a standard deviation of 0.65 per cent, a plausible size for a good signal. The share of the variance that it explains is
R² = 0.0065² ÷ (0.0065² + 0.10²) = 0.0000423 ÷ 0.0100423 = 0.42 per cent
That is the ceiling: even a model that knew the true predictable component exactly would explain 0.42 per cent of the variance of monthly stock returns. In real data the best published models reach out-of-sample R-squared values of a few tenths of one per cent for individual stock returns, as in the large study by Shihao Gu, Bryan Kelly and Dacheng Xiu in 2020, which compared linear models, trees and neural networks on hundreds of features. An R² of 5 per cent in a monthly return forecast is not a good model. It is a bug, almost certainly a look-ahead leak.
The R² and the information coefficient of Lesson 3.3, the correlation between the forecast and the realised return, are linked: for a standardised forecast, R² ≈ IC². An IC of 0.065 gives an R² of 0.42 per cent. And the fundamental law, IR ≈ IC × √breadth, shows how such a small correlation becomes a useful strategy when it is applied to many stocks and many months.
Measuring a model honestly
Three rules from earlier lessons apply with full force.
1. Split by time, and only look forward. Train on the past, test on what follows, as in the walk-forward test of Lesson 4.3. Never let a random split mix future observations into training.
2. Tune on a validation period, and keep a test period clean. Choosing the regularisation strength, the depth of the trees or the number of layers is fitting. If you pick by looking at the test period, the test period has become training data (Lesson 3.4).
3. Compare with a naive forecast. A model must beat the best simple alternative, not just look good in isolation.
The standard measures, all computed out of sample:
Out-of-sample R²: R²oos = 1 − Σ(r − r̂)² ÷ Σr², where r̂ is the model's forecast. It compares the model with a forecast of zero, which is a hard benchmark to beat for returns, since the mean return is tiny compared with the noise. It can be negative, which means the model is worse than predicting nothing.
Information coefficient (IC): the average over months of the correlation between the forecast and the realised return across stocks (or the rank correlation).
Decile spread: the average return of the stocks with the highest forecasts minus that of the lowest, and its Sharpe ratio, before and after the costs of Lesson 4.4.
A hierarchy of baselines
Before any complicated model, build a ladder, and climb a rung only when the evidence says to.
Rung · Model · Why it matters0 · A naive forecast (zero, or the historical mean) · The benchmark every model must beat
1 · A simple rule on one economically motivated signal · A transparent baseline, and often most of the edge
2 · A linear regression on a handful of features chosen in advance · Few parameters, easy to understand and to monitor
3 · A regularised linear model on many features (ridge, lasso, elastic net) · Uses many features while shrinking the noisy ones
4 · Trees, random forests or gradient boosting · Capture non-linear effects and interactions
5 · Neural networks · Maximum flexibility, maximum risk of overfitting at this signal-to-noise ratio
A model on a higher rung must beat the one below it out of sample, by more than noise, and after the multiple-testing deflation of Lesson 3.4 for the number of models you tried. A more complex model also has costs the R² does not show: it is harder to monitor, to explain to a risk committee and to diagnose when it stops working.
A simulation: five models, one market
To see this in practice, we built a market in which we know the truth. There are 200 stocks and 180 months. Each stock-month has 60 features, drawn as independent standard normals. The next-month return is a predictable component plus noise with a standard deviation of 10 per cent. The predictable component depends on only three of the 60 features, linearly, plus a small interaction between the first two. It is scaled to a standard deviation of 0.65 per cent. The other 57 features are pure noise: they look like candidate signals but carry no information. The ceiling for R² is therefore 0.42 per cent.
We trained each model on an expanding window, starting with 60 months of data and re-fitting every 12 months, and measured performance on the following months, for a total of 120 out-of-sample months. We repeated the experiment in three simulated worlds and averaged.
Model · Out-of-sample R² · Mean monthly IC · Long-short decile Sharpe ratio (before costs)Naive forecast of zero · 0 · 0 · 0
Linear regression on the three features that matter · 0.395% · 0.064 · 2.32
Linear regression on all 60 features · 0.140% · 0.051 · 1.82
Ridge regression on all 60 features · 0.251% · 0.051 · 1.78
Random forest on all 60 features · 0.133% · 0.041 · 1.69
Small neural network on all 60 features · −0.361% · 0.004 · 0.13
(The Sharpe ratios are for a 200-stock universe with independent noise, no costs and no market impact, and are much higher than anything achievable in practice. Compare them with each other, not with the real world.)
Several lessons are visible.
A good prior is worth a lot. The linear model on the three right features reaches 0.395 per cent, 94 per cent of the ceiling. It is cheating, in the sense that it knows which three features to use. In practice that knowledge is what economic theory and research experience provide, and it is why a small set of well-motivated features is usually the first rung.
Noise features cost accuracy. The same linear model on all 60 features falls to 0.14 per cent, because it fits the 57 noise features, adding variance to its forecasts.
Regularisation helps. Ridge regression, which shrinks coefficients towards zero, recovers about half of the loss: 0.25 per cent.
The forest does no better than the linear model here. With a truth that is almost linear and a tiny signal, flexibility finds little to add. It would help more if the real structure had strong non-linearities or interactions.
The neural network is worse than predicting zero. Its R² is −0.36 per cent: its forecasts add more noise than information, and the IC of 0.004 is indistinguishable from nothing. It has hundreds of parameters, trained on at most 36,000 noisy observations.
Why noise features cost so much: an estimate
There is a simple formula. A linear model with p features, fitted on N observations, has a fitted-coefficient error that adds approximately p × σe² ÷ N to the variance of its forecasts, where σe² is the variance of the noise. This is the price of variance: the more parameters per observation, the more of the noise the model memorises.
Take our market. The noise variance is 0.01 (a standard deviation of 10 per cent), p = 60 and the training window averages about 24,000 observations over the experiment (200 stocks times an average of 120 months). The excess error is 60 × 0.01 ÷ 24,000 = 2.5 × 10⁻⁵, against a signal variance of 4.2 × 10⁻⁵. The model gives up about 60 per cent of its signal: out-of-sample R² is roughly (4.2 − 2.5) × 10⁻⁵ ÷ 1.0 × 10⁻² = 0.17 per cent, close to the 0.14 per cent we measured. Early in the experiment, with only 12,000 observations, the cost is 5 × 10⁻⁵, more than the signal, and the R² is negative. In a world with such a weak signal, adding features can only help if they carry more signal than the noise they cost.
Use the calculator below to see how the number of features, the amount of data and the strength of the signal interact.
Regularisation, ensembles and other ways to reduce variance
When there are many features and little signal, the aim is to cut variance while keeping the signal. Standard tools:
Shrinkage. Ridge regression penalises large coefficients, lasso sets many exactly to zero, elastic net mixes the two. The strength of the penalty is chosen on a validation period.
Feature selection with a prior. Start from features with an economic reason (Lesson 3.3), and add others only if they pass an out-of-sample test.
Ensembling. Average the forecasts of several different models. Their errors partly cancel, and the average usually beats the typical member.
Early stopping and small models. For trees, limit the depth. For neural networks, use small layers, weight decay, dropout and stop training when the validation error stops improving.
Pooling. Fit one model across many stocks and many months, not one per stock.
Targets that are less noisy. A rank or a market-adjusted return is less affected by outliers than a raw return.
Where machine learning has earned its place
Machine learning is not a failure in finance. The research of Gu, Kelly and Xiu found that models with flexible interactions improved forecasts of individual stock returns compared with linear models, with the gains concentrated where there was more structure to find: in larger, more liquid stocks, and in interactions between features. The practical gains are real but smaller than in image or language tasks, and they need care. Typical uses that have worked:
Combining many weak signals into one forecast, with shrinkage.
Finding interactions, such as momentum working differently in high-volatility stocks.
Dealing with messy data, text, images and large cross-sections (Lessons 10.2 and 10.4).
Forecasting things other than the return: volatility, trading volume, the cost of trading, the probability that a signal decays. These targets have more signal and are easier to predict.
Pitfalls specific to machine learning
Leakage. Any use of future information: standardising features with the whole sample, using a target built from overlapping returns without purging (Lesson 4.3), or features measured after the prediction date. The sign of a leak is a result that is too good.
Non-stationarity. Relationships change, so a model trained on the whole past may be worse than one trained on a recent window, and retraining schedules matter.
Multiple testing. If you tried 20 models, 5 feature sets and 10 hyperparameter settings, you ran a thousand experiments (Lesson 3.4).
Evaluating on statistics but not on economics. A model with a high R² and high turnover may lose after costs (Lesson 4.4). Test the portfolio, not just the forecast.
Overinterpreting importance. The features a model uses are not necessarily the ones that cause returns, and importances can be unstable when features are correlated.
Survivorship and look-ahead in the data (Lesson 3.1), which are as damaging in a machine-learning pipeline as anywhere.
Black boxes in production. A model you cannot explain is hard to monitor and to trust when it starts losing (Lesson 12.2).
A workflow
1. Define the target and the horizon, and fix the evaluation (out-of-sample R², IC, spread, after costs) before looking at any result.
2. Build features that use only past information, standardised within each date.
3. Start with rungs 0, 1 and 2, and record their out-of-sample results.
4. Add complexity one rung at a time, tuning on a validation period only, and keep a record of every model tried.
5. Accept a more complex model only if it beats the simpler one out of sample, by more than the noise, after allowing for the number of models tried.
6. Test the portfolio built from the forecast, with costs, constraints and capacity.
7. Monitor the live forecast against its backtested behaviour, and plan for decay.
The code below builds the simulated market, implements ridge regression, a small random forest and a small neural network from scratch, and runs the expanding-window comparison. It takes about a minute.
python
import numpy as np
from math import sqrt
NSTOCKS, T, P = 200, 180, 60
SIGNALSD, NOISESD = 0.0065, 0.10
def makepanel(seed):
"""60 features per stock-month. Only the first three carry a signal (plus a small interaction)."""
rng = np.random.defaultrng(seed)
X = rng.standardnormal((T, NSTOCKS, P))
signal = X[:, :, 0] 1.0 + X[:, :, 1] 0.8 + X[:, :, 2] -0.6 + 0.5 X[:, :, 0] X[:, :, 1]
signal = signal / signal.std() SIGNALSD
y = signal + rng.normal(0, NOISESD, (T, NSTOCKS))
return X, y
def ols(X, y, lam=0.0):
Xb = np.c[np.ones(len(X)), X]
penalty = lam np.diag(np.r[0, np.ones(X.shape[1])]) # ridge penalty, not on the intercept
w = np.linalg.solve(Xb.T @ Xb + penalty, Xb.T @ y)
return lambda Z: np.c[np.ones(len(Z)), Z] @ w
def fittree(Xb, y, depth, minleaf=100, nbins=16):
"""A regression tree on binned features, choosing at each node the split that most reduces squared error."""
def build(idx, d):
if d == 0 or len(idx) < 2 minleaf:
return ("leaf", y[idx].mean())
total, n0, best = y[idx].sum(), len(idx), None
for j in range(Xb.shape[1]):
cnt = np.bincount(Xb[idx, j], minlength=nbins)
s = np.bincount(Xb[idx, j], weights=y[idx], minlength=nbins)
cl, sl = np.cumsum(cnt)[:-1], np.cumsum(s)[:-1]
cr, sr = n0 - cl, total - sl
ok = (cl >= minleaf) & (cr >= minleaf)
if not ok.any():
continue
gain = np.where(ok, sl 2 / np.maximum(cl, 1) + sr 2 / np.maximum(cr, 1) - total 2 / n0, -1)
k = gain.argmax()
if best is None or gain[k] > best[0]:
best = (gain[k], j, k)
if best is None or best[0] <= 0:
return ("leaf", y[idx].mean())
, j, k = best
return ("split", j, k, build(idx[Xb[idx, j] <= k], d - 1), build(idx[Xb[idx, j] > k], d - 1))
return build(np.arange(len(y)), depth)
def treepredict(node, Xb):
out = np.empty(len(Xb))
def walk(node, idx):
if node[0] == "leaf":
out[idx] = node[1]
return
, j, k, left, right = node
m = Xb[idx, j] <= k
walk(left, idx[m]); walk(right, idx[~m])
walk(node, np.arange(len(Xb)))
return out
def fitforest(X, y, ntrees=40, depth=3, seed=0, nbins=16):
rng = np.random.defaultrng(seed)
edges = np.quantile(X, np.linspace(0, 1, nbins + 1)[1:-1], axis=0)
binned = lambda Z: np.stack([np.searchsorted(edges[:, j], Z[:, j]) for j in range(Z.shape[1])], axis=1)
Xb, trees = binned(X), []
for in range(ntrees): # each tree sees a bootstrap sample and a third of the features
rows = rng.integers(0, len(X), len(X) // 2)
cols = rng.choice(X.shape[1], X.shape[1] // 3, replace=False)
trees.append((cols, fittree(Xb[rows][:, cols], y[rows], depth)))
return lambda Z: np.mean([treepredict(t, binned(Z)[:, c]) for c, t in trees], axis=0)
def fitmlp(X, y, hidden=(16, 8), epochs=40, lr=0.01, l2=1e-4, seed=0):
rng = np.random.defaultrng(seed)
scale = y.std()
target = y / scale
sizes = [X.shape[1], hidden, 1]
W = [rng.normal(0, sqrt(2 / sizes[i]), (sizes[i], sizes[i + 1])) for i in range(len(sizes) - 1)]
B = [np.zeros(sizes[i + 1]) for i in range(len(sizes) - 1)]
for in range(epochs):
order = rng.permutation(len(X))
for s in range(0, len(X), 256):
b = order[s:s + 256]
acts = [X[b]]
for i in range(len(W) - 1):
acts.append(np.maximum(acts[-1] @ W[i] + B[i], 0))
out = (acts[-1] @ W[-1] + B[-1]).ravel()
g = (out - target[b])[:, None] / len(b)
for i in range(len(W) - 1, -1, -1): # backpropagation
gW, gB = acts[i].T @ g + l2 W[i], g.sum(axis=0)
if i > 0:
g = (g @ W[i].T) (acts[i] > 0)
W[i] -= lr gW; B[i] -= lr gB
def predict(Z):
a = Z
for i in range(len(W) - 1):
a = np.maximum(a @ W[i] + B[i], 0)
return (a @ W[-1] + B[-1]).ravel() scale
return predict
models = {
"zero forecast": lambda Xt, yt: (lambda Z: np.zeros(len(Z))),
"linear, 3 right features": lambda Xt, yt: (lambda f: (lambda Z: f(Z[:, :3])))(ols(Xt[:, :3], yt)),
"linear, all 60": lambda Xt, yt: ols(Xt, yt),
"ridge, all 60": lambda Xt, yt: ols(Xt, yt, lam=len(Xt) 0.5),
"random forest": lambda Xt, yt: fitforest(Xt, yt),
"small neural network": lambda Xt, yt: fitmlp(Xt, yt),
}
def evaluate(seed, train0=60, refit=12):
X, y = makepanel(seed)
preds = {k: [] for k in models}
ic = {k: [] for k in models}
spread = {k: [] for k in models}
actual = []
for start in range(train0, T, refit): # expanding window, refit every 12 months
Xt, yt = X[:start].reshape(-1, P), y[:start].ravel()
fitted = {k: f(Xt, yt) for k, f in models.items()}
for t in range(start, min(start + refit, T)):
actual.append(y[t])
for k, f in fitted.items():
p = f(X[t])
preds[k].append(p)
if k != "zero forecast":
ic[k].append(np.corrcoef(p, y[t])[0, 1])
order, q = np.argsort(p), NSTOCKS // 10
spread[k].append(y[t][order[-q:]].mean() - y[t][order[:q]].mean())
actual = np.array(actual)
out = {}
for k in models:
r2 = 1 - ((actual - np.array(preds[k])) 2).sum() / (actual 2).sum()
s = np.array(spread[k]) if k != "zero forecast" else np.array([0.0, 1.0])
out[k] = (100 r2, float(np.mean(ic[k])) if ic[k] else 0.0, s.mean() / s.std() sqrt(12) if k != "zero forecast" else 0.0)
return out
print("ceiling for R-squared:", round(100 SIGNALSD 2 / (SIGNALSD 2 + NOISESD 2), 3), "%")
worlds = [evaluate(seed) for seed in (1, 2, 3)]
print(f"{'model':<26}{'OOS R2 (%)':>12}{'mean IC':>10}{'L/S Sharpe':>12}")
for k in models:
r2, ic, sh = (np.mean([w[k][i] for w in worlds]) for i in range(3))
print(f"{k:<26}{r2:>12.3f}{ic:>10.4f}{sh:>12.2f}")
the price of variance for the linear model on all 60 features
p, n = P, 24000
print("expected excess error p noise^2 / N =", p NOISESD 2 / n, "| signal variance", SIGNALSD 2)
Common mistakes
Expecting an R² of several per cent. In monthly returns the ceiling is a fraction of one per cent, and more is a leak.
Starting with the most complex model. Without a simple baseline, you cannot tell whether complexity helped.
Using a random train-test split. The future leaks into the past, and the result is flattering and false.
Tuning on the test period. It then becomes a validation period, and the reported result is optimistic.
Adding features without a reason. Each noise feature costs variance, and at this signal-to-noise ratio the cost can exceed the signal.
Trusting feature importance as causation. Correlated features share credit unpredictably.
Forgetting costs and turnover. A forecast with a high IC but a fast-changing ranking can be unprofitable.
Ignoring the number of models tried. Deflate the best result for the search, as in Lesson 3.4.
Treating a neural network's flexibility as an advantage when there is little signal. It is an advantage only with enough data and enough structure.
Key takeaways
Return prediction is a panel problem: features known at month t, target the next month's return. The signal is tiny: with a predictable component of 0.65 per cent against 10 per cent noise, the highest possible R² is 0.42 per cent, and R² is about IC².
Measure out of sample, split by time, tune on validation data only, and compare with a naive forecast of zero. Report out-of-sample R², IC and the decile spread after costs.
Build a ladder of baselines from a naive forecast through a simple rule, a small linear model, a regularised model, trees and neural networks, and climb only when the next rung beats the last out of sample by more than noise.
In the simulated market, a linear model on the three right features reached 0.395 per cent, all 60 features 0.14 per cent, ridge 0.25 per cent, a forest 0.13 per cent and a neural network −0.36 per cent.
Extra features cost variance of about p × σe² ÷ N. With 60 features, noise of 10 per cent and 24,000 observations that is 2.5 × 10⁻⁵ against a signal variance of 4.2 × 10⁻⁵.
Regularisation, ensembling, small models and prior-based feature selection reduce variance. Machine learning has earned a place in combining many weak signals, finding interactions, handling text and images, and forecasting easier targets than returns.
Watch for leakage, non-stationarity, multiple testing, economics after costs, unstable importances and unexplainable black boxes.
Check yourself
1. A monthly stock return forecast reports an out-of-sample R² of 4 per cent. What should you suspect?
A leak or an error. The highest plausible R² for monthly stock returns is a fraction of one per cent, so 4 per cent suggests look-ahead, overlapping targets in the test, or a data error.
2. A model has an IC of 0.05. About what R² does that correspond to?
R² ≈ IC² = 0.0025, or 0.25 per cent.
3. You fit a linear model with 100 features on 5,000 observations, where the noise variance is 0.01. What is the approximate variance cost?
p σe² ÷ N = 100 × 0.01 ÷ 5,000 = 0.0002. That is larger than the signal variance of about 4 × 10⁻⁵, so the model will be worse than predicting zero.
4. A researcher tried 8 models and 6 hyperparameter settings each, and reports the best out-of-sample result. What should the reader ask?
How many experiments were run (48), and whether the best result beats what luck alone would give from that many tries (Lesson 3.4), preferably on a clean test period used once.
5. Why might ridge regression beat ordinary least squares on 60 features here?
It shrinks the coefficients of the noise features towards zero, reducing the variance that the noise adds, at the cost of a small bias on the true signal.
Try this
Set aside forty-five minutes. You need Python and the code in this lesson.
1. Run the code. Check that you see an R² near 0.4 per cent for the right-feature model and a negative one for the neural network.
2. Change the number of noise features from 57 to 20 by setting P = 23. How does the linear model on all features change, and why?
3. Double the signal standard deviation to 0.013. Which model gains most?
4. Add a strong non-linear effect to the truth, such as a term that is the square of the first feature. Does the random forest now beat the linear models?
5. Make the relationship change over time, with the sign of the second feature's effect flipping halfway. Compare an expanding window with a rolling window of 36 months.
6. Use the calculator to find the number of observations at which the cost of 60 features equals half the signal.
7. Write a short note: a ladder of baselines for a return-forecasting problem you care about, and the out-of-sample evidence you would need to accept each rung.
A good result is a table of out-of-sample R², IC and spread for a ladder of models, with the simple baselines clearly beating the complex ones in a noisy world, and a clear statement of what evidence would justify more complexity. The next lesson looks at a different kind of input: data that is not prices, from satellite images to news text.
Read more
Shihao Gu, Bryan Kelly and Dacheng Xiu's 2020 paper "Empirical Asset Pricing via Machine Learning" in the Review of Financial Studies is the main benchmark study. Marcos López de Prado's Advances in Financial Machine Learning and Machine Learning for Asset Managers cover leakage, cross-validation and the pitfalls of applying machine learning to finance. Trevor Hastie, Robert Tibshirani and Jerome Friedman's The Elements of Statistical Learning is the standard text on ridge, lasso, trees and the bias–variance trade-off. Stefan Nagel's Machine Learning in Asset Pricing gives a concise and critical overview, and Campbell Harvey and Yan Liu's papers on multiple testing explain the deflation of the best of many results.
Tags: Quant Trader Machine learning Return prediction Out-of-sample R-squared Baselines