diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..a2cc776 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -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 \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 10f8d8f..e7b7bb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/docs/mermaid-diagram.png b/docs/mermaid-diagram.png new file mode 100644 index 0000000..62c711a Binary files /dev/null and b/docs/mermaid-diagram.png differ diff --git a/src/BlackScholesProcess.cpp b/src/BlackScholesProcess.cpp new file mode 100644 index 0000000..127a715 --- /dev/null +++ b/src/BlackScholesProcess.cpp @@ -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); +} + + diff --git a/src/BlackScholesProcess.hpp b/src/BlackScholesProcess.hpp new file mode 100644 index 0000000..97769ab --- /dev/null +++ b/src/BlackScholesProcess.hpp @@ -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 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 \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f86785d..7eb1fa6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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}) diff --git a/src/models/Model.cpp b/src/Exercise.cpp similarity index 68% rename from src/models/Model.cpp rename to src/Exercise.cpp index 22bc73d..fdadc45 100644 --- a/src/models/Model.cpp +++ b/src/Exercise.cpp @@ -2,4 +2,4 @@ // Created by David Doebel on 05.03.2026. // -#include "Model.hpp" \ No newline at end of file +#include "Exercise.hpp" \ No newline at end of file diff --git a/src/Exercise.hpp b/src/Exercise.hpp new file mode 100644 index 0000000..9fb780a --- /dev/null +++ b/src/Exercise.hpp @@ -0,0 +1,53 @@ +// +// Created by David Doebel on 05.03.2026. +// + +#ifndef QUANTENGINE_EXERCISE_HPP +#define QUANTENGINE_EXERCISE_HPP +#include + +class Exercise { +public: + Exercise() = default; + virtual ~Exercise() = default; + enum class Type { + European, + American, + Bermudan + }; + + virtual Type type() const = 0; +protected: + std::vector 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 \ No newline at end of file diff --git a/src/Instrument.cpp b/src/Instrument.cpp new file mode 100644 index 0000000..35d8967 --- /dev/null +++ b/src/Instrument.cpp @@ -0,0 +1,17 @@ +// +// Created by David Doebel on 05.03.2026. +// + +#include "Instrument.hpp" + +Instrument::Instrument(double maturity, std::unique_ptr payoff, + std::unique_ptr engine) : maturity_(maturity), payoff_(std::move(payoff)), engine_ +(std::move(engine)){ +} + + + +double Instrument::price() const { + return engine_->calculate(*this); +} + diff --git a/src/Instrument.hpp b/src/Instrument.hpp new file mode 100644 index 0000000..dfd6a56 --- /dev/null +++ b/src/Instrument.hpp @@ -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 + +class PricingEngine; + +class Instrument { +public: + Instrument() = default; + Instrument(double maturity, std::unique_ptr payoff, std::unique_ptr engine); + double price() const; + + [[nodiscard]] double maturity() const { + return maturity_; + } + + [[nodiscard]] Payoff& payoff() const { + return *payoff_; + } + +protected: + double maturity_; + std::unique_ptr payoff_; + std::unique_ptr engine_; +}; + + +#endif //QUANTENGINE_INSTRUMENT_HPP \ No newline at end of file diff --git a/src/MarketData.cpp b/src/MarketData.cpp new file mode 100644 index 0000000..eba40df --- /dev/null +++ b/src/MarketData.cpp @@ -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_; } \ No newline at end of file diff --git a/src/MarketData.hpp b/src/MarketData.hpp new file mode 100644 index 0000000..1c61c8c --- /dev/null +++ b/src/MarketData.hpp @@ -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 + +class MarketData { +public: + MarketData() = default; + + MarketData(double spot, std::unique_ptr yield_curve, + std::unique_ptr 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 yield_curve_; + std::unique_ptr volatility_surface_; +}; + + +#endif //QUANTENGINE_MARKETDATA_HPP \ No newline at end of file diff --git a/src/MonteCarloEngine.cpp b/src/MonteCarloEngine.cpp new file mode 100644 index 0000000..22b2611 --- /dev/null +++ b/src/MonteCarloEngine.cpp @@ -0,0 +1,24 @@ +// +// Created by David Doebel on 05.03.2026. +// + +#include "MonteCarloEngine.hpp" +#include +#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 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); +} diff --git a/src/MonteCarloEngine.hpp b/src/MonteCarloEngine.hpp new file mode 100644 index 0000000..91c1c3b --- /dev/null +++ b/src/MonteCarloEngine.hpp @@ -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 process, std::shared_ptr rng): + numPaths_(numPaths), PricingEngine(std::move(process)), rng_(std::move(rng)) {} + double calculate(const Instrument& instrument) const override; +private: + int numPaths_; + std::shared_ptr rng_; +}; + + +#endif //QUANTENGINE_MONTECARLOENGINE_HPP \ No newline at end of file diff --git a/src/Option.cpp b/src/Option.cpp new file mode 100644 index 0000000..fb2e80b --- /dev/null +++ b/src/Option.cpp @@ -0,0 +1,10 @@ +// +// Created by David Doebel on 05.03.2026. +// + +#include "Option.hpp" + +Option::Option(double maturity, std::unique_ptr exercise, std::unique_ptr payoff, + std::unique_ptr engine) : Instrument(maturity, std::move(payoff), + std::move(engine)), exercise_(std::move(exercise)){ +} diff --git a/src/Option.hpp b/src/Option.hpp new file mode 100644 index 0000000..49603e6 --- /dev/null +++ b/src/Option.hpp @@ -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, + std::unique_ptr payoff, std::unique_ptr engine); + [[nodiscard]] Exercise& exercise() const { + return *exercise_; + } + +protected: + std::unique_ptr exercise_; +}; + +class VanillaOption : public Option { +public: + using Option::Option; +}; + + + + + +#endif //QUANTENGINE_OPTION_HPP \ No newline at end of file diff --git a/src/Payoff.cpp b/src/Payoff.cpp new file mode 100644 index 0000000..5cb22e3 --- /dev/null +++ b/src/Payoff.cpp @@ -0,0 +1,18 @@ +// +// Created by David Doebel on 05.03.2026. +// + +#include "Payoff.hpp" +#include + +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.; +} diff --git a/src/Payoff.hpp b/src/Payoff.hpp new file mode 100644 index 0000000..cf58cce --- /dev/null +++ b/src/Payoff.hpp @@ -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 \ No newline at end of file diff --git a/src/PricingEngine.cpp b/src/PricingEngine.cpp new file mode 100644 index 0000000..bdcf247 --- /dev/null +++ b/src/PricingEngine.cpp @@ -0,0 +1,5 @@ +// +// Created by David Doebel on 05.03.2026. +// + +#include "PricingEngine.hpp" \ No newline at end of file diff --git a/src/PricingEngine.hpp b/src/PricingEngine.hpp new file mode 100644 index 0000000..06bc254 --- /dev/null +++ b/src/PricingEngine.hpp @@ -0,0 +1,26 @@ +// +// Created by David Doebel on 05.03.2026. +// + +#ifndef QUANTENGINE_PRICINGENGINE_HPP +#define QUANTENGINE_PRICINGENGINE_HPP +#include + +#include "StochasticProcess.hpp" + +class Instrument; + +class PricingEngine { +public: + PricingEngine() = default; + PricingEngine(std::unique_ptr process) : process_(std::move(process)){} + + virtual ~PricingEngine() = default; + virtual double calculate(const Instrument& instrument) const = 0; +protected: + std::unique_ptr process_; + +}; + + +#endif //QUANTENGINE_PRICINGENGINE_HPP \ No newline at end of file diff --git a/src/RandomGenerator.cpp b/src/RandomGenerator.cpp new file mode 100644 index 0000000..55f0043 --- /dev/null +++ b/src/RandomGenerator.cpp @@ -0,0 +1,18 @@ +// +// Created by David Doebel on 06.03.2026. +// + +#include "RandomGenerator.hpp" + + +double MersenneTwister::nextGaussian() { + return distr_(generator_); +} + +std::vector MersenneTwister::nextGaussianVector(std::size_t n) { + std::vector v(n); + for (auto& e : v) { + e = nextGaussian(); + } + return v; +} diff --git a/src/RandomGenerator.hpp b/src/RandomGenerator.hpp new file mode 100644 index 0000000..e4df117 --- /dev/null +++ b/src/RandomGenerator.hpp @@ -0,0 +1,28 @@ +// +// Created by David Doebel on 06.03.2026. +// + +#ifndef QUANTENGINE_RANDOMGENERATOR_HPP +#define QUANTENGINE_RANDOMGENERATOR_HPP +#include + +class RandomGenerator { +public: + RandomGenerator() = default; + virtual ~RandomGenerator() = default; + virtual double nextGaussian() = 0; + virtual std::vector nextGaussianVector(std::size_t n) = 0; +}; + +class MersenneTwister : public RandomGenerator { +public: + MersenneTwister() = default; + double nextGaussian() override; + std::vector nextGaussianVector(std::size_t n) override; +private: + std::mt19937 generator_; + std::normal_distribution<> distr_ {0.0, 1.0}; +}; + + +#endif //QUANTENGINE_RANDOMGENERATOR_HPP \ No newline at end of file diff --git a/src/Statistics.cpp b/src/Statistics.cpp new file mode 100644 index 0000000..0578fe1 --- /dev/null +++ b/src/Statistics.cpp @@ -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; +} diff --git a/src/Statistics.hpp b/src/Statistics.hpp new file mode 100644 index 0000000..153cd7b --- /dev/null +++ b/src/Statistics.hpp @@ -0,0 +1,30 @@ +// +// Created by David Doebel on 06.03.2026. +// + +#ifndef QUANTENGINE_STATISTICS_HPP +#define QUANTENGINE_STATISTICS_HPP +#include + + +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 moments_; + std::size_t n; + double max_, min_; +}; + + +#endif //QUANTENGINE_STATISTICS_HPP \ No newline at end of file diff --git a/src/StochasticProcess.cpp b/src/StochasticProcess.cpp new file mode 100644 index 0000000..52947c3 --- /dev/null +++ b/src/StochasticProcess.cpp @@ -0,0 +1,5 @@ +// +// Created by David Doebel on 05.03.2026. +// + +#include "StochasticProcess.hpp" \ No newline at end of file diff --git a/src/StochasticProcess.hpp b/src/StochasticProcess.hpp new file mode 100644 index 0000000..c326020 --- /dev/null +++ b/src/StochasticProcess.hpp @@ -0,0 +1,28 @@ +// +// Created by David Doebel on 05.03.2026. +// + +#ifndef QUANTENGINE_STOCHASTICPROCESS_HPP +#define QUANTENGINE_STOCHASTICPROCESS_HPP +#include "MarketData.hpp" +#include + +class StochasticProcess { +public: + StochasticProcess() = default; + StochasticProcess(std::unique_ptr 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 data_; + +}; + + +#endif //QUANTENGINE_STOCHASTICPROCESS_HPP \ No newline at end of file diff --git a/src/VolatilitySurface.cpp b/src/VolatilitySurface.cpp new file mode 100644 index 0000000..c719fbc --- /dev/null +++ b/src/VolatilitySurface.cpp @@ -0,0 +1,5 @@ +// +// Created by David Doebel on 06.03.2026. +// + +#include "VolatilitySurface.hpp" \ No newline at end of file diff --git a/src/VolatilitySurface.hpp b/src/VolatilitySurface.hpp new file mode 100644 index 0000000..8d2622c --- /dev/null +++ b/src/VolatilitySurface.hpp @@ -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 \ No newline at end of file diff --git a/src/YieldCurve.cpp b/src/YieldCurve.cpp new file mode 100644 index 0000000..b4e73c3 --- /dev/null +++ b/src/YieldCurve.cpp @@ -0,0 +1,5 @@ +// +// Created by David Doebel on 06.03.2026. +// + +#include "YieldCurve.hpp" \ No newline at end of file diff --git a/src/YieldCurve.hpp b/src/YieldCurve.hpp new file mode 100644 index 0000000..4a4f78d --- /dev/null +++ b/src/YieldCurve.hpp @@ -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 \ No newline at end of file diff --git a/src/calibration/Stats.cpp b/src/calibration/Stats.cpp deleted file mode 100644 index 6d1d0fd..0000000 --- a/src/calibration/Stats.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// -// Created by David Doebel on 04.03.2026. -// - -#include "Stats.hpp" -#include - -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 Stats::CI() const { - return std::make_pair(running_sum_ - 1.96 * std_error(), running_sum_ + 1.96 * std_error()); -} diff --git a/src/calibration/Stats.hpp b/src/calibration/Stats.hpp deleted file mode 100644 index e5917dc..0000000 --- a/src/calibration/Stats.hpp +++ /dev/null @@ -1,28 +0,0 @@ -// -// Created by David Doebel on 04.03.2026. -// - -#ifndef QUANTENGINE_STATS_HPP -#define QUANTENGINE_STATS_HPP -#include -#include - -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 CI() const; // alpha = 5% - -}; - - -#endif //QUANTENGINE_STATS_HPP \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 8b6df59..0000000 --- a/src/main.cpp +++ /dev/null @@ -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 - -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; -} \ No newline at end of file diff --git a/src/models/Model.hpp b/src/models/Model.hpp deleted file mode 100644 index 119c4e1..0000000 --- a/src/models/Model.hpp +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/models/black_scholes.cpp b/src/models/black_scholes.cpp deleted file mode 100644 index 18efb9c..0000000 --- a/src/models/black_scholes.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by David Doebel on 03.03.2026. -// - -#include "black_scholes.hpp" diff --git a/src/models/black_scholes.hpp b/src/models/black_scholes.hpp deleted file mode 100644 index 0c86c2d..0000000 --- a/src/models/black_scholes.hpp +++ /dev/null @@ -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 -#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 \ No newline at end of file diff --git a/src/models/payoff.cpp b/src/models/payoff.cpp deleted file mode 100644 index b03bf83..0000000 --- a/src/models/payoff.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// -// Created by David Doebel on 03.03.2026. -// - -#include "payoff.hpp" - -#include - -double CallPayoff::operator()(double ST) const { - return std::max(ST - K_, 0.0); -} diff --git a/src/models/payoff.hpp b/src/models/payoff.hpp deleted file mode 100644 index 10cc0fd..0000000 --- a/src/models/payoff.hpp +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/simulation/monte_carlo.cpp b/src/simulation/monte_carlo.cpp deleted file mode 100644 index cfd4ab1..0000000 --- a/src/simulation/monte_carlo.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by David Doebel on 03.03.2026. -// - -#include "monte_carlo.hpp" diff --git a/src/simulation/monte_carlo.hpp b/src/simulation/monte_carlo.hpp deleted file mode 100644 index 81efee4..0000000 --- a/src/simulation/monte_carlo.hpp +++ /dev/null @@ -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 -#include - -class MonteCarloEngine { -public: - MonteCarloEngine(unsigned long seed = 42) - : gen_(seed), dist_(0.0, 1.0) {} - - template - 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 \ No newline at end of file diff --git a/tests/stubs/FakeMarketData.cpp b/tests/stubs/FakeMarketData.cpp new file mode 100644 index 0000000..6fd84e1 --- /dev/null +++ b/tests/stubs/FakeMarketData.cpp @@ -0,0 +1,2 @@ +// Minimal TU to satisfy CMake for test stubs +#include "FakeMarketData.hpp" diff --git a/tests/stubs/FakeMarketData.hpp b/tests/stubs/FakeMarketData.hpp new file mode 100644 index 0000000..52f6115 --- /dev/null +++ b/tests/stubs/FakeMarketData.hpp @@ -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 yieldCurve_ = std::make_unique(); + std::unique_ptr volatilitySurface_ = std::make_unique(); +}; +#endif \ No newline at end of file diff --git a/tests/stubs/FlatVolatilitySurface.cpp b/tests/stubs/FlatVolatilitySurface.cpp new file mode 100644 index 0000000..41ae145 --- /dev/null +++ b/tests/stubs/FlatVolatilitySurface.cpp @@ -0,0 +1,2 @@ +// Minimal TU to satisfy CMake for test stubs +#include "FlatVolatilitySurface.hpp" diff --git a/tests/stubs/FlatVolatilitySurface.hpp b/tests/stubs/FlatVolatilitySurface.hpp new file mode 100644 index 0000000..f56eb25 --- /dev/null +++ b/tests/stubs/FlatVolatilitySurface.hpp @@ -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 \ No newline at end of file diff --git a/tests/stubs/FlatYieldCurve.cpp b/tests/stubs/FlatYieldCurve.cpp new file mode 100644 index 0000000..9f5830b --- /dev/null +++ b/tests/stubs/FlatYieldCurve.cpp @@ -0,0 +1,2 @@ +// Minimal TU to satisfy CMake for test stubs +#include "FlatYieldCurve.hpp" diff --git a/tests/stubs/FlatYieldCurve.hpp b/tests/stubs/FlatYieldCurve.hpp new file mode 100644 index 0000000..87aba7e --- /dev/null +++ b/tests/stubs/FlatYieldCurve.hpp @@ -0,0 +1,16 @@ +// +// Created by David Doebel on 07.03.2026. +// +#ifndef QUANTENGINE_FLATYIELDCURVE_HPP +#define QUANTENGINE_FLATYIELDCURVE_HPP +#include "YieldCurve.hpp" +#include + +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 \ No newline at end of file diff --git a/tests/test_black_scholes.cpp b/tests/test_black_scholes.cpp new file mode 100644 index 0000000..a10bb88 --- /dev/null +++ b/tests/test_black_scholes.cpp @@ -0,0 +1,49 @@ +// +// Created by David Doebel on 06.03.2026. +// + +#include +#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(std::make_unique()); + auto processPut = std::make_unique(std::make_unique()); + + // RNG shared between engines is fine + auto rng = std::make_shared(); + + // Pricing engines + auto mcCall = std::make_unique(numPaths, std::move(processCall), rng); + auto mcPut = std::make_unique(numPaths, std::move(processPut), rng); + + // Instruments (European vanilla) with call and put payoffs + Instrument callInstr(T, std::make_unique(K), std::move(mcCall)); + Instrument putInstr(T, std::make_unique(K), std::move(mcPut)); + + const double callPrice = callInstr.price(); + const double putPrice = putInstr.price(); + + // Ground truth Black–Scholes 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); +} \ No newline at end of file