Part III · Models · Chapter 3C

Fitting a model

Closed-form solutions and gradient descent

Where we are

Chapter 3B wrote down the AR(1) model and its two parameters. This chapter finds their values from data, either exactly with a formula or step by step with gradient descent, the method under every modern machine learning (ML) library. It ends with regularization, which builds Chapter 3A's preference for simple models into the fit itself.

What you need first
  • The AR(1) model, its weight ww and its bias bb (Chapter 3B).
  • Covariance and correlation as computed from a sample (Chapter 1F).
  • The bias-variance tradeoff and overfitting (Chapter 3A).
In plain terms

Fitting a model means choosing its parameters so that its predictions miss the data by as little as possible. For a straight-line model a formula gives the answer at once. For large models no formula is practical, so the computer starts somewhere, measures which way the error falls most steeply, takes a small step that way and repeats. The size of that step is the setting that most often goes wrong.

1 · The loss: what fitting minimizes

Once a model is specified, you need to fit its parameters: find the weight and bias, in the AR(1) case, that minimize the prediction error on training data. The error is measured by a loss function, most often the mean squared error of Chapter 3A:

L(w,b)=1n∑t=1n(rt−w rt−1−b)2 L(w, b) = \frac{1}{n}\sum_{t=1}^{n} \big(r_t - w\,r_{t-1} - b\big)^2 (3C.1)

Each term is the squared gap between a return and its prediction, averaged over nn pairs of previous return and return.

Worked example: A loss computed by hand

Step 1: the data. Take five hypothetical daily returns, in percent, of 0.6, −1.2, 0.4, 0.2 and 0.6, which make four pairs of previous return and return: (0.6, −1.2), (−1.2, 0.4), (0.4, 0.2) and (0.2, 0.6).

Step 2: the loss at w=0w = 0. With b=0b = 0 as well, every prediction is zero, so the loss is the mean of the squared returns: (1.44+0.16+0.04+0.36)/4=0.5(1.44 + 0.16 + 0.04 + 0.36)/4 = 0.5.

Step 3: the loss at w=−0.5w = -0.5. The gaps rt+0.5 rt−1r_t + 0.5\,r_{t-1} are −0.9, −0.2, 0.4 and 0.7, whose squares average (0.81+0.04+0.16+0.49)/4=0.375(0.81 + 0.04 + 0.16 + 0.49)/4 = 0.375, so the weight of −0.5 fits better.

With b=0b = 0, expanding the squares for these four pairs gives the whole loss curve at once:

L(w)=0.5 (w2+w+1) L(w) = 0.5\,(w^2 + w + 1) (3C.2)

2 · The slope of the loss

To find the best weight you need the slope of the loss curve. The derivative of LL with respect to ww, written dL/dwdL/dw, is the slope of the curve at a point: how fast LL changes per unit change in ww, for a very small change. Three rules cover everything in this chapter: w2w^2 has derivative 2w2w, c wc\,w has derivative cc and a constant has derivative zero. Applied to (3C.2), they give

dLdw=0.5 (2w+1)=w+0.5 \frac{dL}{dw} = 0.5\,(2w + 1) = w + 0.5 (3C.3)

To check it with numbers, note that at w=0w = 0 the formula gives a slope of 0.5. Moving from w=0w = 0 to w=0.01w = 0.01 raises the loss from 0.5 to 0.50505, a rise of 0.00505 for a step of 0.01, which is 0.505 per unit.

At the bottom of the curve the slope is zero, so the best weight solves w+0.5=0w + 0.5 = 0, which gives w=−0.5w = -0.5. With two parameters, the partial derivative ∂L/∂w\partial L/\partial w is the slope in the ww direction with bb held fixed, while ∂L/∂b\partial L/\partial b is the slope in the bb direction. Together they form the gradient, which points in the direction in which the loss rises most steeply.

3 · The closed form: ordinary least squares

For linear regression with squared-error loss there is a one-line analytical solution, ordinary least squares (OLS). Stack the data in a design matrix XX, whose rows are observations and whose columns are features, with a column of ones for the bias. The targets go in a vector yy. The OLS estimator of the vector of weights β\beta is

β^=(X⊤X)−1X⊤y \hat{\beta} = (X^\top X)^{-1} X^\top y (3C.4)

Here X⊤X^\top is the transpose of XX, its rows turned into columns, while (⋅)−1(\cdot)^{-1} is the matrix inverse, the matrix version of dividing. The result is exact and fast for small datasets, which is why it is used inside scikit-learn's LinearRegression, R's lm() and basically every statistical package's linear regression function.

