Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Practice · ML coding

Statistical inference in code: standard errors, permutation tests, the Kalman filter

An estimate without its uncertainty is half an answer. Four pieces of inference, each small enough to write in an interview.


Standard errors are the point

Fitting a regression is the easy half. The question a research interviewer actually asks is whether the coefficient means anything, and that needs its standard error. Under the usual assumptions (errors independent, with constant variance σ²) the least-squares estimate β̂ = (XᵀX)⁻¹Xᵀy has covariance σ²(XᵀX)⁻¹. σ² is unknown, so it is estimated from the residuals:

sigma2 = resid @ resid / (n - p)      # p coefficients used up p degrees of freedom
se = np.sqrt(sigma2 * np.diag(XtX_inv))
t = beta / se

A t-statistic above about 2 in absolute value is the conventional bar, but in quant research it is a low one: try twenty signals and one will clear it by chance. Researchers who have been burned ask for 3 or more.

Computing (XᵀX)⁻¹ explicitly squares the condition number of X. A QR factorisation, X = QR, gives the same quantities without that loss: β̂ solves Rβ = Qᵀy, and the diagonal of (XᵀX)⁻¹ is the squared row norms of R⁻¹.

A test without a table

Two groups of numbers, and the question whether the difference in their means is real. A t-test needs assumptions about the distribution. A permutation test needs only one: if there is no difference, the group labels are arbitrary. So try every way of relabelling the pooled values into groups of the same sizes, compute the difference for each, and count how often it is at least as extreme as the one observed. That fraction is the p-value, exactly.

There are C(n, k) relabellings, 924 for two groups of 6, which is nothing for a computer. Beyond about 20 values, sample random relabellings instead. Compare differences in integer arithmetic when the data are integers: two relabellings with the same true difference must count the same, and floating-point rounding can split them.

The permutation distribution for two groups of three.

  1. Step 1 / 3
    groups7a9a12a3b4b6b

    Observed: mean(a) − mean(b) = 9.3333 − 4.3333 = 5.

  2. Step 2 / 3
    -51-4.33331-32-2.33331-1.66671-13-0.333310.33331131.666712.33331324.3333151

    Pool the six values and try all 20 ways to split them into groups of three. Each bar counts the splits giving one difference in means.

  3. Step 3 / 3
    -51-4.33331-32-2.33331-1.66671-13-0.333310.33331131.666712.33331324.3333151

    Splits at least as far apart as the observed one, in either direction: 2 of 20. The exact two-sided p-value is 2/20 = 0.1.

The Kalman filter is Bayes' rule, repeated

A level drifts at random, and you see it only through noisy measurements: a fair price under a noisy quote, a position under a noisy sensor. Model the level as a random walk with step variance q, and each measurement as the level plus noise of variance r. The filter keeps a Gaussian belief, mean m and variance P, and alternates two steps:

  • Predict. The level may have moved: P grows by q. The mean stays where it was.
  • Update. A measurement y arrives. The gain K = P / (P + r) says how far to move towards it: m ← m + K(y − m), and the uncertainty shrinks, P ← (1 − K)P.

When the measurements are noisy (r large) the gain is small and the estimate barely moves. When the level is volatile (q large) the gain is large and the filter follows the data. The answer at every step is exactly the posterior you would get by conditioning on all measurements at once, which is how the problem below is checked.

Four noisy measurements of a drifting level.

  1. Step 1 / 5
    beliefm = 8meanP = 9variance

    Before any data: the level is believed to be about 8, with variance 9. Measurements have noise variance r = 4; the level drifts with variance q = 1 per step.

  2. Step 2 / 5
    y1 = 109prior P0.6923gain K9.3846new mean2.7692new P

    Gain K = 9 / (9 + 4) = 0.6923. The mean moves 0.6923 of the way from 8 towards 10: 9.3846. The variance shrinks to 2.7692.

  3. Step 3 / 5
    y2 = 123.7692P + q0.4851gain K10.6535new mean1.9406new P

    Predict: the variance grows to 2.7692 + 1 = 3.7692. Gain K = 3.7692 / (3.7692 + 4) = 0.4851. The mean moves 0.4851 of the way from 9.3846 towards 12: 10.6535. The variance shrinks to 1.9406.

  4. Step 4 / 5
    y3 = 112.9406P + q0.4237gain K10.8003new mean1.6947new P

    Predict: the variance grows to 1.9406 + 1 = 2.9406. Gain K = 2.9406 / (2.9406 + 4) = 0.4237. The mean moves 0.4237 of the way from 10.6535 towards 11: 10.8003. The variance shrinks to 1.6947.

  5. Step 5 / 5
    y4 = 152.6947P + q0.4025gain K12.4907new mean1.6101new P

    Predict: the variance grows to 1.6947 + 1 = 2.6947. Gain K = 2.6947 / (2.6947 + 4) = 0.4025. The mean moves 0.4025 of the way from 10.8003 towards 15: 12.4907. The variance shrinks to 1.6101.

Autocorrelation: the first thing to check

Before modelling a return series, look at how each value relates to the ones before it. The sample autocorrelation at lag k is

d = x - x.mean()
r_k = (d[:n - k] @ d[k:]) / (d @ d)

Both sums use the mean of the whole series, and the denominator is the same at every lag. That is the standard estimator; it is slightly biased towards zero but guarantees a valid correlation structure. For white noise, about 95% of the r_k fall within ±1.96/√n. Returns usually pass; squared returns usually do not, which is volatility clustering, and the reason GARCH models exist.

What interviewers listen for

  • That σ² is estimated with n − p in the denominator, and why.
  • What a t-statistic of 2 means after testing many signals, and what you would do about it.
  • The assumption behind a permutation test, stated in one sentence.
  • The Kalman gain explained in words, before any matrices.
  • The difference between autocorrelation in returns and in squared returns.

Practise it