SigmaField
Mathematical Foundations for Quant Finance
0 of 256 lessons
      1. 4.7.1 Double integralsConcept · 35 min
      2. 4.7.2 Change of variables and the JacobianConcept · 35 min
      3. 4.7.3 Polar coordinates and the Gaussian integralConcept · 30 min
      4. 4.7.4 Practice set: multiple integralsPractice set · 25 min
      5. 4.7.5 Case study: joint densities and Monte Carlo integrationCase study · 35 min

Level 4, Multivariable calculus and optimisation 4.7 Multiple integrals Lesson 5 of 5

Case studyLesson 4.7.5 in Level 4, Multivariable calculus and optimisation. About 35 minutes.

Case study: joint densities and Monte Carlo integration

The model

Take the annual returns RAR_A and RBR_B of the two assets as normal, with the numbers used since case study 3.2.5. Their expected returns are 6% and 8%, their volatilities 12% and 18%, and their correlation ρ=0.3\rho = 0.3.

The numbers are illustrative, and normal annual returns are a simplification. They are used here because the answer can then be checked exactly.

Each asset alone is a one-variable question (lesson 2.6.3). A loses more than 10% when RA<−0.10R_A < -0.10. Since −0.10−0.060.12=−1.3333\frac{-0.10 - 0.06}{0.12} = -1.3333, that is 1.3333 standard deviations below its mean, so the chance is N(−1.3333)=0.0912N(-1.3333) = 0.0912. For B it is N(−1)=0.1587N(-1) = 0.1587. Their product is 0.01447.

The product is right only if the two returns are independent: if one tells you nothing about the other. They are correlated, so the question needs both returns at once.

A joint density is a surface with volume 1

Start with two independent standard normal draws Z1Z_1 and Z2Z_2, as case study 3.7.4 drew them. Their joint density is the surface

f(z1,z2)=φ(z1) φ(z2)=12π e−(z12+z22)/2f(z_1, z_2) = \varphi(z_1)\,\varphi(z_2) = \frac{1}{2\pi}\,e^{-(z_1^2 + z_2^2)/2}

with φ\varphi the standard normal density. Lesson 4.7.3 found that its volume over the whole plane is 1. The probability that the pair lands in a region is the volume over that region.

Rule: Joint density

f≥0,∬R2f dA=1P((X,Y)∈R)=∬Rf dA\begin{gathered} f \ge 0, \quad \iint_{\mathbb{R}^2} f\,dA = 1 \\[4pt] P\big((X, Y) \in R\big) = \iint_R f\,dA \end{gathered}

A density for two random variables XX and YY is a probability per unit area: per unit of xx times unit of yy. Lesson 5.5.1 studies joint densities in general.

Two regions show the idea. Both draws are negative on the quarter plane z1<0z_1 < 0, z2<0z_2 < 0, a rectangle, so the product rule of lesson 4.7.1 gives 12×12=14\frac{1}{2} \times \frac{1}{2} = \frac{1}{4}. The pair lands within distance 2 of the origin with probability

12π∫02π∫02e−r2/2 r dr dθ=1−e−2\frac{1}{2\pi}\int_0^{2\pi} \int_0^2 e^{-r^2/2}\,r\,dr\,d\theta = 1 - e^{-2}

in polar coordinates (lesson 4.7.3). That is 0.8647, the share of draws that the dashed circle of radius 2 in case study 3.7.4 should hold.

Correlated returns: the density by a change of variables

Case study 3.7.4 made correlated returns from independent draws with the Cholesky factor LL of the covariance matrix (lesson 3.7.2): x=μ+Lz\mathbf{x} = \boldsymbol{\mu} + L\mathbf{z}. For two assets,

L=(σA0ρ σBσB1−ρ2)≈(0.1200.0540.171709)\begin{gathered} L = \begin{pmatrix} \sigma_A & 0 \\ \rho\,\sigma_B & \sigma_B\sqrt{1 - \rho^2} \end{pmatrix} \\[4pt] \approx \begin{pmatrix} 0.12 & 0 \\ 0.054 & 0.171709 \end{pmatrix} \end{gathered}