For the AR(1) case with one feature, (3C.4) collapses to a particularly clean form, with bars for sample means:

w^=Cov⁡(rt,rt−1)Var⁡(rt−1)b^=rˉt−w^ rˉt−1 \begin{aligned} \hat{w} &= \frac{\operatorname{Cov}(r_t, r_{t-1})}{\operatorname{Var}(r_{t-1})} \\ \hat{b} &= \bar{r}_t - \hat{w}\,\bar{r}_{t-1} \end{aligned} (3C.5)

That is two numbers, with no iteration and no learning rate: compute the sample covariance and the sample variance, and divide.

Where the AR(1) formulas come from

Step 1: the slope in bb. Write xt=rt−1x_t = r_{t-1} and yt=rty_t = r_t. The derivative of a square u2u^2 is 2u2u times the derivative of uu. Since the derivative of yt−wxt−by_t - w x_t - b with respect to bb is −1, the slope in bb is

∂L∂b=−2n∑t(yt−wxt−b) \frac{\partial L}{\partial b} = -\frac{2}{n}\sum_t \big(y_t - w x_t - b\big) (3C.6)

Step 2: set it to zero. Dividing the sum by nn turns it into means, so yˉ−wxˉ−b=0\bar{y} - w\bar{x} - b = 0, which gives b=yˉ−wxˉb = \bar{y} - w\bar{x}, the second line of (3C.5).

Step 3: the slope in ww. The derivative of yt−wxt−by_t - w x_t - b with respect to ww is −xt-x_t, so

∂L∂w=−2n∑txt(yt−wxt−b) \frac{\partial L}{\partial w} = -\frac{2}{n}\sum_t x_t\big(y_t - w x_t - b\big) (3C.7)

Step 4: substitute and solve. Put in b=yˉ−wxˉb = \bar{y} - w\bar{x}, so each gap becomes (yt−yˉ)−w(xt−xˉ)(y_t - \bar{y}) - w(x_t - \bar{x}). Setting the slope to zero leaves ∑(xt−xˉ)(yt−yˉ)=w∑(xt−xˉ)2\sum (x_t - \bar{x})(y_t - \bar{y}) = w \sum (x_t - \bar{x})^2, where the leading xtx_t has become xt−xˉx_t - \bar{x} because deviations from a mean sum to zero. Dividing both sides by nn turns them into the sample covariance and variance. So ww is their ratio, the first line of (3C.5).

Applied to the five returns, the means of the previous returns and of the returns are both zero. The covariance is (−0.72−0.48+0.08+0.12)/4=−0.25(-0.72 - 0.48 + 0.08 + 0.12)/4 = -0.25 and the variance of the previous returns is (0.36+1.44+0.16+0.04)/4=0.5(0.36 + 1.44 + 0.16 + 0.04)/4 = 0.5. So w^=−0.25/0.5=−0.5\hat{w} = -0.25/0.5 = -0.5 and b^=0\hat{b} = 0, the minimum found from the slope.

Where the closed form breaks

The flaw of OLS is that it requires inverting a matrix of size features × features. While for a model with 5 features and 1,000 observations this is trivial, for a model with 50,000 features and 100 million observations, typical of large-scale machine learning, the matrix inversion becomes infeasible. You cannot load all of the data into memory at once: 100 million rows of 50,000 numbers is 5×10125 \times 10^{12} numbers, 40 terabytes at 8 bytes each. And inversion is O(n3)O(n^3) in the feature count, where nn here counts features: its work grows with the cube of their number. Five features cost on the order of 125 operations, while 50,000 cost on the order of 1.25×10141.25 \times 10^{14}.

4 · Gradient descent

Gradient descent is iterative, repeating four actions at each step:

  1. Compute the current loss on the training data, or on a mini-batch of it.
  2. Compute the gradient of the loss with respect to each parameter.
  3. Take a small step in the direction opposite to the gradient, downhill on the loss surface.
  4. Repeat until the loss stops falling.

The update rule for a parameter ww at step ii is

wi+1=wi−η ∂L∂w w_{i+1} = w_i - \eta\,\frac{\partial L}{\partial w} (3C.8)

where η\eta (eta) is the learning rate, the step size. Too large a learning rate and the optimizer overshoots, oscillates and may diverge entirely, while too small a rate makes convergence painfully slow. Tuning η\eta is part of the art of optimization: getting it right is most of the challenge in training modern machine learning models.

On the five returns, starting at w=0w = 0 with η=0.5\eta = 0.5, the slope is 0.5, so the first step moves ww to 0−0.5×0.5=−0.250 - 0.5 \times 0.5 = -0.25. At the new slope of 0.25 the next step reaches −0.375, halving the remaining distance to −0.5 as every step does.

