Level 4 — ApplicationBacktesting Frameworks

Backtesting Frameworks

60 minutes60 marksprintable — key stays hidden on paper

Level: 4 (Application — novel problems, no hints) Time limit: 60 minutes Total marks: 60

Answer all questions. Show all working. Use ...... notation where required.


Question 1 — Performance Metrics on a Novel Equity Curve (14 marks)

A backtest of a momentum strategy produces the following month-end portfolio values (in USD), starting 1 Jan 2021 and ending 1 Jan 2024 (exactly 3 years of data):

Date Value
2021-01-01 100,000
2021-07-01 130,000
2022-01-01 90,000
2022-07-01 150,000
2023-01-01 120,000
2024-01-01 180,000

(a) Compute the CAGR of the strategy over the full period. Give your answer as a percentage to 2 decimal places. (4)

(b) Using the six values above as the equity curve, compute the Maximum Drawdown (MDD) as a percentage. Identify the peak and trough values used. (4)

(c) The strategy's annualised return is 21.64% and annualised volatility of monthly returns is 30%. The risk-free rate is 3% annually. Compute the annualised Sharpe ratio. (3)

(d) Explain why CAGR alone is an insufficient measure for comparing this strategy against a benchmark, referencing one other metric. (3)


Question 2 — Transaction Costs & Slippage (13 marks)

A mean-reversion strategy trades a stock 250 times per year (round-trips: each round-trip is one buy + one sell). Average position size per trade is $50,000. Assume:

  • Commission: $0.005 per share, average share price $100.
  • Exchange/regulatory fees: 0.001% of notional per side.
  • Slippage: modelled as half the bid-ask spread; the spread is 4 basis points of price.

(a) Compute the total commission cost per round-trip (both sides). (3)

(b) Compute the slippage cost per round-trip in dollars. (3)

(c) Compute total annual transaction cost (commission + fees + slippage) across all 250 round-trips. (4)

(d) The gross annual return of the strategy is $60,000 on $50,000 deployed capital. Compute the net return after the annual costs from (c), as a percentage. Comment on whether the strategy survives cost modelling. (3)


Question 3 — Bias Identification & Correction (12 marks)

For each of the following backtest design choices, (i) name the specific bias introduced, and (ii) state a concrete correction. Answer in full sentences.

(a) A researcher downloads today's S&P 500 constituent list and backtests a strategy on those tickers over 2010–2024. (4)

(b) A signal computed as "buy if today's close > 20-day moving average" is executed at today's close price in the backtest. (4)

(c) A strategy is optimised over 2015–2023 to maximise Sharpe, then reported with that same period's Sharpe as its expected performance. (4)


Question 4 — In-Sample / Out-of-Sample Design (11 marks)

You have 10 years of daily data (2014–2023).

(a) Propose a walk-forward validation scheme using a 3-year in-sample training window and a 1-year out-of-sample window, rolling forward. State how many out-of-sample test folds you obtain and list their year ranges. (5)

(b) Your in-sample Sharpe is 2.1 and out-of-sample Sharpe is 0.4. Compute the degradation ratio (OOS/IS) and interpret what this suggests about the strategy. (3)

(c) Explain one reason why a single train/test split is inferior to walk-forward analysis for a strategy exposed to regime changes. (3)


Question 5 — Monte Carlo Simulation of Returns (10 marks)

A strategy's daily returns are approximately normally distributed with mean μ=0.0008\mu = 0.0008 and standard deviation σ=0.012\sigma = 0.012 per trading day (252 trading days/year).

(a) Compute the expected annualised return using (1+μ)2521(1+\mu)^{252} - 1. Give as a percentage to 2 dp. (3)

(b) Compute the annualised volatility σ252\sigma\sqrt{252} as a percentage. (2)

(c) Describe the Monte Carlo procedure you would use to estimate the 5th-percentile (worst-case) 1-year terminal wealth from a $100,000 account, and explain what advantage this offers over reporting a single backtest equity curve. (5)

