Skip to content
Finance Lessons

Deep Learning for Market Data

Recurrent Models: RNN, LSTM & GRU

Reading a market sequence one step at a time — the recurrence and the unrolling mental model, the vanishing- and exploding-gradient problem that cripples plain RNNs, and how LSTM and GRU gating build an information highway that survives a long window.

16 min Updated Jun 19, 2026

Last lesson we watched a multilayer perceptron stumble in finance: with a tiny effective sample size (a few hundred non-overlapping regimes hiding behind thousands of autocorrelated rows), it either memorized noise or learned nothing. But the MLP had a deeper sin — it treated each day as an unordered bag of features. It didn’t know that yesterday came before today.

Markets are sequences. Returns arrive in order, volatility clusters (your GARCH lesson), and yesterday’s shock leaks into today. The natural fix is a model that reads the series one step at a time, carrying a running memory — a summary of everything it has seen so far. That is exactly what a recurrent neural network does. Whether it helps more than it hurts is the question we’ll keep poking at.

The recurrent cell and unrolling

Before you read — take a guess

A 'recurrent' cell processes a length-T sequence. How many distinct sets of trainable weights does it use across the T steps?

A recurrent cell keeps a hidden state hth_t — its mental summary so far — and updates it as each new input xtx_t arrives:

ht=tanh(Wxt+Uht1+b)h_t = \tanh\left(W x_t + U h_{t-1} + b\right)

Read it left to right: the new input xtx_t gets transformed by WW, the old memory ht1h_{t-1} gets transformed by UU, you add a bias bb, and squash through tanh\tanh to keep values in [1,1][-1, 1]. An output (say, a return prediction) is read off hth_t via another linear layer. The matrices WW, UU, bb are the same at every step — they are the cell, and the cell is the model.

Analogy. You’re reading a sentence. You don’t re-read the whole paragraph at every word — you keep a running gist in your head and update it with each new word. ht1h_{t-1} is your gist before the current word, xtx_t is the word, hth_t is your updated gist. “Unrolling” is just drawing that one reader as a chain of copies, one per word — but it’s still one reader, not a committee of TT readers.

Worked example

Take a scalar toy cell: W=0.5W = 0.5, U=0.8U = 0.8, b=0b = 0, and a sequence of returns x1=1.0x_1 = 1.0, x2=0.5x_2 = -0.5. Start h0=0h_0 = 0.

  • Step 1: h1=tanh(0.51.0+0.80)=tanh(0.5)0.462h_1 = \tanh(0.5 \cdot 1.0 + 0.8 \cdot 0) = \tanh(0.5) \approx 0.462.
  • Step 2: h2=tanh(0.5(0.5)+0.80.462)=tanh(0.25+0.370)=tanh(0.120)0.119h_2 = \tanh(0.5 \cdot (-0.5) + 0.8 \cdot 0.462) = \tanh(-0.25 + 0.370) = \tanh(0.120) \approx 0.119.

Notice h2h_2 depends on x1x_1 only through h1h_1 — the memory is the channel through which the past reaches the present. Break that channel and history disappears.

Unrolling a recurrent model — and watching the past fade
x1h1x2h2x3h3x4h4x5h5x6h6x7h7x8h8Input xₜHidden state hₜŷ
Step-1 info surviving at h_T3.5%

One cell, unrolled over T steps: each input x_t feeds the hidden cell h_t, which also receives an arrow from h_{t-1}. Shading shows how much of the FIRST input survives down the chain. Toggle Vanilla RNN vs Gated and drag the length slider.

Pick a term, then click its definition.

Warning:

Unrolling does not mean more parameters

A common beginner mistake: “I’ll unroll over 60 days, so my model has 60× the capacity.” No. Unrolling is a visualization of one shared cell applied 60 times. The parameter count is fixed regardless of sequence length. What grows with TT is the computation graph (and the memory needed to backprop through it), not the number of weights.

When to use it

Reach for recurrence when order genuinely carries information and the same local update rule should apply at every step: tick-by-tick order-flow, intraday bars, a rolling window of returns feeding a next-step forecast. If your features are an unordered cross-section (today’s value, momentum, quality scores for 500 stocks), recurrence buys you nothing — that’s a job for the MLP or, more likely, gradient-boosted trees.

The vanishing (and exploding) gradient

Before you read — take a guess

Training an RNN means backpropagating the loss back through every time step. What mathematical operation accumulates as the gradient travels from step T back to step 1?

To train, we backpropagate the loss through the unrolled chain — backpropagation through time (BPTT). The chain rule says the gradient of a late loss with respect to an early hidden state is a product of one Jacobian per step:

