Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 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
5dfc605
update readme with new workflow badges
mccaffers May 2, 2026
21dd5f3
ignoring cpp:S2245 as it's just for testing
mccaffers May 2, 2026
e4a6343
ignoring cpp:S2245 as it's just for testing
mccaffers May 2, 2026
a3f77c4
Refactoring to add in pip scalling
mccaffers May 3, 2026
afeefae
updated build to reference boost-decimal
mccaffers May 3, 2026
9988f84
refactoring to use decimal
mccaffers May 3, 2026
5983150
Fixing test dependencies
mccaffers May 3, 2026
4590430
Added decimal support for json strings
mccaffers May 4, 2026
5a59345
Fixed references and removed old resources file
mccaffers May 4, 2026
6543044
update the build script
mccaffers May 4, 2026
c94a2ee
Updating workflows
mccaffers May 4, 2026
6491098
Updating workflows
mccaffers May 4, 2026
724754b
updating test references
mccaffers May 4, 2026
4511b80
Updating references
mccaffers May 4, 2026
c4c702f
fixing workflow, referenced build folder that didn't exist
mccaffers May 4, 2026
6dffe6f
fixing the tests for the json ingest of a strategy
mccaffers May 4, 2026
6a13724
Updated test coverage script and added auto pr response
mccaffers May 4, 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
58 changes: 58 additions & 0 deletions .github/workflows/auto-close-external-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: "Auto-Close External PRs"

on:
pull_request_target:
types: [opened]
workflow_dispatch:

jobs:
close-on-open:
if: github.event_name == 'pull_request_target' && github.event.pull_request.user.login != github.repository_owner
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
steps:
- uses: actions/github-script@v7
with:
script: |
const pr = context.issue.number;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr,
body: "Thanks for the interest! This repository isn't currently accepting external contributions as I am actively experimenting with different approaches and want to avoid merge conflicts. Therefore, this PR is being closed automatically by Github Bot."
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr,
state: "closed"
});

sweep-existing:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
steps:
- uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const prs = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', per_page: 100
});
for (const pr of prs) {
if (pr.user.login === owner) continue;
core.info(`Closing PR #${pr.number} by ${pr.user.login}`);
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number,
body: "Thanks for the interest! This repository isn't currently accepting external contributions as I am actively experimenting with different approaches and want to avoid merge conflicts. Therefore, this PR is being closed automatically by Github Bot."
});
await github.rest.pulls.update({
owner, repo, pull_number: pr.number, state: "closed"
});
}
5 changes: 1 addition & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,12 @@ jobs:
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
xcodebuild
-scheme tests
-destination 'platform=macOS'
-destination 'platform=macOS,arch=arm64'
-resultBundlePath TestResult/
-enableCodeCoverage YES
-derivedDataPath "${RUNNER_TEMP}/Build/DerivedData"
-parallelizeTargets
-jobs "$(sysctl -n hw.logicalcpu)"
HEADER_SEARCH_PATHS="./external/libpqxx/include/pqxx/internal ./external/libpqxx/include/ ./external/libpqxx/build/include/ ./external/boost-decimal/include/ ./external/boost-decimal/include/decimal/ ./external/"
LIBRARY_SEARCH_PATHS="./external/libpqxx/src/ ./external/libpqxx/build/src/"
OTHER_LDFLAGS="-L./external/libpqxx/build/src -lpqxx -lpq -L$(brew --prefix pkgconf)/lib -L$(brew --prefix pkgconf)/lib/pkgconfig -L$(brew --prefix postgresql@18)/lib/postgresql -L$(brew --prefix postgresql@18)/lib/postgresql/pgxs -L$(brew --prefix postgresql@18)/lib/postgresql/pkgconfig"
clean build test
| xcpretty -r junit && exit ${PIPESTATUS[0]}

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scripts/brew.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ check_and_install() {
}

