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

Manage alerts

Loading saved threads...

Matt · External communityPost link
External question — Quantitative Finance Stack Exchange Author: Matt Original post: https://quant.stackexchange.com/questions/70402 License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/ Adaptation: HTML converted to plain text; contact email addresses removed. 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)
Quote
Report
danp · External communityPost link
External answer — Quantitative Finance Stack Exchange Author: danp Original post: https://quant.stackexchange.com/a/82251 License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/ Adaptation: HTML converted to plain text; contact email addresses removed. 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
Quote
Report

Post Reply

Quoted from Forex.com.bd-Editorial External question — Quantitative Finance Stack Exchange Author: Matt Source score (net votes, not local likes): 1 Original post: https://quant.stackexchange.com/questions/70402 License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/ Adaptation: HTML converted to plain text; contact email addresses removed. 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)

Cancel quote

Checking account access…