That halving is no accident, and the figure below shows why. It runs gradient descent on the loss L(w)=(w−0.5)2+0.2L(w) = (w - 0.5)^2 + 0.2, whose minimum is at w=0.5w = 0.5 and whose slope is 2(w−0.5)2(w - 0.5). Subtract 0.5 from both sides of (3C.8) to see what one step does to the distance from the minimum:

wi+1−0.5=(1−2η)(wi−0.5) w_{i+1} - 0.5 = (1 - 2\eta)(w_i - 0.5) (3C.9)

Every step multiplies the distance by 1−2η1 - 2\eta, so starting from w=−1w = -1, a distance of 1.5 from the minimum, the distance after nn steps is 1.5 ∣1−2η∣n1.5\,|1 - 2\eta|^n. Five cases follow from the size of η\eta:

  • Below 0.5, the factor lies between 0 and 1, so the steps shrink and stay on one side.
  • At exactly 0.5, the factor is zero and the first step lands on the minimum.
  • Between 0.5 and 1, the factor lies between −1 and 0, so each step overshoots and the path zigzags inward.
  • At exactly 1, the factor is −1, so the path bounces between −1 and 2 forever.
  • Above 1, the factor is below −1, so each overshoot is bigger than the last and the path diverges.

On the five returns the slope, w+0.5w + 0.5, is the distance from −0.5 itself rather than twice it. There each step multiplies the distance by 1−η1 - \eta, which at η=0.5\eta = 0.5 is the halving you saw.

The figure has two sliders, the learning rate η\eta from 0.01 to 1.2 (starting at 0.05) and the number of steps from 1 to 30 (starting at 15). The readouts give the start, where the last step lands and its distance to the optimum. Try learning rates of 0.01, 0.5, 0.9, 1.0 and 1.1 in turn.

0.05
15
start w = −1.000
end w = 0.191
distance to optimum = 0.3088
Too small a step crawls; too large a step overshoots. Gradient descent on the convex loss L(w) = (w − 0.5)² + 0.2, starting from w = −1, each step moving w by −η × ∂L/∂w. Set the learning rate η and the number of steps; the dots are the iterates, from the start (red) to where the last step lands (green). At η = 0.5 the first step lands exactly on the optimum; above 0.5 every step overshoots it and the path zigzags, and from η = 1 up it never settles: at 1 it bounces between −1 and 2, and above 1 it diverges.

At the starting settings the last step lands at w=0.191w = 0.191, a distance of 0.3088, which is 1.5×0.9151.5 \times 0.9^{15}. At 0.01 the path crawls to −0.608 in 15 steps. At 0.9 it zigzags to 0.553, the same 0.0528 from the optimum that 0.1 reaches from the other side, because both multiply the distance by 0.8 in size. At 1.0 it ends at 2.000, while at 1.1 it is at 23.6, off the chart.

Mini-batches: stochastic gradient descent

The crucial property of gradient descent is that it does not require loading all the data at once. You can compute the gradient on a mini-batch, a small random subset of the training data, and take a step. The mini-batch gradient is a noisy but unbiased estimate of the full one, so the steps wander but head downhill on average. This is stochastic gradient descent (SGD), which scales to arbitrarily large datasets. Modern variants such as Adam (Kingma and Ba, 2014), RMSProp (Tieleman and Hinton, 2012) and SGD with momentum (Polyak, 1964) all build on this idea. Instead of inverting a giant matrix, they take many small steps with smart adaptations.

5 · The shape of the loss

A loss surface is convex when it is bowl-shaped, so that the straight line between any two points on it lies on or above it, as squared error on a linear model does. For convex loss functions, gradient descent converges to the global optimum from any starting point, provided the learning rate is appropriate. Convex problems are easy.

Non-convex loss functions include everything in deep learning, plus most realistic trading objectives. On them, gradient descent converges to some local minimum that depends on the starting point. Real-world loss surfaces have four hazards:

  • Local minima: points where every nearby direction increases the loss, but the global optimum lies elsewhere.
  • Saddle points: points where some directions go up and others go down. These are vastly more common than local minima in high-dimensional spaces.
  • Plateaus: large regions where the gradient is nearly zero, slowing progress dramatically.
  • Cliffs: regions where the gradient is enormous, causing standard SGD to take huge destabilizing steps.

6 · Modern optimizers

The plain SGD update is rarely used in practice, because modern variants address its specific failure modes.

