Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/sonarcloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:

build:
name: Build & Test
runs-on: macos-14 # Use macOS 14 (Sonoma) runner
runs-on: macos-26

steps:
# Step 1: Check out the repository code
Expand All @@ -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
Expand Down
15 changes: 12 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
# 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)
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
add_executable(BacktestingEngine source/main.cpp)
Expand Down
18 changes: 15 additions & 3 deletions backtesting-engine-cpp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 = "<group>"; };
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 = "<group>"; };
94724A852F8B92E30029B940 /* operations.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = operations.hpp; sourceTree = "<group>"; };
948A9CCD2C906A5600E23669 /* CONVENTIONS.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONVENTIONS.md; sourceTree = "<group>"; };
948A9CED2C906AFE00E23669 /* 2020.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = 2020.csv; sourceTree = "<group>"; };
94BBA4512D2EA2640010E04D /* build.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build.sh; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1302,6 +1306,7 @@
isa = PBXGroup;
children = (
94280BA22D2FC00200F1CF56 /* base64.cpp */,
943398232D57E53400287A2D /* jsonParser.cpp */,
);
path = utilities;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 = "<group>";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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 */,
Expand Down Expand Up @@ -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)";
Expand All @@ -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)";
Expand All @@ -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)";
Expand All @@ -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)";
Expand Down
5 changes: 5 additions & 0 deletions documents/questdb.md
Original file line number Diff line number Diff line change
@@ -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
```
2 changes: 1 addition & 1 deletion include/databaseConnection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class DatabaseConnection {
const std::string& password = "");

void printResults(const std::vector<PriceData>& results) const;
std::vector<PriceData> executeQuery(const std::string& query) const;
std::vector<PriceData> streamQuery(const std::string& query) const;

const std::string& getConnectionString() const {
return connection_string;
Expand Down
14 changes: 9 additions & 5 deletions include/models/priceData.hpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
// 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
#include <chrono>
#include <string>

struct PriceData {
double value1;
double value2;
double ask;
double bid;
std::chrono::system_clock::time_point timestamp;
std::string symbol;

// 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, const std::string& symbol)
: ask(ask), bid(bid), timestamp(ts), symbol(symbol) {}

PriceData() : ask(0.0), bid(0.0), timestamp{}, symbol("") {}
};
15 changes: 15 additions & 0 deletions include/operations.hpp
Original file line number Diff line number Diff line change
@@ -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 <vector>
#include "models/priceData.hpp"

class Operations {

public:
static void run(const std::vector<PriceData>& priceData);
};
9 changes: 4 additions & 5 deletions include/sqlManager.hpp
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,8 +11,7 @@

class SqlManager {
public:
static std::vector<PriceData> getInitialPriceData(const DatabaseConnection& db);
static std::string getBaseQuery();
private:
static constexpr int DEFAULT_LIMIT = 1000;
static std::vector<PriceData> streamPriceData(const DatabaseConnection& db, const std::vector<std::string>& symbols, int LAST_MONTHS = 1);
static std::string getBaseQuery(const std::vector<std::string>& symbols, int LAST_MONTHS = 1);

};
11 changes: 11 additions & 0 deletions scripts/build_dep.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
cd ./external/libpqxx

mkdir -p build
Comment on lines +1 to +3
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script is missing a shebang (e.g., #!/bin/bash or #!/usr/bin/env bash). As-is, running it directly (instead of sh scripts/build_dep.sh) may fail depending on permissions/default shell. Add a shebang and consider set -euo pipefail so dependency builds fail fast.

Copilot uses AI. Check for mistakes.
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
11 changes: 9 additions & 2 deletions scripts/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ 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
source "$current_dir/clean.sh"
if ! source "$current_dir/build.sh"; then
echo "Error: Build failed. Aborting."
exit 1
fi

# Debug: Check if the executable exists
if [ -f "$BUILD_DIR/$EXECUTABLE_NAME" ]; then
Expand Down Expand Up @@ -49,5 +52,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"

70 changes: 70 additions & 0 deletions source/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
cmake_minimum_required(VERSION 3.30)

# CMAKE_OSX_SYSROOT is a macOS-specific setting that specifies the SDK path.
# 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)

# 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)

Comment on lines +56 to +67
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CMake file links OpenMP::OpenMP_CXX unconditionally, but find_package(OpenMP REQUIRED) is only called inside if(APPLE). On non-Apple platforms this will fail because the imported target won’t exist. Consider calling find_package(OpenMP REQUIRED) for all platforms (keeping only the Apple-specific hints in the if(APPLE) block) or conditionally linking OpenMP when found.

Copilot uses AI. Check for mistakes.
# Main executable
add_executable(BacktestingEngine source/main.cpp)
target_link_libraries(BacktestingEngine BacktestingEngineLib)
Loading
Loading