Restructure C++ core into cpp module and package bindings.
Move the pricing engine sources out of src/ into cpp/, add the closed-form engine and pybind wiring, and align tests/build targets with the new project layout. Made-with: Cursor
This commit is contained in:
@@ -4,31 +4,53 @@ project(QuantEngine)
|
|||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_FLAGS "-O3 -march=native")
|
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(Eigen3 REQUIRED)
|
||||||
|
find_package(pybind11 CONFIG REQUIRED)
|
||||||
#find_package(PostgreSQL REQUIRED)
|
#find_package(PostgreSQL REQUIRED)
|
||||||
#find_package(PkgConfig REQUIRED)
|
#find_package(PkgConfig REQUIRED)
|
||||||
#pkg_check_modules(PQXX REQUIRED IMPORTED_TARGET libpqxx)
|
#pkg_check_modules(PQXX REQUIRED IMPORTED_TARGET libpqxx)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(cpp)
|
||||||
|
|
||||||
# Testing
|
find_package(Doxygen OPTIONAL_COMPONENTS dot)
|
||||||
enable_testing()
|
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(
|
if(BUILD_TESTING)
|
||||||
googletest
|
enable_testing()
|
||||||
URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
|
||||||
)
|
|
||||||
|
|
||||||
FetchContent_MakeAvailable(googletest)
|
include(FetchContent)
|
||||||
|
|
||||||
add_executable(qengine_tests
|
FetchContent_Declare(
|
||||||
tests/test_black_scholes.cpp
|
googletest
|
||||||
tests/stubs/FlatYieldCurve.cpp
|
URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
|
||||||
tests/stubs/FlatVolatilitySurface.cpp)
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(qengine_tests qengine GTest::gtest_main)
|
FetchContent_MakeAvailable(googletest)
|
||||||
include(GoogleTest)
|
|
||||||
gtest_discover_tests(qengine_tests)
|
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()
|
||||||
|
|||||||
49
cpp/BSWrapper.cpp
Normal file
49
cpp/BSWrapper.cpp
Normal file
@@ -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 <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class FlatYieldCurve;
|
||||||
|
|
||||||
|
double BSWrapper::bs_price_wrapper(double S, double K, double T, double r, double sigma,
|
||||||
|
bool is_call) {
|
||||||
|
std::shared_ptr<FlatYieldCurve> flat_curve = std::make_shared<FlatYieldCurve>(r);
|
||||||
|
auto flat_vol_surface = std::make_shared<FlatVolatilitySurface>(sigma);
|
||||||
|
MarketData data(S,flat_curve, flat_vol_surface);
|
||||||
|
std::unique_ptr<BlackScholesProcess> process = std::make_unique<BlackScholesProcess>(data);
|
||||||
|
std::unique_ptr<BlackScholesClosedFormEngine> pricing_engine =
|
||||||
|
std::make_unique<BlackScholesClosedFormEngine>(std::move(process));
|
||||||
|
std::unique_ptr<Payoff> payoff;
|
||||||
|
if (is_call)
|
||||||
|
payoff = std::make_unique<CallPayoff>(K);
|
||||||
|
else payoff = std::make_unique<PutPayoff>(K);
|
||||||
|
EuropeanExercise exercise(T);
|
||||||
|
VanillaOption option(T,std::make_unique<EuropeanExercise>(exercise),
|
||||||
|
std::move(payoff),std::move(pricing_engine));
|
||||||
|
return option.price();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> BSWrapper::batch_bs_price_wrapper(const std::vector<double> &S, const std::vector<double> &K,
|
||||||
|
const std::vector<double> &T, const std::vector<double> &r, const std::vector<double> &sigma,
|
||||||
|
const std::vector<bool> &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<double> 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;
|
||||||
|
}
|
||||||
24
cpp/BSWrapper.hpp
Normal file
24
cpp/BSWrapper.hpp
Normal file
@@ -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 <vector>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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<double> batch_bs_price_wrapper(const std::vector<double>& S, const std::vector<double>& K,
|
||||||
|
const std::vector<double>& T, const std::vector<double>& r, const std::vector<double>& sigma,
|
||||||
|
const std::vector<bool>& is_call);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //QUANTENGINE_BSWRAPPER_HPP
|
||||||
69
cpp/BlackScholesClosedFormEngine.cpp
Normal file
69
cpp/BlackScholesClosedFormEngine.cpp
Normal file
@@ -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 <cmath>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
22
cpp/BlackScholesClosedFormEngine.hpp
Normal file
22
cpp/BlackScholesClosedFormEngine.hpp
Normal file
@@ -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<StochasticProcess> process)
|
||||||
|
: PricingEngine(std::move(process)) {}
|
||||||
|
|
||||||
|
double calculate(const Instrument &instrument) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QUANTENGINE_BLACKSCHOLESCLOSEDFORMENGINE_HPP
|
||||||
@@ -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"
|
#include "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
|
#ifndef QUANTENGINE_BLACKSCHOLESPROCESS_HPP
|
||||||
#define QUANTENGINE_BLACKSCHOLESPROCESS_HPP
|
#define QUANTENGINE_BLACKSCHOLESPROCESS_HPP
|
||||||
#include "StochasticProcess.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{
|
class BlackScholesProcess : public StochasticProcess{
|
||||||
public:
|
public:
|
||||||
explicit BlackScholesProcess(MarketData data) : StochasticProcess(std::move(data)){}
|
explicit BlackScholesProcess(MarketData data) : StochasticProcess(std::move(data)){}
|
||||||
65
cpp/CMakeLists.txt
Normal file
65
cpp/CMakeLists.txt
Normal file
@@ -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}")
|
||||||
@@ -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 "DBIngest.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
// Queries
|
|
||||||
// Query for selecting the volatility surface parameters
|
|
||||||
std::string vol_surface_query = ""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool DBIngest::connect() {
|
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()) {
|
if(connection_.is_open()) {
|
||||||
std::cout << "Connected\n";
|
std::cout << "Connected\n";
|
||||||
@@ -31,6 +36,7 @@ bool DBIngest::connect() {
|
|||||||
|
|
||||||
bool DBIngest::disconnect() {
|
bool DBIngest::disconnect() {
|
||||||
connection_.close();
|
connection_.close();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DBIngest::update(VolatilitySurface &surface) {
|
bool DBIngest::update(VolatilitySurface &surface) {
|
||||||
@@ -48,8 +54,11 @@ bool DBIngest::update(VolatilitySurface &surface) {
|
|||||||
for (auto row : result) {
|
for (auto row : result) {
|
||||||
std::cout << row[0] << " " << row[1] << " " << row[2] << " " << row[3] << std::endl;
|
std::cout << row[0] << " " << row[1] << " " << row[2] << " " << row[3] << std::endl;
|
||||||
}
|
}
|
||||||
|
(void)surface;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DBIngest::update(YieldCurve &yield_curve) {
|
bool DBIngest::update(YieldCurve &yield_curve) {
|
||||||
|
(void)yield_curve;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
@@ -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
|
#ifndef QUANTENGINE_DBINGEST_HPP
|
||||||
#define QUANTENGINE_DBINGEST_HPP
|
#define QUANTENGINE_DBINGEST_HPP
|
||||||
@@ -10,6 +11,9 @@
|
|||||||
#include "VolatilitySurface.hpp"
|
#include "VolatilitySurface.hpp"
|
||||||
#include "YieldCurve.hpp"
|
#include "YieldCurve.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connects to Postgres via libpqxx and queries quotes for surface building.
|
||||||
|
*/
|
||||||
class DBIngest {
|
class DBIngest {
|
||||||
|
|
||||||
bool connect();
|
bool connect();
|
||||||
6
cpp/Exercise.cpp
Normal file
6
cpp/Exercise.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @file Exercise.cpp
|
||||||
|
* @brief @ref Exercise translation unit (interface only).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "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
|
#ifndef QUANTENGINE_EXERCISE_HPP
|
||||||
#define QUANTENGINE_EXERCISE_HPP
|
#define QUANTENGINE_EXERCISE_HPP
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Describes when the holder may exercise (metadata for pricing engines).
|
||||||
|
*/
|
||||||
class Exercise {
|
class Exercise {
|
||||||
public:
|
public:
|
||||||
Exercise() = default;
|
Exercise() = default;
|
||||||
@@ -22,7 +26,9 @@ protected:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief Single exercise at maturity. */
|
||||||
class EuropeanExercise : public Exercise {
|
class EuropeanExercise : public Exercise {
|
||||||
|
public:
|
||||||
EuropeanExercise() : type_(Type::European) {};
|
EuropeanExercise() : type_(Type::European) {};
|
||||||
EuropeanExercise(double maturity) : type_(Type::European){
|
EuropeanExercise(double maturity) : type_(Type::European){
|
||||||
exercise_times_.push_back(maturity);
|
exercise_times_.push_back(maturity);
|
||||||
@@ -35,7 +41,9 @@ private:
|
|||||||
Type type_;
|
Type type_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief Continuous American exercise from @f$t=0@f$ to maturity (placeholder grid). */
|
||||||
class AmericanExercise : public Exercise{
|
class AmericanExercise : public Exercise{
|
||||||
|
public:
|
||||||
AmericanExercise() : type_(Type::American) {};
|
AmericanExercise() : type_(Type::American) {};
|
||||||
AmericanExercise(double maturity) : type_(Type::American) {
|
AmericanExercise(double maturity) : type_(Type::American) {
|
||||||
exercise_times_.push_back(0);
|
exercise_times_.push_back(0);
|
||||||
5
cpp/FlatVolatilitySurface.cpp
Normal file
5
cpp/FlatVolatilitySurface.cpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* @file FlatVolatilitySurface.cpp
|
||||||
|
* @brief Ensures link visibility for @ref FlatVolatilitySurface.
|
||||||
|
*/
|
||||||
|
#include "FlatVolatilitySurface.hpp"
|
||||||
21
cpp/FlatVolatilitySurface.hpp
Normal file
21
cpp/FlatVolatilitySurface.hpp
Normal file
@@ -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
|
||||||
5
cpp/FlatYieldCurve.cpp
Normal file
5
cpp/FlatYieldCurve.cpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* @file FlatYieldCurve.cpp
|
||||||
|
* @brief Ensures link visibility for @ref FlatYieldCurve (inline methods in header).
|
||||||
|
*/
|
||||||
|
#include "FlatYieldCurve.hpp"
|
||||||
22
cpp/FlatYieldCurve.hpp
Normal file
22
cpp/FlatYieldCurve.hpp
Normal file
@@ -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 <cmath>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
93
cpp/ImpliedVolatility/Pybind.cpp
Normal file
93
cpp/ImpliedVolatility/Pybind.cpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* @file Pybind.cpp
|
||||||
|
* @brief pybind11 module @c qengine exposing @ref BSWrapper::bs_price_wrapper overloads.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <pybind11/numpy.h>
|
||||||
|
#include <pybind11/pybind11.h>
|
||||||
|
#include <pybind11/stl.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "BSWrapper.hpp"
|
||||||
|
|
||||||
|
namespace py = pybind11;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::vector<double> to_vector_double(const py::array_t<double> &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<const double *>(info.ptr);
|
||||||
|
const ssize_t n = info.shape[0];
|
||||||
|
return std::vector<double>(p, p + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<bool> to_vector_bool_1d(const py::array_t<bool> &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<const std::uint8_t *>(info.ptr);
|
||||||
|
std::vector<bool> out(static_cast<size_t>(n));
|
||||||
|
for (ssize_t i = 0; i < n; ++i) {
|
||||||
|
out[static_cast<size_t>(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<double> S, py::array_t<double> K, py::array_t<double> T, py::array_t<double> r,
|
||||||
|
py::array_t<double> sigma, py::array_t<bool> is_call) {
|
||||||
|
std::vector<double> vS = to_vector_double(S);
|
||||||
|
std::vector<double> vK = to_vector_double(K);
|
||||||
|
std::vector<double> vT = to_vector_double(T);
|
||||||
|
std::vector<double> vr = to_vector_double(r);
|
||||||
|
std::vector<double> vsig = to_vector_double(sigma);
|
||||||
|
std::vector<bool> 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<double> &S, const std::vector<double> &K, const std::vector<double> &T,
|
||||||
|
const std::vector<double> &r, const std::vector<double> &sigma, const std::vector<bool> &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"));
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//
|
/**
|
||||||
// Created by David Doebel on 05.03.2026.
|
* @file Instrument.cpp
|
||||||
//
|
* @brief @ref Instrument implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "Instrument.hpp"
|
#include "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
|
#ifndef QUANTENGINE_INSTRUMENT_HPP
|
||||||
#define QUANTENGINE_INSTRUMENT_HPP
|
#define QUANTENGINE_INSTRUMENT_HPP
|
||||||
|
#include "Exercise.hpp"
|
||||||
#include "Payoff.hpp"
|
#include "Payoff.hpp"
|
||||||
#include "PricingEngine.hpp"
|
#include "PricingEngine.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class PricingEngine;
|
class PricingEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents a tradeable claim priced via a @ref PricingEngine.
|
||||||
|
*/
|
||||||
class Instrument {
|
class Instrument {
|
||||||
public:
|
public:
|
||||||
Instrument() = default;
|
Instrument() = default;
|
||||||
@@ -24,6 +29,9 @@ public:
|
|||||||
return *payoff_;
|
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:
|
protected:
|
||||||
double maturity_;
|
double maturity_;
|
||||||
std::unique_ptr<Payoff> payoff_;
|
std::unique_ptr<Payoff> payoff_;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//
|
/**
|
||||||
// Created by David Doebel on 06.03.2026.
|
* @file MarketData.cpp
|
||||||
//
|
* @brief @ref MarketData accessors.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "MarketData.hpp"
|
#include "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
|
#ifndef QUANTENGINE_MARKETDATA_HPP
|
||||||
#define QUANTENGINE_MARKETDATA_HPP
|
#define QUANTENGINE_MARKETDATA_HPP
|
||||||
@@ -8,6 +9,9 @@
|
|||||||
#include "VolatilitySurface.hpp"
|
#include "VolatilitySurface.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Immutable snapshot of inputs needed to simulate or price.
|
||||||
|
*/
|
||||||
class MarketData {
|
class MarketData {
|
||||||
public:
|
public:
|
||||||
MarketData() = delete;
|
MarketData() = delete;
|
||||||
@@ -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 "MonteCarloEngine.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -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
|
#ifndef QUANTENGINE_MONTECARLOENGINE_HPP
|
||||||
#define QUANTENGINE_MONTECARLOENGINE_HPP
|
#define QUANTENGINE_MONTECARLOENGINE_HPP
|
||||||
#include "PricingEngine.hpp"
|
#include "PricingEngine.hpp"
|
||||||
#include "RandomGenerator.hpp"
|
#include "RandomGenerator.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Simple path simulation: one Euler/exact step to horizon, average discounted payoff.
|
||||||
|
*/
|
||||||
class MonteCarloEngine : public PricingEngine{
|
class MonteCarloEngine : public PricingEngine{
|
||||||
public:
|
public:
|
||||||
MonteCarloEngine() = default;
|
MonteCarloEngine() = default;
|
||||||
8
cpp/NewtonSolver.cpp
Normal file
8
cpp/NewtonSolver.cpp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @file NewtonSolver.cpp
|
||||||
|
* @brief Placeholder translation unit for @ref NewtonSolver.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "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
|
#ifndef QUANTENGINE_GAUSSSOLVER_HPP
|
||||||
#define QUANTENGINE_GAUSSSOLVER_HPP
|
#define QUANTENGINE_GAUSSSOLVER_HPP
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Template Newton step loop with relative/absolute tolerances.
|
||||||
|
*/
|
||||||
class NewtonSolver {
|
class NewtonSolver {
|
||||||
template<typename F, typename DFinv, typename T>
|
template<typename F, typename DFinv, typename T>
|
||||||
bool solve(F&& func, DFinv&& dfinv,T x0 , double rtol, double atol) {
|
bool solve(F&& func, DFinv&& dfinv,T x0 , double rtol, double atol) {
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//
|
/**
|
||||||
// Created by David Doebel on 05.03.2026.
|
* @file Option.cpp
|
||||||
//
|
* @brief @ref Option implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "Option.hpp"
|
#include "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
|
#ifndef QUANTENGINE_OPTION_HPP
|
||||||
#define QUANTENGINE_OPTION_HPP
|
#define QUANTENGINE_OPTION_HPP
|
||||||
#include "Instrument.hpp"
|
#include "Instrument.hpp"
|
||||||
#include "Exercise.hpp"
|
#include "Exercise.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extends @ref Instrument with exercise schedule / style metadata.
|
||||||
|
*/
|
||||||
class Option : public Instrument{
|
class Option : public Instrument{
|
||||||
public:
|
public:
|
||||||
Option() = default;
|
Option() = default;
|
||||||
@@ -17,10 +21,13 @@ public:
|
|||||||
return *exercise_;
|
return *exercise_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Exercise::Type exerciseType() const override { return exercise_->type(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::unique_ptr<Exercise> exercise_;
|
std::unique_ptr<Exercise> exercise_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief Plain-vanilla option using the base @ref Option constructor. */
|
||||||
class VanillaOption : public Option {
|
class VanillaOption : public Option {
|
||||||
public:
|
public:
|
||||||
using Option::Option;
|
using Option::Option;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//
|
/**
|
||||||
// Created by David Doebel on 05.03.2026.
|
* @file Payoff.cpp
|
||||||
//
|
* @brief Payoff function implementations.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "Payoff.hpp"
|
#include "Payoff.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -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
|
#ifndef QUANTENGINE_PAYOFF_HPP
|
||||||
#define 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 {
|
class Payoff {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@@ -14,35 +22,42 @@ public:
|
|||||||
virtual ~Payoff() = default;
|
virtual ~Payoff() = default;
|
||||||
virtual double operator()(double S) = 0;
|
virtual double operator()(double S) = 0;
|
||||||
virtual double strike() = 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 {
|
class CallPayoff : public Payoff {
|
||||||
public:
|
public:
|
||||||
CallPayoff() = default;
|
CallPayoff() = default;
|
||||||
CallPayoff(double strike) : strike_(strike) {}
|
CallPayoff(double strike) : strike_(strike) {}
|
||||||
double operator()(double S) override;
|
double operator()(double S) override;
|
||||||
double strike() override {return strike_;}
|
double strike() override {return strike_;}
|
||||||
|
[[nodiscard]] PayoffKind kind() const override { return PayoffKind::Call; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double strike_;
|
double strike_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief Standard European put @f$\max(K-S,0)@f$. */
|
||||||
class PutPayoff : public Payoff {
|
class PutPayoff : public Payoff {
|
||||||
public:
|
public:
|
||||||
PutPayoff() = default;
|
PutPayoff() = default;
|
||||||
PutPayoff(double strike) : strike_(strike) {}
|
PutPayoff(double strike) : strike_(strike) {}
|
||||||
double operator()(double S) override;
|
double operator()(double S) override;
|
||||||
double strike() override {return strike_;}
|
double strike() override {return strike_;}
|
||||||
|
[[nodiscard]] PayoffKind kind() const override { return PayoffKind::Put; }
|
||||||
private:
|
private:
|
||||||
double strike_;
|
double strike_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief Digital (cash-or-nothing style) payoff @f$1_{S>K}@f$. */
|
||||||
class DigitalPayoff : public Payoff {
|
class DigitalPayoff : public Payoff {
|
||||||
public:
|
public:
|
||||||
DigitalPayoff() = default;
|
DigitalPayoff() = default;
|
||||||
DigitalPayoff(double strike) : strike_(strike) {}
|
DigitalPayoff(double strike) : strike_(strike) {}
|
||||||
double operator()(double S) override;
|
double operator()(double S) override;
|
||||||
double strike() override {return strike_;}
|
double strike() override {return strike_;}
|
||||||
|
[[nodiscard]] PayoffKind kind() const override { return PayoffKind::Digital; }
|
||||||
private:
|
private:
|
||||||
double strike_;
|
double strike_;
|
||||||
};
|
};
|
||||||
6
cpp/PricingEngine.cpp
Normal file
6
cpp/PricingEngine.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @file PricingEngine.cpp
|
||||||
|
* @brief @ref PricingEngine translation unit (interface only).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "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
|
#ifndef QUANTENGINE_PRICINGENGINE_HPP
|
||||||
#define QUANTENGINE_PRICINGENGINE_HPP
|
#define QUANTENGINE_PRICINGENGINE_HPP
|
||||||
@@ -10,6 +11,9 @@
|
|||||||
|
|
||||||
class Instrument;
|
class Instrument;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Computes model price of an instrument (e.g. Monte Carlo, PDE, closed form).
|
||||||
|
*/
|
||||||
class PricingEngine {
|
class PricingEngine {
|
||||||
public:
|
public:
|
||||||
PricingEngine() = default;
|
PricingEngine() = default;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//
|
/**
|
||||||
// Created by David Doebel on 06.03.2026.
|
* @file RandomGenerator.cpp
|
||||||
//
|
* @brief @ref MersenneTwister implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "RandomGenerator.hpp"
|
#include "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
|
#ifndef QUANTENGINE_RANDOMGENERATOR_HPP
|
||||||
#define QUANTENGINE_RANDOMGENERATOR_HPP
|
#define QUANTENGINE_RANDOMGENERATOR_HPP
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
|
/** @brief Interface for standard normal variates. */
|
||||||
class RandomGenerator {
|
class RandomGenerator {
|
||||||
public:
|
public:
|
||||||
RandomGenerator() = default;
|
RandomGenerator() = default;
|
||||||
@@ -14,6 +16,7 @@ public:
|
|||||||
virtual std::vector<double> nextGaussianVector(std::size_t n) = 0;
|
virtual std::vector<double> nextGaussianVector(std::size_t n) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief @c std::mt19937 with normal distribution. */
|
||||||
class MersenneTwister : public RandomGenerator {
|
class MersenneTwister : public RandomGenerator {
|
||||||
public:
|
public:
|
||||||
MersenneTwister() = default;
|
MersenneTwister() = default;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//
|
/**
|
||||||
// Created by David Doebel on 06.03.2026.
|
* @file Statistics.cpp
|
||||||
//
|
* @brief Streaming moment and extrema updates.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "Statistics.hpp"
|
#include "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
|
#ifndef QUANTENGINE_STATISTICS_HPP
|
||||||
#define QUANTENGINE_STATISTICS_HPP
|
#define QUANTENGINE_STATISTICS_HPP
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Accumulates count, mean/variance-related sums, and running min/max.
|
||||||
|
*/
|
||||||
class Statistics {
|
class Statistics {
|
||||||
public:
|
public:
|
||||||
Statistics() : moments_({0., 0., 0.}), n(0), max_(0.), min_(0.) {}
|
Statistics() : moments_({0., 0., 0.}), n(0), max_(0.), min_(0.) {}
|
||||||
6
cpp/StochasticProcess.cpp
Normal file
6
cpp/StochasticProcess.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @file StochasticProcess.cpp
|
||||||
|
* @brief @ref StochasticProcess translation unit (interface only).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "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
|
#ifndef QUANTENGINE_STOCHASTICPROCESS_HPP
|
||||||
#define QUANTENGINE_STOCHASTICPROCESS_HPP
|
#define QUANTENGINE_STOCHASTICPROCESS_HPP
|
||||||
#include "MarketData.hpp"
|
#include "MarketData.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stochastic model for the underlying, driven by @ref MarketData.
|
||||||
|
*/
|
||||||
class StochasticProcess {
|
class StochasticProcess {
|
||||||
public:
|
public:
|
||||||
StochasticProcess() = delete;
|
StochasticProcess() = delete;
|
||||||
6
cpp/VolatilitySurface.cpp
Normal file
6
cpp/VolatilitySurface.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @file VolatilitySurface.cpp
|
||||||
|
* @brief @ref VolatilitySurface translation unit (interface only).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "VolatilitySurface.hpp"
|
||||||
28
cpp/VolatilitySurface.hpp
Normal file
28
cpp/VolatilitySurface.hpp
Normal file
@@ -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 <vector>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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<double> K, std::vector<double> rho, std::vector<double> S, std::vector<double> T);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //QUANTENGINE_VOLATILITYSURFACE_HPP
|
||||||
6
cpp/YieldCurve.cpp
Normal file
6
cpp/YieldCurve.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @file YieldCurve.cpp
|
||||||
|
* @brief @ref YieldCurve translation unit (interface only).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "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
|
#ifndef QUANTENGINE_YIELDCURVE_HPP
|
||||||
#define QUANTENGINE_YIELDCURVE_HPP
|
#define QUANTENGINE_YIELDCURVE_HPP
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Risk-free rate term structure for discounting and risk-neutral drift.
|
||||||
|
*/
|
||||||
class YieldCurve {
|
class YieldCurve {
|
||||||
public:
|
public:
|
||||||
YieldCurve() = default;
|
YieldCurve() = default;
|
||||||
50
docs/Doxyfile
Normal file
50
docs/Doxyfile
Normal file
@@ -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
|
||||||
5
qengine/__init__.py
Normal file
5
qengine/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"""Qengine: quant pricing backend (native extension in qengine.qengine)."""
|
||||||
|
|
||||||
|
from .qengine import bs_price
|
||||||
|
|
||||||
|
__all__ = ["bs_price"]
|
||||||
65
scripts/test_qengine_bindings.py
Normal file
65
scripts/test_qengine_bindings.py
Normal file
@@ -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())
|
||||||
@@ -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)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by David Doebel on 05.03.2026.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "Exercise.hpp"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by David Doebel on 13.03.2026.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "NewtonSolver.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by David Doebel on 05.03.2026.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "PricingEngine.hpp"
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by David Doebel on 05.03.2026.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "StochasticProcess.hpp"
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by David Doebel on 06.03.2026.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "VolatilitySurface.hpp"
|
|
||||||
@@ -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
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by David Doebel on 06.03.2026.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "YieldCurve.hpp"
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include "BlackScholesClosedFormEngine.hpp"
|
||||||
#include "BlackScholesProcess.hpp"
|
#include "BlackScholesProcess.hpp"
|
||||||
#include "MonteCarloEngine.hpp"
|
#include "MonteCarloEngine.hpp"
|
||||||
#include "Instrument.hpp"
|
#include "Instrument.hpp"
|
||||||
@@ -51,3 +52,28 @@ TEST(BlackScholesProcess, ExpectedValue) {
|
|||||||
ASSERT_NEAR(callPrice, callGT, tol);
|
ASSERT_NEAR(callPrice, callGT, tol);
|
||||||
ASSERT_NEAR(putPrice, putGT, 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<FlatYieldCurve>(0.01),
|
||||||
|
std::make_shared<FlatVolatilitySurface>(0.2));
|
||||||
|
|
||||||
|
auto processCall = std::make_unique<BlackScholesProcess>(marketData);
|
||||||
|
auto processPut = std::make_unique<BlackScholesProcess>(marketData);
|
||||||
|
|
||||||
|
auto analyticCall = std::make_unique<BlackScholesClosedFormEngine>(std::move(processCall));
|
||||||
|
auto analyticPut = std::make_unique<BlackScholesClosedFormEngine>(std::move(processPut));
|
||||||
|
|
||||||
|
Instrument callInstr(T, std::make_unique<CallPayoff>(K), std::move(analyticCall));
|
||||||
|
Instrument putInstr(T, std::make_unique<PutPayoff>(K), std::move(analyticPut));
|
||||||
|
|
||||||
|
const double callGT = 8.4333186901;
|
||||||
|
const double putGT = 7.4383020650;
|
||||||
|
|
||||||
|
ASSERT_NEAR(callInstr.price(), callGT, 1e-9);
|
||||||
|
ASSERT_NEAR(putInstr.price(), putGT, 1e-9);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user