SGD with momentum (Polyak, 1964) accumulates a running average of past gradients and uses it to dampen oscillation and escape saddle points. With vv the velocity and γ\gamma (gamma) a number between 0 and 1:

vi+1=γ vi+η ∂L∂wwi+1=wi−vi+1 \begin{aligned} v_{i+1} &= \gamma\,v_i + \eta\,\frac{\partial L}{\partial w} \\ w_{i+1} &= w_i - v_{i+1} \end{aligned} (3C.10)

The velocity keeps you moving in directions that have consistently pointed downhill, even when the current local gradient is noisy. Under a steady gradient gg, the velocity settles where v=γv+ηgv = \gamma v + \eta g, that is at v=ηg/(1−γ)v = \eta g/(1 - \gamma). With γ=0.9\gamma = 0.9, that is 10 ηg10\,\eta g: steps ten times the plain ones along a consistent slope.

AdaGrad (Duchi et al., 2011) adapts the learning rate per parameter, dividing it by the square root of the running sum of squared past gradients. Parameters that have already seen big updates get smaller updates from then on.

RMSProp (Tieleman and Hinton, 2012) is like AdaGrad but uses an exponential moving average instead of a cumulative sum, which prevents the learning rate from shrinking to zero.

Adam (Kingma and Ba, 2014) combines momentum with RMSProp. It is the default choice in most modern machine learning codebases: robust, fast and in need of minimal tuning.

You do not need to know the internals of these. You need to know that the optimizer is doing something smarter than vanilla gradient descent and that the choice of optimizer can dramatically affect training speed and final performance. For most trading applications with linear or simple nonlinear models, vanilla SGD or Adam with default hyperparameters works fine.

7 · Why optimization matters for trading

The optimization step is where most of the computational expense in modern quant research lives. Backtesting a strategy means fitting models, generating predictions, simulating execution, computing performance and iterating. Fitting a model on millions of (feature, target) pairs is the slow step. Modern quant firms run massive distributed-training infrastructure precisely because they are optimizing thousands of model variants in parallel.

For retail-scale work, fitting a model on a few thousand observations, optimization is essentially free. The bottleneck is data acquisition, feature engineering and avoiding overfitting, not the optimization step itself.

8 · Regularization

Regularization is a core technique that addresses overfitting at the optimization stage. It adds a penalty term to the loss function that discourages large parameter values. The two most common forms penalize the squared weights (L2 regularization, or ridge regression) or their absolute values (L1 regularization, or the lasso):

Lridge=Ldata+λ∑iwi2Llasso=Ldata+λ∑i∣wi∣ \begin{aligned} L_{\text{ridge}} &= L_{\text{data}} + \lambda \sum_i w_i^2 \\ L_{\text{lasso}} &= L_{\text{data}} + \lambda \sum_i |w_i| \end{aligned} (3C.11)

Here LdataL_{\text{data}} is the loss of (3C.1) and the wiw_i are the model's weights. The hyperparameter λ\lambda (lambda) controls the strength of regularization. Higher λ\lambda means smaller fitted weights, which means a smoother model with less overfitting risk.

L2 keeps all features but shrinks their weights toward zero. L1 has the additional property that it zeros out many weights entirely, performing feature selection automatically. For trading models with many candidate features, L1 is often the right choice, because it produces sparse, interpretable models.

Worked example: Ridge and lasso on one weight

Step 1: the formulas. For a single weight on data with zero means, write CC for the covariance and VV for the variance of the feature. Setting the slope of each penalized loss to zero gives

wridge=CV+λ∣wlasso∣=max⁡(∣C∣−λ/2, 0)V \begin{aligned} w_{\text{ridge}} &= \frac{C}{V + \lambda} \\ |w_{\text{lasso}}| &= \frac{\max(|C| - \lambda/2,\ 0)}{V} \end{aligned} (3C.12)

with the lasso weight taking the sign of CC.

Step 2: the five returns. Here C=−0.25C = -0.25 and V=0.5V = 0.5, so the unpenalized weight is −0.5, while with λ=0.2\lambda = 0.2 ridge gives −0.25/0.7=−0.36-0.25/0.7 = -0.36 and the lasso −(0.25−0.1)/0.5=−0.3-(0.25 - 0.1)/0.5 = -0.3.

Step 3: a stronger penalty. With λ=0.5\lambda = 0.5, ridge gives −0.25/1.0=−0.25-0.25/1.0 = -0.25, half the unpenalized weight, while the lasso gives max⁡(0.25−0.25,0)=0\max(0.25 - 0.25, 0) = 0 and drops the feature entirely.

