Progress · 0/5 phases
📐 Phase 1 — Mathematical & Statistical Foundations
8 min read · Notion
Core insight: Every quant model is built from probability, statistics, linear algebra, and stochastic calculus. You don't need a PhD's worth of pure math — you need working fluency with the specific tools that price options, measure risk, and test strategies. This phase builds that toolkit with code alongside every derivation.
📚 Topics in order
Days 1–5 — Probability foundations
Core distributions you must know cold:
- Normal (Gaussian): the backbone of Black-Scholes, CLT, most risk models
- Lognormal: stock prices are often modelled as lognormal (returns are normal, prices are not)
- Binomial: discrete option pricing trees
- Poisson: jump processes, default events
- Student's t: fat-tailed returns, small-sample inference
Key concepts:
- Expectation, variance, covariance, correlation
- Conditional probability and Bayes' theorem — the foundation of Bayesian updating in trading signals
- Law of large numbers vs Central Limit Theorem — why CLT justifies normal approximations, and why it fails for fat-tailed assets
- Moment generating functions — used to derive distributions of sums of returns
import numpy as np
from scipy import stats
# Simulate returns and check CLT in action
np.random.seed(42)
single_day_returns = np.random.standard_t(df=3, size=100_000) # fat-tailed
monthly_returns = single_day_returns.reshape(-1, 21).sum(axis=1) # sum of 21 days
print("Single day kurtosis:", stats.kurtosis(single_day_returns)) # high (fat tails)
print("Monthly kurtosis:", stats.kurtosis(monthly_returns)) # lower (CLT effect)Days 6–10 — Statistical inference
- Hypothesis testing: t-tests, p-values, confidence intervals — and why p-hacking is rampant (and dangerous) in strategy research
- Maximum likelihood estimation (MLE) — how distribution parameters are fit to data
- Regression: OLS, assumptions (linearity, homoscedasticity, no autocorrelation in residuals), and what happens when these are violated in financial data (they almost always are)
- Multiple regression and multicollinearity — critical for factor models in Phase 5
- Bootstrapping — resampling to estimate the distribution of a statistic without parametric assumptions
import statsmodels.api as sm
# OLS regression: does yesterday's return predict today's? (test for autocorrelation)
returns = np.random.normal(0, 0.01, 1000)
X = sm.add_constant(returns[:-1]) # yesterday's return
y = returns[1:] # today's return
model = sm.OLS(y, X).fit()
print(model.summary())
# In efficient markets, the coefficient on lagged return should be ~0Days 11–15 — Linear algebra for finance
- Vectors, matrices, matrix multiplication — portfolio weights as vectors, covariance as matrices
- Eigenvalues/eigenvectors — PCA for yield curve decomposition and factor extraction
- Covariance matrix: the foundation of portfolio variance,
σ²_p = w^T Σ w - Cholesky decomposition — generating correlated random variables for Monte Carlo simulation
- Positive semi-definiteness — why covariance matrices must satisfy this, and what happens (numerically) when they don't
import numpy as np
# Portfolio variance from covariance matrix
weights = np.array([0.4, 0.3, 0.3])
cov_matrix = np.array([
[0.04, 0.01, 0.02],
[0.01, 0.09, 0.01],
[0.02, 0.01, 0.16]
])
portfolio_variance = weights @ cov_matrix @ weights.T
portfolio_vol = np.sqrt(portfolio_variance)
print(f"Portfolio volatility: {portfolio_vol:.4f}")
# Cholesky decomposition: generate correlated random returns for Monte Carlo
L = np.linalg.cholesky(cov_matrix)
uncorrelated = np.random.standard_normal((3, 10000))
correlated_returns = L @ uncorrelated
print("Empirical correlation:\n", np.corrcoef(correlated_returns))Days 16–22 — Time series analysis
Why this matters: asset prices and returns are time series with specific properties — autocorrelation, volatility clustering, non-stationarity — that violate the i.i.d. assumptions of basic statistics.
- Stationarity: a series whose statistical properties don't change over time. Prices are NOT stationary; returns usually are (approximately).
- Augmented Dickey-Fuller (ADF) test — formally test for stationarity
- Autocorrelation (ACF) and partial autocorrelation (PACF) — detect serial dependence in returns
- AR, MA, ARMA, ARIMA models — model the conditional mean of a series
- GARCH models — model the conditional VARIANCE. Volatility clusters: big moves follow big moves. This is THE key stylised fact of financial time series.
- Cointegration — two non-stationary series that move together long-run. Foundation of pairs trading (Phase 5).
import pandas as pd
from statsmodels.tsa.stattools import adfuller
from arch import arch_model
# Test for stationarity
prices = pd.Series(np.cumsum(np.random.normal(0, 1, 1000)) + 100)
returns = prices.pct_change().dropna()
price_adf = adfuller(prices)
return_adf = adfuller(returns)
print(f"Price series ADF p-value: {price_adf[1]:.4f}") # likely > 0.05 (non-stationary)
print(f"Return series ADF p-value: {return_adf[1]:.4f}") # likely < 0.05 (stationary)
# GARCH(1,1) for volatility clustering
model = arch_model(returns * 100, vol='GARCH', p=1, q=1)
result = model.fit(disp='off')
print(result.summary())
# Conditional volatility forecast
forecast = result.forecast(horizon=5)
print(forecast.variance.tail())Days 23–30 — Stochastic calculus essentials
The minimum stochastic calculus toolkit for derivatives pricing:
- Brownian motion (Wiener process):
W(t), the building block of continuous-time finance. Properties:W(0)=0, independent increments,W(t)-W(s) ~ N(0, t-s). - Geometric Brownian Motion (GBM): the standard model for stock prices.
dS = μS dt + σS dW. This is WHY stock prices are lognormally distributed. - Itô's Lemma: the chain rule for stochastic processes. Essential for deriving the Black-Scholes PDE.
- Itô integral vs ordinary integral — why
∫ dW ≠ 0in the way you'd expect from ordinary calculus - Risk-neutral pricing intuition — why derivatives can be priced "as if" the world were risk-neutral, using the no-arbitrage argument
import numpy as np
import matplotlib.pyplot as plt
# Simulate Geometric Brownian Motion paths
def simulate_gbm(S0, mu, sigma, T, n_steps, n_paths):
dt = T / n_steps
Z = np.random.standard_normal((n_paths, n_steps))
increments = (mu - 0.5 * sigma**2) * dt + sigma * np.sqrt(dt) * Z
log_paths = np.log(S0) + np.cumsum(increments, axis=1)
return np.exp(np.hstack([np.full((n_paths, 1), np.log(S0)), log_paths]))
paths = simulate_gbm(S0=100, mu=0.05, sigma=0.2, T=1, n_steps=252, n_paths=10)
plt.plot(paths.T)
plt.title("Simulated GBM Stock Price Paths")
plt.xlabel("Trading days")
plt.ylabel("Price")
plt.show()
# Verify: log returns should be approximately normal with mean (mu - 0.5*sigma^2)*dt
log_returns = np.diff(np.log(paths), axis=1)
print(f"Empirical mean: {log_returns.mean():.6f}")
print(f"Theoretical mean: {(0.05 - 0.5*0.2**2) * (1/252):.6f}")🔨 Phase 1 Project: Statistical Analysis of Real Market Data
Deliverable: A Jupyter notebook analysing real equity data
Using free data (yfinance), for SPY and 4 individual stocks across different sectors:
- Download 5 years of daily price data
- Compute log returns. Test for stationarity (ADF test) on prices vs returns
- Plot return distributions vs fitted normal — visually demonstrate fat tails (Q-Q plot)
- Compute and interpret: mean, std, skewness, kurtosis, Jarque-Bera test for normality
- Plot ACF/PACF of returns AND squared returns — show returns are uncorrelated but squared returns are autocorrelated (volatility clustering)
- Fit a GARCH(1,1) model. Plot conditional volatility vs realised volatility
- Build the covariance matrix for all 5 assets. Compute portfolio variance for an equal-weight portfolio. Run PCA on the covariance matrix — how much variance does PC1 explain? (This is the "market factor")
- Write a 1-page summary: what stylised facts did you observe, and which textbook assumptions do they violate?
⚠️ Common mistakes
Mistake 1
❌ Assuming returns are normally distributed without checking.
Most intro finance courses use normal returns for simplicity. Real returns have fat tails (excess kurtosis) and are slightly left-skewed (crashes are bigger than rallies).
✅ Correct approach: Always plot the empirical distribution against a fitted normal. Run a Jarque-Bera test. If kurtosis > 3 (excess kurtosis > 0), your tail-risk models based on normality will UNDERESTIMATE extreme moves. Consider Student's t or empirical/historical distributions for tail risk.
Mistake 2
❌ Testing for stationarity on price levels and concluding the series is "predictable" because of high autocorrelation.
Price levels are almost always non-stationary (they have a trend/random walk component) and will show high autocorrelation purely as an artifact. This is meaningless for trading.
✅ Correct approach: Always work with returns (or log-differences) for statistical testing. Test for stationarity with ADF before doing time series modelling. Non-stationary inputs to regression produce spurious (meaningless but statistically "significant") relationships — the classic spurious regression problem.
Mistake 3
❌ Treating correlation as constant.
Correlations between assets are NOT stable — they tend to increase sharply during market crashes ("correlation goes to 1 in a crisis"). A covariance matrix estimated from calm-period data underestimates portfolio risk during stress.
✅ Correct approach: Compute rolling correlations and visualise how they change over time, especially during known stress periods (2008, 2020). For risk management, consider stress-period covariance matrices or models that explicitly account for regime changes (Phase 4-5).
Mistake 4
❌ Confusing statistical significance with economic significance.
With enough data, even a tiny, economically meaningless coefficient (e.g., 0.0001 predictive power) can be "statistically significant" at p<0.05.
✅ Correct approach: Always ask: does this effect size matter after transaction costs? A regression coefficient that predicts 0.01% of next-day return is statistically detectable with enough data but completely useless once you account for bid-ask spread and commissions.