This map multiplies every area by det⁡L=σAσB1−ρ2=0.020605\det L = \sigma_A\sigma_B\sqrt{1 - \rho^2} = 0.020605 (lesson 4.7.2). A small cell keeps its probability as it moves, so its density, probability per unit area, is divided by det⁡L\det L.

Rule: Density of correlated normal returns

f(x,y)=φ(z1) φ(z2)det⁡Lz=L−1(x−μ)\begin{gathered} f(x, y) = \frac{\varphi(z_1)\,\varphi(z_2)}{\det L} \\[4pt] \mathbf{z} = L^{-1}(\mathbf{x} - \boldsymbol{\mu}) \end{gathered}

z\mathbf{z} comes from x\mathbf{x} by forward substitution (lesson 3.7.2): z1=x−μAσAz_1 = \frac{x - \mu_A}{\sigma_A}, then z2=y−μB−ρσBz1σB1−ρ2z_2 = \frac{y - \mu_B - \rho\sigma_B z_1}{\sigma_B\sqrt{1 - \rho^2}}. Lesson 5.5.3 writes the same density with Σ−1\Sigma^{-1} and det⁡Σ=(det⁡L)2\det \Sigma = (\det L)^2.

The peak, at the expected returns, is 12πdet⁡L=7.7241\frac{1}{2\pi \det L} = 7.7241 per unit of return squared. The units make sense in numbers. Both returns land within half a percentage point of their means, a square of side 0.01, with probability about 7.7241×0.012=0.0007727.7241 \times 0.01^2 = 0.000772; dblquad gives 0.000772 too.

Try it

density is the rule. It agrees with SciPy's multivariate_normal, which Level 5 meets properly.

dblquad (lesson 4.7.1) checks that the volume over the whole plane is 1, then measures the small square around the means.

⌘/Ctrl + Enter runs the cell

The probability of a joint loss

Both lose more than 10% on the corner x<−0.10x < -0.10, y<−0.10y < -0.10. Its probability is the volume over the corner:

∫−∞−0.1∫−∞−0.1f(x,y) dy dx=0.0288\int_{-\infty}^{-0.1} \int_{-\infty}^{-0.1} f(x, y)\,dy\,dx = 0.0288

Both limits are infinite, as in lesson 2.6.3, and dblquad accepts -np.inf. Cutting the corner off 8 standard deviations below the means gives the same 0.028810: the tails beyond hold almost nothing.

The joint density of the two returns, independent and with correlation 0.3. Correlation tilts the level curves, so more of the volume, and more simulated years, fall in the corner where both lose.

Correlation does not change either asset alone: A still loses more than 10% in 9.12% of years. It moves volume towards the corners where both are low or both are high. The corner's probability rises with ρ\rho:

ρ\rho−0.3-0.300.30.60.9
P(both lose>10%)P(\text{both lose} > 10\%)0.00500.01450.02880.04880.0793

At ρ=0\rho = 0 the double integral is the product, 0.0145, as lesson 4.7.1's product rule says.

A region between curves

The regions need not be rectangles. A portfolio of half A and half B loses more than 10% when 0.5x+0.5y<−0.100.5x + 0.5y < -0.10, that is when y<−0.2−xy < -0.2 - x. The inner limit depends on xx, as in lesson 4.7.1, and dblquad gives 0.082139.

The answer can be checked another way. The portfolio's return is itself normal (Level 5), with mean 7% and, by case study 1.2.4's formula, volatility 12.2229%. So the chance is N(−0.10−0.070.122229)=0.082139N\left(\frac{-0.10 - 0.07}{0.122229}\right) = 0.082139.

Try it

The corner by dblquad, with infinite limits and cut off at μ−8σ\mu - 8\sigma. Then the product of the two single chances, and the loop over ρ\rho.

