Skip to content
Merged
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
6 changes: 4 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,10 @@ jobs:
wget -q "https://archives.boost.io/release/${BOOST_VERSION}/source/${BOOST_DIR}.tar.gz"
tar xzf "${BOOST_DIR}.tar.gz"
cd "${BOOST_DIR}"
./bootstrap.sh
sudo ./b2 install --prefix=/usr/local --with-system -j"$(nproc)"
./bootstrap.sh > /dev/null
# -d0 silences per-action output (otherwise b2 prints one line per
# copied header — ~15k lines just for the header install).
sudo ./b2 install -d0 --prefix=/usr/local --with-system -j"$(nproc)"
- name: Check compiler version, for debugging
run: |
g++ --version
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ add_subdirectory(external/libpqxx EXCLUDE_FROM_ALL)
# Include directories
include_directories(
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/include/commands
${CMAKE_SOURCE_DIR}/include/utilities
${CMAKE_SOURCE_DIR}/include/models
${CMAKE_SOURCE_DIR}/include/trading
Expand Down
42 changes: 37 additions & 5 deletions backtesting-engine-cpp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
94724A842F8B92C10029B940 /* operations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94724A822F8B92C10029B940 /* operations.cpp */; };
94CD8BA02D2E8CE500041BBA /* databaseConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */; };
94CD8BA12D2E8CE500041BBA /* databaseConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */; };
94D3A7262FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D3A7242FC1B3AD00EBEA32 /* loadCommand.cpp */; };
94D3A7272FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D3A7242FC1B3AD00EBEA32 /* loadCommand.cpp */; };
94D3A7292FC1B41500EBEA32 /* runCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D3A7282FC1B41500EBEA32 /* runCommand.cpp */; };
94D3A72A2FC1B41500EBEA32 /* runCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D3A7282FC1B41500EBEA32 /* runCommand.cpp */; };
94D601102FA9CD700066F51A /* randomStrategy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D6010E2FA9CD700066F51A /* randomStrategy.cpp */; };
94D601112FA9CD700066F51A /* randomStrategy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D6010E2FA9CD700066F51A /* randomStrategy.cpp */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -1279,6 +1283,10 @@
94CD8B9A2D2DCF6E00041BBA /* libpqxx-7.10.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libpqxx-7.10.a"; path = "build/external/libpqxx/src/libpqxx-7.10.a"; sourceTree = "<group>"; };
94CD8B9E2D2E8CE500041BBA /* databaseConnection.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = databaseConnection.hpp; sourceTree = "<group>"; };
94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = databaseConnection.cpp; sourceTree = "<group>"; };
94D3A7222FC1B3A600EBEA32 /* loadCommand.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = loadCommand.hpp; sourceTree = "<group>"; };
94D3A7242FC1B3AD00EBEA32 /* loadCommand.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = loadCommand.cpp; sourceTree = "<group>"; };
94D3A7282FC1B41500EBEA32 /* runCommand.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = runCommand.cpp; sourceTree = "<group>"; };
94D3A72B2FC1B41D00EBEA32 /* runCommand.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = runCommand.hpp; sourceTree = "<group>"; };
94D6010E2FA9CD700066F51A /* randomStrategy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = randomStrategy.cpp; sourceTree = "<group>"; };
94D601122FA9CD890066F51A /* randomStrategy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = randomStrategy.hpp; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -1420,14 +1428,15 @@
9470B5A22C8C5AD0007D9CC6 /* source */ = {
isa = PBXGroup;
children = (
941B54982D3BBAD800E3BF64 /* trading_definitions */,
94674B862D533B4000973137 /* trading */,
94674B8C2D533E7800973137 /* models */,
94D6010F2FA9CD700066F51A /* strategies */,
94D3A7252FC1B3AD00EBEA32 /* commands */,
94280BA72D2FC29F00F1CF56 /* utilities */,
942EC5642FBEF95000CCBB5D /* backtestRunner.cpp */,
942EC5652FBEF95000CCBB5D /* redisLoader.cpp */,
942EC5662FBEF95000CCBB5D /* redisRunner.cpp */,
94D6010F2FA9CD700066F51A /* strategies */,
94674B8C2D533E7800973137 /* models */,
94674B862D533B4000973137 /* trading */,
941B54982D3BBAD800E3BF64 /* trading_definitions */,
94280BA72D2FC29F00F1CF56 /* utilities */,
9470B5A32C8C5AD0007D9CC6 /* main.cpp */,
940A61112C92CE210083FEB8 /* configManager.cpp */,
940A61152C92CE960083FEB8 /* serviceA.cpp */,
Expand Down Expand Up @@ -3531,6 +3540,24 @@
path = "../../../../../opt/homebrew/Cellar/postgresql@14/14.15/lib/postgresql@14/pgxs";
sourceTree = "<group>";
};
94D3A7232FC1B3A600EBEA32 /* commands */ = {
isa = PBXGroup;
children = (
94D3A72B2FC1B41D00EBEA32 /* runCommand.hpp */,
94D3A7222FC1B3A600EBEA32 /* loadCommand.hpp */,
);
path = commands;
sourceTree = "<group>";
};
94D3A7252FC1B3AD00EBEA32 /* commands */ = {
isa = PBXGroup;
children = (
94D3A7282FC1B41500EBEA32 /* runCommand.cpp */,
94D3A7242FC1B3AD00EBEA32 /* loadCommand.cpp */,
);
path = commands;
sourceTree = "<group>";
};
94D6010F2FA9CD700066F51A /* strategies */ = {
isa = PBXGroup;
children = (
Expand All @@ -3551,6 +3578,7 @@
94DE4F772C8C3E7C00FE48FF /* include */ = {
isa = PBXGroup;
children = (
94D3A7232FC1B3A600EBEA32 /* commands */,
942EC55E2FBEF93A00CCBB5D /* backtestRunner.hpp */,
942EC55F2FBEF93A00CCBB5D /* redisLoader.hpp */,
942EC5602FBEF93A00CCBB5D /* redisRunner.hpp */,
Expand Down Expand Up @@ -3664,8 +3692,10 @@
buildActionMask = 2147483647;
files = (
941408AE2D59F93F000ED1F9 /* sqlManager.cpp in Sources */,
94D3A7272FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */,
9470B5A42C8C5AD0007D9CC6 /* main.cpp in Sources */,
943398252D57E53400287A2D /* jsonParser.cpp in Sources */,
94D3A72A2FC1B41500EBEA32 /* runCommand.cpp in Sources */,
942EC56A2FBEF95000CCBB5D /* backtestRunner.cpp in Sources */,
942EC56B2FBEF95000CCBB5D /* redisLoader.cpp in Sources */,
942EC56C2FBEF95000CCBB5D /* redisRunner.cpp in Sources */,
Expand Down Expand Up @@ -3694,10 +3724,12 @@
943398242D57E53400287A2D /* jsonParser.cpp in Sources */,
942EC5672FBEF95000CCBB5D /* backtestRunner.cpp in Sources */,
942EC5682FBEF95000CCBB5D /* redisLoader.cpp in Sources */,
94D3A7262FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */,
942EC5692FBEF95000CCBB5D /* redisRunner.cpp in Sources */,
94280BA42D2FC00200F1CF56 /* base64.cpp in Sources */,
94674B8D2D533E7800973137 /* trade.cpp in Sources */,
941B549A2D3BBADE00E3BF64 /* trading_definitions_json.cpp in Sources */,
94D3A7292FC1B41500EBEA32 /* runCommand.cpp in Sources */,
94D601102FA9CD700066F51A /* randomStrategy.cpp in Sources */,
94674B8A2D533BDA00973137 /* tradeManager.mm in Sources */,
94724A832F8B92C10029B940 /* operations.cpp in Sources */,
Expand Down
14 changes: 14 additions & 0 deletions include/commands/loadCommand.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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

// Backs the `load` subcommand: reads each strategy file from disk and LPUSHes
// it onto the Redis `strategy_queue` via RedisLoader.
class LoadCommand {
public:
static int run(int argc, const char* argv[]);
};
14 changes: 14 additions & 0 deletions include/commands/runCommand.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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

// Backs the `run` subcommand: executes one strategy popped from the Redis
// `strategy_queue`, or a Base64 config supplied directly on the command line.
class RunCommand {
public:
static int run(int argc, const char* argv[]);
};
44 changes: 44 additions & 0 deletions source/commands/loadCommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Backtesting Engine in C++
//
// (c) 2026 Ryan McCaffery | https://mccaffers.com
// This code is licensed under MIT license (see LICENSE.txt for details)
// ---------------------------------------

#include "loadCommand.hpp"

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

#include "redisLoader.hpp"

int LoadCommand::run(int argc, const char* argv[]) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " load <path> [path...]"
<< std::endl;
return 1;
}
for (int i = 2; i < argc; ++i) {
const std::string path = argv[i];
std::ifstream ifs(path);
if (!ifs) {
std::cerr << "BacktestingEngine: failed to open file: " << path
<< std::endl;
return 1;
}
std::ostringstream buffer;
buffer << ifs.rdbuf();
if (ifs.bad()) {
std::cerr << "BacktestingEngine: failed to read file: " << path
<< std::endl;
return 1;
}
const int rc = RedisLoader::load(buffer.str(), "127.0.0.1", 6379,
"strategy_queue");
if (rc != 0) {
return rc;
}
}
return 0;
}
37 changes: 37 additions & 0 deletions source/commands/runCommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Backtesting Engine in C++
//
// (c) 2026 Ryan McCaffery | https://mccaffers.com
// This code is licensed under MIT license (see LICENSE.txt for details)
// ---------------------------------------

#include "runCommand.hpp"

#include <iostream>
#include <string>

#include "backtestRunner.hpp"
#include "jsonParser.hpp"
#include "redisRunner.hpp"

namespace {

int runBacktestFromBase64(const std::string& questdbHost,
const std::string& base64Config) {
auto config = JsonParser::parseConfigurationFromBase64(base64Config);
return runBacktest(questdbHost, config);
}

} // namespace

int RunCommand::run(int argc, const char* argv[]) {
if (argc < 3) {
std::cerr << "Usage: BacktestingEngine run <questdb-host>\n"
<< " BacktestingEngine run <questdb-host> <base64-config>"
<< std::endl;
return 1;
}
if (argc == 3) {
return RedisRunner::run(argv[2]);
}
return runBacktestFromBase64(argv[2], argv[3]);
}
99 changes: 11 additions & 88 deletions source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,103 +5,26 @@
// ---------------------------------------

// std headers
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>

// backtesting engine headers
#include "backtestRunner.hpp"
#include "jsonParser.hpp"
#include "redisLoader.hpp"
#include "redisRunner.hpp"
#include "loadCommand.hpp"
#include "runCommand.hpp"

static int runBacktest(const std::string& questdbHost,
const std::string& base64Config) {
auto config = JsonParser::parseConfigurationFromBase64(base64Config);
return runBacktest(questdbHost, config);
}

static void printUsage(std::ostream& out) {
out << "Usage: BacktestingEngine <subcommand> [args...]\n"
<< "\n"
<< "Subcommands:\n"
<< " load <path> [path...] Push Base64-encoded JSON strategies\n"
<< " from path(s) onto Redis\n"
<< " `strategy_queue`.\n"
<< " run <questdb-host> Pop one Base64 strategy from the\n"
<< " Redis `strategy_queue` and execute\n"
<< " it.\n"
<< " run <questdb-host> <base64-config>\n"
<< " Decode the supplied Base64 strategy\n"
<< " and execute it.\n"
<< " -h, --help Show this help message."
<< std::endl;
}

// Entry point. Dispatches on argv[1] to one of the BacktestingEngine
// subcommands: `load <raw-json>` is the Redis enqueue path — it LPUSHes a
// base64-encoded JSON payload onto `strategy_queue` via RedisLoader, pairing
// with the dequeue side handled by `RedisRunner`; `run <questdb-host>` RPOPs
// the next strategy from `strategy_queue` and executes it against QuestDB,
// while `run <questdb-host> <base64-config>` skips Redis and executes the
// supplied Base64 strategy directly; `-h`/`--help` prints usage.
int main(int argc, const char * argv[]) {
int main(int argc, const char* argv[]) {
if (argc < 2) {
printUsage(std::cerr);
std::cerr << "BacktestingEngine: missing subcommand. See README.md for usage."
<< std::endl;
return 1;
}

const std::string subcommand = argv[1];
const std::string_view subcommand = argv[1];

if (subcommand == "-h" || subcommand == "--help") {
printUsage(std::cout);
return 0;
}

if (subcommand == "load") {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " load <path> [path...]"
<< std::endl;
return 1;
}
for (int i = 2; i < argc; ++i) {
const std::string path = argv[i];
std::ifstream ifs(path);
if (!ifs) {
std::cerr << "BacktestingEngine: failed to open file: " << path
<< std::endl;
return 1;
}
std::ostringstream buffer;
buffer << ifs.rdbuf();
if (ifs.bad()) {
std::cerr << "BacktestingEngine: failed to read file: " << path
<< std::endl;
return 1;
}
const int rc = RedisLoader::load(buffer.str(), "127.0.0.1", 6379,
"strategy_queue");
if (rc != 0) {
return rc;
}
}
return 0;
}

if (subcommand == "run") {
if (argc < 3) {
std::cerr << "Usage: BacktestingEngine run <questdb-host>\n"
<< " BacktestingEngine run <questdb-host> <base64-config>"
<< std::endl;
return 1;
}
if (argc == 3) {
return RedisRunner::run(argv[2]);
}
return runBacktest(argv[2], argv[3]);
}
if (subcommand == "load") return LoadCommand::run(argc, argv);
if (subcommand == "run") return RunCommand::run(argc, argv);

printUsage(std::cerr);
std::cerr << "BacktestingEngine: unknown subcommand. See README.md for usage."
<< std::endl;
return 1;
}
Loading