# Install packages if they don't exist
check_and_install postgresql # Check and install PostgreSQL (which includes libpq)
check_and_install postgresql@18 # Check and install PostgreSQL (which includes libpq)
check_and_install pkg-config # Check and install pkg-config
62 changes: 32 additions & 30 deletions backtesting-engine-cpp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
943398252D57E53400287A2D /* jsonParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 943398232D57E53400287A2D /* jsonParser.cpp */; };
943398272D57E54000287A2D /* jsonParser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 943398262D57E54000287A2D /* jsonParser.mm */; };
94364CB62D416D8D00F35B55 /* db.mm in Sources */ = {isa = PBXBuildFile; fileRef = 94364CB52D416D8000F35B55 /* db.mm */; };
944698852D3A545B0070E30F /* libpq.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 94CD8A972D2D34A100041BBA /* libpq.a */; };
9464E5F12FA7467200D82BAD /* symbolScale.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9464E5F02FA7467200D82BAD /* symbolScale.mm */; };
94674B872D533B4000973137 /* tradeManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94674B852D533B4000973137 /* tradeManager.cpp */; };
94674B882D533B4000973137 /* tradeManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94674B852D533B4000973137 /* tradeManager.cpp */; };
Expand All @@ -32,11 +31,8 @@
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 */; };
94CD8BA12D2E8CE500041BBA /* databaseConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */; };
94CD8BA22D2E8FC600041BBA /* libpq.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 94CD8A972D2D34A100041BBA /* libpq.a */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand Down Expand Up @@ -95,8 +91,8 @@
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>"; };
94C331A02FA899A8006BD690 /* decimal_json.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = decimal_json.hpp; sourceTree = "<group>"; };
94CD832A2D2D22C900041BBA /* config.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = config.yml; sourceTree = "<group>"; };
94CD832C2D2D22C900041BBA /* codeql.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = codeql.yml; sourceTree = "<group>"; };
94CD832D2D2D22C900041BBA /* stale.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = stale.yml; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1265,17 +1261,13 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
94CD8BA22D2E8FC600041BBA /* libpq.a in Frameworks */,
94CD8B992D2DCDD800041BBA /* libpqxx-7.10.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9470B5A92C8C5B99007D9CC6 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
944698852D3A545B0070E30F /* libpq.a in Frameworks */,
94CD8B9C2D2DD02A00041BBA /* libpqxx-7.10.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1330,7 +1322,6 @@
94DE4F772C8C3E7C00FE48FF /* include */,
9470B5A22C8C5AD0007D9CC6 /* source */,
9470B5AD2C8C5B99007D9CC6 /* tests */,
948A9CEC2C906ADB00E23669 /* resources */,
944D0DCF2C8C3704004DD0FC /* sonar-project.properties */,
944D0DD32C8C3704004DD0FC /* CMakeLists.txt */,
944D0DC82C8C3704004DD0FC /* LICENSE.MD */,
Expand Down Expand Up @@ -1422,17 +1413,10 @@
path = tests;
sourceTree = "<group>";
};
948A9CEC2C906ADB00E23669 /* resources */ = {
isa = PBXGroup;
children = (
948A9CED2C906AFE00E23669 /* 2020.csv */,
);
path = resources;
sourceTree = "<group>";
};
94B8C7932D3D770800E17EB6 /* utilities */ = {
isa = PBXGroup;
children = (
94C331A02FA899A8006BD690 /* decimal_json.hpp */,
94280BA12D2FC00200F1CF56 /* base64.hpp */,
);
path = utilities;
Expand Down Expand Up @@ -3781,19 +3765,23 @@
CODE_SIGN_STYLE = Automatic;
FRAMEWORK_SEARCH_PATHS = "";
HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/include\"",
"\"$(SRCROOT)/external/libpqxx/include\"",
"\"$(SRCROOT)/external/libpqxx/include/pqxx/internal\"",
"\"$(SRCROOT)/external/libpqxx/build/include\"",
"\"$(SRCROOT)/external/\"",
"\"$(SRCROOT)/external/boost-decimal/include\"",
);
INCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES = "";
LIBRARY_SEARCH_PATHS = (
"\"$(SRCROOT)/external/libpqxx/build/src\"",
"/opt/homebrew/Cellar/postgresql@18/18.3/lib/postgresql",
"\"$(SRCROOT)/external/boost-decimal/build\"",
"/opt/homebrew/opt/postgresql@18/lib/postgresql",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
OTHER_LDFLAGS = "";
OTHER_LDFLAGS = (
"-lpq",
"-lpqxx",
);
OTHER_LIBTOOLFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
SECTORDER_FLAGS = "";
Expand All @@ -3806,19 +3794,23 @@
CODE_SIGN_STYLE = Automatic;
FRAMEWORK_SEARCH_PATHS = "";
HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/include\"",
"\"$(SRCROOT)/external/libpqxx/include\"",
"\"$(SRCROOT)/external/libpqxx/include/pqxx/internal\"",
"\"$(SRCROOT)/external/libpqxx/build/include\"",
"\"$(SRCROOT)/external/\"",
"\"$(SRCROOT)/external/boost-decimal/include\"",
);
INCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES = "";
LIBRARY_SEARCH_PATHS = (
"\"$(SRCROOT)/external/libpqxx/build/src\"",
"/opt/homebrew/Cellar/postgresql@18/18.3/lib/postgresql",
"\"$(SRCROOT)/external/boost-decimal/build\"",
"/opt/homebrew/opt/postgresql@18/lib/postgresql",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
OTHER_LDFLAGS = "";
OTHER_LDFLAGS = (
"-lpq",
"-lpqxx",
);
OTHER_LIBTOOLFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
SECTORDER_FLAGS = "";
Expand All @@ -3832,18 +3824,23 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/include\"",
"\"$(SRCROOT)/external/\"",
"\"$(SRCROOT)/external/libpqxx/include\"",
"\"$(SRCROOT)/external/libpqxx/include/pqxx/internal\"",
"\"$(SRCROOT)/external/\"",
"\"$(SRCROOT)/external/libpqxx/build/include\"",
"\"$(SRCROOT)/external/boost-decimal/include\"",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SRCROOT)/external/libpqxx/build/src\"",
"/opt/homebrew/Cellar/postgresql@18/18.3/lib/postgresql",
"\"$(SRCROOT)/external/boost-decimal/build\"",
"/opt/homebrew/opt/postgresql@18/lib/postgresql",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
OTHER_LDFLAGS = (
"-lpq",
"-lpqxx",
);
PRODUCT_BUNDLE_IDENTIFIER = com.mccaffers.tests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
Expand All @@ -3857,18 +3854,23 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/include\"",
"\"$(SRCROOT)/external/\"",
"\"$(SRCROOT)/external/libpqxx/include\"",
"\"$(SRCROOT)/external/libpqxx/include/pqxx/internal\"",
"\"$(SRCROOT)/external/\"",
"\"$(SRCROOT)/external/libpqxx/build/include\"",
"\"$(SRCROOT)/external/boost-decimal/include\"",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SRCROOT)/external/libpqxx/build/src\"",
"/opt/homebrew/Cellar/postgresql@18/18.3/lib/postgresql",
"\"$(SRCROOT)/external/boost-decimal/build\"",
"/opt/homebrew/opt/postgresql@18/lib/postgresql",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
OTHER_LDFLAGS = (
"-lpq",
"-lpqxx",
);
PRODUCT_BUNDLE_IDENTIFIER = com.mccaffers.tests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
Expand Down
6 changes: 4 additions & 2 deletions include/operations.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
#pragma once
#include <vector>
#include "models/priceData.hpp"
#include "trading_definitions/configuration.hpp"