hTht=k=t+1Thkhk1\frac{\partial h_T}{\partial h_t} = \prod_{k=t+1}^{T} \frac{\partial h_k}{\partial h_{k-1}}

If each factor has magnitude roughly ρ\rho, the whole product scales like ρTt\rho^{\,T-t}. Products of many numbers are unforgiving: a little under 1 and they collapse toward zero (vanishing); a little over 1 and they detonate (exploding).

Analogy. A whispered rumor passed down a line of people. If each person preserves 60% of the message, after 20 people almost nothing of the original survives — that’s vanishing. If each person exaggerates it 1.2× , after 20 people you’ve got a screaming conspiracy theory — that’s exploding.

Worked example

Suppose the typical per-step factor is ρ=0.6\rho = 0.6. Over T=20T = 20 steps the gradient to the first input scales by

0.620=e20ln0.6=e20(0.511)=e10.23.6×105.0.6^{20} = e^{20 \ln 0.6} = e^{20 \cdot (-0.511)} = e^{-10.2} \approx 3.6 \times 10^{-5}.

The distant past contributes essentially nothing to the weight update — the model is structurally blind to dependencies older than a handful of steps. Now flip it: ρ=1.2\rho = 1.2 gives 1.22038.31.2^{20} \approx 38.3, and over 60 steps 1.2605.6×1041.2^{60} \approx 5.6 \times 10^{4} — a gradient that overflows and produces NaN losses.

Per-step factor ρ\rhoAfter 10 stepsAfter 20 stepsAfter 60 stepsVerdict
0.66.0×1036.0\times10^{-3}3.6×1053.6\times10^{-5}4.7×10144.7\times10^{-14}vanishes
0.90.3490.1221.8×1031.8\times10^{-3}fades
1.01.01.01.0knife-edge
1.26.1938.35.6×1045.6\times10^{4}explodes

The exploding case has a cheap patch — gradient clipping: if the gradient’s norm exceeds a threshold τ\tau, rescale it to τ\tau before the update. That caps the blow-up. Vanishing has no such patch; you can’t rescale a near-zero gradient back into something informative, because the signal is gone, not just the magnitude. That asymmetry is why the architecture itself had to change.

Fill in the failure mode and its remedy.

Pick the right option for each blank, then check.

When the per-step Jacobian magnitude sits below 1, the BPTT gradient geometrically with depth, which ; the above-1 case is tamed by .

Warning:

Long windows don't mean long memory

Feeding a plain RNN a 250-day window does not give it 250 days of memory. With ρ<1\rho < 1, its effective memory is only a few steps; the rest of the window is decoration that slows training and adds variance. If you need long-range dependence, the window length is not the lever — the cell architecture is.

Look back at the hero island: toggle Vanilla RNN and drag the length slider out. Watch the shading of the first input’s contribution fade to near-black within a dozen steps — that’s ρT\rho^{T} rendered as opacity.

When to use it

A vanilla RNN is fine when the genuine dependency is short (a few steps) and you want a cheap, low-parameter sequence model — sometimes a virtue on tiny finance samples. The moment you suspect dependence spanning dozens of steps (a slow regime shift, a multi-day vol build-up), the vanilla RNN is the wrong tool, and you graduate to a gated cell.

LSTM: the cell-state highway

Before you read — take a guess

What is the key structural change in an LSTM that lets gradients survive long sequences?

The Long Short-Term Memory cell keeps two states: the usual hidden state hth_t and a separate cell state ctc_t — a protected conveyor belt for long-term information. Three sigmoid gates (each outputs values in [0,1][0,1], i.e. “how much to let through”) regulate it:

  • Forget gate ftf_t — how much of the old cell state to keep.
  • Input gate iti_t — how much of the new candidate to write.
  • Output gate oto_t — how much of the cell state to expose as hth_t.

The candidate is c~t=tanh(Wcxt+Ucht1+bc)\tilde{c}_t = \tanh(W_c x_t + U_c h_{t-1} + b_c), and the cell state updates additively:

ct=ftct1+itc~t,ht=ottanh(ct)c_t = f_t \odot c_{t-1} + i_t \odot \tilde{c}_t, \qquad h_t = o_t \odot \tanh(c_t)

(\odot is elementwise multiplication.) That addition is the whole trick. The path from ct1c_{t-1} to ctc_t is roughly multiply by ftf_t, and if the cell learns ft1f_t \approx 1, the gradient along the belt is multiplied by ~1 each step instead of ~ρ<1\rho < 1. The information highway stays open.