Answer keyMark scheme & solutions

Question 1 (14 marks)

(a) CAGR(4) Formula: CAGR=(Vf/Vi)1/n1\text{CAGR} = (V_f/V_i)^{1/n} - 1, with n=3n=3 years. CAGR=(180000/100000)1/31=1.80.33331\text{CAGR} = (180000/100000)^{1/3} - 1 = 1.8^{0.3333} - 1 1.81/3=1.216441.8^{1/3} = 1.21644, so CAGR =0.21644=21.64%= 0.21644 = \mathbf{21.64\%}.

  • 1 mark formula, 1 mark n=3n=3, 1 mark computation, 1 mark % answer.

(b) Max Drawdown(4) Track running peak, find largest peak→trough decline.

  • Peak = 130,000 (2021-07); subsequent trough = 90,000 (2022-01). MDD=(90000130000)/130000=40000/130000=0.30769=30.77%\text{MDD} = (90000 - 130000)/130000 = -40000/130000 = -0.30769 = \mathbf{-30.77\%} (Later drop 150k→120k = −20% is smaller.) Peak = $130,000, Trough = $90,000.
  • 2 marks correct peak/trough, 2 marks computed −30.77%.

(c) Sharpe(3) Sharpe=RpRfσ=0.21640.030.30=0.18640.30=0.621\text{Sharpe} = \frac{R_p - R_f}{\sigma} = \frac{0.2164 - 0.03}{0.30} = \frac{0.1864}{0.30} = \mathbf{0.621}

  • 1 mark formula, 1 mark numerator, 1 mark answer ≈ 0.62.

(d)(3) CAGR ignores risk/path. Two identical CAGRs can have very different volatility and drawdown; a strategy with 30% CAGR and −60% MDD is far worse risk-adjusted than one with 20% CAGR and −10% MDD. Should also report Sharpe (risk-adjusted return) and/or MDD to capture volatility and downside path. (3 marks: mention risk-adjustment + name Sharpe/MDD + coherent reasoning.)


Question 2 (13 marks)

(a) Commission per round-trip(3) Shares per trade = $50,000 / $100 = 500 shares. Commission per side = 500 × $0.005 = $2.50. Round-trip (buy + sell) = $5.00.

  • 1 mark shares, 1 mark per side, 1 mark round-trip.

(b) Slippage per round-trip(3) Half-spread = ½ × 4 bp = 2 bp = 0.0002 of price. Cost per side = 0.0002 × $50,000 = $10. Round-trip (both sides) = $20.00.

  • 1 mark half-spread, 1 mark per side, 1 mark round-trip = $20.

(c) Total annual cost(4) Fees per side = 0.001% × $50,000 = 0.00001 × 50000 = $0.50; both sides = $1.00/round-trip. Per round-trip total = commission $5 + fees $1 + slippage $20 = $26. Annual (250 round-trips) = 250 × $26 = $6,500.

  • 1 mark fees, 1 mark per-round-trip sum, 2 marks × 250 = $6,500.

(d) Net return(3) Net profit = $60,000 − $6,500 = $53,500. Net return = 53,500 / 50,000 = 1.07 = 107%. Comment: Costs consume $6,500 (~10.8% of gross profit); strategy remains strongly profitable and survives cost modelling. (1 mark net profit, 1 mark %, 1 mark comment.)


Question 3 (12 marks)

(a)(4) (i) Survivorship bias — using today's constituents omits companies that were delisted/removed/bankrupt during the test window, inflating returns. (2) (ii) Correction: use a point-in-time / historical constituent database that includes delisted and removed tickers as they existed on each historical date. (2)

(b)(4) (i) Look-ahead bias — executing at today's close using a signal that itself uses today's close assumes information available only at end-of-day is tradeable at that same instant (and you can't act on the close you're computing from). (2) (ii) Correction: execute at the next bar's open (or next-day price), lagging the signal by one period so only past-available data drives trades. (2)

