EDIT 2: He encontrado el problema(s) y los precios parecen comportarse como se espera ahora. Para cualquiera que esté interesado, había un error al normalizar las variantes normales dependientes utilizadas en la simulación, por lo que mientras tenían la correlación correcta una de ellas tenía una desviación estándar de 1 y la otra una desviación estándar mucho mayor que 1. Causando que el precio no cayera (e incluso aumentara) incluso cuando la correlación aumentaba. La relación de paridad sugerida por @ir7 parece mantenerse ahora, lo que me hace confiar en que todo está bien.
Estoy valorando un opción arco iris numéricamente con una simulación monte-carlo y estoy obteniendo algunos resultados inesperados. El precio de una opción de compra "best-of" disminuye como correlación hasta un cierto punto en el que empieza a aumentar, en contra de mi intuición. El caso de la opción "worst-of" se comporta mucho mejor siendo una función creciente de la correlación como se esperaba. Dado que las estoy valorando prácticamente de la misma manera (sólo tomando min(...) en lugar de max(...) en mi código) estoy muy confundido en cuanto a lo que podría estar mal, o es posible que el precio se comporte de esta manera? Si es completamente irracional, ¿alguien quiere arriesgarse a adivinar por qué mis cálculos podrían estar rompiendo a medida que aumenta la correlación? El programa está escrito en C++, así que si alguien que domine C++ quiere echar un vistazo a mi código en busca de algo erróneo, estaré encantado de publicarlo.
EDIT 1: Después de la solución de problemas un poco con la ayuda de @ir7 parece que hay algo fuera con mi simulación de Monte Carlo para varios activos (el caso de un solo activo funciona bien). He publicado (parte) de mi código C++ para el problema más simple de valorar una opción de rendimiento que es una solución de forma cerrada para que él (y cualquier otro que quiera, por supuesto) lo compruebe y le ayude. Los caclulations de nuevo parecen romper una vez que la correlación va por encima de ~ 0,5, ver la imagen de abajo. Si hay algunas llamadas a funciones usadas que quieres que explique o publique el código estaré encantado de hacerlo, por ahora intentaré mantenerlo algo desnudo:
La clase y la función que hace la valoración real:
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;
}
La función de simulación Monte Carlo que se llama en OneStepMonteCarloValuation (esto parece funcionar bien para las opciones de un solo activo, como las opciones de compra/venta de vainilla)
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;
}
El StatisticAllPaths que se utiliza como entrada en la simulación que recoge todos los valores finales de la simulación
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];
}
El PayOffVect se utiliza aquí para tomar el payoff de cada camino en la función de valoración MC, pero como sólo estamos recogiendo todos los caminos aquí y procesarlos más tarde (en la última parte de la clase de valoración principal) no hace realmente nada aquí. Se utiliza en este caso sólo para hacer los valores relativos de rendimiento con esta clase heredada:
PayOffRelPerformance::PayOffRelPerformance(double startValue_) : startValue(startValue_)
{
}
double PayOffRelPerformance::operator()(double spot) const
{
return spot / startValue;
}
El GetArraysOfCorrelatedGauassiansByBoxMuller hace el trabajo de generar los vectores de variantes normales que se utilizarán en la simulación. He comprobado que la Matriz de Cholezky es correcta para los casos reales, y también he comprobado que las variantes normales emitidas son efectivamente dependientes con la correlación que implica la Matriz de covarianza.
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;
}
0 votos
Estoy de acuerdo en que el precio de la mejor opción debería ser menor a medida que aumenta la correlación. Parece que hay algo que ocurre cuando alrededor de $\rho = 0.5$ . ¿Puede comprobar que ocurre un patrón similar cuando la correlación es inferior a 0?