The last lines do the portfolio's region, with the upper limit lambda x: -0.2 - x, and check it with the portfolio's own normal distribution.

⌘/Ctrl + Enter runs the cell

The cut-off −1.5-1.5 is more than 8 standard deviations below both means, since 0.06−8×0.12=−0.90.06 - 8 \times 0.12 = -0.9 and 0.08−8×0.18=−1.360.08 - 8 \times 0.18 = -1.36.

Check yourself
With correlation 0.3, the chance that both assets lose more than 10% is 2.88%, against 1.45% if they were independent. What has the correlation changed?

Monte Carlo: the same probability by simulation

There is a second way to find a volume: simulate. Draw many years of returns with x=μ+Lz\mathbf{x} = \boldsymbol{\mu} + L\mathbf{z}, as in case study 3.7.4, and count the share that land in the corner. That share estimates the probability, and the estimate is called Monte Carlo integration.

With the seed 47, here is the share among the first NN of a million draws, against the double integral's 0.028810:

draws NN1,00010,000100,0001,000,000
years in the corner262932,85628,921
estimate0.0260.02930.028560.02892
error−0.00281-0.00281+0.00049+0.00049−0.00025-0.00025+0.00011+0.00011
standard error0.005030.001690.000530.00017

The standard error p^(1−p^)/N\sqrt{\hat{p}(1 - \hat{p})/N}, with p^\hat{p} the estimate, is the typical size of the error; module 5.7 explains where it comes from. Each error here is within two standard errors. A Monte Carlo number should always be reported with its standard error.

Rule: Monte Carlo integration

∬Rf dA≈kNstandard error≈p^(1−p^)/N\begin{gathered} \iint_R f\,dA \approx \frac{k}{N} \\[4pt] \text{standard error} \approx \sqrt{\hat{p}(1 - \hat{p})/N} \end{gathered}

Draw NN points from the density ff and count the kk that land in RR. For any integral over a box BB, the same idea gives ∬Bg dA≈area(B)×\iint_B g\,dA \approx \text{area}(B) \times the mean of gg at NN uniform random points. Either way the error shrinks like 1N\frac{1}{\sqrt{N}}.

The box version works for any integral. Lesson 4.7.1's volume under x2+2y2x^2 + 2y^2 over [0,2]×[0,1][0, 2] \times [0, 1] is 4.

With 1,000 uniform points and the seed 49, the area 2 times the mean height gives 3.8932, with standard error 0.0837. With 100,000 points it gives 4.0011, with standard error 0.0084.

Error like one over the square root of NN

One run can be lucky or unlucky, so run the simulation 200 times at each size and measure the spread of the estimates. The spread falls from 0.01896 at 100 draws to 0.00052 at 100,000, close to the formula p(1−p)/N\sqrt{p(1 - p)/N} each time.

The spread of a Monte Carlo estimate against the number of draws, on log scales. The slope is about −12-\frac{1}{2}: 100 times the draws buy only 10 times the accuracy.

On log scales the points lie on a line of slope −12-\frac{1}{2}, read as in lesson 2.8.2, where Simpson's rule had slope −4-4. Case study 3.7.4 met the same pattern for estimated correlations: ten times the draws narrowed the band only about 10≈3.16\sqrt{10} \approx 3.16 times.

Rare events cost more. The relative error is (1−p)/(pN)\sqrt{(1 - p)/(pN)}: with 10,000 draws it is 5.8% for this corner. For a 1-in-1,000 event it is 31.6%, and a 10% relative error needs about 99,900 draws.

Try it

The first lines draw a million years once and count the corner among the first NN. hits[:n] keeps the first n entries.

Then 4.7.1's volume by uniform points, and the spread of 200 runs at each size, with the seed 48.

⌘/Ctrl + Enter runs the cell

dblquad against simulation, and where Monte Carlo wins

In two dimensions the double integral wins easily. With the corner cut off 8 standard deviations out, dblquad gives 0.028810 to about eight digits from about 700 values of the density. A million simulated years give only about three digits, 0.02892±0.000170.02892 \pm 0.00017.

