diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index a2cc776..cdb4f4a 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - name: Configure run: | - mkdir build + mkdir build cd build cmake .. diff --git a/CMakeLists.txt b/CMakeLists.txt index 46caaf2..5ff7f7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,9 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS "-O3 -march=native") find_package(Eigen3 REQUIRED) +#find_package(PostgreSQL REQUIRED) +#find_package(PkgConfig REQUIRED) +#pkg_check_modules(PQXX REQUIRED IMPORTED_TARGET libpqxx) add_subdirectory(src) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7eb1fa6..030c61a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,9 +25,17 @@ add_library(qengine Statistics.hpp BlackScholesProcess.cpp BlackScholesProcess.hpp + DBIngest.cpp + DBIngest.hpp + GaussSolver.cpp + GaussSolver.hpp ) target_include_directories(qengine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(qengine Eigen3::Eigen) \ No newline at end of file +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/DBIngest.cpp b/src/DBIngest.cpp new file mode 100644 index 0000000..08ec793 --- /dev/null +++ b/src/DBIngest.cpp @@ -0,0 +1,55 @@ +// +// Created by David Doebel on 13.03.2026. +// + +#include "DBIngest.hpp" + +#include + +// Queries +// Query for selecting the volatility surface parameters +std::string vol_surface_query = "" + + + + +// + + + + +bool DBIngest::connect() { + connection_ = pqxx::connection("dbname=options_db user=quant_user port = 5432 host = localhost password = strong_password" ); + + if(connection_.is_open()) { + std::cout << "Connected\n"; + return true; + } + std::cout << "Not connected\n"; + return false; +} + +bool DBIngest::disconnect() { + connection_.close(); +} + +bool DBIngest::update(VolatilitySurface &surface) { + std::string vol_surface_query = "SELECT c.strike, c.expiration_date, q.mid, u.price " + "FROM option_quotes q" + "JOIN option_contracts c " + "ON q.contract_id = c.id " + "JOIN underlying_prices u" + "ON u.underlying_id = c.underlying_id" + "WHERE q.timestamp = (" + "SELECT MAX(timestamp) FROM option_quotes" + ")"; + pqxx::work work(connection_); + pqxx::result result = work.exec(vol_surface_query); + for (auto row : result) { + std::cout << row[0] << " " << row[1] << " " << row[2] << " " << row[3] << std::endl; + } + +} + +bool DBIngest::update(YieldCurve &yield_curve) { +} diff --git a/src/DBIngest.hpp b/src/DBIngest.hpp new file mode 100644 index 0000000..07b230e --- /dev/null +++ b/src/DBIngest.hpp @@ -0,0 +1,24 @@ +// +// Created by David Doebel on 13.03.2026. +// + +#ifndef QUANTENGINE_DBINGEST_HPP +#define QUANTENGINE_DBINGEST_HPP + +#include + +#include "VolatilitySurface.hpp" +#include "YieldCurve.hpp" + +class DBIngest { + + bool connect(); + bool disconnect(); + bool update(VolatilitySurface& surface); + bool update(YieldCurve& yield_curve); +private: + pqxx::connection connection_; +}; + + +#endif //QUANTENGINE_DBINGEST_HPP \ No newline at end of file diff --git a/src/NewtonSolver.cpp b/src/NewtonSolver.cpp new file mode 100644 index 0000000..eeb97c2 --- /dev/null +++ b/src/NewtonSolver.cpp @@ -0,0 +1,7 @@ +// +// Created by David Doebel on 13.03.2026. +// + +#include "NewtonSolver.hpp" + + diff --git a/src/NewtonSolver.hpp b/src/NewtonSolver.hpp new file mode 100644 index 0000000..1e36680 --- /dev/null +++ b/src/NewtonSolver.hpp @@ -0,0 +1,26 @@ +// +// Created by David Doebel on 13.03.2026. +// + +#ifndef QUANTENGINE_GAUSSSOLVER_HPP +#define QUANTENGINE_GAUSSSOLVER_HPP + +#include + +class NewtonSolver { + template + bool solve(F&& func, DFinv&& dfinv,T x0 , double rtol, double atol) { + T x = x0; + int i = 0; + T increment; + do { + increment = dfinv(x) * func(x); + x -= increment; + ++i; + } while (i < 1000 && std::abs(increment)/ std::abs(x) > rtol && std::abs(increment) > atol); + + } +}; + + +#endif //QUANTENGINE_GAUSSSOLVER_HPP \ No newline at end of file diff --git a/src/data/ingestion/fred_data_ingestion.py b/src/data/ingestion/fred_data_ingestion.py new file mode 100644 index 0000000..bf9ef4c --- /dev/null +++ b/src/data/ingestion/fred_data_ingestion.py @@ -0,0 +1,4 @@ +from fredapi import Fred +fred = Fred(api_key='471be0178bfc20ce10bb93e3fcceee3b') +data = fred.get_series_latest_release('DTB3') +print(data.tail()) diff --git a/src/data/ingestion/ingest_ubs_comparison.py b/src/data/ingestion/ingest_ubs_comparison.py new file mode 100644 index 0000000..d1fa615 --- /dev/null +++ b/src/data/ingestion/ingest_ubs_comparison.py @@ -0,0 +1,74 @@ +from datetime import datetime, timedelta +import pandas as pd +import yfinance as yf +from sqlalchemy import create_engine + +# --- CONFIG --- +TICKERS = ["UBS", "^GSPC"] +DAYS_BACK = 21 # ~3 weeks +TABLE_NAME = "prices" + +DB_URI = "postgresql://quant_user:strong_password@localhost:5432/options_db" + + +def fetch_data(tickers, start_date, end_date): + data = yf.download( + tickers, + start=start_date, + end=end_date, + group_by="ticker", + auto_adjust=True, + progress=False + ) + return data + + +def transform_data(raw_data): + frames = [] + + for ticker in raw_data.columns.levels[0]: + df = raw_data[ticker].copy() + df["ticker"] = ticker + df = df.reset_index() + + # Keep only what we need + df = df[["Date", "ticker", "Close", "Volume"]] + + df.rename(columns={ + "Date": "date", + "Close": "close", + "Volume": "volume" + }, inplace=True) + + # Compute daily returns + df["return"] = df["close"].pct_change() + + frames.append(df) + + return pd.concat(frames, ignore_index=True) + + +def load_to_postgres(df, engine): + df.to_sql( + TABLE_NAME, + engine, + if_exists="append", + index=False + ) + + +def main(): + end_date = datetime.utcnow() + start_date = end_date - timedelta(days=DAYS_BACK) + + raw = fetch_data(TICKERS, start_date, end_date) + df = transform_data(raw) + + engine = create_engine(DB_URI) + load_to_postgres(df, engine) + + print("Ingestion complete.") + + +if __name__ == "__main__": + main() \ No newline at end of file