Level 5 — MasteryAlgorithmic & Quant Trading

Algorithmic & Quant Trading

90 minutes60 marksprintable — key stays hidden on paper

Chapter: 6.1 Algorithmic & Quant Trading Time limit: 90 minutes Total marks: 60 Instructions: Answer ALL three questions. Show derivations, proofs and pseudocode/code where asked. Use ...... / ...... for mathematics.


Question 1 — Pairs Trading, Cointegration & Mean-Reversion (24 marks)

You are researching a pair of stocks AA and BB with log-prices ptA,ptBp_t^A, p_t^B.

(a) Define the hedge ratio β\beta via the regression ptA=α+βptB+εtp_t^A = \alpha + \beta\, p_t^B + \varepsilon_t. Explain precisely why cointegration (rather than mere correlation) is the correct statistical condition for a tradeable pair, and state what property the spread st=ptAβptBs_t = p_t^A - \beta p_t^B must have. (4 marks)

(b) Assume the spread follows an Ornstein–Uhlenbeck (mean-reverting) process dst=θ(μst)dt+σdWt,θ>0.ds_t = \theta(\mu - s_t)\,dt + \sigma\,dW_t,\quad \theta>0. Derive the expression for the expected value E[sts0]\mathbb{E}[s_t \mid s_0] and show that the half-life of mean reversion is H=ln2θ.H = \frac{\ln 2}{\theta}. (6 marks)

(c) In discrete time the OU process is estimated by the AR(1) regression st=a+bst1+uts_t = a + b\, s_{t-1} + u_t. Given an estimated b=0.94b = 0.94 on daily data, compute the half-life in trading days (numeric). State the relationship between bb and θ\theta used. (4 marks)

(d) Design a complete signal-generation rule set for this pair using the z-score zt=(stμ)/σsz_t = (s_t - \mu)/\sigma_s. Specify entry, exit and stop-loss rules, and prove that under the pure OU model (no stop) the strategy's expected holding period is bounded. Discuss ONE reason a fixed β\beta can be dangerous in live trading. (10 marks)


Question 2 — Momentum Strategy, Backtest Statistics & Overfitting (22 marks)

(a) A cross-sectional momentum strategy ranks NN assets by their trailing 12-month return (skipping the most recent month) and goes long the top decile, short the bottom decile. Explain the "skip-a-month" convention and give the microstructure/behavioural justification. (4 marks)

(b) A backtest of a single strategy configuration produces daily returns with mean rˉ=0.0006\bar r = 0.0006, standard deviation σ=0.011\sigma = 0.011. Compute the annualised Sharpe ratio (252 trading days, risk-free = 0). Show the formula and the number. (4 marks)

(c) A researcher tries M=200M = 200 independent strategy variants on the same data and reports only the best one. Assuming each variant's Sharpe estimate is N(0,1)\mathcal{N}(0,1) under the null of no skill, derive the expected maximum Sharpe (use the approximation E[max]2lnM\mathbb{E}[\max] \approx \sqrt{2\ln M}) and compute it numerically. Explain what this proves about the reported result and name the phenomenon. (6 marks)

(d) Define deflated Sharpe ratio conceptually and explain how walk-forward analysis and a proper train/validation/test split reduce curve-fitting. Then write pseudocode for a walk-forward optimisation loop over KK folds that reports out-of-sample performance only. (8 marks)


Question 3 — Building a Trading System & ML Caution (14 marks)

(a) Draw (as a labelled block diagram in text/ASCII) the components of a complete algorithmic trading system, from data ingestion to execution and risk management. Briefly state the role of each block. (6 marks)

(b) A team fits a random-forest classifier to predict next-day up/down using 300 features on 500 days of data and reports 82% in-sample accuracy. Give THREE distinct, technically-specific reasons this result is untrustworthy, and for each give one concrete mitigation. (5 marks)

(c) Explain why "look-ahead bias" and "survivorship bias" in the dataset can silently inflate ML backtest results even when the model code is correct. (3 marks)


Answer keyMark scheme & solutions

Question 1

(a) [4 marks]

  • β\beta = OLS slope of pAp^A on pBp^B; it is the number of units of BB to short per unit of AA long so the combined position is dollar/price neutral in the common factor. (1)
  • Correlation measures co-movement of returns/changes but two random walks can be highly correlated yet drift apart without bound. (1)
  • Cointegration means a linear combination of two I(1)I(1) (unit-root) series is I(0)I(0) (stationary) — the spread does not wander off, it reverts. This is the property required for a bounded, tradeable spread. (1)
  • The spread sts_t must be stationary (mean-reverting, I(0)I(0)) — constant mean & variance, so deviations are temporary. (1)

