This commit is contained in:
@@ -5,6 +5,9 @@ set(CMAKE_CXX_STANDARD 20)
|
|||||||
set(CMAKE_CXX_FLAGS "-O3 -march=native")
|
set(CMAKE_CXX_FLAGS "-O3 -march=native")
|
||||||
|
|
||||||
find_package(Eigen3 REQUIRED)
|
find_package(Eigen3 REQUIRED)
|
||||||
|
#find_package(PostgreSQL REQUIRED)
|
||||||
|
#find_package(PkgConfig REQUIRED)
|
||||||
|
#pkg_check_modules(PQXX REQUIRED IMPORTED_TARGET libpqxx)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,17 @@ add_library(qengine
|
|||||||
Statistics.hpp
|
Statistics.hpp
|
||||||
BlackScholesProcess.cpp
|
BlackScholesProcess.cpp
|
||||||
BlackScholesProcess.hpp
|
BlackScholesProcess.hpp
|
||||||
|
DBIngest.cpp
|
||||||
|
DBIngest.hpp
|
||||||
|
GaussSolver.cpp
|
||||||
|
GaussSolver.hpp
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(qengine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
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 Eigen3::Eigen)
|
||||||
|
target_link_libraries(qengine pqxx pq)
|
||||||
55
src/DBIngest.cpp
Normal file
55
src/DBIngest.cpp
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// Created by David Doebel on 13.03.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "DBIngest.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
}
|
||||||
24
src/DBIngest.hpp
Normal file
24
src/DBIngest.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// Created by David Doebel on 13.03.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef QUANTENGINE_DBINGEST_HPP
|
||||||
|
#define QUANTENGINE_DBINGEST_HPP
|
||||||
|
|
||||||
|
#include <pqxx/pqxx>
|
||||||
|
|
||||||
|
#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
|
||||||
7
src/NewtonSolver.cpp
Normal file
7
src/NewtonSolver.cpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//
|
||||||
|
// Created by David Doebel on 13.03.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "NewtonSolver.hpp"
|
||||||
|
|
||||||
|
|
||||||
26
src/NewtonSolver.hpp
Normal file
26
src/NewtonSolver.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// Created by David Doebel on 13.03.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef QUANTENGINE_GAUSSSOLVER_HPP
|
||||||
|
#define QUANTENGINE_GAUSSSOLVER_HPP
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class NewtonSolver {
|
||||||
|
template<typename F, typename DFinv, typename T>
|
||||||
|
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
|
||||||
4
src/data/ingestion/fred_data_ingestion.py
Normal file
4
src/data/ingestion/fred_data_ingestion.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from fredapi import Fred
|
||||||
|
fred = Fred(api_key='471be0178bfc20ce10bb93e3fcceee3b')
|
||||||
|
data = fred.get_series_latest_release('DTB3')
|
||||||
|
print(data.tail())
|
||||||
74
src/data/ingestion/ingest_ubs_comparison.py
Normal file
74
src/data/ingestion/ingest_ubs_comparison.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user