Create option pricing engine structure, test architecture.
Some checks failed
C++ CI / build (push) Has been cancelled

This commit is contained in:
David Doebel
2026-03-08 10:15:23 +01:00
parent 1c61e664b3
commit 08298439ea
47 changed files with 815 additions and 223 deletions

35
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,35 @@
name: C++ CI
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake g++ libeigen3-dev
- name: Configure
run: |
mkdir build
cd build
cmake ..
- name: Build
run: |
cd build
make -j
- name: Run tests
run: |
cd build
ctest --output-on-failure

View File

@@ -8,3 +8,25 @@ find_package(Eigen3 REQUIRED)
add_subdirectory(src)
# Testing
enable_testing()
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(googletest)
add_executable(qengine_tests
tests/test_black_scholes.cpp
tests/stubs/FlatYieldCurve.cpp
tests/stubs/FlatVolatilitySurface.cpp
tests/stubs/FakeMarketData.cpp)
target_link_libraries(qengine_tests qengine GTest::gtest_main)
include(GoogleTest)
gtest_discover_tests(qengine_tests)

BIN
docs/mermaid-diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

View File

@@ -0,0 +1,23 @@
//
// Created by David Doebel on 06.03.2026.
//
#include "BlackScholesProcess.hpp"
double BlackScholesProcess::drift(double t, double s) {
double r = this->data().yield_curve().zeroRate(t);
return r * s;
}
double BlackScholesProcess::diffusion(double t, double s) {
double sigma = this->data().volatility_surface().sigma(s,t);
return sigma*s;
}
double BlackScholesProcess::step(double t, double s, double dt, double dW) {
double r = this->data().yield_curve().zeroRate(t);
double sigma = this->data().volatility_surface().sigma(s,t);
return s*exp((r-0.5*sigma*sigma)*dt + sigma*sqrt(dt)*dW);
}

View File

@@ -0,0 +1,24 @@
//
// Created by David Doebel on 06.03.2026.
//
#ifndef QUANTENGINE_BLACKSCHOLESPROCESS_HPP
#define QUANTENGINE_BLACKSCHOLESPROCESS_HPP
#include "StochasticProcess.hpp"
class BlackScholesProcess : public StochasticProcess{
public:
BlackScholesProcess() = default;
BlackScholesProcess(std::unique_ptr<MarketData> data) : StochasticProcess(std::move(data)){}
double drift(double t, double s) override;
double diffusion(double t, double s) override;
double step(double t, double s, double dt, double dW) override;
};
#endif //QUANTENGINE_BLACKSCHOLESPROCESS_HPP

View File