Analogy. The vanilla RNN forces every memory through a lossy tanh mixer at every step — like photocopying a photocopy. The LSTM adds a sealed envelope (the cell state) you can choose to carry forward untouched, only opening it (output gate) or editing it (input gate) when you decide to.

Worked example

Compare the long-range survival of a vanilla factor versus an LSTM forget gate. Suppose the cell learns ft=0.97f_t = 0.97. Over 20 steps the cell-state contribution scales by

0.9720=e20ln0.97=e20(0.03046)=e0.6090.54.0.97^{20} = e^{20 \ln 0.97} = e^{20 \cdot (-0.03046)} = e^{-0.609} \approx 0.54.

Roughly half the signal survives 20 steps. The vanilla RNN’s 0.6203.6×1050.6^{20} \approx 3.6\times10^{-5} has already vanished by six orders of magnitude. Push to 60 steps: 0.97600.160.97^{60} \approx 0.16 still meaningful, versus 0.6604.7×10140.6^{60} \approx 4.7\times10^{-14} — dead.

Steps TTVanilla 0.6T0.6^{T}LSTM 0.97T0.97^{T}
50.0780.859
203.6×1053.6\times10^{-5}0.544
604.7×10144.7\times10^{-14}0.161
1202.2×10272.2\times10^{-27}0.026

The forget gate isn’t fixed at 0.97 — it’s learned per step, so the cell can hold a memory open across a regime and slam it shut when the regime breaks. That adaptivity is what raw ρT\rho^T can never do.

Sort each piece by whether it belongs to a vanilla RNN or is new in the LSTM.

Place each item in the right group.

  • Shared weights across time
  • Learned per-step retention near 1
  • Additive cell state c_t
  • Forget, input and output gates
  • Single hidden state h_t through tanh
Warning:

Gates cost parameters — and parameters cost samples

An LSTM has roughly 4× the recurrent parameters of a vanilla RNN (one weight set per gate plus the candidate). On the small effective sample sizes from lesson 1, that extra capacity is a double-edged sword: it solves vanishing but invites overfitting. A more powerful sequence model on 300 effective observations can still memorize noise — exactly the trap the previous lesson hammered.

When to use it

Use an LSTM when you have genuine long-range dependence and enough data to feed the extra parameters — high-frequency or intraday series where samples are plentiful, or sequences where holding a state open for dozens of steps clearly matters. On a few hundred non-overlapping daily regimes, an LSTM is usually over-armed; regularize aggressively or reach for the leaner cousin below.

GRU: the streamlined cousin

Before you read — take a guess

Versus an LSTM, the Gated Recurrent Unit (GRU) primarily differs by...

The GRU is the LSTM after a diet. It keeps a single state vector (no separate cell state) and uses just two gates:

  • Update gate ztz_t — how much of the old state to keep versus overwrite (it does the forget and input job at once).
  • Reset gate rtr_t — how much past state to let into the new candidate.

h~t=tanh ⁣(Wxt+U(rtht1)+b),ht=(1zt)ht1+zth~t\tilde{h}_t = \tanh\!\big(W x_t + U (r_t \odot h_{t-1}) + b\big), \qquad h_t = (1 - z_t)\odot h_{t-1} + z_t \odot \tilde{h}_t

That convex blend ht=(1zt)ht1+zth~th_t = (1-z_t)h_{t-1} + z_t\tilde{h}_t is the GRU’s highway: when zt0z_t \approx 0 the state is carried almost untouched (long memory), when zt1z_t \approx 1 it’s fully rewritten (react to a shock). Same additive-path trick as the LSTM, fewer moving parts.

Analogy. If the LSTM is a car with separate accelerator, brake, and a sealed glovebox, the GRU is an electric car with one regenerative pedal that does both accelerate and brake. Fewer controls, usually just as good a drive.

Worked example

Parameter count is the headline. For hidden size HH and input size DD, the recurrent blocks need roughly:

CellGate/candidate blocksApprox. parameters
Vanilla RNN1H(D+H)+HH(D+H) + H
GRU3 (reset, update, candidate)3[H(D+H)+H]3\,[H(D+H)+H]
LSTM4 (forget, input, output, candidate)4[H(D+H)+H]4\,[H(D+H)+H]

With D=10D = 10 inputs and H=32H = 32 hidden units, one block is 32(10+32)+32=137632(10+32)+32 = 1376. So GRU 4128\approx 4128 and LSTM 5504\approx 5504 recurrent parameters — the GRU saves ~25%. On a dataset with a few hundred effective observations, every parameter you don’t have is a parameter you can’t overfit. That’s why GRUs frequently match or beat LSTMs on small, noisy financial series despite being “less powerful.”

