Skip to content
All library documents

Approximating Weighted Basket Options with Value-Weighted Volatility

Article Quant Q&A · Author: Matt

Summary

The discussion explains how to extend a simple analytic approximation for an equally weighted basket option to baskets with different constituent weights. It says the weights used in the approximation should reflect each asset’s contribution to the basket’s forward value: multiply its basket weight by its forward price, then divide by the total basket forward. The basket forward is the sum of weighted constituent forwards. These normalized contributions are then used with constituent volatilities and correlations to calculate an approximate basket variance.

That variance can be inserted into a Black–Scholes-style European option formula. The answer reports that this approximation agrees with a Monte Carlo calculation to within 1% for the example’s inputs, and gives a basket volatility for that example. It also points to an enhanced estimate that accounts for implied-volatility skew. The result is an approximation for a basket whose constituents are modeled under Black assumptions; the example does not demonstrate accuracy across other baskets, strikes, or market conditions.

Key ideas

  • Normalize each constituent’s weight by its share of the basket forward value.\nCompute approximate basket variance from the normalized weights, constituent volatilities, and correlations.\nUse the resulting basket volatility in a European option formula as a simple analytic estimate.\nThe example reports close agreement with Monte Carlo, while a skew adjustment is suggested for an improved estimate.

Tags

Full text
# Extend basket analytic solution (equal weighted) to a various weight basket, also put formula


# Extend basket analytic solution (equal weighted) to a various weight basket, also put formula












So I coded up the solution from here: Do basket options have a closed form valuation formula?

Which provides a good solution for equally-weighted underlyings under a Black model. The simplified Python code is here after modifying it quite a bit. I tried to add a weight vector to the code which seems to diverge when ATM from the referenced solution, so I'm pretty sure my implementation is wrong (the analytical one, not MC). If I set all the weights back to equal (sum=1), then the solution seems to converge when ATM and OTM pretty well, so something is definitely wrong with my weight formula in the analytical solution. Hoping someone can spot my error here:

```
import numpy as np
from scipy.stats import multivariate_normal, norm

def Basket_Euro_MC(F, vols, corr, T, strike, paths, weights, callput):
    seed = 1
    means = np.zeros(F.shape[0])
    cov_mat = np.diag(vols).dot(corr).dot(np.diag(vols))
    results = np.zeros((paths, F.shape[0]))
    rng = multivariate_normal(means, cov_mat).rvs(size=paths, random_state=seed)

    for i in range(paths):
        results[i] = F * np.exp(-0.5*vols**2*T) * np.exp(T * rng[i])
    if callput == 1:
        return max(np.mean(np.sum(results*weights, axis=1)-strike),0)
    else:
        return max(np.mean(strike-np.sum(results*weights, axis=1)),0)

def Basket_Euro_Analytic(F, vols, corr, T, strike, weights, callput):
    mod_vol = vols.dot(corr).dot(vols) / len(vols)**2
    mod_fwd = np.product(F)**(1/len(vols))
    d_plus = (np.log(mod_fwd / max(strike,0.0001)) + 0.5 * mod_vol * T) / np.sqrt(mod_vol * T)
    d_minus = d_plus - np.sqrt(mod_vol * T)

    if callput == 1: # call formula is fine
        return np.sum(weights.dot(mod_fwd * norm.cdf(d_plus) - strike * norm.cdf(d_minus)))
    else: # put formula looks pretty suspect...
        return np.sum(weights.dot(max(-(norm.cdf(-d_minus) * mod_fwd - strike * norm.cdf(-d_plus)),0)))

if __name__ == '__main__':
    F = np.array([80., 85., 82., 81., 84.])
    corr = np.array(([1, 0.1, -0.1, 0, 0], [0.1, 1, 0, 0, 0.2], [-0.1, 0, 1, 0, 0], [0, 0, 0, 1, 0.15], [0, 0.2, 0, 0.15, 1]))
    vols = np.array([0.1, 0.12, 0.13, 0.09, 0.11])
    paths = 100000
    T=1
    strike = 78# note BS makes strike >0 in divide!  
    weights = np.array([0.25,0.1,0.2,0.2,0.25])
    callput = 1
    MC_result = Basket_Euro_MC(F, vols, corr, T, strike, paths, weights, callput)
    analytic_result = Basket_Euro_Analytic(F, vols, corr, T, strike, weights, callput)
    print('MC result:', MC_result, 'Analytic Result', analytic_result)
```

## Answer by danp (score 0)

https://quant.stackexchange.com/a/82251

The weights in the analytical formula should be "value-weighted weights" $p_i = w_i F_i/\sum_j w_j F_j$. These weights add up to 1.

The normalization sum in the denominator is the "basket forward" $F_B = \sum_j w_j F_j$. With the numerical values in your code this comes out $F_B = 82.1$.

As a sanity check of the MC computation one can price the basket option as an European option with volatility $\sigma_B^2 = \sum_{i,j} \rho_{ij} p_i p_j \sigma_i \sigma_j$. Plugging the numerical values for the correlation matrix and vols from the code, gives $\sigma_B=5.247\%$.

The table below compares the MC result with this analytical result using the Black-Scholes formula and the fixed basket volatility $\sigma_B$. The agreement is always better than 1%. Not bad for such a simple estimate.

An improved estimate includes also the skew of the basket implied volatility, using the method from this paper https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4702005

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.