(c)(4) (i) Overfitting / in-sample (data-snooping) bias — reporting the optimised in-sample Sharpe overstates expected live performance. (2) (ii) Correction: reserve out-of-sample data (or walk-forward/cross-validation) and report performance on data never used for optimisation. (2)


Question 4 (11 marks)

(a) Walk-forward scheme(5) 3-yr train + 1-yr test, rolling by 1 year:

Fold In-sample (train) Out-of-sample (test)
1 2014–2016 2017
2 2015–2017 2018
3 2016–2018 2019
4 2017–2019 2020
5 2018–2020 2021
6 2019–2021 2022
7 2020–2022 2023

7 out-of-sample folds.

  • 3 marks correct rolling structure, 2 marks count = 7 with ranges.

(b) Degradation ratio(3) ratio=0.4/2.1=0.190\text{ratio} = 0.4 / 2.1 = 0.190 (≈ 19%). Interpretation: OOS retains only ~19% of IS performance — severe degradation indicating overfitting; the strategy is unlikely to generalise. (1 mark ratio, 2 marks interpretation.)

(c)(3) A single split trains on one regime and tests on one regime; results depend heavily on where the split falls and can be luck. Walk-forward tests across multiple, sequential out-of-sample periods spanning different market regimes (bull, bear, high/low vol), giving a more robust, regime-diverse estimate of live robustness. (3 marks.)


Question 5 (10 marks)

(a) Annualised return(3) (1.0008)2521=1.223261=0.22326=22.33%(1.0008)^{252} - 1 = 1.22326 - 1 = 0.22326 = \mathbf{22.33\%}

  • 1 mark formula, 1 mark exponent, 1 mark % (≈ 22.33%).

(b) Annualised volatility(2) 0.012×252=0.012×15.8745=0.19049=19.05%0.012 \times \sqrt{252} = 0.012 \times 15.8745 = 0.19049 = \mathbf{19.05\%}

  • 1 mark 252\sqrt{252}, 1 mark answer.

(c) Monte Carlo procedure(5) Procedure (3 marks):

  1. Simulate NN (e.g. 10,000) independent 1-year paths: for each path draw 252 daily returns rtN(0.0008,0.0122)r_t \sim \mathcal{N}(0.0008, 0.012^2).
  2. Compute terminal wealth W=100000×t=1252(1+rt)W = 100000 \times \prod_{t=1}^{252}(1+r_t) for each path.
  3. Sort the NN terminal wealths and take the 5th percentile as the worst-case (VaR-style) 1-year outcome.

Advantage (2 marks): a single backtest is one realisation of a random process; Monte Carlo produces a distribution of outcomes, quantifying the range/tail risk (e.g. 5th-percentile drawdown) rather than relying on one lucky/unlucky historical path — better for risk assessment and confidence intervals.


[
  {"claim":"CAGR of equity curve = 21.64%","code":"cagr=(180000/100000)**(1/3)-1; result=abs(cagr-0.2164)<0.001"},
  {"claim":"Max drawdown = -30.77%","code":"mdd=(90000-130000)/130000; result=abs(mdd+0.30769)<0.001"},
  {"claim":"Sharpe ratio = 0.621","code":"s=(0.2164-0.03)/0.30; result=abs(s-0.6213)<0.001"},
  {"claim":"Annual transaction cost = $6500","code":"per_rt=5+1+20; total=250*per_rt; result=total==6500"},
  {"claim":"Net return = 107%","code":"net=(60000-6500)/50000; result=abs(net-1.07)<1e-9"},
  {"claim":"MC annualised return = 22.33%","code":"r=(1.0008)**252-1; result=abs(r-0.22326)<0.001"},
  {"claim":"Annualised vol = 19.05%","code":"import sympy; v=0.012*sympy.sqrt(252); result=abs(float(v)-0.19049)<0.001"},
  {"claim":"Degradation ratio = 0.19","code":"result=abs(0.4/2.1-0.1905)<0.001"}
]