\[
% MathJax has no bm package; redefine \bm in terms of \boldsymbol, which it supports natively
\newcommand{\bm}[1]{\boldsymbol{#1}}
% --- Operators -----------------------------------------------------------
% Expectation operator
\DeclareMathOperator{\E}{E}
% Variance operator
\DeclareMathOperator{\Var}{Var}
% Covariance operator
\DeclareMathOperator{\Cov}{Cov}
% Correlation operator
\DeclareMathOperator{\Corr}{Corr}
% Rank operator (Spearman rank correlation)
\DeclareMathOperator{\Rank}{Rank}
% Skewness operator
\DeclareMathOperator{\Skewness}{Skewness}
% Kurtosis operator
\DeclareMathOperator{\Kurtosis}{Kurtosis}
% Difference/differential operator (upright d per ISO 80000-2)
\newcommand{\Diff}{\mathrm{d}}
% --- Risk Measures -------------------------------------------------------
% Value at Risk
\DeclareMathOperator{\VaR}{VaR}
% Expected Shortfall
\DeclareMathOperator{\ES}{ES}
% Marginal Value at Risk
\DeclareMathOperator{\MVaR}{MVaR}
% Component Value at Risk
\DeclareMathOperator{\CompVaR}{CVaR}
% Incremental Value at Risk
\DeclareMathOperator{\IVaR}{IVaR}
% Component Expected Shortfall
\DeclareMathOperator{\CompES}{CES}
% Weighted sensitivity for risk factor in FRTB SBM bucket
\DeclareMathOperator{\WS}{WS}
% Hedge-benefit ratio for FRTB DRC bucket
\DeclareMathOperator{\HBR}{HBR}
% Stress scenario risk measure for FRTB NMRF capital add-on
\DeclareMathOperator{\SES}{SES}
% Jump-to-default exposure for obligor (FRTB DRC)
\DeclareMathOperator{\JTD}{JTD}
% Loss given default for obligor (FRTB DRC)
\DeclareMathOperator{\LGD}{LGD}
% Default risk weight for obligor (FRTB DRC)
\DeclareMathOperator{\RW}{RW}
% ES ratio function
\newcommand{\ESratio}{\lambda}
% Arbitrary risk measure function (coherence axioms)
\newcommand{\RiskMeasure}{\varphi}
% Asset position (coherence axioms)
\newcommand{\Asset}{A}
% First named asset in examples
\newcommand{\AssetA}{A}
% Second named asset in examples
\newcommand{\AssetB}{B}
% Third named asset in examples
\newcommand{\AssetC}{C}
% Constant (risk measure axioms)
\newcommand{\Constant}{c}
% Bucket-level aggregate sensitivity in FRTB SBM
\newcommand{\BucketAgg}{S}
% Cross-bucket correlation in FRTB SBM
\newcommand{\CrossBucketCorr}{\gamma}
% Notional amount for an instrument (FRTB DRC and RRAO)
\newcommand{\Notional}{\text{Notional}}
% --- Distributions -------------------------------------------------------
% Binomial distribution
\DeclareMathOperator{\Binomial}{Binomial}
% Uniform distribution
\DeclareMathOperator{\Uniform}{Uniform}
% Normal distribution
\newcommand{\NormalDist}{\mathcal{N}}
% Student-t CDF
\newcommand{\StudentCDF}{t}
% Student-t PDF
\newcommand{\StudentPDF}{f}
% --- Returns -------------------------------------------------------------
% Price
\newcommand{\Price}{P}
% Simple (arithmetic) return
\newcommand{\SimpleReturns}{R}
% Log (compound) return
\newcommand{\CompoundReturns}{y}
% Matrix of compound returns
\newcommand{\ReturnMatrix}{\bm{y}}
% Dividend payment
\newcommand{\dividend}{d}
% --- Volatility ----------------------------------------------------------
% Realised variance
\DeclareMathOperator{\RealisedVar}{RV}
% Realised volatility (square root of realised variance)
\DeclareMathOperator{\RealisedVol}{RVol}
% Volatility
\newcommand{\Vol}{\sigma}
% GARCH intercept parameter
\newcommand{\GARCHconst}{\omega}
% ARCH coefficient (weight on past squared returns)
\newcommand{\ARCHcoeff}{\alpha}
% GARCH coefficient (weight on past variance)
\newcommand{\GARCHcoeff}{\beta}
% EWMA decay factor (typically 0.94)
\newcommand{\EWMAdecay}{\lambda}
% Standardized residual / error term
\newcommand{\StdNormal}{\epsilon}
% Leverage parameter in apARCH
\newcommand{\APARCHleverage}{\zeta}
% Power parameter in apARCH
\newcommand{\APARCHpower}{\delta}
% Degrees of freedom (Student-t)
\newcommand{\DOF}{\nu}
% GJR-GARCH leverage parameter
\newcommand{\GJRleverage}{\gamma}
% GARCH-X external regressor coefficient
\newcommand{\RegressorCoeff}{\xi}
% Mean (first moment)
\newcommand{\Mean}{\mu}
% Lag order in volatility models
\newcommand{\Lag}{L}
% Dummy variable (indicator in regressions)
\newcommand{\DummyVar}{D}
% --- Portfolio -----------------------------------------------------------
% Portfolio weight (scalar)
\newcommand{\weight}{w}
% Portfolio weight vector
\newcommand{\weights}{\bm{w}}
% Covariance matrix
\newcommand{\CovMatrix}{\bm{\Sigma}}
% Correlation matrix
\newcommand{\CorrMatrix}{\bm{C}}
% Correlation coefficient
\newcommand{\correlation}{\rho}
% Number of assets in portfolio
\newcommand{\NumberAssets}{K}
% Portfolio value
\newcommand{\PortfolioValue}{\vartheta}
% Asset beta (CAPM)
\newcommand{\AssetBeta}{\beta}
% Ledoit-Wolf shrinkage intensity
\newcommand{\ShrinkageIntensity}{\delta}
% Sample covariance matrix
\newcommand{\SampleCov}{\bm{S}}
% --- Time Series ---------------------------------------------------------
% Sample size
\newcommand{\SampleSize}{T}
% Generic count (number of lags, payments, etc.)
\newcommand{\Count}{N}
% Estimation window length
\newcommand{\EstWindow}{W_E}
% Testing window length
\newcommand{\TestWindow}{W_T}
% Stress window length
\newcommand{\StressWindow}{W_S}
% AR coefficient
\newcommand{\ARcoeff}{\phi}
% MA coefficient
\newcommand{\MAcoeff}{\psi}
% --- Probability ---------------------------------------------------------
% Probability level (e.g. 0.01 for 1% VaR)
\newcommand{\probability}{p}
% Cumulative sorted scenario weight
\newcommand{\CumScenarioWeight}{\Omega}
% p-quantile of the P and L distribution
\newcommand{\Quantile}{q}
% Quantile function
\newcommand{\QuantileFunction}{Q}
% Probability density function
\newcommand{\PDF}{f}
% Cumulative distribution function
\newcommand{\CDF}{F}
% Standard normal CDF
\newcommand{\NormalCDF}{\Phi}
% Inverse standard normal (quantile function)
\newcommand{\NormalQuantile}{\Phi^{-1}}
% Standard normal density function
\newcommand{\NormalPDF}{\phi}
% Likelihood function
\newcommand{\lik}{\mathcal{L}}
% Log-likelihood
\newcommand{\LogLikelihood}{\ell}
% Parameter vector (MLE estimation)
\newcommand{\ParamSet}{\theta}
% Parameter space
\newcommand{\ParamSpace}{\Theta}
% Fisher information matrix
\newcommand{\FisherInfo}{\mathcal{I}}
% Statistical power (Type II error rate)
\newcommand{\StatPower}{\beta}
% Significance level (CI coverage complement)
\newcommand{\SignifLevel}{\gamma}
% --- Options -------------------------------------------------------------
% Call option label
\newcommand{\CallOption}{\text{call}}
% Put option label
\newcommand{\PutOption}{\text{put}}
% Strike price
\newcommand{\Strike}{X}
% Risk-free interest rate
\newcommand{\RiskFree}{r_f}
% Option maturity (time to expiration)
\newcommand{\OptionMaturity}{\tau}
% Option delta
\newcommand{\OptionDelta}{\Delta}
% Option gamma
\newcommand{\OptionGamma}{\Gamma}
% Option vega
\newcommand{\Vega}{\mathcal{V}}
% Holding period
\newcommand{\HoldingPeriod}{H}
% Simulation profit/loss (signed; negative is a loss)
\newcommand{\ProfitLoss}{\Pi}
% Futures price
\newcommand{\Futures}{F}
% Hedge ratio
\newcommand{\HedgeRatio}{h}
% Units of basic asset held
\newcommand{\StockHolding}{x^b}
% Units of options held
\newcommand{\OptionHolding}{x^o}
% --- Interest Rates ------------------------------------------------------
% Bond convexity
\newcommand{\Convexity}{C}
% Interest rate / yield
\newcommand{\Yields}{r}
% Key rate change, basis points (scalar tenor component); used as \KeyRateChange_k or \KeyRateChange_{t,k}. EXCEPTION to the no-macro-to-macro-indirection rule, decided 2026-08-09: composed from \Yields on purpose, so a future change to the rate glyph propagates here without a follow-up edit. \Yields is defined earlier in generated output (same interest_rates category, TOML order), so expansion order is safe; verify this still holds if either entry is reordered.
\newcommand{\KeyRateChange}{\Delta \Yields^{\mathrm{bp}}}
% Key rate changes, basis points (vector); used bare or as \KeyRateChanges_t. EXCEPTION to the no-macro-to-macro-indirection rule, decided 2026-08-09: composed from \Yields on purpose, so a future change to the rate glyph propagates here without a follow-up edit. \bm{\Yields} depends on \bm/\boldsymbol correctly expanding a macro argument rather than a bare letter --- confirm this renders correctly the first time either consumer compiles it.
\newcommand{\KeyRateChanges}{\Delta \bm{\Yields}^{\mathrm{bp}}}
% Dollar value of a basis point
\newcommand{\DV}{\text{DV01}}
% Mean reversion speed (interest rate models)
\newcommand{\MeanReversion}{\varkappa}
% Modified duration
\newcommand{\ModDur}{D}
% Macaulay duration
\newcommand{\MacDur}{D_{\text{Mac}}}
% Effective duration
\newcommand{\EffDur}{D_{\text{eff}}}
% DV01 vector
\newcommand{\DVvec}{\bm{d}}
% Long-run mean rate (Vasicek/CIR/Hull-White)
\newcommand{\LongRunRate}{\theta}
% Hull-White time-varying drift
\newcommand{\HullWhiteDrift}{\varphi}
% Cash flow (coupon/principal payment)
\newcommand{\CashFlow}{c}
% --- Extreme Value -------------------------------------------------------
% Tail index (EVT, Pareto-type tails)
\newcommand{\TailIndex}{\iota}
% Shape parameter (xi = 1/iota)
\newcommand{\ShapeParam}{\varsigma}
% Threshold value (EVT)
\newcommand{\Threshold}{u}
% Extremal index (Leadbetter 1983)
\newcommand{\ExtremalIndex}{\theta}
% Multivariate extremal coefficient, theta in [1,d]. theta=1 complete tail dependence; theta=d asymptotic independence. Glyph collides with ExtremalIndex and ParamSet; disambiguated at source by macro name.
\newcommand{\ExtremalCoef}{\theta}
% GEV distribution function
\newcommand{\GEV}{\mathcal{H}}
% GPD distribution function
\newcommand{\GPD}{\mathcal{G}}
% GPD scale parameter
\newcommand{\GPDscale}{\beta}
% GPD scale parameter at a threshold u, distinct from the generic GPD scale
\newcommand{\GPDscaleAtThreshold}{\beta_u}
% Number of threshold exceedances (EVT)
\newcommand{\TailCount}{C}
% Sample maximum (block maxima, EVT)
\newcommand{\SampleMax}{M}
% Kurtosis value (realized, e.g. sample kurtosis)
\newcommand{\KurtosisVal}{\kappa}
% Pareto scaling constant (EVT)
\newcommand{\ParetoConst}{\mathcal{A}}
% Generic constant (EVT asymptotics)
\newcommand{\RemainderConst}{\mathcal{C}}
% Little-o asymptotic notation
\newcommand{\LittleO}{o}
% Moment order (EVT)
\newcommand{\MomentOrder}{\mathscr{m}}
% Doubled sample size (EVT block maxima)
\newcommand{\DoubleSample}{D}
% GEV normalizing location constant
\newcommand{\GEVloc}{a}
% GEV normalizing scale constant
\newcommand{\GEVscale}{b}
% Negative return (loss, EVT context)
\newcommand{\NegativeReturn}{L}
% --- Copulas -------------------------------------------------------------
% Copula function
\newcommand{\Copula}{\mathcal{C}}
% Copula density
\newcommand{\CopulaDensity}{c}
% Generator function (Archimedean copulas)
\newcommand{\generator}{\varphi}
% Copula dependence parameter
\newcommand{\CopulaParam}{\theta}
% Copula marginal parameter set
\newcommand{\MarginalParamSet}{\eta}
% Lower tail dependence coefficient
\newcommand{\LowerTailDep}{\lambda_L}
% Upper tail dependence coefficient
\newcommand{\UpperTailDep}{\lambda_U}
% Marginal CDF (second variable)
\newcommand{\MarginalCDF}{G}
% Joint density function
\newcommand{\JointDensity}{h}
% Marginal density function
\newcommand{\MarginalDensity}{g}
% Joint distribution function
\newcommand{\JointCDF}{H}
% Gaussian copula correlation parameter
\newcommand{\CopulaCorr}{\rho}
% --- Multivariate --------------------------------------------------------
% Diagonal matrix of conditional volatilities
\newcommand{\DiagVolD}{\bm{D}}
% Idiosyncratic variance matrix (diagonal, factor models)
\newcommand{\IdioVarMatrix}{\bm{\Psi}}
% Covariance matrix entry (two indices)
\newcommand{\CovElement}{\sigma}
% Auxiliary matrix in DCC dynamics
\newcommand{\DCCauxQ}{\bm{Q}}
% BEKK constant matrix
\newcommand{\BEKKconst}{\bm{\Omega}}
% BEKK ARCH parameter matrix
\newcommand{\BEKKarch}{\bm{A}}
% BEKK GARCH parameter matrix
\newcommand{\BEKKgarch}{\bm{B}}
% DCC rescaling diagonal matrix
\newcommand{\DCCrescaleZ}{\bm{Z}}
% DCC auxiliary matrix element
\newcommand{\DCCelement}{q}
% Factor loadings matrix (loadings/eigenvectors of the covariance matrix)
\newcommand{\FactorLoadings}{\bm{\Lambda}}
% Factor subscript label
\newcommand{\factor}{\text{factor}}
% PCA eigenvalue
\newcommand{\Eigenvalue}{\lambda}
% Number of factors in factor model
\newcommand{\NumberFactors}{m}
% Factor return vector
\newcommand{\FactorReturn}{\bm{g}}
% PCA factor score (scalar; not an eigenvector)
\newcommand{\PCAFactor}{F}
% BEKK cross-covariance coefficient
\newcommand{\BEKKcross}{\delta}
% Factor loading element
\newcommand{\FactorLoading}{\Lambda}
% DCC correlation persistence parameter
\newcommand{\DCCxi}{\xi}
% DCC news coefficient (weight on recent shocks)
\newcommand{\DCCzeta}{\zeta}
% --- Simulation ----------------------------------------------------------
% Number of simulation paths
\newcommand{\NumberSims}{B}
% Cholesky factor of covariance matrix
\newcommand{\Cholesky}{\bm{L}}
% Uniform random number
\newcommand{\UniformDraw}{\tilde{u}}
% --- Backtesting ---------------------------------------------------------
% Violation ratio (observed/expected violations)
\DeclareMathOperator{\ViolRatio}{VR}
% Stressed Value at Risk
\DeclareMathOperator{\StressedVaR}{SVaR}
% QLIKE loss function for variance forecasts
\DeclareMathOperator{\QLIKE}{QLIKE}
% Mean squared error (forecast loss function)
\DeclareMathOperator{\MSE}{MSE}
% Mean absolute error (forecast loss function)
\DeclareMathOperator{\MAE}{MAE}
% Markov transition probability
\newcommand{\TransProb}{\pi}
% Exception indicator: 1 if y_t < -VaR_t
\newcommand{\ExceptionInd}{\eta}
% Exception count
\newcommand{\ExceptionCount}{\upsilon}
% Probability Integral Transform
\newcommand{\ProbIntegralTransform}{\hat{u}}
% Quantile score (pinball loss function)
\newcommand{\QuantileScore}{\mathcal{S}}
% Kolmogorov-Smirnov test statistic
\newcommand{\KSstat}{D}
% Test statistic (generic base letter)
\newcommand{\TestStat}{J}
% --- General -------------------------------------------------------------
% Regression intercept
\newcommand{\RegressionIntercept}{a}
% Regression slope
\newcommand{\RegressionSlope}{\beta}
% Risk factor
\newcommand{\RiskFactor}{x}
% Forecast horizon
\newcommand{\ForecastHorizon}{h}
% Block length (bootstrap)
\newcommand{\BlockLength}{\ell}
% Capital tau variant
\newcommand{\DeliveryTime}{\Upsilon}
% Today's calendar time in years (option pricing input; distinct from trading-date index t)
\newcommand{\CalendarTime}{t^*}
% Probability measure
\newcommand{\ProbMeasure}{\mathbb{P}}
% Risk-neutral measure
\newcommand{\RiskNeutral}{\mathbb{Q}}
% Pricing function (Black-Scholes, bond pricing, etc.)
\newcommand{\PricingFn}{V}
% Indicator function
\newcommand{\Indicator}{\bm{1}}
% Vector of ones
\newcommand{\OnesVector}{\bm{1}}
% Risk factor sensitivity (first-order P&L)
\newcommand{\Sensitivity}{\delta}
% Second-order sensitivity (convexity/gamma)
\newcommand{\ConvexitySens}{\gamma}
% Output floor percentage (Basel)
\newcommand{\OutputFloor}{\alpha}
% --- Subscript Labels ----------------------------------------------------
% Subscript label: annualized
\newcommand{\Annual}{a}
% Subscript label: implied
\newcommand{\Implied}{I}
% Subscript label: portfolio
\newcommand{\Portfolio}{\pi}
% --- Text Abbreviations --------------------------------------------
% S&P 500 index
\newcommand{\SP}{\text{S\&P-500}}
% Student-t distribution (text)
\newcommand{\St}{\text{Student-t}}
% Geopolitical Risk index (Caldara and Iacoviello)
\newcommand{\GPR}{\text{GPR}}
\]
Serious risk forecasting requires code. These notes assume no prior programming knowledge, but they do assume that you will learn a programming language.
Excel vs programming languages
Excel is everywhere in finance, but it is not suitable for the risk forecasting we do here. It remains a standard tool for many financial tasks including data exploration, reporting and ad hoc analysis.
Programming languages are the better tool for the systematic risk forecasting we do in these notes. Excel becomes cumbersome once the calculations, the datasets or the need to reproduce results grow. While Excel can interface with external packages for sophisticated analysis, this approach limits your understanding of the underlying methods. For learning how to implement risk forecasting techniques from first principles, a programming environment is more suitable.
Excel and these languages can work well together — many workflows involve R, Python or Julia for analysis and Excel for presentation and reporting.
Numerical programming language options
There are many programming languages one could use, ranging from general-purpose languages such as C, C++ and Rust to mathematics languages such as Fortran and specialised statistical languages like Stata. The general-purpose and mathematics languages are designed for other purposes and are not recommended here unless there is a special need for them, in which case you know you need them. Stata is designed for statistical work, but its own section below explains why it is not our choice here.
Four languages are suitable for risk forecasting.
- Matlab;
- Python (NumPy);
- Julia;
We compared the four in Choosing a numerical programming language for economic research: Julia, MATLAB, Python or R. The website for Financial Risk Forecasting contains basic code in all four languages.
R
R is a widely used open-source language designed for statistical analysis. It has the statistical libraries this book needs, and RStudio is a capable interface with many resources for learning it.
R, like any programming language, has limitations. It is over 30 years old and carries some design decisions that can be confusing for new programmers. These include inconsistent function naming conventions, multiple ways to accomplish the same task and some counterintuitive default behaviours. Patrick Burns, in his The R Inferno, catalogues these issues.
For the statistical and risk analysis work we do here, these problems are manageable. Solutions to common problems are well documented, and RStudio smooths over most of the historical rough edges.
Python
Python is one of the most commonly used programming languages in the world. Compared to Matlab, it is relatively young, dating back to the early 1990s.
Python’s numerical ecosystem is built on NumPy, pandas for data manipulation and SciPy for scientific computing. Its popularity means new libraries often appear in Python first, particularly for machine learning (PyTorch, TensorFlow), and it is widely used in quantitative finance.
For this notebook, Python handles data loading with pandas, GARCH estimation with the arch package and plotting with plotnine.
Julia
Julia is a modern and well-designed language. Unlike R, Python and Matlab, it is a child of this century, dating back to 2012. It is designed with numerical work in mind, and once past the initial learning curve, we have found it productive.
Julia is fast, and its ARCHModels package covers the models we estimate. Development environments like VSCode with Julia extensions work well.
Matlab
One of the most widely used numerical programming languages is Matlab. It has been around since the 1970s and is particularly suited to calculations involving linear algebra. It comes with a number of high-quality libraries and remains widely used in a number of industries, not least in engineering.
While we can certainly do the type of work we are doing here with Matlab, it has some practical drawbacks. It is commercial software with significant licensing costs, which can be a barrier for students and smaller organisations. Because it is proprietary, the ecosystem of third-party packages is smaller than for open-source alternatives, and some of the specialist libraries we use in this work are not available. Its syntax also differs from the languages that dominate modern data science workflows.
Stata
Stata is a specialist statistics package that economists use for panel regressions and survey analysis. It is expensive proprietary software with limited programming capabilities compared to general-purpose languages, and R, Python and Julia offer greater programmability and larger ecosystems of packages relevant to financial risk analysis.
Lower-level languages
Some programming languages prioritise performance and system control over ease of use. C and C++ have been extensively used for decades and remain important for performance-intensive applications. Fortran, despite its age, continues to be used for intensive numerical computations and forms the backbone of many mathematical libraries. Rust offers memory safety with C-like performance.
High-frequency traders and exchanges use these languages when microseconds matter. Many of the libraries we use in R, Python and Julia are actually implemented in C, C++ or Fortran underneath, providing the speed while hiding the complexity.
C, C++, Fortran and Rust give more control over the computation, and the cost is that the implementation detail they require is not the subject of these notes. Development time is longer, debugging is harder, and attention moves from the statistics to memory management. We do not use them here.
Practical considerations
Learning considerations
These notes reach for R first because its core statistical tools map directly to the methods being taught. Every method is then implemented in Python and Julia as well, so you can work in whichever you prefer.
Python asks for more general programming before it becomes productive for statistical work, and that knowledge is useful elsewhere. A Matlab licence is usually tied to a university or an employer, which limits practice outside it. Julia has a smaller set of statistical packages, so more has to be written by hand. Which of these matters depends on prior experience, the software available and the packages a piece of work needs.
For the focused statistical work in these notes, R, Python and Julia allow you to start producing meaningful results quickly while building programming skills gradually.
Cost considerations
R is free and open-source, as are Python and Julia. Matlab requires expensive commercial licences that can cost thousands of pounds per user annually, making it impractical for individual learning or small organisations. While some universities provide Matlab access, this limits your ability to continue using it after graduation. The free alternatives also tend to have more active development communities and faster adoption of new methods.
Industry usage
Firms rarely standardise on one language. A bank may use Python for data pipelines, R for regulatory reporting, Julia for heavy computation and Matlab in legacy pricing code.
What do we use?
In our daily work, we use R, Python and Julia. We pick the best language for the task at hand. For example, our website Extreme Risk, which has risk forecasts that are updated every day, is based on all three. Python handles downloading and basic data processing and runs the processing pipeline. Julia performs the actual risk calculations. R creates the graphics.