Ridge is the shrinkage toward zero that Chapter 3A's zero-forecast example argued for, a little bias bought with a large cut in variance. Choose λ\lambda by walk-forward testing, never by the in-sample fit, which always prefers λ=0\lambda = 0.

For AR(1), with a single feature, regularization is unnecessary, whereas for multivariate models with dozens of features it is essential.

Familiar ground

Regularization is the shrinkage you already demand of a clinical prediction model. A score built from many candidate predictors and few events overfits its development cohort: its coefficients come out too extreme and its predictions too confident in new patients. Penalized regression, ridge or lasso, shrinks the coefficients to correct that optimism, with the lasso also choosing among candidate biomarkers. The penalty is doing the same job in a trading model with dozens of candidate features.

In practice
  • For one to a handful of features, use the closed form. OLS, which is what LinearRegression and lm() compute, is exact and instant at retail scale.
  • Standardize features before regularizing, dividing each by its standard deviation, so that the penalty treats them alike rather than punishing whichever happens to be measured in larger units.
  • Tune λ\lambda and the learning rate out of sample, inside the walk-forward folds of Chapter 3A, counting each value tried as a trial.
  • Watch the loss as you fit: a loss that rises or oscillates means the learning rate is too high, while one that barely moves means it is too low.
When it fails

An optimizer finds the parameters that minimize the loss you gave it, on the data you gave it. If the loss is not what you care about, such as squared error when you trade only the sign, or the data contain lookahead, it optimizes the wrong thing perfectly. On non-convex problems it may also stop at a local minimum, so two runs from different starts can disagree.

Decision rule
  • Use OLS when the features are few, turning to gradient descent or a modern optimizer only when the data or the feature count is too large for the closed form.
  • Regularize any model with more than a few features, with λ\lambda chosen by walk-forward testing.
  • Prefer the lasso when you have many candidate features and suspect most are noise.
Common mistake: judging the fit by its in-sample loss

Every added feature lowers the in-sample loss, as does every reduction in λ\lambda. The optimizer is doing its job, which is the problem: it is fitting noise as eagerly as signal. The only fair judge of a fitted model is its loss out of sample.

What to carry forward
  • Fitting means minimizing a loss, usually the mean squared error of the predictions.
  • For linear models OLS gives the exact answer, (X⊤X)−1X⊤y(X^\top X)^{-1}X^\top y, which for AR(1) is the covariance over the variance. It breaks only at a scale retail work never reaches.
  • Gradient descent steps downhill by η\eta times the slope. Too small a learning rate crawls, while too large a rate overshoots, zigzags or diverges.
  • Ridge shrinks weights and the lasso zeros them, with the penalty chosen out of sample.

9 · Check yourself

Question 1

A sample of daily returns has Cov⁡(rt,rt−1)=−0.02\operatorname{Cov}(r_t, r_{t-1}) = -0.02 and Var⁡(rt−1)=0.8\operatorname{Var}(r_{t-1}) = 0.8, in squared percent, with means rˉt=0.05%\bar{r}_t = 0.05\% and rˉt−1=0.04%\bar{r}_{t-1} = 0.04\%. What are the OLS weight and bias?

Answer

By (3C.5), w^=−0.02/0.8=−0.025\hat{w} = -0.02/0.8 = -0.025 and b^=0.05−(−0.025)(0.04)=0.051%\hat{b} = 0.05 - (-0.025)(0.04) = 0.051\%.

Question 2

Run gradient descent on the figure's loss, (w−0.5)2+0.2(w - 0.5)^2 + 0.2, from w=−1w = -1 with η=0.25\eta = 0.25. Where is ww after three steps?

Answer

By (3C.9) each step multiplies the distance from 0.5 by 1−2×0.25=0.51 - 2 \times 0.25 = 0.5. After three steps the distance is 1.5×0.53=0.18751.5 \times 0.5^3 = 0.1875, so w=0.5−0.1875=0.3125w = 0.5 - 0.1875 = 0.3125.

Question 3

Why does a learning rate of 1.1 fail on that loss while 0.9 succeeds?

Answer

The factor 1−2η1 - 2\eta is −0.8 at 0.9, so each step overshoots but by less, and the path zigzags inward. At 1.1 it is −1.2, so each overshoot is 20% larger than the last and the path diverges.

Question 4

With a covariance of −0.1 and a variance of 1, what do ridge and the lasso give for the weight at λ=0.2\lambda = 0.2?

Answer

Ridge gives −0.1/(1+0.2)=−0.083-0.1/(1 + 0.2) = -0.083, while the lasso gives max⁡(0.1−0.1,0)/1=0\max(0.1 - 0.1, 0)/1 = 0 and so drops the feature.