@@ -1,12 +1,32 @@
add_library(qengine
models/black_scholes.cpp
simulation/monte_carlo.cpp
models/payoff.cpp
main.cpp
calibration/Stats.cpp
calibration/Stats.hpp
models/Model.cpp
models/Model.hpp
Instrument.cpp
Instrument.hpp
Payoff.cpp
Payoff.hpp
Option.cpp
Option.hpp
PricingEngine.cpp
PricingEngine.hpp
MonteCarloEngine.cpp
MonteCarloEngine.hpp
StochasticProcess.cpp
StochasticProcess.hpp
Exercise.cpp
Exercise.hpp
MarketData.cpp
MarketData.hpp
YieldCurve.cpp
YieldCurve.hpp
VolatilitySurface.cpp
VolatilitySurface.hpp
RandomGenerator.cpp
RandomGenerator.hpp
Statistics.cpp
Statistics.hpp
BlackScholesProcess.cpp
BlackScholesProcess.hpp
)
target_include_directories(qengine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -2,4 +2,4 @@
// Created by David Doebel on 05.03.2026.
//
#include "Model.hpp"
#include "Exercise.hpp"

53
src/Exercise.hpp Normal file
View File

@@ -0,0 +1,53 @@
//
// Created by David Doebel on 05.03.2026.
//
#ifndef QUANTENGINE_EXERCISE_HPP
#define QUANTENGINE_EXERCISE_HPP
#include <vector>
class Exercise {
public:
Exercise() = default;
virtual ~Exercise() = default;
enum class Type {
European,
American,
Bermudan
};
virtual Type type() const = 0;
protected:
std::vector<double> exercise_times_;
};
class EuropeanExercise : public Exercise {
EuropeanExercise() : type_(Type::European) {};
EuropeanExercise(double maturity) : type_(Type::European){
exercise_times_.push_back(maturity);
}
~EuropeanExercise() override = default;
[[nodiscard]] Type type() const override {
return type_;
}
private:
Type type_;
};
class AmericanExercise : public Exercise{
AmericanExercise() : type_(Type::American) {};
AmericanExercise(double maturity) : type_(Type::American) {
exercise_times_.push_back(0);
exercise_times_.push_back(maturity);
}
[[nodiscard]] Type type() const override {
return type_;
}
private:
Type type_;
};
#endif //QUANTENGINE_EXERCISE_HPP

17
src/Instrument.cpp Normal file
View File

@@ -0,0 +1,17 @@
//
// Created by David Doebel on 05.03.2026.
//
#include "Instrument.hpp"
Instrument::Instrument(double maturity, std::unique_ptr<Payoff> payoff,
std::unique_ptr<PricingEngine> engine) : maturity_(maturity), payoff_(std::move(payoff)), engine_
(std::move(engine)){
}
double Instrument::price() const {
return engine_->calculate(*this);
}

34
src/Instrument.hpp Normal file
View File

@@ -0,0 +1,34 @@
//
// Created by David Doebel on 05.03.2026.
//
#ifndef QUANTENGINE_INSTRUMENT_HPP
#define QUANTENGINE_INSTRUMENT_HPP
#include "Payoff.hpp"
#include "PricingEngine.hpp"
#include <memory>
class PricingEngine;
class Instrument {
public:
Instrument() = default;
Instrument(double maturity, std::unique_ptr<Payoff> payoff, std::unique_ptr<PricingEngine> engine);
double price() const;
[[nodiscard]] double maturity() const {
return maturity_;
}
[[nodiscard]] Payoff& payoff() const {
return *payoff_;
}
protected:
double maturity_;
std::unique_ptr<Payoff> payoff_;
std::unique_ptr<PricingEngine> engine_;
};
#endif //QUANTENGINE_INSTRUMENT_HPP

9
src/MarketData.cpp Normal file
View File

@@ -0,0 +1,9 @@
//
// Created by David Doebel on 06.03.2026.
//
#include "MarketData.hpp"
double MarketData::spot() const { return spot_; }
YieldCurve& MarketData::yield_curve() { return *yield_curve_; }
VolatilitySurface& MarketData::volatility_surface() { return *volatility_surface_; }

33
src/MarketData.hpp Normal file
View File

@@ -0,0 +1,33 @@
//
// Created by David Doebel on 06.03.2026.
//
#ifndef QUANTENGINE_MARKETDATA_HPP
#define QUANTENGINE_MARKETDATA_HPP
#include "YieldCurve.hpp"
#include "VolatilitySurface.hpp"
#include <memory>
class MarketData {
public:
MarketData() = default;
MarketData(double spot, std::unique_ptr<YieldCurve> yield_curve,
std::unique_ptr<VolatilitySurface> volatility_surface)
: spot_(spot),
yield_curve_(std::move(yield_curve)),
volatility_surface_(std::move(volatility_surface)) {
}
double spot() const;
YieldCurve& yield_curve();
VolatilitySurface& volatility_surface();
private:
double spot_;
std::unique_ptr<YieldCurve> yield_curve_;
std::unique_ptr<VolatilitySurface> volatility_surface_;
};
#endif //QUANTENGINE_MARKETDATA_HPP

24
src/MonteCarloEngine.cpp Normal file
View File

@@ -0,0 +1,24 @@
//
// Created by David Doebel on 05.03.2026.
//
#include "MonteCarloEngine.hpp"
#include <iostream>
#include "Instrument.hpp"
#include "Statistics.hpp"
double MonteCarloEngine::calculate(const Instrument &instrument) const {
// parameters
double T = instrument.maturity();
double spot = process_->data().spot();
Statistics stats;
auto rNumbers = rng_->nextGaussianVector(numPaths_);
std::vector<double> payoffs(numPaths_);
for (std::size_t i = 0; i < numPaths_; ++i) {
double terminalPrice = process_->step(0.0,spot,T,rNumbers[i]);
double payoff = instrument.payoff()(terminalPrice);
stats.dump(payoff);
}
return stats.mean() * process_->data().yield_curve().discount(T);
}

23
src/MonteCarloEngine.hpp Normal file
View File

@@ -0,0 +1,23 @@
//
// Created by David Doebel on 05.03.2026.
//
#ifndef QUANTENGINE_MONTECARLOENGINE_HPP
#define QUANTENGINE_MONTECARLOENGINE_HPP
#include "PricingEngine.hpp"
#include "RandomGenerator.hpp"
class MonteCarloEngine : public PricingEngine{
public:
MonteCarloEngine() = default;
MonteCarloEngine(int numPaths, std::unique_ptr<StochasticProcess> process, std::shared_ptr<RandomGenerator> rng):
numPaths_(numPaths), PricingEngine(std::move(process)), rng_(std::move(rng)) {}
double calculate(const Instrument& instrument) const override;
private:
int numPaths_;
std::shared_ptr<RandomGenerator> rng_;
};
#endif //QUANTENGINE_MONTECARLOENGINE_HPP

10
src/Option.cpp Normal file
View File

@@ -0,0 +1,10 @@
//
// Created by David Doebel on 05.03.2026.
//
#include "Option.hpp"
Option::Option(double maturity, std::unique_ptr<Exercise> exercise, std::unique_ptr<Payoff> payoff,
std::unique_ptr<PricingEngine> engine) : Instrument(maturity, std::move(payoff),
std::move(engine)), exercise_(std::move(exercise)){
}

33
src/Option.hpp Normal file
View File

@@ -0,0 +1,33 @@
//
// Created by David Doebel on 05.03.2026.
//
#ifndef QUANTENGINE_OPTION_HPP
#define QUANTENGINE_OPTION_HPP
#include "Instrument.hpp"
#include "Exercise.hpp"
class Option : public Instrument{
public:
Option() = default;
virtual ~Option() = default;
Option(double maturity, std::unique_ptr<Exercise> exercise,
std::unique_ptr<Payoff> payoff, std::unique_ptr<PricingEngine> engine);
[[nodiscard]] Exercise& exercise() const {
return *exercise_;
}
protected:
std::unique_ptr<Exercise> exercise_;
};
class VanillaOption : public Option {
public:
using Option::Option;
};
#endif //QUANTENGINE_OPTION_HPP

18
src/Payoff.cpp Normal file
View File

@@ -0,0 +1,18 @@
//
// Created by David Doebel on 05.03.2026.
//
#include "Payoff.hpp"
#include <algorithm>
double CallPayoff::operator()(double S) {
return std::max(0., S - strike_);
}
double PutPayoff::operator()(double S) {
return std::max(0., strike_ - S);
}
double DigitalPayoff::operator()(double S) {
return S > strike_ ? 1. : 0.;
}

51
src/Payoff.hpp Normal file
View File

@@ -0,0 +1,51 @@
//
// Created by David Doebel on 05.03.2026.
//
#ifndef QUANTENGINE_PAYOFF_HPP
#define QUANTENGINE_PAYOFF_HPP
class Payoff {
public:
Payoff() = default;
virtual ~Payoff() = default;
virtual double operator()(double S) = 0;
virtual double strike() = 0;
};
class CallPayoff : public Payoff {
public:
CallPayoff() = default;
CallPayoff(double strike) : strike_(strike) {}
double operator()(double S) override;
double strike() override {return strike_;}
private:
double strike_;
};
class PutPayoff : public Payoff {
public:
PutPayoff() = default;
PutPayoff(double strike) : strike_(strike) {}
double operator()(double S) override;
double strike() override {return strike_;}
private:
double strike_;
};
class DigitalPayoff : public Payoff {
public:
DigitalPayoff() = default;
DigitalPayoff(double strike) : strike_(strike) {}
double operator()(double S) override;
double strike() override {return strike_;}
private:
double strike_;
};
#endif //QUANTENGINE_PAYOFF_HPP

5
src/PricingEngine.cpp Normal file
View File

@@ -0,0 +1,5 @@
//
// Created by David Doebel on 05.03.2026.
//
#include "PricingEngine.hpp"

26
src/PricingEngine.hpp Normal file
View File

@@ -0,0 +1,26 @@
//
// Created by David Doebel on 05.03.2026.
//
#ifndef QUANTENGINE_PRICINGENGINE_HPP
#define QUANTENGINE_PRICINGENGINE_HPP
#include <memory>
#include "StochasticProcess.hpp"
class Instrument;
class PricingEngine {
public:
PricingEngine() = default;
PricingEngine(std::unique_ptr<StochasticProcess> process) : process_(std::move(process)){}
virtual ~PricingEngine() = default;
virtual double calculate(const Instrument& instrument) const = 0;
protected:
std::unique_ptr<StochasticProcess> process_;
};
#endif //QUANTENGINE_PRICINGENGINE_HPP

18
src/RandomGenerator.cpp Normal file
View File

@@ -0,0 +1,18 @@
//
// Created by David Doebel on 06.03.2026.
//
#include "RandomGenerator.hpp"
double MersenneTwister::nextGaussian() {
return distr_(generator_);
}
std::vector<double> MersenneTwister::nextGaussianVector(std::size_t n) {
std::vector<double> v(n);
for (auto& e : v) {
e = nextGaussian();
}
return v;
}

28
src/RandomGenerator.hpp Normal file
View File

@@ -0,0 +1,28 @@
//
// Created by David Doebel on 06.03.2026.
//
#ifndef QUANTENGINE_RANDOMGENERATOR_HPP
#define QUANTENGINE_RANDOMGENERATOR_HPP
#include <random>
class RandomGenerator {
public:
RandomGenerator() = default;
virtual ~RandomGenerator() = default;
virtual double nextGaussian() = 0;
virtual std::vector<double> nextGaussianVector(std::size_t n) = 0;
};
class MersenneTwister : public RandomGenerator {
public:
MersenneTwister() = default;
double nextGaussian() override;
std::vector<double> nextGaussianVector(std::size_t n) override;
private:
std::mt19937 generator_;
std::normal_distribution<> distr_ {0.0, 1.0};
};
#endif //QUANTENGINE_RANDOMGENERATOR_HPP

52
src/Statistics.cpp Normal file
View File

@@ -0,0 +1,52 @@
//
// Created by David Doebel on 06.03.2026.
//
#include "Statistics.hpp"
void Statistics::dump(double value) {
for (std::size_t i = 0; i < 3; ++i) {
moments_[i] += std::pow(value, i+1);
}
++n;
max_ = std::max(max_, value);
min_ = std::min(min_, value);
}
void Statistics::clear() {
n = 0;
moments_ = {0.,0.,0.};
}
double Statistics::mean() {
return moments_[0]/n;
}
double Statistics::variance() {
return moments_[1]/n - std::pow(mean(), 2);
}
double Statistics::standardDeviation() {
return std::sqrt(variance());
}
double Statistics::skewness() {
return moments_[2]/std::pow(n, 3);
}
double Statistics::max() {
return max_;
}
double Statistics::min() {
return min_;
}
double Statistics::sum() {
return moments_[0];
}
double Statistics::count() {
return n;
}

30
src/Statistics.hpp Normal file
View File

@@ -0,0 +1,30 @@
//
// Created by David Doebel on 06.03.2026.
//
#ifndef QUANTENGINE_STATISTICS_HPP
#define QUANTENGINE_STATISTICS_HPP
#include <vector>
class Statistics {
public:
Statistics() : moments_({0., 0., 0.}), max_(0.), min_(0.) {}
void dump(double value);
void clear();
double mean();
double variance();
double standardDeviation();
double skewness();
double max();
double min();
double sum();
double count();
private:
std::vector<double> moments_;
std::size_t n;
double max_, min_;
};
#endif //QUANTENGINE_STATISTICS_HPP

View File

@@ -0,0 +1,5 @@
//
// Created by David Doebel on 05.03.2026.
//
#include "StochasticProcess.hpp"

28
src/StochasticProcess.hpp Normal file
View File

@@ -0,0 +1,28 @@
//
// Created by David Doebel on 05.03.2026.
//
#ifndef QUANTENGINE_STOCHASTICPROCESS_HPP
#define QUANTENGINE_STOCHASTICPROCESS_HPP
#include "MarketData.hpp"
#include <memory>
class StochasticProcess {
public:
StochasticProcess() = default;
StochasticProcess(std::unique_ptr<MarketData> data) : data_(std::move(data)){}
virtual ~StochasticProcess() = default;
virtual double drift(double t, double s) = 0;
virtual double diffusion(double t, double s) = 0;
virtual double step(double t, double s, double dt, double dW) = 0;
MarketData& data() const {return *data_;}
private:
std::shared_ptr<MarketData> data_;
};
#endif //QUANTENGINE_STOCHASTICPROCESS_HPP

View File

@@ -0,0 +1,5 @@
//
// Created by David Doebel on 06.03.2026.
//
#include "VolatilitySurface.hpp"

18
src/VolatilitySurface.hpp Normal file
View File

@@ -0,0 +1,18 @@
//
// Created by David Doebel on 06.03.2026.
//
#ifndef QUANTENGINE_VOLATILITYSURFACE_HPP
#define QUANTENGINE_VOLATILITYSURFACE_HPP
class VolatilitySurface {
public:
virtual ~VolatilitySurface() = default;
virtual double sigma(double K, double T) = 0;
private:
};
#endif //QUANTENGINE_VOLATILITYSURFACE_HPP

5
src/YieldCurve.cpp Normal file
View File

@@ -0,0 +1,5 @@
//
// Created by David Doebel on 06.03.2026.
//
#include "YieldCurve.hpp"

37
src/YieldCurve.hpp Normal file
View File

@@ -0,0 +1,37 @@
//
// Created by David Doebel on 06.03.2026.
//
#ifndef QUANTENGINE_YIELDCURVE_HPP
#define QUANTENGINE_YIELDCURVE_HPP
class YieldCurve {
public:
YieldCurve() = default;
YieldCurve(const YieldCurve &other) {
}
YieldCurve(YieldCurve &&other) noexcept {
}
YieldCurve & operator=(const YieldCurve &other) {
if (this == &other)
return *this;
return *this;
}
YieldCurve & operator=(YieldCurve &&other) noexcept {
if (this == &other)
return *this;
return *this;
}
virtual ~YieldCurve() = default;
virtual double discount(double t) = 0;
virtual double zeroRate(double t) = 0;
};
#endif //QUANTENGINE_YIELDCURVE_HPP

View File

@@ -1,36 +0,0 @@
//
// Created by David Doebel on 04.03.2026.
//
#include "Stats.hpp"
#include <cmath>
void Stats::update(double x) {
running_sum_ += x;
running_square_sum_ += x * x;
n_++;
}
double Stats::mean() const {
return running_sum_ / n_;
}
double Stats::square_mean() const {
return running_square_sum_ / n_;
}
double Stats::variance() const {
double mean = this->mean();
double square_mean = this->square_mean();
return square_mean * square_mean - mean * mean;
}
double Stats::std_error() const {
return std::sqrt(variance()/n_);
}
std::pair<double, double> Stats::CI() const {
return std::make_pair(running_sum_ - 1.96 * std_error(), running_sum_ + 1.96 * std_error());
}

View File

@@ -1,28 +0,0 @@
//
// Created by David Doebel on 04.03.2026.
//
#ifndef QUANTENGINE_STATS_HPP
#define QUANTENGINE_STATS_HPP
#include <cstddef>
#include <utility>
class Stats {
private:
size_t n_ = 0;
double running_sum_ = 0.0;
double running_square_sum_ = 0.0;
public:
Stats() = delete;
void update(double x);
double mean() const;
double square_mean() const;
double variance() const;
double std_error() const;
std::pair<double, double> CI() const; // alpha = 5%
};
#endif //QUANTENGINE_STATS_HPP

View File

@@ -1,22 +0,0 @@
//
// Created by David Doebel on 03.03.2026.
//
#include "models/black_scholes.hpp"
#include "simulation/monte_carlo.hpp"
#include "models/payoff.hpp"
#include <iostream>
int main() {
BlackScholes model(100.0, 0.05, 0.2, 1.0);
CallPayoff payoff(100.0);
MonteCarloEngine mc;
double price = mc.price(model, payoff, 1000000);
std::cout << "MC Price: " << price << std::endl;
return 0;
}

View File

@@ -1,18 +0,0 @@
//
// Created by David Doebel on 05.03.2026.
//
#ifndef QUANTENGINE_MODEL_HPP
#define QUANTENGINE_MODEL_HPP
class Model {
public:
Model() = default;
virtual ~Model() = 0;
[[nodiscard]] virtual double terminal_price(double Z) const = 0;
[[nodiscard]] virtual double discount() const = 0;
};
#endif //QUANTENGINE_MODEL_HPP

View File

@@ -1,5 +0,0 @@
//
// Created by David Doebel on 03.03.2026.
//
#include "black_scholes.hpp"

View File

@@ -1,32 +0,0 @@
//
// Created by David Doebel on 03.03.2026.
//
#ifndef OPTION_PRICING_BLACK_SCHOLES_HPP
#define OPTION_PRICING_BLACK_SCHOLES_HPP
#include <cmath>
#include "Model.hpp"
class BlackScholes : public Model{
public:
BlackScholes(double S0, double r, double sigma, double T)
: Model(), S0_(S0), r_(r), sigma_(sigma), T_(T) {
}
[[nodiscard]] double terminal_price(double Z) const override{
return S0_ * std::exp(
(r_ - 0.5 * sigma_ * sigma_) * T_
+ sigma_ * std::sqrt(T_) * Z
);
}
[[nodiscard]] double discount() const override{
return std::exp(-r_ * T_);
}
private:
double S0_, r_, sigma_, T_;
};
#endif //OPTION_PRICING_BLACK_SCHOLES_HPP

View File

@@ -1,11 +0,0 @@
//
// Created by David Doebel on 03.03.2026.
//
#include "payoff.hpp"
#include <algorithm>
double CallPayoff::operator()(double ST) const {
return std::max(ST - K_, 0.0);
}

View File

@@ -1,21 +0,0 @@
//
// Created by David Doebel on 03.03.2026.
//
#ifndef OPTION_PRICING_PAYOFF_HPP
#define OPTION_PRICING_PAYOFF_HPP
class Payoff {
public:
virtual double operator()(double ST) const = 0;
virtual ~Payoff() = default;
};
class CallPayoff : public Payoff {
public:
CallPayoff(double K) : K_(K) {}
double operator()(double ST) const override;
private:
double K_;
};
#endif //OPTION_PRICING_PAYOFF_HPP

View File

@@ -1,5 +0,0 @@
//
// Created by David Doebel on 03.03.2026.
//
#include "monte_carlo.hpp"

View File

@@ -1,36 +0,0 @@
//
// Created by David Doebel on 03.03.2026.
//
#ifndef OPTION_PRICING_MONTE_CARLO_HPP
#define OPTION_PRICING_MONTE_CARLO_HPP
#pragma once
#include <random>
#include <vector>
class MonteCarloEngine {
public:
MonteCarloEngine(unsigned long seed = 42)
: gen_(seed), dist_(0.0, 1.0) {}
template<typename Model, typename Payoff>
double price(const Model& model,
const Payoff& payoff,
std::size_t N) {
double sum = 0.0;
for (std::size_t i = 0; i < N; ++i) {
double Z = dist_(gen_);
double ST = model.terminal_price(Z);
sum += payoff(ST);
}
return model.discount() * sum / N;
}
private:
std::mt19937_64 gen_;
std::normal_distribution<> dist_;
};
#endif //OPTION_PRICING_MONTE_CARLO_HPP

View File

@@ -0,0 +1,2 @@
// Minimal TU to satisfy CMake for test stubs
#include "FakeMarketData.hpp"

View File

@@ -0,0 +1,38 @@
//
// Created by David Doebel on 07.03.2026.
//
#ifndef QUANTENGINE_FAKEMARKETDATA_HPP
#define QUANTENGINE_FAKEMARKETDATA_HPP
#include "MarketData.hpp"
#include "FlatYieldCurve.hpp"
#include "FlatVolatilitySurface.hpp"
class FakeMarketData : public MarketData {
public:
FakeMarketData() = default;
FakeMarketData(const FakeMarketData &other)
{
}
FakeMarketData(FakeMarketData &&other) noexcept
{
}
FakeMarketData & operator=(const FakeMarketData &other) {
return *this;
}
FakeMarketData & operator=(FakeMarketData &&other) noexcept {
return *this;
}
double spot() const {return 100.0;}
YieldCurve& yield_curve(){return *yieldCurve_; };
VolatilitySurface& volatility_surface(){return *volatilitySurface_; };
private:
std::unique_ptr<FlatYieldCurve> yieldCurve_ = std::make_unique<FlatYieldCurve>();
std::unique_ptr<FlatVolatilitySurface> volatilitySurface_ = std::make_unique<FlatVolatilitySurface>();
};
#endif

View File

@@ -0,0 +1,2 @@
// Minimal TU to satisfy CMake for test stubs
#include "FlatVolatilitySurface.hpp"

View File

@@ -0,0 +1,11 @@
//
// Created by David Doebel on 07.03.2026.
//
#ifndef QUANTENGINE_FLATVOLATILITYSURFACE_HPP
#define QUANTENGINE_FLATVOLATILITYSURFACE_HPP
#include "VolatilitySurface.hpp"
class FlatVolatilitySurface : public VolatilitySurface {
double sigma(double K, double T) {return 0.2;}
};
#endif

View File

@@ -0,0 +1,2 @@
// Minimal TU to satisfy CMake for test stubs
#include "FlatYieldCurve.hpp"

View File

@@ -0,0 +1,16 @@
//
// Created by David Doebel on 07.03.2026.
//
#ifndef QUANTENGINE_FLATYIELDCURVE_HPP
#define QUANTENGINE_FLATYIELDCURVE_HPP
#include "YieldCurve.hpp"
#include <cmath>
class FlatYieldCurve : public YieldCurve{
double discount(double t) override {return std::exp(-rate_ * t); };
double zeroRate(double t) override {return rate_; }
private:
double rate_ = 0.01;
};
#endif

View File

@@ -0,0 +1,49 @@
//
// Created by David Doebel on 06.03.2026.
//
#include <gtest/gtest.h>
#include "BlackScholesProcess.hpp"
#include "MonteCarloEngine.hpp"
#include "Instrument.hpp"
#include "Option.hpp"
#include "Payoff.hpp"
#include "stubs/FlatYieldCurve.hpp"
#include "stubs/FlatVolatilitySurface.hpp"
#include "stubs/FakeMarketData.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
// Build Black-Scholes process with fake flat market data
auto processCall = std::make_unique<BlackScholesProcess>(std::make_unique<FakeMarketData>());
auto processPut = std::make_unique<BlackScholesProcess>(std::make_unique<FakeMarketData>());
// 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 BlackScholes prices provided
const double callGT = 10.450583572;
const double putGT = 5.573526022;
// Monte Carlo tolerance
const double tol = 0.10; // 10 cents tolerance
ASSERT_NEAR(callPrice, callGT, tol);
ASSERT_NEAR(putPrice, putGT, tol);
}