A GRU has fewer parameters than an LSTM, so it must be strictly weaker. True or false — and why does it matter in finance?

Answer. False. “Weaker” only matters if the extra LSTM capacity is used productively. On finance’s tiny effective samples, the LSTM’s extra parameters more often fit noise than signal. The GRU’s smaller hypothesis space is a built-in regularizer, so it often generalizes better out of sample. Capacity is only an asset when you have the data to discipline it — the central lesson of the previous chapter.

When to use it

Default to the GRU over the LSTM when data is scarce and you want gating without the parameter bloat — which describes most daily-frequency finance problems. Prefer the LSTM only when you have abundant data and empirically find its extra gate earns its keep (sometimes true on very long, information-rich sequences like full limit-order-book streams).

Do RNNs actually help in finance?

Before you read — take a guess

In most daily-frequency financial forecasting tasks, how do LSTMs/GRUs typically fare against gradient-boosted trees?

Here’s the sobering part. Everything above is elegant — and on most finance problems, an RNN still loses to a boosted tree. Why?

  • Tiny effective sample (callback to lesson 1). Recall: thousands of autocorrelated daily rows collapse to a few hundred non-overlapping, independent regimes. Recurrent nets have more parameters than an MLP and the same starved data — overfitting risk goes up, not down.
  • Sequential, un-parallel training. BPTT must process steps in order; you can’t parallelize across the time axis the way a Transformer or a tree-split scan can. Training is slow, which means fewer experiments, which means more overfitting via researcher degrees of freedom (your deflated-Sharpe instincts should be twitching).
  • Trees and TCNs eat your lunch. Gradient-boosted trees devour engineered lag features and are wildly sample-efficient. Temporal Convolutional Networks (dilated causal convolutions) get long receptive fields with full parallelism and often better stability than RNNs.

Where do recurrent nets genuinely earn their keep? Large, structured, higher-frequency data with rich sequential micro-structure: limit-order-book (LOB) sequences, tick streams, intraday event sequences — places where samples are abundant and the order of micro-events truly carries signal. There, the effective sample problem eases and the sequence inductive bias pays off.

Overfitting: backtest soars, live performance collapses
In-sample (backtest)Out-of-sample (live)
Model complexity / strategies triedApparent performance
gap9%

In-sample loss keeps falling as model complexity rises, but out-of-sample loss turns up — and a high-capacity LSTM on a few hundred effective observations sits well to the right of the sweet spot.

Warning:

An RNN's in-sample IC will dazzle you. Ignore it.

A flexible recurrent net can post a gorgeous in-sample information coefficient — and a deflated, purged, out-of-sample IC near zero. Always evaluate with purged, embargoed walk-forward CV (Machine Learning for Alpha), report the deflated Sharpe, and budget your trials. If an RNN can’t beat a regularized GBM on honest out-of-sample numbers, it doesn’t ship.

When to use it

Use a recurrent model in finance when (1) the data is high-frequency or otherwise large in effective sample, (2) sequential micro-structure plausibly carries signal, and (3) you’ve already confirmed a simpler baseline (regularized linear, GBM, TCN) doesn’t dominate. Otherwise, the honest default is: start with trees, and make the RNN prove it adds out-of-sample value. Regularize hard either way — dropout, weight decay, tiny hidden sizes, short windows.

Which of these are legitimate reasons RNNs often underperform on daily-frequency finance data? (Select all that apply.)

Recap

Big picture

Recurrent models for market sequences

  • Recurrent models
    • Recurrent cell
      • h_t = tanh(Wx + Uh_{t-1} + b)
      • Unrolling = one shared cell, T copies
    • Vanishing/exploding
      • BPTT = product of Jacobians ≈ ρ^T
      • ρ<1 vanishes; ρ>1 explodes (clip)
    • LSTM
      • Additive cell state c_t
      • Forget gate ≈1 keeps highway open
    • GRU
      • Reset + update gates, one state
      • Fewer params → less overfit
    • In finance
      • Tiny effective sample; slow BPTT
      • Shines on LOB/high-frequency data
From the shared recurrent cell to gated highways to the finance reality check.

Recurrent models — mixed check

Question 1 of 50 correct

A vanilla RNN's per-step Jacobian magnitude is about 0.9. Roughly what fraction of the gradient survives 20 steps back?

Check your answer to continue.

Mark lesson as complete