2026-03-08 10:15:23 +01:00
|
|
|
|
//
|
|
|
|
|
|
// Created by David Doebel on 06.03.2026.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#include <gtest/gtest.h>
|
2026-04-02 16:30:33 +02:00
|
|
|
|
#include "BlackScholesClosedFormEngine.hpp"
|
2026-03-08 10:15:23 +01:00
|
|
|
|
#include "BlackScholesProcess.hpp"
|
|
|
|
|
|
#include "MonteCarloEngine.hpp"
|
|
|
|
|
|
#include "Instrument.hpp"
|
|
|
|
|
|
#include "Option.hpp"
|
|
|
|
|
|
#include "Payoff.hpp"
|
|
|
|
|
|
|
|
|
|
|
|
#include "stubs/FlatYieldCurve.hpp"
|
|
|
|
|
|
#include "stubs/FlatVolatilitySurface.hpp"
|
|
|
|
|
|
|
|
|
|
|
|
TEST(BlackScholesProcess, ExpectedValue) {
|
|
|
|
|
|
// Market setup (via test stubs): S0=100, r=1%, sigma=20%
|
|
|
|
|
|
const double K = 100.0;
|
|
|
|
|
|
const double T = 1.0;
|
|
|
|
|
|
const int numPaths = 300000; // enough for stable MC estimate
|
|
|
|
|
|
|
2026-03-12 12:10:13 +01:00
|
|
|
|
const MarketData marketData(
|
|
|
|
|
|
100.0,
|
|
|
|
|
|
std::make_shared<FlatYieldCurve>(0.01),
|
|
|
|
|
|
std::make_shared<FlatVolatilitySurface>(0.2));
|
|
|
|
|
|
|
|
|
|
|
|
// Build Black-Scholes process from an immutable market snapshot
|
|
|
|
|
|
auto processCall = std::make_unique<BlackScholesProcess>(marketData);
|
|
|
|
|
|
auto processPut = std::make_unique<BlackScholesProcess>(marketData);
|
2026-03-08 10:15:23 +01:00
|
|
|
|
|
|
|
|
|
|
// RNG shared between engines is fine
|
|
|
|
|
|
auto rng = std::make_shared<MersenneTwister>();
|
|
|
|
|
|
|
|
|
|
|
|
// Pricing engines
|
|
|
|
|
|
auto mcCall = std::make_unique<MonteCarloEngine>(numPaths, std::move(processCall), rng);
|
|
|
|
|
|
auto mcPut = std::make_unique<MonteCarloEngine>(numPaths, std::move(processPut), rng);
|
|
|
|
|
|
|
|
|
|
|
|
// Instruments (European vanilla) with call and put payoffs
|
|
|
|
|
|
Instrument callInstr(T, std::make_unique<CallPayoff>(K), std::move(mcCall));
|
|
|
|
|
|
Instrument putInstr(T, std::make_unique<PutPayoff>(K), std::move(mcPut));
|
|
|
|
|
|
|
|
|
|
|
|
const double callPrice = callInstr.price();
|
|
|
|
|
|
const double putPrice = putInstr.price();
|
|
|
|
|
|
|
|
|
|
|
|
// Ground truth Black–Scholes prices provided
|
2026-03-12 12:10:13 +01:00
|
|
|
|
const double callGT = 8.4333186901;
|
|
|
|
|
|
const double putGT = 7.4383020650;
|
2026-03-08 10:15:23 +01:00
|
|
|
|
|
|
|
|
|
|
// Monte Carlo tolerance
|
|
|
|
|
|
const double tol = 0.10; // 10 cents tolerance
|
|
|
|
|
|
|
|
|
|
|
|
ASSERT_NEAR(callPrice, callGT, tol);
|
|
|
|
|
|
ASSERT_NEAR(putPrice, putGT, tol);
|
2026-03-12 12:10:13 +01:00
|
|
|
|
}
|
2026-04-02 16:30:33 +02:00
|
|
|
|
|
|
|
|
|
|
TEST(BlackScholesClosedForm, MatchesReference) {
|
|
|
|
|
|
const double K = 100.0;
|
|
|
|
|
|
const double T = 1.0;
|
|
|
|
|
|
|
|
|
|
|
|
const MarketData marketData(
|
|
|
|
|
|
100.0,
|
|
|
|
|
|
std::make_shared<FlatYieldCurve>(0.01),
|
|
|
|
|
|
std::make_shared<FlatVolatilitySurface>(0.2));
|
|
|
|
|
|
|
|
|
|
|
|
auto processCall = std::make_unique<BlackScholesProcess>(marketData);
|
|
|
|
|
|
auto processPut = std::make_unique<BlackScholesProcess>(marketData);
|
|
|
|
|
|
|
|
|
|
|
|
auto analyticCall = std::make_unique<BlackScholesClosedFormEngine>(std::move(processCall));
|
|
|
|
|
|
auto analyticPut = std::make_unique<BlackScholesClosedFormEngine>(std::move(processPut));
|
|
|
|
|
|
|
|
|
|
|
|
Instrument callInstr(T, std::make_unique<CallPayoff>(K), std::move(analyticCall));
|
|
|
|
|
|
Instrument putInstr(T, std::make_unique<PutPayoff>(K), std::move(analyticPut));
|
|
|
|
|
|
|
|
|
|
|
|
const double callGT = 8.4333186901;
|
|
|
|
|
|
const double putGT = 7.4383020650;
|
|
|
|
|
|
|
|
|
|
|
|
ASSERT_NEAR(callInstr.price(), callGT, 1e-9);
|
|
|
|
|
|
ASSERT_NEAR(putInstr.price(), putGT, 1e-9);
|
|
|
|
|
|
}
|