Momentum, and the two rules it usually needs
Time-series momentum is one of the most durable facts in markets: an asset that has gone up over the past year tends to keep going up over the next month, and one that has fallen tends to keep falling. It is time-series momentum because each market is judged against its own past, not against its peers. Moskowitz, Ooi & Pedersen (2012) documented it across dozens of futures — equities, bonds, currencies, commodities — and a whole industry of trend-following funds runs on it.
To turn that fact into a strategy you make two decisions by hand. First a trend estimator: is this market trending up or down? The classic answer is the sign of the past year's return — up means long, down means short. Second a position-sizing rule: how large a bet? Here the standard trick is volatility scaling — divide the position by the market's recent volatility so a calm market and a wild one contribute the same risk. A quiet bond future gets levered up; a thrashing energy contract gets sized down. Everything is measured in Sharpe ratio: the average return divided by its standard deviation (annualised), i.e. reward per unit of risk. A Sharpe of 1 is a good strategy; a Sharpe of 2 is a great one.
The paper's idea: learn the position, optimise Sharpe directly
Lim, Zohren & Roberts ask a simple question: why hand-pick either rule? Keep the volatility-scaling layer — it is just good risk hygiene — but replace the trend estimator and the sizing rule with a network that maps recent returns straight to a position in (−1, 1). They call the result a Deep Momentum Network. The clever part is the training objective. Instead of teaching the network to forecast returns (a regression) and hoping good forecasts make a good strategy, they train it on the thing they actually care about: the Sharpe ratio of its own P&L. The Sharpe ratio is a smooth function of the positions, so you can differentiate it and backpropagate.
# The captured return of ONE trade. The network outputs a position in (-1, 1); the
# volatility-scaling layer then levers it so every asset carries the same risk.
def captured_return(position, sigma, r_next, sigma_tgt=0.15):
return position * (sigma_tgt / sigma) * r_next # sigma = annualised vol
# The loss the network MINIMISES: the negative annualised Sharpe ratio of its own
# returns. Sharpe is differentiable, so this whole thing backpropagates.
def sharpe_loss(R, ann=252):
mu = mean(R)
var = mean([(x - mu) ** 2 for x in R]) # population variance
return -(mu / sqrt(var)) * sqrt(ann) # maximise Sharpe = minimise -SharpeThat sharpe_loss is the entire innovation. Each trade's captured return is the position, levered to a common 15% volatility target, times the next day's move; the loss is the negative annualised Sharpe of those returns pooled across every asset and day. Gradient descent on it nudges the weights so the whole book's risk-adjusted return goes up. Our site runs the TypeScript above; the Python and C++ are line-for-line translations. To keep the demo honest and fully from-scratch, our “network” is a linear map through a tanh — a single-neuron net — rather than the paper's LSTM, but the loss, the volatility scaling and the gradient are exactly theirs.
It learns — and it beats the hand-made rule
We simulate a panel of 12 trending markets (each a persistent latent drift plus noise, seeded), hand the network normalised trailing returns over 5, 21, 63, 252-day windows, and ascend on the Sharpe loss. From a random start the strategy is worse than a coin flip; within a few dozen steps it has found the trend and the gross Sharpe climbs past 2.80.
Against the textbook baseline — position = sign of the trailing one-year return, then vol-scaled — the learned book is far ahead on gross Sharpe. That reproduces the paper's central “it works” claim, which reported the Sharpe-optimised LSTM roughly doubling the classic strategies before costs.
| strategy | Sharpe (before costs) |
|---|---|
| Long only | 0.738 |
| Sgn(returns) — classic TSMOM | 1.192 |
| MACD trend signal | 0.976 |
| Linear · Sharpe loss | 1.094 |
| MLP · Sharpe loss | 1.383 |
| LSTM · Sharpe loss | 2.804 |
Sgn(returns) rule. These are the paper's numbers; our seeded reproduction shows the same ordering at its own scale.The thing a skim misses: the loss never pays for turnover
Read the objective again. The Sharpe loss is computed on position × scale × return— the gross return of each trade. Nowhere in it is the cost of putting the trade on. So when the optimiser discovers that flipping the position a little more often nudges the Sharpe up a hair, it takes the trade every time; it is never billed. In backtest-world that is free money. In the real world every change in the position crosses a spread and pays a commission.
# What the strategy ACTUALLY earns. u is the vol-scaled position; every day-to-day
# change in it is a trade, and every trade pays a cost c. The Sharpe loss above never
# sees this term — so the optimiser is free to trade as much as it likes.
def net_return(u, u_prev, r_next, c):
return u * r_next - c * abs(u - u_prev)
# The fix the paper adds: penalise squared position changes during training, so the
# network learns to sit still unless the signal is worth the trade.
def objective(R, u_series, lam):
return sharpe(R) - lam * mean_sq_change(u_series) # gradient-ASCEND on thisNow re-score the very same learned strategy, but subtract a cost c on every day-to-day change in the vol-scaled position — the honest P&L a desk would actually book. The result is Fig 3: the gross Sharpe you were admiring is the value at c = 0, and it slides relentlessly as costs rise. At a cost of 0.01 in our units (≈9 basis points on the traded notional, in the paper's convention) the learned book's net Sharpe is down to 1.18; a little past that it crosses the far-cheaper baseline and then goes negative outright.
The fix hiding in one extra term
The paper's remedy is a single addition to the objective: a turnover regularisation term that penalises how much the position moves from one day to the next. Ascend on Sharpe − λ · (turnover) and the network learns to sit still unless the signal genuinely justifies a trade. The striking thing is how cheap this is. Sweeping the penalty λ from zero upward, turnover collapses while the gross Sharpe barely flinches:
λ grows (0 is the unconstrained Sharpe-optimiser, in orange). Trading falls from 6.8 to 1.2 — roughly 5.7× less churn — while gross Sharpe stays put (see the table). Most of that turnover was never buying any return; it was the loss gaming a metric that ignored it.| penalty λ | gross Sharpe | net Sharpe (c = 0.006) | turnover |
|---|---|---|---|
| 0 | 2.80 | 1.18 | 6.8 |
| 10 | 2.80 | 1.15 | 4.8 |
| 30 | 2.80 | 1.16 | 3.3 |
| 100 | 2.79 | 1.22 | 2.5 |
| 300 | 2.79 | 1.17 | 1.7 |
| 800 | 2.79 | 1.19 | 1.2 |
λ from 0 to 100 cuts trading from 6.8 to 2.5 while gross Sharpe moves only from 2.80 to 2.79 — and the net Sharpe you actually keep is unchanged or better. In the paper's costed backtest the same term is the difference between a Sharpe of −5.31 and +0.91.What I'd probe next
- Cost-aware training, not cost-aware scoring. The cleanest version puts the real cost inside the loss, so the network optimises net Sharpe end-to-end. That couples consecutive days through the
|Δu|term — a small but real change to the gradient, and the honest way to let the model decide when an edge clears the spread. - Does the edge survive an LSTM's memory — or just overfit it? Our single neuron can only take a fixed, linear view of the trailing windows. The paper's LSTM can condition on the path, which is where its extra gross Sharpe comes from — and also, plausibly, where its catastrophic turnover comes from. More capacity, more churn, more to regularise.
- The Sharpe loss is fragile in small samples. Optimising a ratio of two noisy estimates (mean over standard deviation) rewards any lucky low-variance streak. A walk-forward split — train on the past, score on the untouched future — is the only test that separates a learned edge from a fitted one.
The mechanism at the heart of this is just gradient descent on a differentiable objective — the same loop behind every model on this site. Go turn the knobs on the neural-network page and picture the loss not as squared error but as a Sharpe ratio. Deep Momentum Networks are a genuinely elegant idea: let the strategy optimise the thing you care about. Just make sure the thing you care about is the number you actually get to keep.
References
- Lim, B., Zohren, S., & Roberts, S. (2019). Enhancing Time Series Momentum Strategies Using Deep Neural Networks. The Journal of Financial Data Science 1(4), 19–38 · arXiv:1904.04912
- Moskowitz, T. J., Ooi, Y. H., & Pedersen, L. H. (2012). Time Series Momentum. Journal of Financial Economics 104(2), 228–250
- Baz, J., Granger, N., Harvey, C. R., Le Roux, N., & Rattray, S. (2015). Dissecting Investment Strategies in the Cross Section and Time Series. SSRN 2695101