More dimensions turn this round. A grid with nn points along each of dd axes needs ndn^d values. With n=100n = 100, that is 10410^4 in two dimensions, 10610^6 in three and 102010^{20} in ten.

With a fixed budget of NN values, a midpoint grid's error shrinks like N−2/dN^{-2/d}, while Monte Carlo's shrinks like N−1/2N^{-1/2} in any number of dimensions.

dimensions dd234610
points per axis for 10610^6 values100010031.6104
grid error, roughly10−610^{-6}10−410^{-4}10−310^{-3}10−210^{-2}6×10−26 \times 10^{-2}
Monte Carlo error, roughly10−310^{-3}10−310^{-3}10−310^{-3}10−310^{-3}10−310^{-3}

The two meet near four dimensions; beyond that, simulation wins. The rows give orders of size only, since each method's error also has a constant in front.

Three assets already show the cost. The chance that A, B and C (case study 3.2.5) all lose more than 10% is a triple integral.

SciPy's tplquad finds 0.013030, but needs about 29,000 values of the density instead of 700. A million simulated years give 0.013038±0.000110.013038 \pm 0.00011 at the same cost as before. The independence product, 0.0029, is more than four times too small.

Try it

count wraps the density to count how often dblquad and tplquad call it. The three-asset density uses the 3×33 \times 3 Cholesky factor, with forward substitution for z\mathbf{z}.

The integrals stop 8 standard deviations below the means. The last lines simulate the three assets with the seed 47 and compare with the independence product.

⌘/Ctrl + Enter runs the cell
Check yourself
A Monte Carlo estimate from 10,000 draws has standard error 0.0017. About how many draws give a standard error of 0.00017?
Check yourself
A desk models 20 stocks as normal, with a given covariance matrix. It needs the chance that all 20 fall more than 15% in a month. Which method fits?
Exercise

Joint loss probability, two ways

Write two functions for two normal returns with means mu = (mu_a, mu_b), volatilities sigma = (sigma_a, sigma_b) and correlation rho.

  1. joint_loss_exact(mu, sigma, rho, threshold) returns P(RA<threshold,RB<threshold)P(R_A < \text{threshold}, R_B < \text{threshold}) by dblquad of the density above, with limits from -np.inf to threshold.
  2. joint_loss_mc(mu, sigma, rho, threshold, n, seed) draws n pairs with np.random.default_rng(seed) and x=μ+Lz\mathbf{x} = \boldsymbol{\mu} + L\mathbf{z}. It returns the pair (estimate, standard_error), with the standard error p^(1−p^)/n\sqrt{\hat{p}(1 - \hat{p})/n}.

Both raise ValueError if a volatility is not positive or if ∣ρ∣≥1\lvert\rho\rvert \ge 1; joint_loss_mc also raises it if n is less than 1. The tests use this case study's assets, independent returns, standard normals with correlation 0.5 (both negative a third of the time), and daily returns.

See the tests
import math

from scipy.stats import norm

MU = (0.06, 0.08)
SIGMA = (0.12, 0.18)


def test_case_study():
    """Assets A and B with correlation 0.3 both lose more than 10% with probability about 0.028810"""
    expect_close(joint_loss_exact(MU, SIGMA, 0.3, -0.10), 0.028809628207340565, what="joint_loss_exact(A, B, 0.3, -0.10)", rel=1e-6)


def test_independent():
    """With correlation 0 the answer is the product of the single chances, about 0.014471"""
    product = norm.cdf(-0.16 / 0.12) * norm.cdf(-0.18 / 0.18)
    expect_close(joint_loss_exact(MU, SIGMA, 0.0, -0.10), product, what="joint_loss_exact with rho = 0", rel=1e-6)


def test_negative_correlation():
    """With correlation −0.3 a joint loss is rarer: about 0.004982"""
    expect_close(joint_loss_exact(MU, SIGMA, -0.3, -0.10), 0.004982408097745086, what="joint_loss_exact with rho = -0.3", rel=1e-6)


