Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
05435e4
Added QuestDB tick streaming
mccaffers Apr 11, 2026
16fb2c3
Refactoring tick flow
mccaffers Apr 11, 2026
3ba4cec
Looping around tick data from multiple symbols
mccaffers Apr 12, 2026
46e720d
Update source/CMakeLists.txt
mccaffers Apr 12, 2026
61309bd
Apply suggestions from code review
mccaffers Apr 12, 2026
48a9a99
Refactoring
mccaffers Apr 12, 2026
fab3106
Merge branch '10-create-end-to-end-trade-management-logic' of github.…
mccaffers Apr 12, 2026
85be15b
fix run script, shell scopping behaviour was putting the build script…
mccaffers Apr 12, 2026
cbc033d
Update build os to target macos-latest
mccaffers Apr 12, 2026
bb24f06
Update build os to target macos-latest
mccaffers Apr 12, 2026
27f82f1
Updating github workflow os
mccaffers Apr 12, 2026
e4e8e13
Refactoring build pipeline
mccaffers Apr 19, 2026
0b6d40e
fixing workflow
mccaffers Apr 25, 2026
8a0a710
Updating github workflow os
mccaffers Apr 26, 2026
801c846
Updating github workflow os
mccaffers Apr 26, 2026
b57db36
Updating github workflow os
mccaffers Apr 26, 2026
9a60a27
Updating github workflow os
mccaffers Apr 26, 2026
edb253f
Updating main flow, extracting the strategy to query questdB
mccaffers Apr 27, 2026
657b5e2
Updating trade management operations
mccaffers Apr 28, 2026
87bc95e
Refactoring operations flow
mccaffers Apr 29, 2026
83d838f
updated workflows and optimised build & test
mccaffers May 2, 2026
e1ddeaa
updated workflow references
mccaffers May 2, 2026
28ce018
updated workflow references
mccaffers May 2, 2026
cb840fd
updated workflow references
mccaffers May 2, 2026
3f52047
updated workflow references
mccaffers May 2, 2026
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
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)
# 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)
Comment on lines +53 to +60

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

Hard-coding OpenMP_omp_LIBRARY and the include path to /opt/homebrew/... will break on Apple systems that don’t use that prefix (or don’t have libomp installed there). Prefer letting find_package(OpenMP) locate OpenMP, or derive the path dynamically (e.g., via brew --prefix libomp) and fail with a clear message if it’s missing.