(b) [6 marks] Solve the OU SDE. Consider d(eθtst)d(e^{\theta t}s_t): d(eθtst)=θeθtstdt+eθtdst=θeθtμdt+σeθtdWt.d(e^{\theta t}s_t) = \theta e^{\theta t}s_t\,dt + e^{\theta t}ds_t = \theta e^{\theta t}\mu\,dt + \sigma e^{\theta t}dW_t. (2) Integrate 0t0\to t: eθtsts0=μ(eθt1)+σ0teθudWu.e^{\theta t}s_t - s_0 = \mu(e^{\theta t}-1) + \sigma\int_0^t e^{\theta u}dW_u. Taking expectation kills the Itô integral term (mean 0): (1) E[sts0]=μ+(s0μ)eθt.\boxed{\mathbb{E}[s_t\mid s_0] = \mu + (s_0-\mu)e^{-\theta t}.} (1) Half-life HH = time for deviation (s0μ)(s_0-\mu) to halve: eθH=12θH=ln2H=ln2θ.e^{-\theta H} = \tfrac12 \Rightarrow \theta H = \ln 2 \Rightarrow H = \frac{\ln 2}{\theta}. (2)

(c) [4 marks]

  • Discrete AR(1): b=eθΔtb = e^{-\theta \Delta t} so θ=lnb\theta = -\ln b (with Δt=1\Delta t = 1 day). (1)
  • H=ln2lnb=ln2ln0.94H = \dfrac{\ln 2}{-\ln b} = \dfrac{\ln 2}{-\ln 0.94}. (1)
  • ln0.94=0.061875-\ln 0.94 = 0.061875, ln2=0.693147\ln 2 = 0.693147. (1)
  • H=0.693147/0.06187511.2H = 0.693147/0.061875 \approx 11.2 trading days. (1)

(d) [10 marks] Rule set (award up to 5):

  • Entry: short spread when zt+zinz_t \ge +z_{\text{in}} (e.g. +2+2): sell AA, buy β\beta of BB. Long spread when ztzinz_t \le -z_{\text{in}}. (2)
  • Exit: close when ztz_t reverts toward 0 (e.g. ztzout=0.5|z_t|\le z_{\text{out}}=0.5 or crosses mean). (1)
  • Stop-loss: exit if ztzstop|z_t| \ge z_{\text{stop}} (e.g. ±3.5\pm 3.5) or if cointegration test fails / holding time > cap. (2)

Boundedness of expected holding period (up to 3):

  • The spread process zz is a positive-recurrent OU/AR(1) with θ>0\theta>0; a stationary ergodic mean-reverting process visits every neighbourhood of the mean in finite expected time. (1)
  • Formally the expected first-passage (hitting) time from level zinz_{\text{in}} to the exit band is finite because the OU process has a proper stationary distribution and drift θz-\theta z pushes it toward 0; hence E[τ]<\mathbb{E}[\tau]<\infty. (2)

Fixed-β\beta danger (up to 2):

  • The cointegrating relationship / hedge ratio is not constant — regime changes, corporate actions, changing fundamentals cause β\beta to drift, so the "spread" stops being stationary and the position accumulates unbounded loss. Mitigation: rolling/Kalman-filtered β\beta and periodic re-testing of cointegration. (2)

Question 2

(a) [4 marks]

  • Rank on months t12t-12 to t1t-1, skip month tt before forming the portfolio held in t+1t+1. (2)
  • Justification: the most recent month exhibits short-term reversal (bid-ask bounce, liquidity/microstructure effects, one-month mean reversion); including it contaminates the momentum signal. Skipping isolates the persistent intermediate-horizon momentum. (2)

(b) [4 marks] SRann=rˉσ252=0.00060.011252.\text{SR}_{\text{ann}} = \frac{\bar r}{\sigma}\sqrt{252} = \frac{0.0006}{0.011}\sqrt{252}. (2) =0.0545455×15.8745=0.86590.87.= 0.0545455 \times 15.8745 = 0.8659 \approx 0.87. (2)

(c) [6 marks]

  • Under the null each of MM trials has SR N(0,1)\sim \mathcal N(0,1); picking the best is an extreme-value (maximum) selection. (1)
  • E[maxM]2lnM\mathbb{E}[\max_M] \approx \sqrt{2\ln M}. (1)
  • ln200=5.2983\ln 200 = 5.2983, 2ln200=10.59662\ln 200 = 10.5966, 10.5966=3.255\sqrt{10.5966} = 3.255. (2)
  • So even with zero true skill, the best of 200 tries has expected Sharpe ≈ 3.26 — an impressive-looking number that is pure selection artefact. The reported best is not evidence of skill. (1) Phenomenon: multiple-testing / data-snooping bias (backtest overfitting, selection bias). (1)

