Correlation Effects in Best-of Rainbow Options and Monte Carlo Checks
Summary
The document investigates how asset correlation affects best-of and worst-of rainbow option values in Monte Carlo simulations. The author initially observes unexpected best-of behavior and a breakdown in an outperformance option calculation as correlation rises. Replies explain that exchange options are short correlation and relate the best-of payoff to exchange and vanilla option payoffs. They also give a parity relationship: best-of plus worst-of equals the sum of the individual calls, which is independent of correlation under the stated setup.
The author later identifies the simulation defect: correlated normal variates were normalized incorrectly, leaving one variate with a standard deviation far above one despite the intended correlation. After correcting the normalization, the prices behaved as expected and the proposed parity check held. The episode shows why validating marginal distributions matters as well as checking correlations. The parity and intuition are useful debugging guides, but the discussion concerns the described simulation and assumes its option and market setup; it does not establish general pricing results beyond that context.
Key ideas
- Best-of rainbow option payoffs are described as short correlation, while worst-of payoffs have the opposite correlation exposure in the stated setup.
- The sum of best-of and worst-of values equals the sum of the individual calls, providing a correlation-independent parity check.
- Monte Carlo paths must have both the intended correlation and correctly scaled marginal standard deviations.
- Incorrect normalization caused the reported anomalous price behavior in the simulation.
- Comparing Monte Carlo results with payoff bounds and closed-form cases can help locate implementation errors.
Tags
Full text
# Effect of correlation on a best-of rainbow option
# Effect of correlation on a best-of rainbow option
EDIT 2: I found the problem(s) and the prices seem to behave as expected now. For anyone interested there was a bug when normalizing the dependant ranom normal variates used in the simulation, so while they had the correct correlation one of them had a standard deviation of 1 and the other a standard deviation much greater than 1. Causing the price to not drop (and even increase) even as correlation increased. The parity relation suggested by @ir7 seems to hold now which gives me confident that all is well.
I'm valuing a rainbow option numerically with a monte-carlo simulation and I'm getting some unexpected results. The price of a best-of call option is decreasing in price as correlation up to a certain point where it starts to increase, contrary to my intuition. The worst-of option case is much more well behaved with being an increasing function of the correlation as expected. Since I'm valuing them in much the same way (just taking min(...) instead of max(...) in my code) I'm very confused as to what could be wrong, or is it possible for the price to behave in this way? If it's completely unreasonable, does anyone want to hazard a guess as to why my calculations might be breaking down as the correlation increases? The program is written in C++ so if anyone fluent in C++ wants to have a look at my code for something erroneous I'd be more than happy to post it.
EDIT 1: After troubleshooting a bit with the help of @ir7 it seems there's something off with my Monte Carlo simulation for several assets (the single asset case works fine). Posting (some of) my C++ code below for the simpler problem of valuing an out-performance option which as a closed form solution for him (and anyone else who wants to of course) to check out and give help. The caclulations again seem to break down once the correlation goes above ~0.5, see below image. If there are some function calls used that you want me to explain or post the code for I'd be happy to do so, for now I'll try to keep it somewhat bare:
The class and function that does the actual valuation:
```
MonteCarloOutPerformanceOptionFunction::MonteCarloOutPerformanceOptionFunction(std::string uniqueIdentifier_, int nominal_, std::vector<double> S0_vect, std::vector<Wrapper<PayOff>> ThePayOffVect_, double r_, std::vector<double> d_vect_, std::vector<double> impvol_vect_, std::vector<std::vector<double>> covMatrix_, double TTM_, unsigned long numberOfPaths_)
: r(r_), S_vect(S0_vect), ThePayOffVect(ThePayOffVect_), d_vect(d_vect_), covMatrix(covMatrix_), valuationFunction(uniqueIdentifier_, TTM_, nominal_), numberOfPaths(numberOfPaths_), impvol_vect(impvol_vect_)
{
if (covMatrix.size() != S_vect.size())
throw("Missmatched Covariance matrix and initial spot values array sizes in OutPerformance Option");
if (2 != S_vect.size())
throw("More than two equities specified in OutPerformance Option");
}
void MonteCarloOutPerformanceOptionFunction::ValueInstrument()
{
std::vector<MJArray> correlatedNormVariates = GetArraysOfCorrelatedGauassiansByBoxMuller(numberOfPaths, covMatrix);
std::vector<StatisticAllPaths> thesePathGatherers;
for (unsigned long i = 0; i < S_vect.size(); i++)
{
StandardExcerciseOption thisOption(ThePayOffVect[i], TTM);
StatisticAllPaths onePathGatherer;
thesePathGatherers.push_back(onePathGatherer);
OneStepMonteCarloValuation(thisOption, S_vect[i], impvol_vect[i], r, d_vect[i], numberOfPaths, correlatedNormVariates[i], thesePathGatherers[i]);
}
f = 0;
for (unsigned long i = 0; i < numberOfPaths; i++)
{
std::vector<double> outcomes;
outcomes.reserve(S_vect.size());
for (unsigned long j = 0; j < S_vect.size(); j++)
{
outcomes.push_back(thesePathGatherers[j].GetOneValueFromResultsSoFar(i));
}
f += std::max(outcomes[0] - outcomes[1], 0.0);
}
f *= ((double)nominal / numberOfPaths);
return;
}
```
The Monte Carlo simulation function being called at OneStepMonteCarloValuation (this seems to work fine for single asset options like vanilla calls/puts)
```
void OneStepMonteCarloValuation(const StandardExcerciseOption& TheOption, double Spot, double Vol, double r, double d, unsigned long NumberOfPaths, MJArray normVariates, StatisticsMC& gatherer)
{
if (normVariates.size() != NumberOfPaths)
throw("mismatched number of paths and normal variates");
//Pre-calculate as much as possible
double Expiry = TheOption.GetExpiry();
double variance = Vol * Vol * Expiry;
double rootVariance = sqrt(variance);
double itoCorrection = -0.5 * variance;
double movedSpot = Spot * exp((r-d) * Expiry + itoCorrection);
double thisSpot;
double discounting = exp(-r * Expiry);
for (unsigned long i = 0; i < NumberOfPaths; i++)
{
thisSpot = movedSpot * exp(rootVariance * normVariates[i]);
double thisPayoff = TheOption.OptionPayOff(thisSpot);
gatherer.DumpOneResult(discounting * thisPayoff);
}
return;
}
```
The StatisticAllPaths class which is used as input in the simulation that collects all the final values of the simulation
```
StatisticAllPaths::StatisticAllPaths(const unsigned long minimumNumberOfPaths) : PathsDone(0)
{
ResultList.reserve(minimumNumberOfPaths);
}
void StatisticAllPaths::DumpOneResult(double result)
{
ResultList.push_back(result);
PathsDone++;
}
const double& StatisticAllPaths::GetOneValueFromResultsSoFar(unsigned long index) const
{
return ResultList[index];
}
```
The PayOffVect used is used here to take the payoff of each path in the MC valuation function, but since we're just collecting all the paths here and processing them later (in the last part of the main valuation class) it doesn't really do anything here. It it used in this case just to make the outperformance relative values with this inherited class:
```
PayOffRelPerformance::PayOffRelPerformance(double startValue_) : startValue(startValue_)
{
}
double PayOffRelPerformance::operator()(double spot) const
{
return spot / startValue;
}
```
The GetArraysOfCorrelatedGauassiansByBoxMuller does the job of generating the vectors of normal variates that will be used in the simulation. I have checked that the Cholezky Matrix is correct for real cases, and I have also checked that the outputted normal variates are in fact dependent with the correlation implied by the covariance Matrix.
```
std::vector<MJArray> GetArraysOfCorrelatedGauassiansByBoxMuller(unsigned long numberOfVariates, std::vector<std::vector<double>> covMatrix)
{
//Calculate the cholezky Matrix
std::vector<std::vector<double>> cholezkyMatrix = Cholesky_Decomposition(covMatrix);
//Fix the size of the arrays to contain correlated normal variates
std::vector<MJArray> corrNormVariatesVector(cholezkyMatrix.size());
for (unsigned long j = 0; j < corrNormVariatesVector.size(); j++) {
corrNormVariatesVector[j].resize(numberOfVariates);
corrNormVariatesVector[j] = 0;
}
//calculate correlated normal variates and fill the arrays with values
MJArray NormVariates(cholezkyMatrix.size());
for (unsigned long k = 0; k < numberOfVariates; k++) {
for (unsigned long i = 0; i < cholezkyMatrix.size(); i++)
{
NormVariates[i] = GetOneGaussianByBoxMuller();
for (unsigned long j = 0; j < cholezkyMatrix[i].size(); j++) {
corrNormVariatesVector[i][k] += cholezkyMatrix[i][j] * NormVariates[j];
}
corrNormVariatesVector[i][k] /= cholezkyMatrix[i][i]; //normalize the random variates
}
}
return corrNormVariatesVector;
}
```
## Answer by ir7 (score 3)
https://quant.stackexchange.com/a/55432
Intuitively, they should both be short correlation, that is the less correlated the assets are the higher the value of the worst of/best of option.
The best of option payoff is sandwiched by an exchange option payoff (plus other vanilla forward/option payoffs on single stock, insensitive to correlation):
$$ X_T -K + (Y_T-X_T)^+ \leq \max(X_T - K ,Y_T - K,0) \leq (X_T-K)^+ + (Y_T-X_T)^+ $$
It is clear intuitively that the exchange option is short correlation (also explicitly seen in Margrabe's world).
Edit: For worst of option we have a similar relation:
$$ K-X_T + (X_T-Y_T)^+ \leq \max(K-X_T,K-Y_T,0) \leq (K-X_T)^+ + (X_T-Y_T)^+ $$
Edit2: You can look at the behaviour of the product packages I claim sandwich the rainbow, for various correlations. This could expose something quickly. Remember that those product packages need both MC prices (from the same loops you use for rainbows) and also have closed-form solutions (of course, vols are flat etc. in this debugging phase), so insert both versions in your comparisons.
## Answer by Peter A (score 1)
https://quant.stackexchange.com/a/55438
Best-of + Worst-of = Call1 + Call2
The right hand side is independent of correlation (and you can check it in your model).
Therefore if Best-of is short correlation, worst-of must be long correlation.
Increasing correlation makes the two assets more similar and therefore makes the best-of more like a vanilla. This is why a best-of is short correlation.
(Hope I understood the question right!)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.