Suggested change
# 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)
# Configure OpenMP. On Apple, provide dynamic 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")
execute_process(
COMMAND brew --prefix libomp
OUTPUT_VARIABLE LIBOMP_PREFIX
RESULT_VARIABLE LIBOMP_PREFIX_RESULT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(NOT LIBOMP_PREFIX_RESULT EQUAL 0 OR LIBOMP_PREFIX STREQUAL "")
message(FATAL_ERROR "OpenMP support on Apple requires libomp. Install it with Homebrew (`brew install libomp`) or make it discoverable to CMake.")
endif()
set(OpenMP_ROOT "${LIBOMP_PREFIX}")

Copilot uses AI. Check for mistakes.
endif()

Comment thread
mccaffers marked this conversation as resolved.
Outdated
target_link_libraries(BacktestingEngineLib PUBLIC pqxx OpenMP::OpenMP_CXX)

# Main executable
add_executable(BacktestingEngine source/main.cpp)
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
```
1 change: 1 addition & 0 deletions include/databaseConnection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class DatabaseConnection {

void printResults(const std::vector<PriceData>& results) const;
std::vector<PriceData> executeQuery(const std::string& query) const;
Comment thread
mccaffers marked this conversation as resolved.
Outdated
std::vector<PriceData> streamQuery(const std::string& query) const;

const std::string& getConnectionString() const {
return connection_string;
Expand Down
12 changes: 8 additions & 4 deletions include/models/priceData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
// ---------------------------------------
#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);
};
7 changes: 3 additions & 4 deletions include/sqlManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 +3 to +5

Copilot AI Apr 12, 2026

Copy link

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
8 changes: 8 additions & 0 deletions scripts/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
mccaffers marked this conversation as resolved.
Outdated
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 +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"

68 changes: 68 additions & 0 deletions source/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
Comment thread
mccaffers marked this conversation as resolved.
Outdated

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)
Comment on lines +24 to +28

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

This CMakeLists globally appends -w//w, which disables warnings for all targets (including your own code) and can mask real issues. Prefer limiting warning suppression to third-party targets only (e.g., via per-target compile options or SYSTEM includes).

Copilot uses AI. Check for mistakes.
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")
Comment on lines +49 to +50

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

file(GLOB_RECURSE SOURCES "source/*.cpp") inside source/CMakeLists.txt will look for source/source/*.cpp relative to this CMakeLists, which is likely not intended and can produce an empty library. Use paths relative to the correct root (e.g., "*.cpp" if this file is the project root) or switch to explicit target sources to avoid fragile globs.

Suggested change
# Collect all .cpp files in the src directory
file(GLOB_RECURSE SOURCES "source/*.cpp")
# Collect all .cpp files in the current source directory
file(GLOB_RECURSE SOURCES "*.cpp")

Copilot uses AI. Check for mistakes.

# 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 +62 to +66

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

OpenMP::OpenMP_CXX is linked unconditionally, but find_package(OpenMP REQUIRED) is only executed inside the if(APPLE) block. This will fail on non-Apple platforms because OpenMP::OpenMP_CXX won’t be defined; move find_package(OpenMP ...) outside the Apple-only conditional (or conditionally link).

Copilot uses AI. Check for mistakes.

Comment on lines +56 to +67

Copilot AI Apr 12, 2026

Copy link

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)
73 changes: 42 additions & 31 deletions source/databaseConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@
#include "databaseConnection.hpp"
#include "base64.hpp"
#include <pqxx/pqxx>
#include <cstdio>
#include <charconv>
#include <execution>
#include <algorithm>
Comment thread
mccaffers marked this conversation as resolved.
Outdated

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);
Comment thread
mccaffers marked this conversation as resolved.
Outdated

// 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);
Comment on lines +22 to +26

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

fastParseTimestamp uses shared mutable function-local static cache state. If streamQuery is called concurrently, this is a data race (UB). Consider making the cache thread_local or moving it into a caller-owned object.

Copilot uses AI. Check for mistakes.
std::tm tm = {};
Comment on lines +22 to +27

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

fastParseTimestamp uses std::memcmp/std::memcpy, but this file doesn’t include <cstring>. This can fail to compile on some toolchains. Add <cstring> (or avoid the C string APIs).

Copilot uses AI. Check for mistakes.
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,
Expand All @@ -21,37 +46,23 @@ DatabaseConnection::DatabaseConnection(const std::string& endpoint, int port,

}

std::vector<PriceData> DatabaseConnection::executeQuery(const std::string& query) const {
std::vector<PriceData> 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);
std::vector<PriceData> DatabaseConnection::streamQuery(const std::string& query) const {
pqxx::connection conn(this->connection_string);
pqxx::nontransaction txn(conn);
pqxx::result result = txn.exec(query);

Comment on lines +52 to 56

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

streamQuery removed the previous try/catch and will now let pqxx exceptions propagate to callers. Since main doesn’t catch exceptions, a connection/query failure will terminate the process without a clear error path. Either restore structured error handling (return an error / empty vector + log) or introduce an exception boundary at the CLI level.

Copilot uses AI. Check for mistakes.
// Convert results to PriceData objects
for (const auto& row : result) {
double value1 = row[0].as<double>();
double value2 = row[1].as<double>();
std::string timestamp_str = row[2].as<std::string>();

auto timestamp = Utilities::parseTimestamp(timestamp_str);

results.emplace_back(value1, value2, timestamp);
}

txn.commit();
std::vector<PriceData> results(result.size());

} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
for (int i = 0; i < (int)result.size(); ++i) {
Comment thread
mccaffers marked this conversation as resolved.
Outdated
const auto& row = result[i];
double ask, bid;
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);
Comment on lines +61 to +66

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

std::from_chars results are ignored and ask/bid are uninitialized if parsing fails (e.g., NULL/empty/non-numeric fields), which can corrupt downstream logic. Initialize the variables and check the std::from_chars_result (ptr/ec) to handle parse failures explicitly.

Suggested change
double ask, bid;
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);
double ask = 0.0, bid = 0.0;
auto symbol = row[0].view();
if (row[1].is_null() || row[2].is_null()) {
throw std::runtime_error("Invalid price data: NULL ask/bid field");
}
auto sv1 = row[1].view();
auto sv2 = row[2].view();
const auto askResult = std::from_chars(sv1.data(), sv1.data() + sv1.size(), ask);
const auto bidResult = std::from_chars(sv2.data(), sv2.data() + sv2.size(), bid);
if (askResult.ec != std::errc() || askResult.ptr != sv1.data() + sv1.size()) {
throw std::runtime_error("Invalid price data: failed to parse ask");
}
if (bidResult.ec != std::errc() || bidResult.ptr != sv2.data() + sv2.size()) {
throw std::runtime_error("Invalid price data: failed to parse bid");
}

Copilot uses AI. Check for mistakes.
results[i] = PriceData(ask, bid, fastParseTimestamp(row[3].c_str()), std::string(symbol));
}

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

ask/bid are uninitialized and the std::from_chars results are ignored. If parsing fails (empty field, NaN text, etc.), you’ll store indeterminate values in PriceData. Initialize ask/bid and check from_chars(...).ec (and/or ptr) before using the parsed numbers.

Copilot uses AI. Check for mistakes.

return results;
}
Expand All @@ -70,8 +81,8 @@ void DatabaseConnection::printResults(const std::vector<PriceData>& 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;
}
}
30 changes: 9 additions & 21 deletions source/main.cpp
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)
// ---------------------------------------

Expand All @@ -23,36 +23,24 @@
#include "tradeManager.hpp"
#include "jsonParser.hpp"
#include "sqlManager.hpp"
#include "operations.hpp"

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[]) {

Comment thread
mccaffers marked this conversation as resolved.
// 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> 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;

auto tradeManager = TradeManager::getInstance();

// Open a trade
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;
std::vector<std::string> symbols = {"AUSIDXAUD", "EURUSD"};
std::vector<PriceData> ticks = SqlManager::streamPriceData(db, symbols, 1);
printf("Total ticks streamed: %zu\n", ticks.size());

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

printf is used here, but <cstdio> isn’t included in this file. Add the missing include (or switch to std::cout) to avoid relying on indirect includes.

Copilot uses AI. Check for mistakes.
// Close trade
bool closed = tradeManager->closeTrade(tradeId);
std::cout << "Trade closed: " << (closed ? "yes" : "no") << std::endl;
Operations::run(ticks);

return 0;

Expand Down
52 changes: 52 additions & 0 deletions source/operations.cpp
Original file line number Diff line number Diff line change
@@ -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 <iostream>
#include <vector>
#include <memory>
#include <string>
#include <iomanip>
Comment thread
mccaffers marked this conversation as resolved.
#include "tradeManager.hpp"

void Operations::run(const std::vector<PriceData>& ticks) {

// Loop aroudn every tick
Comment thread
mccaffers marked this conversation as resolved.
Outdated
// 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;

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

If localtime_r fails, the code logs an error but still calls strftime on tm, which can produce misleading output; consider continue on failure. Also rename time_t to avoid shadowing the time_t type name.

Suggested change
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;
auto tick_time = std::chrono::system_clock::to_time_t(tick.timestamp);
struct tm tm = {};
if (localtime_r(&tick_time, &tm) == nullptr) {
std::cerr << "Error: failed to convert timestamp" << std::endl;
continue;

Copilot uses AI. Check for mistakes.
}
char buffer[20];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(tick.timestamp.time_since_epoch()) % 1000;

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

If localtime_r fails, the code logs an error but then continues to call std::strftime using an uninitialized tm value. This can print garbage timestamps. Consider continue; (or otherwise handling the error) after the failure so the formatting code is skipped.

Copilot uses AI. Check for mistakes.
printf("symbol=%s, ask=%.4f, bid=%.4f timestamp=%s.%03lld\n", tick.symbol.c_str(), tick.ask, tick.bid, buffer,
static_cast<long long>(ms.count()));
}

auto tradeManager = TradeManager::getInstance();

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

tradeManager is retrieved but never used (all example calls are commented). This will trigger unused-variable warnings and adds dead code. Either remove it for now or implement the intended trade-management flow so Operations::run has a concrete effect.

Copilot uses AI. Check for mistakes.
// 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;
}
Loading
Loading