From 05435e438db31a4eee24bf7724e10c2f69641ca0 Mon Sep 17 00:00:00 2001 From: Ryan <16667079+mccaffers@users.noreply.github.com> Date: Sat, 11 Apr 2026 11:28:27 +0100 Subject: [PATCH 01/10] Added QuestDB tick streaming --- CMakeLists.txt | 15 ++++++-- documents/questdb.md | 5 +++ include/databaseConnection.hpp | 1 + include/models/priceData.hpp | 2 + include/sqlManager.hpp | 5 ++- scripts/build_dep.sh | 11 ++++++ scripts/run.sh | 8 ++++ source/CMakeLists.txt | 68 ++++++++++++++++++++++++++++++++++ source/databaseConnection.cpp | 45 ++++++++++++++++++++++ source/main.cpp | 39 ++++++++++--------- source/sqlManager.cpp | 10 +++-- 11 files changed, 184 insertions(+), 25 deletions(-) create mode 100644 documents/questdb.md create mode 100644 scripts/build_dep.sh create mode 100644 source/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b6c5634..f39c14e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,6 @@ project(BacktestingEngine) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) - # Configure libpqxx build set(PQXX_LIBRARIES_INSTALL ON) set(SKIP_BUILD_TEST ON) @@ -51,8 +50,18 @@ file(GLOB_RECURSE SOURCES "source/*.cpp") # Create a library of your project's code add_library(BacktestingEngineLib STATIC ${SOURCES}) -# Link against pqxx -target_link_libraries(BacktestingEngineLib pqxx) +# Replace find_package(OpenMP REQUIRED) with this: +if(APPLE) + set(OpenMP_C_FLAGS "-Xclang -fopenmp") + set(OpenMP_CXX_FLAGS "-Xclang -fopenmp") + set(OpenMP_C_LIB_NAMES "omp") + set(OpenMP_CXX_LIB_NAMES "omp") + set(OpenMP_omp_LIBRARY /opt/homebrew/opt/libomp/lib/libomp.dylib) + find_package(OpenMP REQUIRED) + target_include_directories(BacktestingEngineLib PRIVATE /opt/homebrew/opt/libomp/include) +endif() + +target_link_libraries(BacktestingEngineLib PUBLIC pqxx OpenMP::OpenMP_CXX) # Main executable add_executable(BacktestingEngine source/main.cpp) diff --git a/documents/questdb.md b/documents/questdb.md new file mode 100644 index 0000000..015d25c --- /dev/null +++ b/documents/questdb.md @@ -0,0 +1,5 @@ +Start QuestDB (on macOS) + +``` +JAVA_HOME="/opt/homebrew/opt/openjdk@17" sh $HOME/dev/questdb/questdb.sh start -d $HOME/dev/questdb/data +``` \ No newline at end of file diff --git a/include/databaseConnection.hpp b/include/databaseConnection.hpp index 1502ec2..b885761 100644 --- a/include/databaseConnection.hpp +++ b/include/databaseConnection.hpp @@ -21,6 +21,7 @@ class DatabaseConnection { void printResults(const std::vector& results) const; std::vector executeQuery(const std::string& query) const; + std::vector streamQuery(const std::string& query) const; const std::string& getConnectionString() const { return connection_string; diff --git a/include/models/priceData.hpp b/include/models/priceData.hpp index 4047ffd..cecdd1a 100644 --- a/include/models/priceData.hpp +++ b/include/models/priceData.hpp @@ -14,4 +14,6 @@ struct PriceData { // Constructor for easy creation PriceData(double v1, double v2, const std::chrono::system_clock::time_point& ts) : value1(v1), value2(v2), timestamp(ts) {} + + PriceData() : value1(0.0), value2(0.0), timestamp{} {} }; diff --git a/include/sqlManager.hpp b/include/sqlManager.hpp index 1f65287..62f1935 100644 --- a/include/sqlManager.hpp +++ b/include/sqlManager.hpp @@ -11,8 +11,9 @@ class SqlManager { public: - static std::vector getInitialPriceData(const DatabaseConnection& db); + static std::vector streamPriceData(const DatabaseConnection& db); static std::string getBaseQuery(); private: - static constexpr int DEFAULT_LIMIT = 1000; + static constexpr int LAST_MONTHS = 1; + static constexpr int STREAM_LIMIT = 200000; }; diff --git a/scripts/build_dep.sh b/scripts/build_dep.sh new file mode 100644 index 0000000..9afd5e6 --- /dev/null +++ b/scripts/build_dep.sh @@ -0,0 +1,11 @@ +cd ./external/libpqxx + +mkdir -p build +cd ./build + +export PATH="$(brew --prefix libpq)/bin:$PATH" +export PKG_CONFIG_PATH="$(brew --prefix libpq)/lib/pkgconfig:$PKG_CONFIG_PATH" +export PostgreSQL_ROOT="$(brew --prefix libpq)" + +cmake .. -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release +make \ No newline at end of file diff --git a/scripts/run.sh b/scripts/run.sh index fa3cd8d..f4b400c 100644 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -7,6 +7,10 @@ current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # source $current_dir/environment.sh - no longer necessary source $current_dir/clean.sh source $current_dir/build.sh +if [ $? -ne 0 ]; then + echo "Error: Build failed. Aborting." + exit 1 +fi # Debug: Check if the executable exists if [ -f "$BUILD_DIR/$EXECUTABLE_NAME" ]; then @@ -49,5 +53,9 @@ output=$(echo "$json" | base64) # Step 6: Run the tests for now (/executable) from the root directory # Passing two arguements, the destination of the QuestDB and the Strategy JSON (in base64) +start_time=$(date +%s%N) ./"$BUILD_DIR/$EXECUTABLE_NAME" localhost "$output" +end_time=$(date +%s%N) +elapsed=$(( (end_time - start_time) / 1000000 )) +echo "Execution time: ${elapsed}ms" diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt new file mode 100644 index 0000000..f39c14e --- /dev/null +++ b/source/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required(VERSION 3.30) + +# CMAKE_OSX_SYSROOT is a macOS-specific setting that specifies the SDK path. +# This is ignored on non-Apple platforms, so it's safe to include in cross-platform builds. +execute_process( + COMMAND xcrun --show-sdk-path + OUTPUT_VARIABLE CMAKE_OSX_SYSROOT + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +project(BacktestingEngine) + +# Set the C++ standard +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Configure libpqxx build +set(PQXX_LIBRARIES_INSTALL ON) +set(SKIP_BUILD_TEST ON) +set(SKIP_CONFIGURE_LIBPQXX OFF) + +# Disable warningsfor external libraries +set(PREV_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") +elseif(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /w") +endif() + +# Quiet CMAKE output +set(CMAKE_INSTALL_MESSAGE NEVER) +set(CMAKE_MESSAGE_LOG_LEVEL "WARNING") + +# Build libpqxx from source +add_subdirectory(external/libpqxx EXCLUDE_FROM_ALL) + +# Include directories +include_directories( + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/include/utilities + ${CMAKE_SOURCE_DIR}/include/models + ${CMAKE_SOURCE_DIR}/include/trading + ${CMAKE_SOURCE_DIR}/include/trading_definitions + ${CMAKE_SOURCE_DIR}/external +) + +# Collect all .cpp files in the src directory +file(GLOB_RECURSE SOURCES "source/*.cpp") + +# Create a library of your project's code +add_library(BacktestingEngineLib STATIC ${SOURCES}) + +# Replace find_package(OpenMP REQUIRED) with this: +if(APPLE) + set(OpenMP_C_FLAGS "-Xclang -fopenmp") + set(OpenMP_CXX_FLAGS "-Xclang -fopenmp") + set(OpenMP_C_LIB_NAMES "omp") + set(OpenMP_CXX_LIB_NAMES "omp") + set(OpenMP_omp_LIBRARY /opt/homebrew/opt/libomp/lib/libomp.dylib) + find_package(OpenMP REQUIRED) + target_include_directories(BacktestingEngineLib PRIVATE /opt/homebrew/opt/libomp/include) +endif() + +target_link_libraries(BacktestingEngineLib PUBLIC pqxx OpenMP::OpenMP_CXX) + +# Main executable +add_executable(BacktestingEngine source/main.cpp) +target_link_libraries(BacktestingEngine BacktestingEngineLib) diff --git a/source/databaseConnection.cpp b/source/databaseConnection.cpp index 292da31..d5b17f3 100644 --- a/source/databaseConnection.cpp +++ b/source/databaseConnection.cpp @@ -7,6 +7,31 @@ #include "databaseConnection.hpp" #include "base64.hpp" #include +#include +#include +#include +#include + +static std::chrono::system_clock::time_point fastParseTimestamp(const char* ts) { + int year, month, day, hour, min, sec, usec = 0; + std::sscanf(ts, "%4d-%2d-%2d %2d:%2d:%2d.%d", &year, &month, &day, &hour, &min, &sec, &usec); + + // Cache timegm per date — tick data is time-ordered so date changes rarely + static char cachedDate[11] = {}; + static time_t cachedEpoch = 0; + if (std::memcmp(ts, cachedDate, 10) != 0) { + std::memcpy(cachedDate, ts, 10); + std::tm tm = {}; + tm.tm_year = year - 1900; + tm.tm_mon = month - 1; + tm.tm_mday = day; + tm.tm_isdst = 0; + cachedEpoch = timegm(&tm); + } + + time_t t = cachedEpoch + hour * 3600 + min * 60 + sec; + return std::chrono::system_clock::from_time_t(t) + std::chrono::microseconds(usec); +} DatabaseConnection::DatabaseConnection(const std::string& endpoint, int port, const std::string& dbname, const std::string& user, @@ -56,6 +81,26 @@ std::vector DatabaseConnection::executeQuery(const std::string& query return results; } +std::vector DatabaseConnection::streamQuery(const std::string& query) const { + pqxx::connection conn(this->connection_string); + pqxx::nontransaction txn(conn); + pqxx::result result = txn.exec(query); + + std::vector results(result.size()); + + for (int i = 0; i < (int)result.size(); ++i) { + const auto& row = result[i]; + double value1, value2; + auto sv1 = row[0].view(); + auto sv2 = row[1].view(); + std::from_chars(sv1.data(), sv1.data() + sv1.size(), value1); + std::from_chars(sv2.data(), sv2.data() + sv2.size(), value2); + results[i] = PriceData(value1, value2, fastParseTimestamp(row[2].c_str())); + } + + return results; +} + // Example usage function to demonstrate how to work with the results void DatabaseConnection::printResults(const std::vector& results) const { for (const auto& data : results) { diff --git a/source/main.cpp b/source/main.cpp index 9fb01da..97b8985 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,6 +1,6 @@ // Backtesting Engine in C++ // -// (c) 2025 Ryan McCaffery | https://mccaffers.com +// (c) 2026 Ryan McCaffery | https://mccaffers.com // This code is licensed under MIT license (see LICENSE.txt for details) // --------------------------------------- @@ -26,33 +26,38 @@ using json = nlohmann::json; +// Entry point. Expects two command-line arguments: +// argv[1] — hostname/IP of the QuestDB instance +// argv[2] — Base64-encoded JSON strategy configuration int main(int argc, const char * argv[]) { - // Connect to QuestDb argv[1] DatabaseConnection db(argv[1], 8812, "qdb", "admin", "quest"); - // Load strategy from Base64 argv[2] JsonParser::parseConfigurationFromBase64(argv[2]); - std::vector priceData = SqlManager::getInitialPriceData(db); - - // Convert timestamp to readable format for debugging - auto timeT = std::chrono::system_clock::to_time_t(priceData[0].timestamp); - std::cout << "Timestamp: " << std::put_time(std::localtime(&timeT), "%Y-%m-%d %H:%M:%S") << std::endl; + std::vector ticks = SqlManager::streamPriceData(db); + printf("Total ticks streamed: %zu\n", ticks.size()); + + // print first tick + auto time_t = std::chrono::system_clock::to_time_t(ticks[0].timestamp); + struct tm tm = {}; + if (localtime_r(&time_t, &tm) == nullptr) { + std::cerr << "Error: failed to convert timestamp" << std::endl; + } + char buffer[20]; + std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); + printf("First tick: ask=%.4f, value2=%.4f timestamp=%s\n", ticks[0].value1, ticks[0].value2, buffer); auto tradeManager = TradeManager::getInstance(); - // Open a trade - std::string tradeId = tradeManager->openTrade(1.2345, 100000, true); - std::cout << "Opened trade: " << tradeId << std::endl; + // std::string tradeId = tradeManager->openTrade(1.2345, 100000, true); + // std::cout << "Opened trade: " << tradeId << std::endl; - // Review account - size_t openTrades = tradeManager->reviewAccount(); - std::cout << "Number of open trades: " << openTrades << std::endl; + // size_t openTrades = tradeManager->reviewAccount(); + // std::cout << "Number of open trades: " << openTrades << std::endl; - // Close trade - bool closed = tradeManager->closeTrade(tradeId); - std::cout << "Trade closed: " << (closed ? "yes" : "no") << std::endl; + // bool closed = tradeManager->closeTrade(tradeId); + // std::cout << "Trade closed: " << (closed ? "yes" : "no") << std::endl; return 0; diff --git a/source/sqlManager.cpp b/source/sqlManager.cpp index 12152cd..68391d7 100644 --- a/source/sqlManager.cpp +++ b/source/sqlManager.cpp @@ -4,11 +4,15 @@ // This code is licensed under MIT license (see LICENSE.txt for details) // --------------------------------------- #include "sqlManager.hpp" +#include +#include std::string SqlManager::getBaseQuery() { - return "SELECT * FROM EURUSD LIMIT " + std::to_string(DEFAULT_LIMIT) + ";"; + return "SELECT * FROM EURUSD WHERE timestamp >= dateadd('M', -" + std::to_string(LAST_MONTHS) + ", now()) LIMIT 40000000"; } -std::vector SqlManager::getInitialPriceData(const DatabaseConnection& db) { - return db.executeQuery(getBaseQuery()); +std::vector SqlManager::streamPriceData(const DatabaseConnection& db) { + std::string query = getBaseQuery(); + std::cout << "Executing query: " << query << std::endl; + return db.streamQuery(query); } From 16fb2c3800991a767ca35423ee77585bbe7f0f8b Mon Sep 17 00:00:00 2001 From: Ryan <16667079+mccaffers@users.noreply.github.com> Date: Sat, 11 Apr 2026 15:15:01 +0100 Subject: [PATCH 02/10] Refactoring tick flow --- include/models/priceData.hpp | 10 +++++----- include/sqlManager.hpp | 5 ++--- source/databaseConnection.cpp | 20 ++++++++++---------- source/main.cpp | 16 ++++++++-------- source/sqlManager.cpp | 8 ++++---- 5 files changed, 29 insertions(+), 30 deletions(-) diff --git a/include/models/priceData.hpp b/include/models/priceData.hpp index cecdd1a..09f52ed 100644 --- a/include/models/priceData.hpp +++ b/include/models/priceData.hpp @@ -7,13 +7,13 @@ #include struct PriceData { - double value1; - double value2; + double ask; + double bid; std::chrono::system_clock::time_point timestamp; // Constructor for easy creation - PriceData(double v1, double v2, const std::chrono::system_clock::time_point& ts) - : value1(v1), value2(v2), timestamp(ts) {} + PriceData(double ask, double bid, const std::chrono::system_clock::time_point& ts) + : ask(ask), bid(bid), timestamp(ts) {} - PriceData() : value1(0.0), value2(0.0), timestamp{} {} + PriceData() : ask(0.0), bid(0.0), timestamp{} {} }; diff --git a/include/sqlManager.hpp b/include/sqlManager.hpp index 62f1935..21a03b5 100644 --- a/include/sqlManager.hpp +++ b/include/sqlManager.hpp @@ -11,9 +11,8 @@ class SqlManager { public: - static std::vector streamPriceData(const DatabaseConnection& db); - static std::string getBaseQuery(); + static std::vector streamPriceData(const DatabaseConnection& db, int LAST_MONTHS = 1); + static std::string getBaseQuery(int LAST_MONTHS = 1); private: - static constexpr int LAST_MONTHS = 1; static constexpr int STREAM_LIMIT = 200000; }; diff --git a/source/databaseConnection.cpp b/source/databaseConnection.cpp index d5b17f3..37ef8d4 100644 --- a/source/databaseConnection.cpp +++ b/source/databaseConnection.cpp @@ -63,13 +63,13 @@ std::vector DatabaseConnection::executeQuery(const std::string& query // Convert results to PriceData objects for (const auto& row : result) { - double value1 = row[0].as(); - double value2 = row[1].as(); + double ask = row[0].as(); + double bid = row[1].as(); std::string timestamp_str = row[2].as(); auto timestamp = Utilities::parseTimestamp(timestamp_str); - results.emplace_back(value1, value2, timestamp); + results.emplace_back(ask, bid, timestamp); } txn.commit(); @@ -90,13 +90,13 @@ std::vector DatabaseConnection::streamQuery(const std::string& query) for (int i = 0; i < (int)result.size(); ++i) { const auto& row = result[i]; - double value1, value2; + double ask, bid; auto sv1 = row[0].view(); auto sv2 = row[1].view(); - std::from_chars(sv1.data(), sv1.data() + sv1.size(), value1); - std::from_chars(sv2.data(), sv2.data() + sv2.size(), value2); - results[i] = PriceData(value1, value2, fastParseTimestamp(row[2].c_str())); - } + std::from_chars(sv1.data(), sv1.data() + sv1.size(), ask); + std::from_chars(sv2.data(), sv2.data() + sv2.size(), bid); + results[i] = PriceData(ask, bid, fastParseTimestamp(row[2].c_str())); + } return results; } @@ -115,8 +115,8 @@ void DatabaseConnection::printResults(const std::vector& results) con ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); std::cout << std::fixed << std::setprecision(4) - << data.value1 << "\t" - << data.value2 << "\t" + << data.ask << "\t" + << data.bid << "\t" << ss.str() << std::endl; } } diff --git a/source/main.cpp b/source/main.cpp index 97b8985..2f1e4c3 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -35,7 +35,7 @@ int main(int argc, const char * argv[]) { JsonParser::parseConfigurationFromBase64(argv[2]); - std::vector ticks = SqlManager::streamPriceData(db); + std::vector ticks = SqlManager::streamPriceData(db, 1); printf("Total ticks streamed: %zu\n", ticks.size()); // print first tick @@ -46,18 +46,18 @@ int main(int argc, const char * argv[]) { } char buffer[20]; std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); - printf("First tick: ask=%.4f, value2=%.4f timestamp=%s\n", ticks[0].value1, ticks[0].value2, buffer); + printf("First tick: ask=%.4f, bid=%.4f timestamp=%s\n", ticks[0].ask, ticks[0].bid, buffer); auto tradeManager = TradeManager::getInstance(); - // std::string tradeId = tradeManager->openTrade(1.2345, 100000, true); - // std::cout << "Opened trade: " << tradeId << std::endl; + std::string tradeId = tradeManager->openTrade(ticks[0].ask, 100000, true); + std::cout << "Opened trade: " << tradeId << std::endl; - // size_t openTrades = tradeManager->reviewAccount(); - // std::cout << "Number of open trades: " << openTrades << std::endl; + size_t openTrades = tradeManager->reviewAccount(); + std::cout << "Number of open trades: " << openTrades << std::endl; - // bool closed = tradeManager->closeTrade(tradeId); - // std::cout << "Trade closed: " << (closed ? "yes" : "no") << std::endl; + bool closed = tradeManager->closeTrade(tradeId); + std::cout << "Trade closed: " << (closed ? "yes" : "no") << std::endl; return 0; diff --git a/source/sqlManager.cpp b/source/sqlManager.cpp index 68391d7..6d74f3d 100644 --- a/source/sqlManager.cpp +++ b/source/sqlManager.cpp @@ -7,12 +7,12 @@ #include #include -std::string SqlManager::getBaseQuery() { - return "SELECT * FROM EURUSD WHERE timestamp >= dateadd('M', -" + std::to_string(LAST_MONTHS) + ", now()) LIMIT 40000000"; +std::string SqlManager::getBaseQuery(int LAST_MONTHS) { + return "SELECT * FROM EURUSD WHERE timestamp >= dateadd('M', -" + std::to_string(LAST_MONTHS) + ", now()) LIMIT " + std::to_string(STREAM_LIMIT); } -std::vector SqlManager::streamPriceData(const DatabaseConnection& db) { - std::string query = getBaseQuery(); +std::vector SqlManager::streamPriceData(const DatabaseConnection& db, int LAST_MONTHS) { + std::string query = getBaseQuery(LAST_MONTHS); std::cout << "Executing query: " << query << std::endl; return db.streamQuery(query); } From 3ba4cec8d7e3f9de578cb221923c730167135e30 Mon Sep 17 00:00:00 2001 From: Ryan <16667079+mccaffers@users.noreply.github.com> Date: Sun, 12 Apr 2026 08:56:04 +0100 Subject: [PATCH 03/10] Looping around tick data from multiple symbols --- include/models/priceData.hpp | 8 +++-- include/operations.hpp | 15 ++++++++ include/sqlManager.hpp | 7 ++-- source/databaseConnection.cpp | 42 +++------------------- source/main.cpp | 25 +++---------- source/operations.cpp | 52 +++++++++++++++++++++++++++ source/sqlManager.cpp | 23 +++++++++--- source/{ => utilities}/jsonParser.cpp | 0 8 files changed, 101 insertions(+), 71 deletions(-) create mode 100644 include/operations.hpp create mode 100644 source/operations.cpp rename source/{ => utilities}/jsonParser.cpp (100%) diff --git a/include/models/priceData.hpp b/include/models/priceData.hpp index 09f52ed..23fe3a6 100644 --- a/include/models/priceData.hpp +++ b/include/models/priceData.hpp @@ -5,15 +5,17 @@ // --------------------------------------- #pragma once #include +#include struct PriceData { double ask; double bid; std::chrono::system_clock::time_point timestamp; + std::string symbol; // Constructor for easy creation - PriceData(double ask, double bid, const std::chrono::system_clock::time_point& ts) - : ask(ask), bid(bid), timestamp(ts) {} + PriceData(double ask, double bid, const std::chrono::system_clock::time_point& ts, const std::string& symbol) + : ask(ask), bid(bid), timestamp(ts), symbol(symbol) {} - PriceData() : ask(0.0), bid(0.0), timestamp{} {} + PriceData() : ask(0.0), bid(0.0), timestamp{}, symbol("") {} }; diff --git a/include/operations.hpp b/include/operations.hpp new file mode 100644 index 0000000..cdb7cec --- /dev/null +++ b/include/operations.hpp @@ -0,0 +1,15 @@ +// Backtesting Engine in C++ +// +// (c) 2026 Ryan McCaffery | https://mccaffers.com +// This code is licensed under MIT license (see LICENSE.txt for details) +// --------------------------------------- + +#pragma once +#include +#include "models/priceData.hpp" + +class Operations { + +public: + static void run(const std::vector& priceData); +}; diff --git a/include/sqlManager.hpp b/include/sqlManager.hpp index 21a03b5..24b70d0 100644 --- a/include/sqlManager.hpp +++ b/include/sqlManager.hpp @@ -11,8 +11,7 @@ class SqlManager { public: - static std::vector streamPriceData(const DatabaseConnection& db, int LAST_MONTHS = 1); - static std::string getBaseQuery(int LAST_MONTHS = 1); -private: - static constexpr int STREAM_LIMIT = 200000; + static std::vector streamPriceData(const DatabaseConnection& db, const std::vector& symbols, int LAST_MONTHS = 1); + static std::string getBaseQuery(const std::vector& symbols, int LAST_MONTHS = 1); + }; diff --git a/source/databaseConnection.cpp b/source/databaseConnection.cpp index 37ef8d4..ef15d38 100644 --- a/source/databaseConnection.cpp +++ b/source/databaseConnection.cpp @@ -46,41 +46,6 @@ DatabaseConnection::DatabaseConnection(const std::string& endpoint, int port, } -std::vector DatabaseConnection::executeQuery(const std::string& query) const { - std::vector results; - - try { - pqxx::connection conn(this->connection_string); - - if (!conn.is_open()) { - throw std::invalid_argument("Failed to open database connection"); - } - - std::cout << "Connected to database successfully!" << std::endl; - - pqxx::work txn(conn); - pqxx::result result = txn.exec(query); - - // Convert results to PriceData objects - for (const auto& row : result) { - double ask = row[0].as(); - double bid = row[1].as(); - std::string timestamp_str = row[2].as(); - - auto timestamp = Utilities::parseTimestamp(timestamp_str); - - results.emplace_back(ask, bid, timestamp); - } - - txn.commit(); - - } catch (const std::exception& e) { - std::cerr << "Error: " << e.what() << std::endl; - } - - return results; -} - std::vector DatabaseConnection::streamQuery(const std::string& query) const { pqxx::connection conn(this->connection_string); pqxx::nontransaction txn(conn); @@ -91,11 +56,12 @@ std::vector DatabaseConnection::streamQuery(const std::string& query) for (int i = 0; i < (int)result.size(); ++i) { const auto& row = result[i]; double ask, bid; - auto sv1 = row[0].view(); - auto sv2 = row[1].view(); + auto symbol = row[0].view(); + auto sv1 = row[1].view(); + auto sv2 = row[2].view(); std::from_chars(sv1.data(), sv1.data() + sv1.size(), ask); std::from_chars(sv2.data(), sv2.data() + sv2.size(), bid); - results[i] = PriceData(ask, bid, fastParseTimestamp(row[2].c_str())); + results[i] = PriceData(ask, bid, fastParseTimestamp(row[3].c_str()), std::string(symbol)); } return results; diff --git a/source/main.cpp b/source/main.cpp index 2f1e4c3..ec8d98a 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -23,6 +23,7 @@ #include "tradeManager.hpp" #include "jsonParser.hpp" #include "sqlManager.hpp" +#include "operations.hpp" using json = nlohmann::json; @@ -35,29 +36,11 @@ int main(int argc, const char * argv[]) { JsonParser::parseConfigurationFromBase64(argv[2]); - std::vector ticks = SqlManager::streamPriceData(db, 1); + std::vector symbols = {"AUSIDXAUD", "EURUSD"}; + std::vector ticks = SqlManager::streamPriceData(db, symbols, 1); printf("Total ticks streamed: %zu\n", ticks.size()); - // print first tick - auto time_t = std::chrono::system_clock::to_time_t(ticks[0].timestamp); - struct tm tm = {}; - if (localtime_r(&time_t, &tm) == nullptr) { - std::cerr << "Error: failed to convert timestamp" << std::endl; - } - char buffer[20]; - std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); - printf("First tick: ask=%.4f, bid=%.4f timestamp=%s\n", ticks[0].ask, ticks[0].bid, buffer); - - auto tradeManager = TradeManager::getInstance(); - - std::string tradeId = tradeManager->openTrade(ticks[0].ask, 100000, true); - std::cout << "Opened trade: " << tradeId << std::endl; - - size_t openTrades = tradeManager->reviewAccount(); - std::cout << "Number of open trades: " << openTrades << std::endl; - - bool closed = tradeManager->closeTrade(tradeId); - std::cout << "Trade closed: " << (closed ? "yes" : "no") << std::endl; + Operations::run(ticks); return 0; diff --git a/source/operations.cpp b/source/operations.cpp new file mode 100644 index 0000000..e3cf9cd --- /dev/null +++ b/source/operations.cpp @@ -0,0 +1,52 @@ +// Backtesting Engine in C++ +// +// (c) 2026 Ryan McCaffery | https://mccaffers.com +// This code is licensed under MIT license (see LICENSE.txt for details) +// --------------------------------------- + +#include "operations.hpp" +// std headers +#include +#include +#include +#include +#include +#include "tradeManager.hpp" + +void Operations::run(const std::vector& ticks) { + + // Loop aroudn every tick + // Example output: + // symbol=AUSIDXAUD, ask=8602.4000, bid=8599.4000 timestamp=2026-03-12 18:39:01.076 + // symbol=AUSIDXAUD, ask=8602.9000, bid=8599.9000 timestamp=2026-03-12 18:39:01.584 + // symbol=EURUSD, ask=1.1513, bid=1.1512 timestamp=2026-03-12 18:39:01.644 + // symbol=AUSIDXAUD, ask=8602.4000, bid=8599.4000 timestamp=2026-03-12 18:39:01.770 + // symbol=AUSIDXAUD, ask=8601.9000, bid=8598.9000 timestamp=2026-03-12 18:39:01.982 + + for (const auto& tick : ticks) { + // (void)tick; + + // print first tick + auto time_t = std::chrono::system_clock::to_time_t(tick.timestamp); + struct tm tm = {}; + if (localtime_r(&time_t, &tm) == nullptr) { + std::cerr << "Error: failed to convert timestamp" << std::endl; + } + char buffer[20]; + std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); + auto ms = std::chrono::duration_cast(tick.timestamp.time_since_epoch()) % 1000; + printf("symbol=%s, ask=%.4f, bid=%.4f timestamp=%s.%03lld\n", tick.symbol.c_str(), tick.ask, tick.bid, buffer, + static_cast(ms.count())); + } + + auto tradeManager = TradeManager::getInstance(); + + // std::string tradeId = tradeManager->openTrade(ticks[0].ask, 100000, true); + // std::cout << "Opened trade: " << tradeId << std::endl; + + // size_t openTrades = tradeManager->reviewAccount(); + // std::cout << "Number of open trades: " << openTrades << std::endl; + + // bool closed = tradeManager->closeTrade(tradeId); + // std::cout << "Trade closed: " << (closed ? "yes" : "no") << std::endl; +} diff --git a/source/sqlManager.cpp b/source/sqlManager.cpp index 6d74f3d..37542f9 100644 --- a/source/sqlManager.cpp +++ b/source/sqlManager.cpp @@ -1,18 +1,31 @@ // Backtesting Engine in C++ // -// (c) 2025 Ryan McCaffery | https://mccaffers.com +// (c) 2026 Ryan McCaffery | https://mccaffers.com // This code is licensed under MIT license (see LICENSE.txt for details) // --------------------------------------- #include "sqlManager.hpp" #include #include -std::string SqlManager::getBaseQuery(int LAST_MONTHS) { - return "SELECT * FROM EURUSD WHERE timestamp >= dateadd('M', -" + std::to_string(LAST_MONTHS) + ", now()) LIMIT " + std::to_string(STREAM_LIMIT); +std::string SqlManager::getBaseQuery(const std::vector& symbols, int LAST_MONTHS) { + if (symbols.empty()) { + return ""; + } + + std::string query; + for (size_t i = 0; i < symbols.size(); ++i) { + if (i > 0) { + query += " UNION ALL "; + } + query += "SELECT '" + symbols[i] + "' as symbol, * FROM '" + symbols[i] + "' WHERE timestamp >= dateadd('M', -" + std::to_string(LAST_MONTHS) + ", now())"; + } + query += " ORDER BY timestamp"; + + return query; } -std::vector SqlManager::streamPriceData(const DatabaseConnection& db, int LAST_MONTHS) { - std::string query = getBaseQuery(LAST_MONTHS); +std::vector SqlManager::streamPriceData(const DatabaseConnection& db, const std::vector& symbols, int LAST_MONTHS) { + std::string query = getBaseQuery(symbols, LAST_MONTHS); std::cout << "Executing query: " << query << std::endl; return db.streamQuery(query); } diff --git a/source/jsonParser.cpp b/source/utilities/jsonParser.cpp similarity index 100% rename from source/jsonParser.cpp rename to source/utilities/jsonParser.cpp From 46e720dc9ef26bd5da49180dad1cd5686e62249b Mon Sep 17 00:00:00 2001 From: mccaffers <16667079+mccaffers@users.noreply.github.com> Date: Sun, 12 Apr 2026 09:37:55 +0100 Subject: [PATCH 04/10] Update source/CMakeLists.txt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- source/CMakeLists.txt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index f39c14e..ce98442 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,12 +1,14 @@ cmake_minimum_required(VERSION 3.30) # CMAKE_OSX_SYSROOT is a macOS-specific setting that specifies the SDK path. -# This is ignored on non-Apple platforms, so it's safe to include in cross-platform builds. -execute_process( - COMMAND xcrun --show-sdk-path - OUTPUT_VARIABLE CMAKE_OSX_SYSROOT - OUTPUT_STRIP_TRAILING_WHITESPACE -) +# Only query and set it on Apple platforms, where xcrun is expected to exist. +if(APPLE) + execute_process( + COMMAND xcrun --show-sdk-path + OUTPUT_VARIABLE CMAKE_OSX_SYSROOT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endif() project(BacktestingEngine) From 61309bd91ab0dc099c640dfd4b872fd4e96ee543 Mon Sep 17 00:00:00 2001 From: mccaffers <16667079+mccaffers@users.noreply.github.com> Date: Sun, 12 Apr 2026 09:41:07 +0100 Subject: [PATCH 05/10] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CMakeLists.txt | 4 ++-- include/databaseConnection.hpp | 1 - scripts/run.sh | 5 ++--- source/databaseConnection.cpp | 13 ++++++++----- source/main.cpp | 4 ++++ source/operations.cpp | 4 +++- source/sqlManager.cpp | 1 + 7 files changed, 20 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f39c14e..ad2c316 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,17 +50,17 @@ file(GLOB_RECURSE SOURCES "source/*.cpp") # Create a library of your project's code add_library(BacktestingEngineLib STATIC ${SOURCES}) -# Replace find_package(OpenMP REQUIRED) with this: +# Configure OpenMP. On Apple, provide Homebrew libomp hints before discovery. if(APPLE) set(OpenMP_C_FLAGS "-Xclang -fopenmp") set(OpenMP_CXX_FLAGS "-Xclang -fopenmp") set(OpenMP_C_LIB_NAMES "omp") set(OpenMP_CXX_LIB_NAMES "omp") set(OpenMP_omp_LIBRARY /opt/homebrew/opt/libomp/lib/libomp.dylib) - find_package(OpenMP REQUIRED) target_include_directories(BacktestingEngineLib PRIVATE /opt/homebrew/opt/libomp/include) endif() +find_package(OpenMP REQUIRED) target_link_libraries(BacktestingEngineLib PUBLIC pqxx OpenMP::OpenMP_CXX) # Main executable diff --git a/include/databaseConnection.hpp b/include/databaseConnection.hpp index b885761..1f81d45 100644 --- a/include/databaseConnection.hpp +++ b/include/databaseConnection.hpp @@ -20,7 +20,6 @@ class DatabaseConnection { const std::string& password = ""); void printResults(const std::vector& results) const; - std::vector executeQuery(const std::string& query) const; std::vector streamQuery(const std::string& query) const; const std::string& getConnectionString() const { diff --git a/scripts/run.sh b/scripts/run.sh index f4b400c..c92878e 100644 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -5,9 +5,8 @@ current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Build the source code # source $current_dir/environment.sh - no longer necessary -source $current_dir/clean.sh -source $current_dir/build.sh -if [ $? -ne 0 ]; then +source "$current_dir/clean.sh" +if ! bash "$current_dir/build.sh"; then echo "Error: Build failed. Aborting." exit 1 fi diff --git a/source/databaseConnection.cpp b/source/databaseConnection.cpp index ef15d38..4a34e2a 100644 --- a/source/databaseConnection.cpp +++ b/source/databaseConnection.cpp @@ -9,12 +9,15 @@ #include #include #include -#include -#include +#include static std::chrono::system_clock::time_point fastParseTimestamp(const char* ts) { - int year, month, day, hour, min, sec, usec = 0; - std::sscanf(ts, "%4d-%2d-%2d %2d:%2d:%2d.%d", &year, &month, &day, &hour, &min, &sec, &usec); + int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, usec = 0; + const int parsedFields = + std::sscanf(ts, "%4d-%2d-%2d %2d:%2d:%2d.%d", &year, &month, &day, &hour, &min, &sec, &usec); + if (parsedFields != 6 && parsedFields != 7) { + throw std::runtime_error("Invalid timestamp format"); + } // Cache timegm per date — tick data is time-ordered so date changes rarely static char cachedDate[11] = {}; @@ -53,7 +56,7 @@ std::vector DatabaseConnection::streamQuery(const std::string& query) std::vector results(result.size()); - for (int i = 0; i < (int)result.size(); ++i) { + for (std::size_t i = 0; i < result.size(); ++i) { const auto& row = result[i]; double ask, bid; auto symbol = row[0].view(); diff --git a/source/main.cpp b/source/main.cpp index ec8d98a..3934e78 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -32,6 +32,10 @@ using json = nlohmann::json; // argv[2] — Base64-encoded JSON strategy configuration int main(int argc, const char * argv[]) { + if (argc < 3) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } DatabaseConnection db(argv[1], 8812, "qdb", "admin", "quest"); JsonParser::parseConfigurationFromBase64(argv[2]); diff --git a/source/operations.cpp b/source/operations.cpp index e3cf9cd..7aae2d1 100644 --- a/source/operations.cpp +++ b/source/operations.cpp @@ -11,11 +11,13 @@ #include #include #include +#include +#include #include "tradeManager.hpp" void Operations::run(const std::vector& ticks) { - // Loop aroudn every tick + // Loop around every tick // Example output: // symbol=AUSIDXAUD, ask=8602.4000, bid=8599.4000 timestamp=2026-03-12 18:39:01.076 // symbol=AUSIDXAUD, ask=8602.9000, bid=8599.9000 timestamp=2026-03-12 18:39:01.584 diff --git a/source/sqlManager.cpp b/source/sqlManager.cpp index 37542f9..9de55ce 100644 --- a/source/sqlManager.cpp +++ b/source/sqlManager.cpp @@ -4,6 +4,7 @@ // This code is licensed under MIT license (see LICENSE.txt for details) // --------------------------------------- #include "sqlManager.hpp" +#include #include #include From 48a9a99d5c5b0eeb435639ac4b8f77d5e2f1fbcd Mon Sep 17 00:00:00 2001 From: Ryan <16667079+mccaffers@users.noreply.github.com> Date: Sun, 12 Apr 2026 09:47:21 +0100 Subject: [PATCH 06/10] Refactoring --- .../project.pbxproj | 18 +++++++++++++++--- include/models/priceData.hpp | 2 +- include/sqlManager.hpp | 2 +- source/databaseConnection.cpp | 2 +- source/utilities/jsonParser.cpp | 2 +- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/backtesting-engine-cpp.xcodeproj/project.pbxproj b/backtesting-engine-cpp.xcodeproj/project.pbxproj index e6f732a..8e066cd 100644 --- a/backtesting-engine-cpp.xcodeproj/project.pbxproj +++ b/backtesting-engine-cpp.xcodeproj/project.pbxproj @@ -29,6 +29,8 @@ 94674B8E2D533E7800973137 /* trade.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94674B8B2D533E7800973137 /* trade.cpp */; }; 9470B5A42C8C5AD0007D9CC6 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9470B5A32C8C5AD0007D9CC6 /* main.cpp */; }; 9470B5B62C8C5BFD007D9CC6 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9470B5A32C8C5AD0007D9CC6 /* main.cpp */; }; + 94724A832F8B92C10029B940 /* operations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94724A822F8B92C10029B940 /* operations.cpp */; }; + 94724A842F8B92C10029B940 /* operations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94724A822F8B92C10029B940 /* operations.cpp */; }; 94CD8B992D2DCDD800041BBA /* libpqxx-7.10.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 94CD8B982D2DCDD800041BBA /* libpqxx-7.10.a */; }; 94CD8B9C2D2DD02A00041BBA /* libpqxx-7.10.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 94CD8B9A2D2DCF6E00041BBA /* libpqxx-7.10.a */; }; 94CD8BA02D2E8CE500041BBA /* databaseConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */; }; @@ -89,6 +91,8 @@ 9470B5A12C8C5AD0007D9CC6 /* source */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = source; sourceTree = BUILT_PRODUCTS_DIR; }; 9470B5A32C8C5AD0007D9CC6 /* main.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; 9470B5AC2C8C5B99007D9CC6 /* tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 94724A822F8B92C10029B940 /* operations.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = operations.cpp; sourceTree = ""; }; + 94724A852F8B92E30029B940 /* operations.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = operations.hpp; sourceTree = ""; }; 948A9CCD2C906A5600E23669 /* CONVENTIONS.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONVENTIONS.md; sourceTree = ""; }; 948A9CED2C906AFE00E23669 /* 2020.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = 2020.csv; sourceTree = ""; }; 94BBA4512D2EA2640010E04D /* build.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build.sh; sourceTree = ""; }; @@ -1302,6 +1306,7 @@ isa = PBXGroup; children = ( 94280BA22D2FC00200F1CF56 /* base64.cpp */, + 943398232D57E53400287A2D /* jsonParser.cpp */, ); path = utilities; sourceTree = ""; @@ -1408,8 +1413,8 @@ 940A61112C92CE210083FEB8 /* configManager.cpp */, 940A61152C92CE960083FEB8 /* serviceA.cpp */, 94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */, - 943398232D57E53400287A2D /* jsonParser.cpp */, 941408AD2D59F93F000ED1F9 /* sqlManager.cpp */, + 94724A822F8B92C10029B940 /* operations.cpp */, ); path = source; sourceTree = ""; @@ -3515,15 +3520,16 @@ 94DE4F772C8C3E7C00FE48FF /* include */ = { isa = PBXGroup; children = ( - 941408B02D59F954000ED1F9 /* sqlManager.hpp */, - 943398222D57E52900287A2D /* jsonParser.hpp */, 94674B842D533B2F00973137 /* trading */, 942966D72D48E84100532862 /* models */, 94B8C7932D3D770800E17EB6 /* utilities */, 941B548F2D3BBA3B00E3BF64 /* trading_definitions */, 941B549C2D3BBFB900E3BF64 /* trading_definitions.hpp */, 940A61162C92CE960083FEB8 /* serviceA.hpp */, + 943398222D57E52900287A2D /* jsonParser.hpp */, 940A61122C92CE210083FEB8 /* configManager.hpp */, + 941408B02D59F954000ED1F9 /* sqlManager.hpp */, + 94724A852F8B92E30029B940 /* operations.hpp */, 94CD8B9E2D2E8CE500041BBA /* databaseConnection.hpp */, ); path = include; @@ -3631,6 +3637,7 @@ 94674B872D533B4000973137 /* tradeManager.cpp in Sources */, 94CD8BA02D2E8CE500041BBA /* databaseConnection.cpp in Sources */, 940A61132C92CE210083FEB8 /* configManager.cpp in Sources */, + 94724A842F8B92C10029B940 /* operations.cpp in Sources */, 940A61172C92CE960083FEB8 /* serviceA.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3646,6 +3653,7 @@ 94674B8D2D533E7800973137 /* trade.cpp in Sources */, 941B549A2D3BBADE00E3BF64 /* trading_definitions_json.cpp in Sources */, 94674B8A2D533BDA00973137 /* tradeManager.mm in Sources */, + 94724A832F8B92C10029B940 /* operations.cpp in Sources */, 940A61182C92CE960083FEB8 /* serviceA.cpp in Sources */, 94674B882D533B4000973137 /* tradeManager.cpp in Sources */, 943398272D57E54000287A2D /* jsonParser.mm in Sources */, @@ -3788,6 +3796,7 @@ "\"$(SRCROOT)/external/libpqxx/build/src\"", "/opt/homebrew/Cellar/postgresql@18/18.3/lib/postgresql", ); + MACOSX_DEPLOYMENT_TARGET = 26.0; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3810,6 +3819,7 @@ "\"$(SRCROOT)/external/libpqxx/build/src\"", "/opt/homebrew/Cellar/postgresql@18/18.3/lib/postgresql", ); + MACOSX_DEPLOYMENT_TARGET = 26.0; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3832,6 +3842,7 @@ "\"$(SRCROOT)/external/libpqxx/build/src\"", "/opt/homebrew/Cellar/postgresql@18/18.3/lib/postgresql", ); + MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.mccaffers.tests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3854,6 +3865,7 @@ "\"$(SRCROOT)/external/libpqxx/build/src\"", "/opt/homebrew/Cellar/postgresql@18/18.3/lib/postgresql", ); + MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.mccaffers.tests; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/include/models/priceData.hpp b/include/models/priceData.hpp index 23fe3a6..c57a2ff 100644 --- a/include/models/priceData.hpp +++ b/include/models/priceData.hpp @@ -1,6 +1,6 @@ // Backtesting Engine in C++ // -// (c) 2025 Ryan McCaffery | https://mccaffers.com +// (c) 2026 Ryan McCaffery | https://mccaffers.com // This code is licensed under MIT license (see LICENSE.txt for details) // --------------------------------------- #pragma once diff --git a/include/sqlManager.hpp b/include/sqlManager.hpp index 24b70d0..0eee8d9 100644 --- a/include/sqlManager.hpp +++ b/include/sqlManager.hpp @@ -1,6 +1,6 @@ // Backtesting Engine in C++ // -// (c) 2025 Ryan McCaffery | https://mccaffers.com +// (c) 2026 Ryan McCaffery | https://mccaffers.com // This code is licensed under MIT license (see LICENSE.txt for details) // --------------------------------------- #pragma once diff --git a/source/databaseConnection.cpp b/source/databaseConnection.cpp index ef15d38..d382e90 100644 --- a/source/databaseConnection.cpp +++ b/source/databaseConnection.cpp @@ -1,6 +1,6 @@ // Backtesting Engine in C++ // -// (c) 2025 Ryan McCaffery | https://mccaffers.com +// (c) 2026 Ryan McCaffery | https://mccaffers.com // This code is licensed under MIT license (see LICENSE.txt for details) // --------------------------------------- diff --git a/source/utilities/jsonParser.cpp b/source/utilities/jsonParser.cpp index 648ab06..5c6c630 100644 --- a/source/utilities/jsonParser.cpp +++ b/source/utilities/jsonParser.cpp @@ -1,6 +1,6 @@ // Backtesting Engine in C++ // -// (c) 2025 Ryan McCaffery | https://mccaffers.com +// (c) 2026 Ryan McCaffery | https://mccaffers.com // This code is licensed under MIT license (see LICENSE.txt for details) // --------------------------------------- From 85be15be1681042453cfd3c9bd4cfdd2421eb128 Mon Sep 17 00:00:00 2001 From: Ryan <16667079+mccaffers@users.noreply.github.com> Date: Sun, 12 Apr 2026 09:53:58 +0100 Subject: [PATCH 07/10] fix run script, shell scopping behaviour was putting the build script in it's own child process --- scripts/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run.sh b/scripts/run.sh index c92878e..cf0baf1 100644 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -6,7 +6,7 @@ current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Build the source code # source $current_dir/environment.sh - no longer necessary source "$current_dir/clean.sh" -if ! bash "$current_dir/build.sh"; then +if ! source "$current_dir/build.sh"; then echo "Error: Build failed. Aborting." exit 1 fi From cbc033d489523f2b591d40234da5a66f41253222 Mon Sep 17 00:00:00 2001 From: Ryan <16667079+mccaffers@users.noreply.github.com> Date: Sun, 12 Apr 2026 10:16:51 +0100 Subject: [PATCH 08/10] Update build os to target macos-latest --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 6897994..fdbe154 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -17,7 +17,7 @@ jobs: build: name: Build & Test - runs-on: macos-14 # Use macOS 14 (Sonoma) runner + runs-on: macos-latest steps: # Step 1: Check out the repository code From bb24f067a8cf0038da2b3e3c57cab345fffb3e3a Mon Sep 17 00:00:00 2001 From: Ryan <16667079+mccaffers@users.noreply.github.com> Date: Sun, 12 Apr 2026 10:23:18 +0100 Subject: [PATCH 09/10] Update build os to target macos-latest --- .github/workflows/sonarcloud.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index fdbe154..b219e3f 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -17,7 +17,7 @@ jobs: build: name: Build & Test - runs-on: macos-latest + runs-on: macos-26.2 steps: # Step 1: Check out the repository code @@ -40,7 +40,7 @@ jobs: # Step 5: Configure Xcode version - name: Select Xcode version run: | - sudo xcode-select -switch /Applications/Xcode_15.2.app + sudo xcode-select -switch /Applications/Xcode_26.2.app /usr/bin/xcodebuild -version # Run XCode tests with specific configurations: # - Builds and runs the test suite From 27f82f1fe7d217c080eff44ab50f71858be9be7f Mon Sep 17 00:00:00 2001 From: Ryan <16667079+mccaffers@users.noreply.github.com> Date: Sun, 12 Apr 2026 20:22:59 +0100 Subject: [PATCH 10/10] Updating github workflow os --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index b219e3f..4004505 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -17,7 +17,7 @@ jobs: build: name: Build & Test - runs-on: macos-26.2 + runs-on: macos-26 steps: # Step 1: Check out the repository code