diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ff7f7d..85e20b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,31 +4,53 @@ project(QuantEngine) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS "-O3 -march=native") +option(BUILD_TESTING "Build GoogleTest target and tests" ON) + +set(PYBIND11_FINDPYTHON ON) +find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module) find_package(Eigen3 REQUIRED) +find_package(pybind11 CONFIG REQUIRED) #find_package(PostgreSQL REQUIRED) #find_package(PkgConfig REQUIRED) #pkg_check_modules(PQXX REQUIRED IMPORTED_TARGET libpqxx) -add_subdirectory(src) +add_subdirectory(cpp) -# Testing -enable_testing() +find_package(Doxygen OPTIONAL_COMPONENTS dot) +if(DOXYGEN_FOUND) + add_custom_target( + docs + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_SOURCE_DIR}/docs/Doxyfile + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Generating API documentation (HTML in docs/html)" + VERBATIM) +endif() -include(FetchContent) +install(FILES "${CMAKE_SOURCE_DIR}/qengine/__init__.py" DESTINATION qengine) +install(TARGETS qengine_cpp + LIBRARY DESTINATION qengine + RUNTIME DESTINATION qengine) -FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip - DOWNLOAD_EXTRACT_TIMESTAMP TRUE -) +if(BUILD_TESTING) + enable_testing() -FetchContent_MakeAvailable(googletest) + include(FetchContent) -add_executable(qengine_tests - tests/test_black_scholes.cpp - tests/stubs/FlatYieldCurve.cpp - tests/stubs/FlatVolatilitySurface.cpp) + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) -target_link_libraries(qengine_tests qengine GTest::gtest_main) -include(GoogleTest) -gtest_discover_tests(qengine_tests) + FetchContent_MakeAvailable(googletest) + + add_executable(qengine_tests + tests/test_black_scholes.cpp + tests/stubs/FlatYieldCurve.cpp + tests/stubs/FlatVolatilitySurface.cpp) + + target_include_directories(qengine_tests PRIVATE ${CMAKE_SOURCE_DIR}/tests) + target_link_libraries(qengine_tests qengine_core GTest::gtest_main) + include(GoogleTest) + gtest_discover_tests(qengine_tests) +endif() diff --git a/cpp/BSWrapper.cpp b/cpp/BSWrapper.cpp new file mode 100644 index 0000000..affc616 --- /dev/null +++ b/cpp/BSWrapper.cpp @@ -0,0 +1,49 @@ +// +// Created by David Doebel on 27.03.2026. +// + +#include "BSWrapper.hpp" + +#include "BlackScholesClosedFormEngine.hpp" +#include "BlackScholesProcess.hpp" +#include "Instrument.hpp" +#include "Option.hpp" +#include "FlatVolatilitySurface.hpp" +#include "FlatYieldCurve.hpp" +#include +#include + +class FlatYieldCurve; + +double BSWrapper::bs_price_wrapper(double S, double K, double T, double r, double sigma, + bool is_call) { + std::shared_ptr flat_curve = std::make_shared(r); + auto flat_vol_surface = std::make_shared(sigma); + MarketData data(S,flat_curve, flat_vol_surface); + std::unique_ptr process = std::make_unique(data); + std::unique_ptr pricing_engine = + std::make_unique(std::move(process)); + std::unique_ptr payoff; + if (is_call) + payoff = std::make_unique(K); + else payoff = std::make_unique(K); + EuropeanExercise exercise(T); + VanillaOption option(T,std::make_unique(exercise), + std::move(payoff),std::move(pricing_engine)); + return option.price(); +} + +std::vector BSWrapper::batch_bs_price_wrapper(const std::vector &S, const std::vector &K, + const std::vector &T, const std::vector &r, const std::vector &sigma, + const std::vector &is_call) { + assert(K.size() == S.size() && K.size() == T.size() && K.size() == r.size() && K.size() == + sigma.size() && K.size() == is_call.size()); + std::size_t n = K.size(); + std::vector result(n); + for (std::size_t i = 0; i < n; ++i) { + result[i] = bs_price_wrapper(S[i], K[i], T[i], r[i], sigma[i], is_call[i]); + if (i % 100 == 0) + std::cout << "i = " << i << " result = " << result[i] << std::endl; // ( i % 1000 == 0) + } + return result; +} diff --git a/cpp/BSWrapper.hpp b/cpp/BSWrapper.hpp new file mode 100644 index 0000000..0ab73d6 --- /dev/null +++ b/cpp/BSWrapper.hpp @@ -0,0 +1,24 @@ +/** + * @file BSWrapper.hpp + * @brief Black–Scholes vanilla price (closed form; used from Python / implied vol). + */ + +#ifndef QUANTENGINE_BSWRAPPER_HPP +#define QUANTENGINE_BSWRAPPER_HPP +#include + +/** + * @brief Static helpers wrapping scalar and batch pricing. + */ +class BSWrapper { +public: + BSWrapper() = delete; + static double bs_price_wrapper(double S, double K, double T, double r, double sigma, bool is_call); + static std::vector batch_bs_price_wrapper(const std::vector& S, const std::vector& K, + const std::vector& T, const std::vector& r, const std::vector& sigma, + const std::vector& is_call); + +}; + + +#endif //QUANTENGINE_BSWRAPPER_HPP \ No newline at end of file diff --git a/cpp/BlackScholesClosedFormEngine.cpp b/cpp/BlackScholesClosedFormEngine.cpp new file mode 100644 index 0000000..d2060d2 --- /dev/null +++ b/cpp/BlackScholesClosedFormEngine.cpp @@ -0,0 +1,69 @@ +/** + * @file BlackScholesClosedFormEngine.cpp + * @brief Black–Scholes closed-form pricing (calls, puts, cash-or-nothing digital). + */ + +#include "BlackScholesClosedFormEngine.hpp" +#include "Instrument.hpp" +#include "Payoff.hpp" +#include +#include + +namespace { + +double norm_cdf(double x) { + return 0.5 * (1.0 + std::erf(x / std::sqrt(2.0))); +} + +} // namespace + +double BlackScholesClosedFormEngine::calculate(const Instrument &instrument) const { + if (instrument.exerciseType() != Exercise::Type::European) { + throw std::invalid_argument("BlackScholesClosedFormEngine: European exercise only"); + } + + const double T = instrument.maturity(); + const MarketData &md = process_->data(); + const double S = md.spot(); + double K = instrument.payoff().strike(); + const PayoffKind pk = instrument.payoff().kind(); + + if (T <= 0.0) { + return instrument.payoff()(S); + } + + const double r = md.yield_curve().zeroRate(T); + const double sigma = md.volatility_surface().sigma(K, T); + if (sigma <= 0.0) { + throw std::invalid_argument("BlackScholesClosedFormEngine: volatility must be positive"); + } + + const double disc = md.yield_curve().discount(T); + const double sqrtT = std::sqrt(T); + const double sig_sqrtT = sigma * sqrtT; + + if (sig_sqrtT < 1e-15) { + const double forward = S * std::exp(r * T); + switch (pk) { + case PayoffKind::Call: + return disc * std::max(0.0, forward - K); + case PayoffKind::Put: + return disc * std::max(0.0, K - forward); + case PayoffKind::Digital: + return (forward > K) ? disc : 0.0; + } + } + + const double d1 = (std::log(S / K) + (r + 0.5 * sigma * sigma) * T) / sig_sqrtT; + const double d2 = d1 - sig_sqrtT; + + switch (pk) { + case PayoffKind::Call: + return S * norm_cdf(d1) - K * disc * norm_cdf(d2); + case PayoffKind::Put: + return K * disc * norm_cdf(-d2) - S * norm_cdf(-d1); + case PayoffKind::Digital: + return disc * norm_cdf(d2); + } + throw std::logic_error("BlackScholesClosedFormEngine: unhandled PayoffKind"); +} diff --git a/cpp/BlackScholesClosedFormEngine.hpp b/cpp/BlackScholesClosedFormEngine.hpp new file mode 100644 index 0000000..769b852 --- /dev/null +++ b/cpp/BlackScholesClosedFormEngine.hpp @@ -0,0 +1,22 @@ +/** + * @file BlackScholesClosedFormEngine.hpp + * @brief Risk-neutral Black–Scholes formula for European payoffs under GBM (flat or surface inputs via @ref MarketData). + */ + +#ifndef QUANTENGINE_BLACKSCHOLESCLOSEDFORMENGINE_HPP +#define QUANTENGINE_BLACKSCHOLESCLOSEDFORMENGINE_HPP + +#include "PricingEngine.hpp" + +/** + * @brief Analytic European vanilla / digital prices using @f$r@f$ and @f$\sigma(K,T)@f$ from the embedded process’s @ref MarketData. + */ +class BlackScholesClosedFormEngine : public PricingEngine { +public: + explicit BlackScholesClosedFormEngine(std::unique_ptr process) + : PricingEngine(std::move(process)) {} + + double calculate(const Instrument &instrument) const override; +}; + +#endif // QUANTENGINE_BLACKSCHOLESCLOSEDFORMENGINE_HPP diff --git a/src/BlackScholesProcess.cpp b/cpp/BlackScholesProcess.cpp similarity index 85% rename from src/BlackScholesProcess.cpp rename to cpp/BlackScholesProcess.cpp index 127a715..43681d9 100644 --- a/src/BlackScholesProcess.cpp +++ b/cpp/BlackScholesProcess.cpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 06.03.2026. -// +/** + * @file BlackScholesProcess.cpp + * @brief Black–Scholes GBM drift, diffusion, and step. + */ #include "BlackScholesProcess.hpp" diff --git a/src/BlackScholesProcess.hpp b/cpp/BlackScholesProcess.hpp similarity index 70% rename from src/BlackScholesProcess.hpp rename to cpp/BlackScholesProcess.hpp index 320f34f..64a67a9 100644 --- a/src/BlackScholesProcess.hpp +++ b/cpp/BlackScholesProcess.hpp @@ -1,12 +1,15 @@ -// -// Created by David Doebel on 06.03.2026. -// +/** + * @file BlackScholesProcess.hpp + * @brief Geometric Brownian motion with yield and volatility surfaces. + */ #ifndef QUANTENGINE_BLACKSCHOLESPROCESS_HPP #define QUANTENGINE_BLACKSCHOLESPROCESS_HPP #include "StochasticProcess.hpp" - +/** + * @brief GBM: drift @f$r_t S@f$, diffusion @f$\sigma(S,t) S@f$, exact log-step. + */ class BlackScholesProcess : public StochasticProcess{ public: explicit BlackScholesProcess(MarketData data) : StochasticProcess(std::move(data)){} diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..eef55bf --- /dev/null +++ b/cpp/CMakeLists.txt @@ -0,0 +1,65 @@ +add_library(qengine_core + 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 + BlackScholesClosedFormEngine.cpp + BlackScholesClosedFormEngine.hpp + BlackScholesProcess.cpp + BlackScholesProcess.hpp + DBIngest.cpp + DBIngest.hpp + BSWrapper.cpp + BSWrapper.hpp + NewtonSolver.cpp + NewtonSolver.hpp +) + +target_include_directories(qengine_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(qengine_core PRIVATE + /opt/homebrew/include +) + +find_library(PQXX_LIB pqxx PATHS /opt/homebrew/lib /usr/local/lib /usr/lib) +find_library(PQ_LIB pq PATHS /opt/homebrew/opt/libpq/lib /opt/homebrew/lib /usr/local/lib /usr/lib) +if(NOT PQXX_LIB OR NOT PQ_LIB) + message(FATAL_ERROR "Could not find libpqxx and/or libpq (install via Homebrew: brew install libpqxx libpq)") +endif() + +target_link_libraries(qengine_core Eigen3::Eigen) +target_link_libraries(qengine_core ${PQXX_LIB} ${PQ_LIB}) + +# Python import path: package qengine, extension submodule qengine (file qengine/qengine*.so) +pybind11_add_module(qengine_cpp MODULE ImpliedVolatility/Pybind.cpp) +set_target_properties(qengine_cpp PROPERTIES OUTPUT_NAME qengine) +target_link_libraries(qengine_cpp PRIVATE qengine_core) + +# Place the module next to qengine/__init__.py so `import qengine` works from the repo root +set(_qengine_py_pkg "${CMAKE_SOURCE_DIR}/qengine") +set_target_properties(qengine_cpp PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${_qengine_py_pkg}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE "${_qengine_py_pkg}" + LIBRARY_OUTPUT_DIRECTORY_DEBUG "${_qengine_py_pkg}" + RUNTIME_OUTPUT_DIRECTORY "${_qengine_py_pkg}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${_qengine_py_pkg}" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${_qengine_py_pkg}") diff --git a/src/DBIngest.cpp b/cpp/DBIngest.cpp similarity index 58% rename from src/DBIngest.cpp rename to cpp/DBIngest.cpp index 08ec793..81782e6 100644 --- a/src/DBIngest.cpp +++ b/cpp/DBIngest.cpp @@ -1,25 +1,30 @@ -// -// Created by David Doebel on 13.03.2026. -// +/** + * @file DBIngest.cpp + * @brief Database connection and placeholder update routines. + */ #include "DBIngest.hpp" +#include #include - -// Queries -// Query for selecting the volatility surface parameters -std::string vol_surface_query = "" - - - - -// - - - +#include bool DBIngest::connect() { - connection_ = pqxx::connection("dbname=options_db user=quant_user port = 5432 host = localhost password = strong_password" ); + const char* db_name = std::getenv("DB_NAME"); + const char* db_user = std::getenv("DB_USER"); + const char* db_password = std::getenv("DB_PASSWORD"); + const char* db_host = std::getenv("DB_HOST"); + const char* db_port = std::getenv("DB_PORT"); + + std::ostringstream conn_str; + conn_str + << "dbname=" << (db_name ? db_name : "options_db") + << " user=" << (db_user ? db_user : "quant_user") + << " host=" << (db_host ? db_host : "localhost") + << " port=" << (db_port ? db_port : "5432") + << " password=" << (db_password ? db_password : ""); + + connection_ = pqxx::connection(conn_str.str()); if(connection_.is_open()) { std::cout << "Connected\n"; @@ -31,6 +36,7 @@ bool DBIngest::connect() { bool DBIngest::disconnect() { connection_.close(); + return true; } bool DBIngest::update(VolatilitySurface &surface) { @@ -48,8 +54,11 @@ bool DBIngest::update(VolatilitySurface &surface) { for (auto row : result) { std::cout << row[0] << " " << row[1] << " " << row[2] << " " << row[3] << std::endl; } - + (void)surface; + return false; } bool DBIngest::update(YieldCurve &yield_curve) { + (void)yield_curve; + return false; } diff --git a/src/DBIngest.hpp b/cpp/DBIngest.hpp similarity index 66% rename from src/DBIngest.hpp rename to cpp/DBIngest.hpp index 07b230e..2cb1405 100644 --- a/src/DBIngest.hpp +++ b/cpp/DBIngest.hpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 13.03.2026. -// +/** + * @file DBIngest.hpp + * @brief PostgreSQL helpers to load market objects (work in progress). + */ #ifndef QUANTENGINE_DBINGEST_HPP #define QUANTENGINE_DBINGEST_HPP @@ -10,6 +11,9 @@ #include "VolatilitySurface.hpp" #include "YieldCurve.hpp" +/** + * @brief Connects to Postgres via libpqxx and queries quotes for surface building. + */ class DBIngest { bool connect(); diff --git a/cpp/Exercise.cpp b/cpp/Exercise.cpp new file mode 100644 index 0000000..22a9595 --- /dev/null +++ b/cpp/Exercise.cpp @@ -0,0 +1,6 @@ +/** + * @file Exercise.cpp + * @brief @ref Exercise translation unit (interface only). + */ + +#include "Exercise.hpp" \ No newline at end of file diff --git a/src/Exercise.hpp b/cpp/Exercise.hpp similarity index 75% rename from src/Exercise.hpp rename to cpp/Exercise.hpp index 9fb780a..033c790 100644 --- a/src/Exercise.hpp +++ b/cpp/Exercise.hpp @@ -1,11 +1,15 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file Exercise.hpp + * @brief Exercise style (European, American, Bermudan) and exercise times. + */ #ifndef QUANTENGINE_EXERCISE_HPP #define QUANTENGINE_EXERCISE_HPP #include +/** + * @brief Describes when the holder may exercise (metadata for pricing engines). + */ class Exercise { public: Exercise() = default; @@ -22,7 +26,9 @@ protected: }; +/** @brief Single exercise at maturity. */ class EuropeanExercise : public Exercise { +public: EuropeanExercise() : type_(Type::European) {}; EuropeanExercise(double maturity) : type_(Type::European){ exercise_times_.push_back(maturity); @@ -35,7 +41,9 @@ private: Type type_; }; +/** @brief Continuous American exercise from @f$t=0@f$ to maturity (placeholder grid). */ class AmericanExercise : public Exercise{ +public: AmericanExercise() : type_(Type::American) {}; AmericanExercise(double maturity) : type_(Type::American) { exercise_times_.push_back(0); diff --git a/cpp/FlatVolatilitySurface.cpp b/cpp/FlatVolatilitySurface.cpp new file mode 100644 index 0000000..a6bdcfb --- /dev/null +++ b/cpp/FlatVolatilitySurface.cpp @@ -0,0 +1,5 @@ +/** + * @file FlatVolatilitySurface.cpp + * @brief Ensures link visibility for @ref FlatVolatilitySurface. + */ +#include "FlatVolatilitySurface.hpp" diff --git a/cpp/FlatVolatilitySurface.hpp b/cpp/FlatVolatilitySurface.hpp new file mode 100644 index 0000000..8cea287 --- /dev/null +++ b/cpp/FlatVolatilitySurface.hpp @@ -0,0 +1,21 @@ +/** + * @file FlatVolatilitySurface.hpp + * @brief Constant implied volatility surface. + */ +#ifndef QUANTENGINE_FLATVOLATILITYSURFACE_HPP +#define QUANTENGINE_FLATVOLATILITYSURFACE_HPP +#include "VolatilitySurface.hpp" + +/** + * @brief @f$\sigma(K,T)\equiv\sigma_0@f$. + */ +class FlatVolatilitySurface : public VolatilitySurface { +public: + explicit FlatVolatilitySurface(double sigma = 0.2) : sigma_(sigma) {} + + double sigma(double K, double T) const override {return sigma_;} + +private: + double sigma_; +}; +#endif diff --git a/cpp/FlatYieldCurve.cpp b/cpp/FlatYieldCurve.cpp new file mode 100644 index 0000000..a204810 --- /dev/null +++ b/cpp/FlatYieldCurve.cpp @@ -0,0 +1,5 @@ +/** + * @file FlatYieldCurve.cpp + * @brief Ensures link visibility for @ref FlatYieldCurve (inline methods in header). + */ +#include "FlatYieldCurve.hpp" diff --git a/cpp/FlatYieldCurve.hpp b/cpp/FlatYieldCurve.hpp new file mode 100644 index 0000000..9427bb8 --- /dev/null +++ b/cpp/FlatYieldCurve.hpp @@ -0,0 +1,22 @@ +/** + * @file FlatYieldCurve.hpp + * @brief Constant zero rate yield curve. + */ +#ifndef QUANTENGINE_FLATYIELDCURVE_HPP +#define QUANTENGINE_FLATYIELDCURVE_HPP +#include "YieldCurve.hpp" +#include + +/** + * @brief @f$P(t)=e^{-r t}@f$, @f$f(t)\equiv r@f$. + */ +class FlatYieldCurve : public YieldCurve{ +public: + explicit FlatYieldCurve(double rate = 0.01) : rate_(rate) {} + + double discount(double t) const override {return std::exp(-rate_ * t); }; + double zeroRate(double t) const override {return rate_; } +private: + double rate_ = 0.01; +}; +#endif diff --git a/cpp/ImpliedVolatility/Pybind.cpp b/cpp/ImpliedVolatility/Pybind.cpp new file mode 100644 index 0000000..89d82e6 --- /dev/null +++ b/cpp/ImpliedVolatility/Pybind.cpp @@ -0,0 +1,93 @@ +/** + * @file Pybind.cpp + * @brief pybind11 module @c qengine exposing @ref BSWrapper::bs_price_wrapper overloads. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "BSWrapper.hpp" + +namespace py = pybind11; + +namespace { + +std::vector to_vector_double(const py::array_t &a) { + py::buffer_info info = a.request(); + if (info.ndim != 1) { + throw std::runtime_error("expected 1-D ndarray for S, K, T, r, sigma"); + } + const auto *p = static_cast(info.ptr); + const ssize_t n = info.shape[0]; + return std::vector(p, p + n); +} + +std::vector to_vector_bool_1d(const py::array_t &a) { + py::buffer_info info = a.request(); + if (info.ndim != 1) { + throw std::runtime_error("expected 1-D ndarray for is_call"); + } + if (info.itemsize != 1) { + throw std::runtime_error("is_call: expected a boolean ndarray (dtype=bool)"); + } + const ssize_t n = info.shape[0]; + const auto *p = static_cast(info.ptr); + std::vector out(static_cast(n)); + for (ssize_t i = 0; i < n; ++i) { + out[static_cast(i)] = (p[i] != 0); + } + return out; +} + +void check_same_length(size_t n, size_t k, const char *name) { + if (n != k) { + throw std::runtime_error(std::string("length mismatch for ") + name); + } +} + +} // namespace + +PYBIND11_MODULE(qengine, m) { + m.doc() = "Binding for the Black Scholes model"; + + m.def( + "bs_price", + [](double S, double K, double T, double r, double sigma, bool is_call) { + return BSWrapper::bs_price_wrapper(S, K, T, r, sigma, is_call); + }, + py::arg("S"), py::arg("K"), py::arg("T"), py::arg("r"), py::arg("sigma"), py::arg("is_call")); + + m.def( + "bs_price", + [](py::array_t S, py::array_t K, py::array_t T, py::array_t r, + py::array_t sigma, py::array_t is_call) { + std::vector vS = to_vector_double(S); + std::vector vK = to_vector_double(K); + std::vector vT = to_vector_double(T); + std::vector vr = to_vector_double(r); + std::vector vsig = to_vector_double(sigma); + std::vector vC = to_vector_bool_1d(is_call); + const size_t n = vS.size(); + check_same_length(n, vK.size(), "K"); + check_same_length(n, vT.size(), "T"); + check_same_length(n, vr.size(), "r"); + check_same_length(n, vsig.size(), "sigma"); + check_same_length(n, vC.size(), "is_call"); + return BSWrapper::batch_bs_price_wrapper(vS, vK, vT, vr, vsig, vC); + }, + py::arg("S"), py::arg("K"), py::arg("T"), py::arg("r"), py::arg("sigma"), py::arg("is_call")); + + m.def( + "bs_price", + [](const std::vector &S, const std::vector &K, const std::vector &T, + const std::vector &r, const std::vector &sigma, const std::vector &is_call) { + return BSWrapper::batch_bs_price_wrapper(S, K, T, r, sigma, is_call); + }, + py::arg("S"), py::arg("K"), py::arg("T"), py::arg("r"), py::arg("sigma"), py::arg("is_call")); +} diff --git a/src/Instrument.cpp b/cpp/Instrument.cpp similarity index 80% rename from src/Instrument.cpp rename to cpp/Instrument.cpp index 35d8967..5106e34 100644 --- a/src/Instrument.cpp +++ b/cpp/Instrument.cpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file Instrument.cpp + * @brief @ref Instrument implementation. + */ #include "Instrument.hpp" diff --git a/src/Instrument.hpp b/cpp/Instrument.hpp similarity index 62% rename from src/Instrument.hpp rename to cpp/Instrument.hpp index dfd6a56..8a1b5cf 100644 --- a/src/Instrument.hpp +++ b/cpp/Instrument.hpp @@ -1,15 +1,20 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file Instrument.hpp + * @brief Generic derivative instrument: payoff plus pricing engine. + */ #ifndef QUANTENGINE_INSTRUMENT_HPP #define QUANTENGINE_INSTRUMENT_HPP +#include "Exercise.hpp" #include "Payoff.hpp" #include "PricingEngine.hpp" #include class PricingEngine; +/** + * @brief Represents a tradeable claim priced via a @ref PricingEngine. + */ class Instrument { public: Instrument() = default; @@ -24,6 +29,9 @@ public: return *payoff_; } + /** @brief Base @ref Instrument is treated as European unless overridden by @ref Option. */ + [[nodiscard]] virtual Exercise::Type exerciseType() const { return Exercise::Type::European; } + protected: double maturity_; std::unique_ptr payoff_; diff --git a/src/MarketData.cpp b/cpp/MarketData.cpp similarity index 78% rename from src/MarketData.cpp rename to cpp/MarketData.cpp index 16bf3c4..ea07d0e 100644 --- a/src/MarketData.cpp +++ b/cpp/MarketData.cpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 06.03.2026. -// +/** + * @file MarketData.cpp + * @brief @ref MarketData accessors. + */ #include "MarketData.hpp" diff --git a/src/MarketData.hpp b/cpp/MarketData.hpp similarity index 82% rename from src/MarketData.hpp rename to cpp/MarketData.hpp index 9b8d200..bff093b 100644 --- a/src/MarketData.hpp +++ b/cpp/MarketData.hpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 06.03.2026. -// +/** + * @file MarketData.hpp + * @brief Spot, discount curve, and volatility surface bundle. + */ #ifndef QUANTENGINE_MARKETDATA_HPP #define QUANTENGINE_MARKETDATA_HPP @@ -8,6 +9,9 @@ #include "VolatilitySurface.hpp" #include +/** + * @brief Immutable snapshot of inputs needed to simulate or price. + */ class MarketData { public: MarketData() = delete; diff --git a/src/MonteCarloEngine.cpp b/cpp/MonteCarloEngine.cpp similarity index 88% rename from src/MonteCarloEngine.cpp rename to cpp/MonteCarloEngine.cpp index 22b2611..d97c946 100644 --- a/src/MonteCarloEngine.cpp +++ b/cpp/MonteCarloEngine.cpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file MonteCarloEngine.cpp + * @brief Monte Carlo mean estimator with discounting. + */ #include "MonteCarloEngine.hpp" #include diff --git a/src/MonteCarloEngine.hpp b/cpp/MonteCarloEngine.hpp similarity index 73% rename from src/MonteCarloEngine.hpp rename to cpp/MonteCarloEngine.hpp index 91c1c3b..10dcb2e 100644 --- a/src/MonteCarloEngine.hpp +++ b/cpp/MonteCarloEngine.hpp @@ -1,13 +1,16 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file MonteCarloEngine.hpp + * @brief Monte Carlo pricing using a @ref StochasticProcess and @ref RandomGenerator. + */ #ifndef QUANTENGINE_MONTECARLOENGINE_HPP #define QUANTENGINE_MONTECARLOENGINE_HPP #include "PricingEngine.hpp" #include "RandomGenerator.hpp" - +/** + * @brief Simple path simulation: one Euler/exact step to horizon, average discounted payoff. + */ class MonteCarloEngine : public PricingEngine{ public: MonteCarloEngine() = default; diff --git a/cpp/NewtonSolver.cpp b/cpp/NewtonSolver.cpp new file mode 100644 index 0000000..325fb28 --- /dev/null +++ b/cpp/NewtonSolver.cpp @@ -0,0 +1,8 @@ +/** + * @file NewtonSolver.cpp + * @brief Placeholder translation unit for @ref NewtonSolver. + */ + +#include "NewtonSolver.hpp" + + diff --git a/src/NewtonSolver.hpp b/cpp/NewtonSolver.hpp similarity index 74% rename from src/NewtonSolver.hpp rename to cpp/NewtonSolver.hpp index 1e36680..0c4a9e2 100644 --- a/src/NewtonSolver.hpp +++ b/cpp/NewtonSolver.hpp @@ -1,12 +1,16 @@ -// -// Created by David Doebel on 13.03.2026. -// +/** + * @file NewtonSolver.hpp + * @brief Generic Newton iteration helper (incomplete / reserved for solvers). + */ #ifndef QUANTENGINE_GAUSSSOLVER_HPP #define QUANTENGINE_GAUSSSOLVER_HPP #include +/** + * @brief Template Newton step loop with relative/absolute tolerances. + */ class NewtonSolver { template bool solve(F&& func, DFinv&& dfinv,T x0 , double rtol, double atol) { diff --git a/src/Option.cpp b/cpp/Option.cpp similarity index 80% rename from src/Option.cpp rename to cpp/Option.cpp index fb2e80b..58643b3 100644 --- a/src/Option.cpp +++ b/cpp/Option.cpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file Option.cpp + * @brief @ref Option implementation. + */ #include "Option.hpp" diff --git a/src/Option.hpp b/cpp/Option.hpp similarity index 63% rename from src/Option.hpp rename to cpp/Option.hpp index 49603e6..5e6c500 100644 --- a/src/Option.hpp +++ b/cpp/Option.hpp @@ -1,12 +1,16 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file Option.hpp + * @brief Option instrument with exercise style (@ref Exercise). + */ #ifndef QUANTENGINE_OPTION_HPP #define QUANTENGINE_OPTION_HPP #include "Instrument.hpp" #include "Exercise.hpp" +/** + * @brief Extends @ref Instrument with exercise schedule / style metadata. + */ class Option : public Instrument{ public: Option() = default; @@ -17,10 +21,13 @@ public: return *exercise_; } + [[nodiscard]] Exercise::Type exerciseType() const override { return exercise_->type(); } + protected: std::unique_ptr exercise_; }; +/** @brief Plain-vanilla option using the base @ref Option constructor. */ class VanillaOption : public Option { public: using Option::Option; diff --git a/src/Payoff.cpp b/cpp/Payoff.cpp similarity index 80% rename from src/Payoff.cpp rename to cpp/Payoff.cpp index 5cb22e3..65a7111 100644 --- a/src/Payoff.cpp +++ b/cpp/Payoff.cpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file Payoff.cpp + * @brief Payoff function implementations. + */ #include "Payoff.hpp" #include diff --git a/src/Payoff.hpp b/cpp/Payoff.hpp similarity index 56% rename from src/Payoff.hpp rename to cpp/Payoff.hpp index cf58cce..3b3bbc6 100644 --- a/src/Payoff.hpp +++ b/cpp/Payoff.hpp @@ -1,11 +1,19 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file Payoff.hpp + * @brief Payoff interface and standard European payoffs (call, put, digital). + */ #ifndef QUANTENGINE_PAYOFF_HPP #define QUANTENGINE_PAYOFF_HPP +/** + * @brief Standard payoff shapes for routing (e.g. analytic vs Monte Carlo). + */ +enum class PayoffKind { Call, Put, Digital }; +/** + * @brief Terminal payoff as a function of spot @f$S_T@f$. + */ class Payoff { public: @@ -14,35 +22,42 @@ public: virtual ~Payoff() = default; virtual double operator()(double S) = 0; virtual double strike() = 0; + [[nodiscard]] virtual PayoffKind kind() const = 0; }; +/** @brief Standard European call @f$\max(S-K,0)@f$. */ class CallPayoff : public Payoff { public: CallPayoff() = default; CallPayoff(double strike) : strike_(strike) {} double operator()(double S) override; double strike() override {return strike_;} + [[nodiscard]] PayoffKind kind() const override { return PayoffKind::Call; } private: double strike_; }; +/** @brief Standard European put @f$\max(K-S,0)@f$. */ class PutPayoff : public Payoff { public: PutPayoff() = default; PutPayoff(double strike) : strike_(strike) {} double operator()(double S) override; double strike() override {return strike_;} + [[nodiscard]] PayoffKind kind() const override { return PayoffKind::Put; } private: double strike_; }; +/** @brief Digital (cash-or-nothing style) payoff @f$1_{S>K}@f$. */ class DigitalPayoff : public Payoff { public: DigitalPayoff() = default; DigitalPayoff(double strike) : strike_(strike) {} double operator()(double S) override; double strike() override {return strike_;} + [[nodiscard]] PayoffKind kind() const override { return PayoffKind::Digital; } private: double strike_; }; diff --git a/cpp/PricingEngine.cpp b/cpp/PricingEngine.cpp new file mode 100644 index 0000000..6a6699d --- /dev/null +++ b/cpp/PricingEngine.cpp @@ -0,0 +1,6 @@ +/** + * @file PricingEngine.cpp + * @brief @ref PricingEngine translation unit (interface only). + */ + +#include "PricingEngine.hpp" \ No newline at end of file diff --git a/src/PricingEngine.hpp b/cpp/PricingEngine.hpp similarity index 72% rename from src/PricingEngine.hpp rename to cpp/PricingEngine.hpp index 06bc254..e3d3c3b 100644 --- a/src/PricingEngine.hpp +++ b/cpp/PricingEngine.hpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file PricingEngine.hpp + * @brief Abstract pricer for @ref Instrument given a stochastic model. + */ #ifndef QUANTENGINE_PRICINGENGINE_HPP #define QUANTENGINE_PRICINGENGINE_HPP @@ -10,6 +11,9 @@ class Instrument; +/** + * @brief Computes model price of an instrument (e.g. Monte Carlo, PDE, closed form). + */ class PricingEngine { public: PricingEngine() = default; diff --git a/src/RandomGenerator.cpp b/cpp/RandomGenerator.cpp similarity index 77% rename from src/RandomGenerator.cpp rename to cpp/RandomGenerator.cpp index 55f0043..59f6c52 100644 --- a/src/RandomGenerator.cpp +++ b/cpp/RandomGenerator.cpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 06.03.2026. -// +/** + * @file RandomGenerator.cpp + * @brief @ref MersenneTwister implementation. + */ #include "RandomGenerator.hpp" diff --git a/src/RandomGenerator.hpp b/cpp/RandomGenerator.hpp similarity index 75% rename from src/RandomGenerator.hpp rename to cpp/RandomGenerator.hpp index e4df117..0f9aaed 100644 --- a/src/RandomGenerator.hpp +++ b/cpp/RandomGenerator.hpp @@ -1,11 +1,13 @@ -// -// Created by David Doebel on 06.03.2026. -// +/** + * @file RandomGenerator.hpp + * @brief Random numbers for Monte Carlo (Gaussian draws). + */ #ifndef QUANTENGINE_RANDOMGENERATOR_HPP #define QUANTENGINE_RANDOMGENERATOR_HPP #include +/** @brief Interface for standard normal variates. */ class RandomGenerator { public: RandomGenerator() = default; @@ -14,6 +16,7 @@ public: virtual std::vector nextGaussianVector(std::size_t n) = 0; }; +/** @brief @c std::mt19937 with normal distribution. */ class MersenneTwister : public RandomGenerator { public: MersenneTwister() = default; diff --git a/src/Statistics.cpp b/cpp/Statistics.cpp similarity index 90% rename from src/Statistics.cpp rename to cpp/Statistics.cpp index 0578fe1..89b39cd 100644 --- a/src/Statistics.cpp +++ b/cpp/Statistics.cpp @@ -1,6 +1,7 @@ -// -// Created by David Doebel on 06.03.2026. -// +/** + * @file Statistics.cpp + * @brief Streaming moment and extrema updates. + */ #include "Statistics.hpp" diff --git a/src/Statistics.hpp b/cpp/Statistics.hpp similarity index 74% rename from src/Statistics.hpp rename to cpp/Statistics.hpp index a4de629..e1aba3a 100644 --- a/src/Statistics.hpp +++ b/cpp/Statistics.hpp @@ -1,12 +1,15 @@ -// -// Created by David Doebel on 06.03.2026. -// +/** + * @file Statistics.hpp + * @brief Online sample moments for Monte Carlo diagnostics. + */ #ifndef QUANTENGINE_STATISTICS_HPP #define QUANTENGINE_STATISTICS_HPP #include - +/** + * @brief Accumulates count, mean/variance-related sums, and running min/max. + */ class Statistics { public: Statistics() : moments_({0., 0., 0.}), n(0), max_(0.), min_(0.) {} diff --git a/cpp/StochasticProcess.cpp b/cpp/StochasticProcess.cpp new file mode 100644 index 0000000..0839005 --- /dev/null +++ b/cpp/StochasticProcess.cpp @@ -0,0 +1,6 @@ +/** + * @file StochasticProcess.cpp + * @brief @ref StochasticProcess translation unit (interface only). + */ + +#include "StochasticProcess.hpp" \ No newline at end of file diff --git a/src/StochasticProcess.hpp b/cpp/StochasticProcess.hpp similarity index 77% rename from src/StochasticProcess.hpp rename to cpp/StochasticProcess.hpp index a0ebb49..f9b8e80 100644 --- a/src/StochasticProcess.hpp +++ b/cpp/StochasticProcess.hpp @@ -1,12 +1,16 @@ -// -// Created by David Doebel on 05.03.2026. -// +/** + * @file StochasticProcess.hpp + * @brief Interface for SDE drift, diffusion, and time stepping. + */ #ifndef QUANTENGINE_STOCHASTICPROCESS_HPP #define QUANTENGINE_STOCHASTICPROCESS_HPP #include "MarketData.hpp" #include +/** + * @brief Stochastic model for the underlying, driven by @ref MarketData. + */ class StochasticProcess { public: StochasticProcess() = delete; diff --git a/cpp/VolatilitySurface.cpp b/cpp/VolatilitySurface.cpp new file mode 100644 index 0000000..f66c6b3 --- /dev/null +++ b/cpp/VolatilitySurface.cpp @@ -0,0 +1,6 @@ +/** + * @file VolatilitySurface.cpp + * @brief @ref VolatilitySurface translation unit (interface only). + */ + +#include "VolatilitySurface.hpp" \ No newline at end of file diff --git a/cpp/VolatilitySurface.hpp b/cpp/VolatilitySurface.hpp new file mode 100644 index 0000000..5c6f2be --- /dev/null +++ b/cpp/VolatilitySurface.hpp @@ -0,0 +1,28 @@ +/** + * @file VolatilitySurface.hpp + * @brief Implied volatility as a function of strike and expiry. + */ + +#ifndef QUANTENGINE_VOLATILITYSURFACE_HPP +#define QUANTENGINE_VOLATILITYSURFACE_HPP +#include + +/** + * @brief Local/vol surface @f$\sigma(K,T)@f$ used by simulation. + */ +class VolatilitySurface { +public: + virtual ~VolatilitySurface() = default; + virtual double sigma(double K, double T) const = 0; +private: + +}; + +class SVI : public VolatilitySurface { +public: + SVI() = default; + SVI(std::vector K, std::vector rho, std::vector S, std::vector T); +}; + + +#endif //QUANTENGINE_VOLATILITYSURFACE_HPP diff --git a/cpp/YieldCurve.cpp b/cpp/YieldCurve.cpp new file mode 100644 index 0000000..fe80603 --- /dev/null +++ b/cpp/YieldCurve.cpp @@ -0,0 +1,6 @@ +/** + * @file YieldCurve.cpp + * @brief @ref YieldCurve translation unit (interface only). + */ + +#include "YieldCurve.hpp" \ No newline at end of file diff --git a/src/YieldCurve.hpp b/cpp/YieldCurve.hpp similarity index 78% rename from src/YieldCurve.hpp rename to cpp/YieldCurve.hpp index 7853382..7221b96 100644 --- a/src/YieldCurve.hpp +++ b/cpp/YieldCurve.hpp @@ -1,11 +1,14 @@ -// -// Created by David Doebel on 06.03.2026. -// +/** + * @file YieldCurve.hpp + * @brief Abstract yield curve: discount factors and zero rates. + */ #ifndef QUANTENGINE_YIELDCURVE_HPP #define QUANTENGINE_YIELDCURVE_HPP - +/** + * @brief Risk-free rate term structure for discounting and risk-neutral drift. + */ class YieldCurve { public: YieldCurve() = default; diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 0000000..e75341e --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,50 @@ +# Doxygen configuration for QuantEngine (option_pricing). +# Run from repo root: doxygen docs/Doxyfile +# Or: cmake --build build --target docs + +PROJECT_NAME = QuantEngine +PROJECT_BRIEF = "Monte Carlo option pricing, market data abstractions, and Python bindings" + +OUTPUT_DIRECTORY = docs/html +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = YES + +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +OPTIMIZE_OUTPUT_FOR_CPLUSPLUS = YES + +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = + +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO +WARN_NO_PARAMDOC = NO + +INPUT = cpp +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.cpp *.hpp *.h +RECURSIVE = YES + +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = + +GENERATE_HTML = YES +HTML_OUTPUT = . +HTML_COLORSTYLE_HUE = 220 +GENERATE_LATEX = NO + +SEARCHENGINE = YES + +SOURCE_BROWSER = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES + +ALPHABETICAL_INDEX = YES +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO + +CLASS_DIAGRAMS = YES +HAVE_DOT = NO + +PREDEFINED = DOXYGEN diff --git a/qengine/__init__.py b/qengine/__init__.py new file mode 100644 index 0000000..c4cb10e --- /dev/null +++ b/qengine/__init__.py @@ -0,0 +1,5 @@ +"""Qengine: quant pricing backend (native extension in qengine.qengine).""" + +from .qengine import bs_price + +__all__ = ["bs_price"] diff --git a/scripts/test_qengine_bindings.py b/scripts/test_qengine_bindings.py new file mode 100644 index 0000000..a523de8 --- /dev/null +++ b/scripts/test_qengine_bindings.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Smoke test: use an installed `qengine` package (pip install .) or a dev build (cmake -> qengine/*.so).""" + +from __future__ import annotations + +import math +import sys +from pathlib import Path + +# Running `python scripts/this.py` puts `scripts/` on sys.path, not the repo root +_REPO_ROOT = Path(__file__).resolve().parents[1] +if str(_REPO_ROOT) not in sys.path: + sys.path.insert(0, str(_REPO_ROOT)) + + +def main() -> int: + try: + import qengine + except ImportError as e: + print( + f"Import failed ({e}). Install the package (pip install .) or build with CMake so " + "qengine/qengine.*.so exists next to qengine/__init__.py.", + file=sys.stderr, + ) + return 1 + + call = qengine.bs_price(100.0, 100.0, 1.0, 0.05, 0.2, True) + put = qengine.bs_price(100.0, 100.0, 1.0, 0.05, 0.2, False) + batch_list = qengine.bs_price( + [100.0, 100.0], + [100.0, 110.0], + [1.0, 1.0], + [0.05, 0.05], + [0.2, 0.2], + [True, False], + ) + + assert math.isfinite(call) and math.isfinite(put) + assert len(batch_list) == 2 and all(math.isfinite(x) for x in batch_list) + + print("qengine.bs_price (call):", call) + print("qengine.bs_price (put):", put) + print("qengine.bs_price (list batch):", list(batch_list)) + + try: + import numpy as np + except ImportError: + print("ok: overloads callable (NumPy not installed; skipped ndarray batch test).") + return 0 + + s = np.array([100.0, 100.0], dtype=np.float64) + k = np.array([100.0, 110.0], dtype=np.float64) + t = np.array([1.0, 1.0], dtype=np.float64) + r = np.array([0.05, 0.05], dtype=np.float64) + sig = np.array([0.2, 0.2], dtype=np.float64) + opt = np.array([True, False], dtype=bool) + batch_np = qengine.bs_price(s, k, t, r, sig, opt) + assert len(batch_np) == 2 and all(math.isfinite(float(x)) for x in batch_np) + print("qengine.bs_price (ndarray batch):", [float(x) for x in batch_np]) + print("ok: overloads callable.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 030c61a..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,41 +0,0 @@ -add_library(qengine - 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 - DBIngest.cpp - DBIngest.hpp - GaussSolver.cpp - GaussSolver.hpp - - -) - -target_include_directories(qengine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_include_directories(qengine PRIVATE - /opt/homebrew/include -) -target_link_libraries(qengine Eigen3::Eigen) -target_link_libraries(qengine pqxx pq) \ No newline at end of file diff --git a/src/Exercise.cpp b/src/Exercise.cpp deleted file mode 100644 index fdadc45..0000000 --- a/src/Exercise.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by David Doebel on 05.03.2026. -// - -#include "Exercise.hpp" \ No newline at end of file diff --git a/src/NewtonSolver.cpp b/src/NewtonSolver.cpp deleted file mode 100644 index eeb97c2..0000000 --- a/src/NewtonSolver.cpp +++ /dev/null @@ -1,7 +0,0 @@ -// -// Created by David Doebel on 13.03.2026. -// - -#include "NewtonSolver.hpp" - - diff --git a/src/PricingEngine.cpp b/src/PricingEngine.cpp deleted file mode 100644 index bdcf247..0000000 --- a/src/PricingEngine.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by David Doebel on 05.03.2026. -// - -#include "PricingEngine.hpp" \ No newline at end of file diff --git a/src/StochasticProcess.cpp b/src/StochasticProcess.cpp deleted file mode 100644 index 52947c3..0000000 --- a/src/StochasticProcess.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by David Doebel on 05.03.2026. -// - -#include "StochasticProcess.hpp" \ No newline at end of file diff --git a/src/VolatilitySurface.cpp b/src/VolatilitySurface.cpp deleted file mode 100644 index c719fbc..0000000 --- a/src/VolatilitySurface.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// 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 deleted file mode 100644 index 438df63..0000000 --- a/src/VolatilitySurface.hpp +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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) const = 0; -private: - -}; - - -#endif //QUANTENGINE_VOLATILITYSURFACE_HPP diff --git a/src/YieldCurve.cpp b/src/YieldCurve.cpp deleted file mode 100644 index b4e73c3..0000000 --- a/src/YieldCurve.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by David Doebel on 06.03.2026. -// - -#include "YieldCurve.hpp" \ No newline at end of file diff --git a/tests/test_black_scholes.cpp b/tests/test_black_scholes.cpp index b9f0f01..725acbe 100644 --- a/tests/test_black_scholes.cpp +++ b/tests/test_black_scholes.cpp @@ -3,6 +3,7 @@ // #include +#include "BlackScholesClosedFormEngine.hpp" #include "BlackScholesProcess.hpp" #include "MonteCarloEngine.hpp" #include "Instrument.hpp" @@ -51,3 +52,28 @@ TEST(BlackScholesProcess, ExpectedValue) { ASSERT_NEAR(callPrice, callGT, tol); ASSERT_NEAR(putPrice, putGT, tol); } + +TEST(BlackScholesClosedForm, MatchesReference) { + const double K = 100.0; + const double T = 1.0; + + const MarketData marketData( + 100.0, + std::make_shared(0.01), + std::make_shared(0.2)); + + auto processCall = std::make_unique(marketData); + auto processPut = std::make_unique(marketData); + + auto analyticCall = std::make_unique(std::move(processCall)); + auto analyticPut = std::make_unique(std::move(processPut)); + + Instrument callInstr(T, std::make_unique(K), std::move(analyticCall)); + Instrument putInstr(T, std::make_unique(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); +}