(d) [8 marks]

  • Deflated Sharpe ratio (DSR): adjusts the observed Sharpe by (i) the number of trials MM, (ii) skew/kurtosis of returns and (iii) sample length, giving the probability the true Sharpe exceeds 0 after accounting for multiple testing — it "deflates" the naive SR toward the selection-corrected benchmark. (2)
  • WFA / splits: parameters are tuned only on training/validation windows; the test window is never seen during optimisation, so reported performance is genuinely out-of-sample and reflects the model's ability to generalise, not memorise. Rolling windows also detect non-stationarity. (2)

Pseudocode (up to 4):

data sorted by time; define K folds by time
oos_results = []
for k in 1..K:
    train = data[fold_start(k) : fold_train_end(k)]      # in-sample
    test  = data[fold_train_end(k) : fold_test_end(k)]   # unseen, later in time
    best_params = None; best_score = -inf
    for params in param_grid:
        score = backtest(strategy, train, params)         # optimise on TRAIN only
        if score > best_score:
            best_score, best_params = score, params
    oos = backtest(strategy, test, best_params)           # evaluate on TEST only
    oos_results.append(oos)
report( aggregate(oos_results) )   # report ONLY out-of-sample metrics

Marks: correct time-ordered no-shuffle split (1), optimise on train only (1), evaluate fixed params on future test (1), report only OOS (1).


Question 3

(a) [6 marks] — 1 mark per block correctly named + role (max 6):

[Market Data Feed] -> [Data Cleaning/Storage] -> [Signal/Alpha Model]
        -> [Portfolio Construction/Sizing] -> [Risk Management]
        -> [Execution/Order Router] -> [Broker/Exchange]
                          ^                          |
                          |__________ [Monitoring / P&L / Logging] <--
  • Data feed: ingest real-time & historical prices/fundamentals.
  • Cleaning/storage: handle missing data, adjustments, survivorship-free DB.
  • Signal/alpha model: generate trading signals from strategy logic.
  • Portfolio construction/sizing: convert signals to target positions, apply capital allocation.
  • Risk management: limits, exposure, stop-losses, drawdown control.
  • Execution/router: slice & send orders, minimise slippage/impact.
  • Monitoring/logging: track live vs expected performance, alerts.

(b) [5 marks] — any three, 1 mark reason + shared mark for mitigation (max 5):

  • Curse of dimensionality / overfitting: 300 features vs 500 samples → model memorises noise. Mitigation: feature selection / regularisation / fewer features, more data.
  • In-sample evaluation: 82% is on training data; no honest estimate of generalisation. Mitigation: purged/embargoed cross-validation or walk-forward OOS test.
  • Serial correlation / non-IID data: standard CV leaks temporal info; financial data is non-stationary with autocorrelation. Mitigation: time-series CV, purge & embargo.
  • Low signal-to-noise: market returns are ~random; 82% accuracy on direction is implausibly high → data leakage suspected. Mitigation: audit features for look-ahead. (Award 5 for three sound reason+mitigation pairs.)

(c) [3 marks]

  • Look-ahead bias: using data not available at decision time (e.g. same-day close to trade at that close, restated fundamentals) inflates accuracy because the model effectively "sees the future." (1.5)
  • Survivorship bias: dataset contains only firms that survived; delisted/bankrupt names removed, so backtest overstates returns and understates risk — the model is trained on a rosier universe than reality. (1.5)
  • Both operate through the data, so even bug-free model code produces optimistic, non-reproducible live results.

[
  {"claim":"OU half-life for AR(1) b=0.94 is ~11.2 days","code":"from sympy import log, N; b=0.94; H=log(2)/(-log(b)); result = abs(N(H)-11.2)<0.1"},
  {"claim":"Annualised Sharpe = 0.0006/0.011*sqrt(252) ~ 0.866","code":"from sympy import sqrt, N; SR=(0.0006/0.011)*sqrt(252); result = abs(N(SR)-0.866)<0.01"},
  {"claim":"Expected max Sharpe over 200 trials ~ 3.26","code":"from sympy import log, sqrt, N; e=sqrt(2*log(200)); result = abs(N(e)-3.255)<0.01"},
  {"claim":"Half-life formula: e^(-theta*H)=1/2 gives H=ln2/theta","code":"from sympy import symbols, Eq, solve, log, exp; theta,H=symbols('theta H',positive=True); sol=solve(Eq(exp(-theta*H),0.5),H)[0]; result = sol.equals(log(2)/theta)"}
]