def test_standard_normals():
    """Standard normals with correlation 0.5 are both negative a third of the time"""
    expect_close(joint_loss_exact((0, 0), (1, 1), 0.5, 0.0), 1 / 3, what="joint_loss_exact((0, 0), (1, 1), 0.5, 0)", rel=1e-6)


def test_daily():
    """Daily returns (means 0.04% and 0.03%, volatilities 1% and 1.5%, correlation 0.6) both fall below −2% with probability about 0.011227"""
    expect_close(joint_loss_exact((0.0004, 0.0003), (0.01, 0.015), 0.6, -0.02), 0.011226545532604399, what="joint_loss_exact for daily returns", rel=1e-5)


def test_mc_close():
    """200,000 simulated pairs land within 4 standard errors of the exact 0.028810"""
    p, se = joint_loss_mc(MU, SIGMA, 0.3, -0.10, 200_000, seed=1)
    assert abs(p - 0.028809628207340565) < 4 * se, f"estimate {p:.5f} is more than 4 standard errors ({se:.5f}) from 0.028810"


def test_mc_standard_error():
    """The standard error is √(p(1 − p)/n) for the estimate p"""
    p, se = joint_loss_mc(MU, SIGMA, 0.3, -0.10, 50_000, seed=2)
    expect_close(se, math.sqrt(p * (1 - p) / 50_000), what="standard error", rel=1e-9)


def test_mc_seed():
    """The same seed gives the same estimate; another seed gives a different one"""
    a = joint_loss_mc(MU, SIGMA, 0.3, -0.10, 10_000, seed=5)
    b = joint_loss_mc(MU, SIGMA, 0.3, -0.10, 10_000, seed=5)
    c = joint_loss_mc(MU, SIGMA, 0.3, -0.10, 10_000, seed=6)
    expect_equal(a, b, what="two runs with seed 5")
    assert a != c, "seeds 5 and 6 gave the same estimate: is the seed used?"


def test_bad_inputs():
    """A volatility of 0, a correlation of 1 or n = 0 raises ValueError"""
    calls = [
        lambda: joint_loss_exact(MU, (0.0, 0.18), 0.3, -0.10),
        lambda: joint_loss_exact(MU, SIGMA, 1.0, -0.10),
        lambda: joint_loss_mc(MU, SIGMA, -1.0, -0.10, 1000, seed=1),
        lambda: joint_loss_mc(MU, SIGMA, 0.3, -0.10, 0, seed=1),
    ]
    for k, call in enumerate(calls, 1):
        try:
            call()
        except ValueError:
            continue
        raise AssertionError(f"case {k}: expected ValueError")

Summary card

  • A joint density f(x,y)f(x, y) is a surface with volume 1; the probability of a region is the volume over it, ∬Rf dA\iint_R f\,dA.
  • Independent standard normals have density φ(z1)φ(z2)\varphi(z_1)\varphi(z_2). Correlated returns x=μ+Lz\mathbf{x} = \boldsymbol{\mu} + L\mathbf{z} have density φ(z1)φ(z2)det⁡L\frac{\varphi(z_1)\varphi(z_2)}{\det L} with z=L−1(x−μ)\mathbf{z} = L^{-1}(\mathbf{x} - \boldsymbol{\mu}): the change of variables of lesson 4.7.2.
  • For assets A and B, both lose more than 10% with probability 0.0288, not the independence product 0.0145. Correlation leaves each asset alone and moves volume into the corners.
  • Monte Carlo integration counts the share of simulated points in the region. Its standard error is p^(1−p^)/N\sqrt{\hat{p}(1 - \hat{p})/N}, shrinking like 1N\frac{1}{\sqrt{N}}.
  • In two dimensions dblquad is faster and far more accurate. In many dimensions a grid needs ndn^d values and Monte Carlo wins.

Free further reading

Optional. This lesson covers what you need; these add more examples and practice. All are free to read.