class Operations {

public:
static void run(const std::vector<PriceData>& priceData);
static void run(const std::vector<PriceData>& priceData,
const trading_definitions::Configuration& config);
};
8 changes: 5 additions & 3 deletions include/trading_definitions/trading_variables.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@

#pragma once
#include <string>
#include <boost/decimal.hpp>
#include <nlohmann/json.hpp>
#include "utilities/decimal_json.hpp"

namespace trading_definitions {
struct TradingVariables {
std::string STRATEGY;
double STOP_DISTANCE_IN_PIPS;
double LIMIT_DISTANCE_IN_PIPS;
double TRADING_SIZE;
boost::decimal::decimal64_t STOP_DISTANCE_IN_PIPS;
boost::decimal::decimal64_t LIMIT_DISTANCE_IN_PIPS;
boost::decimal::decimal64_t TRADING_SIZE;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(TradingVariables,
STRATEGY,
Expand Down
51 changes: 51 additions & 0 deletions include/utilities/decimal_json.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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 <stdexcept>
#include <string>
#include <system_error>
#include <boost/decimal.hpp>
#include <boost/decimal/charconv.hpp>
#include <nlohmann/json.hpp>

// Bridge boost::decimal::decimal64_t into nlohmann::json. Without this,
// NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE on structs containing decimal64_t fields
// fails to compile because the library can't find a (de)serializer for the type.
//
// Values move through JSON as strings (e.g. "0.0001") and are parsed with
// boost::decimal::from_chars. Going via a string skips the binary-float
// detour, so a literal like 0.1 in the source JSON survives the round-trip
// without snapping to the nearest IEEE-754 double.
//
// nlohmann's recommended way to add support for a third-party type is to
// specialize adl_serializer rather than injecting free functions into someone
// else's namespace. The C# analogue would be writing a custom JsonConverter<T>
// and registering it on the serializer options.
namespace nlohmann {
template <>
struct adl_serializer<boost::decimal::decimal64_t> {
static void to_json(json& j, const boost::decimal::decimal64_t& value) {
// 64 chars is comfortably above the longest possible decimal64_t
// representation (16 significant digits + sign + point + exponent).
char buffer[64];
const auto result = boost::decimal::to_chars(buffer, buffer + sizeof(buffer), value);
if (result.ec != std::errc{}) {
throw std::runtime_error("decimal_json: to_chars failed serializing decimal64_t");
}
j = std::string(buffer, result.ptr);
}

static void from_json(const json& j, boost::decimal::decimal64_t& value) {
const auto& str = j.get_ref<const std::string&>();
const auto result = boost::decimal::from_chars(str.data(), str.data() + str.size(), value);
if (result.ec != std::errc{}) {
throw std::runtime_error("decimal_json: from_chars failed parsing '" + str + "'");
}
}
};
}
Loading