Skip to content

Commit 15cf528

Browse files
authored
Refactoring to improve flow and readability (#38)
* Refactoring to improve flow and readability * fixed a random char bug
1 parent b70e5dc commit 15cf528

8 files changed

Lines changed: 162 additions & 95 deletions

File tree

.github/workflows/build.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,10 @@ jobs:
9191
wget -q "https://archives.boost.io/release/${BOOST_VERSION}/source/${BOOST_DIR}.tar.gz"
9292
tar xzf "${BOOST_DIR}.tar.gz"
9393
cd "${BOOST_DIR}"
94-
./bootstrap.sh
95-
sudo ./b2 install --prefix=/usr/local --with-system -j"$(nproc)"
94+
./bootstrap.sh > /dev/null
95+
# -d0 silences per-action output (otherwise b2 prints one line per
96+
# copied header — ~15k lines just for the header install).
97+
sudo ./b2 install -d0 --prefix=/usr/local --with-system -j"$(nproc)"
9698
- name: Check compiler version, for debugging
9799
run: |
98100
g++ --version

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ add_subdirectory(external/libpqxx EXCLUDE_FROM_ALL)
3737
# Include directories
3838
include_directories(
3939
${CMAKE_SOURCE_DIR}/include
40+
${CMAKE_SOURCE_DIR}/include/commands
4041
${CMAKE_SOURCE_DIR}/include/utilities
4142
${CMAKE_SOURCE_DIR}/include/models
4243
${CMAKE_SOURCE_DIR}/include/trading

backtesting-engine-cpp.xcodeproj/project.pbxproj

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
94724A842F8B92C10029B940 /* operations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94724A822F8B92C10029B940 /* operations.cpp */; };
4444
94CD8BA02D2E8CE500041BBA /* databaseConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */; };
4545
94CD8BA12D2E8CE500041BBA /* databaseConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */; };
46+
94D3A7262FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D3A7242FC1B3AD00EBEA32 /* loadCommand.cpp */; };
47+
94D3A7272FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D3A7242FC1B3AD00EBEA32 /* loadCommand.cpp */; };
48+
94D3A7292FC1B41500EBEA32 /* runCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D3A7282FC1B41500EBEA32 /* runCommand.cpp */; };
49+
94D3A72A2FC1B41500EBEA32 /* runCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D3A7282FC1B41500EBEA32 /* runCommand.cpp */; };
4650
94D601102FA9CD700066F51A /* randomStrategy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D6010E2FA9CD700066F51A /* randomStrategy.cpp */; };
4751
94D601112FA9CD700066F51A /* randomStrategy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D6010E2FA9CD700066F51A /* randomStrategy.cpp */; };
4852
/* End PBXBuildFile section */
@@ -1279,6 +1283,10 @@
12791283
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>"; };
12801284
94CD8B9E2D2E8CE500041BBA /* databaseConnection.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = databaseConnection.hpp; sourceTree = "<group>"; };
12811285
94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = databaseConnection.cpp; sourceTree = "<group>"; };
1286+
94D3A7222FC1B3A600EBEA32 /* loadCommand.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = loadCommand.hpp; sourceTree = "<group>"; };
1287+
94D3A7242FC1B3AD00EBEA32 /* loadCommand.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = loadCommand.cpp; sourceTree = "<group>"; };
1288+
94D3A7282FC1B41500EBEA32 /* runCommand.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = runCommand.cpp; sourceTree = "<group>"; };
1289+
94D3A72B2FC1B41D00EBEA32 /* runCommand.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = runCommand.hpp; sourceTree = "<group>"; };
12821290
94D6010E2FA9CD700066F51A /* randomStrategy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = randomStrategy.cpp; sourceTree = "<group>"; };
12831291
94D601122FA9CD890066F51A /* randomStrategy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = randomStrategy.hpp; sourceTree = "<group>"; };
12841292
/* End PBXFileReference section */
@@ -1420,14 +1428,15 @@
14201428
9470B5A22C8C5AD0007D9CC6 /* source */ = {
14211429
isa = PBXGroup;
14221430
children = (
1431+
941B54982D3BBAD800E3BF64 /* trading_definitions */,
1432+
94674B862D533B4000973137 /* trading */,
1433+
94674B8C2D533E7800973137 /* models */,
1434+
94D6010F2FA9CD700066F51A /* strategies */,
1435+
94D3A7252FC1B3AD00EBEA32 /* commands */,
1436+
94280BA72D2FC29F00F1CF56 /* utilities */,
14231437
942EC5642FBEF95000CCBB5D /* backtestRunner.cpp */,
14241438
942EC5652FBEF95000CCBB5D /* redisLoader.cpp */,
14251439
942EC5662FBEF95000CCBB5D /* redisRunner.cpp */,
1426-
94D6010F2FA9CD700066F51A /* strategies */,
1427-
94674B8C2D533E7800973137 /* models */,
1428-
94674B862D533B4000973137 /* trading */,
1429-
941B54982D3BBAD800E3BF64 /* trading_definitions */,
1430-
94280BA72D2FC29F00F1CF56 /* utilities */,
14311440
9470B5A32C8C5AD0007D9CC6 /* main.cpp */,
14321441
940A61112C92CE210083FEB8 /* configManager.cpp */,
14331442
940A61152C92CE960083FEB8 /* serviceA.cpp */,
@@ -3531,6 +3540,24 @@
35313540
path = "../../../../../opt/homebrew/Cellar/postgresql@14/14.15/lib/postgresql@14/pgxs";
35323541
sourceTree = "<group>";
35333542
};
3543+
94D3A7232FC1B3A600EBEA32 /* commands */ = {
3544+
isa = PBXGroup;
3545+
children = (
3546+
94D3A72B2FC1B41D00EBEA32 /* runCommand.hpp */,
3547+
94D3A7222FC1B3A600EBEA32 /* loadCommand.hpp */,
3548+
);
3549+
path = commands;
3550+
sourceTree = "<group>";
3551+
};
3552+
94D3A7252FC1B3AD00EBEA32 /* commands */ = {
3553+
isa = PBXGroup;
3554+
children = (
3555+
94D3A7282FC1B41500EBEA32 /* runCommand.cpp */,
3556+
94D3A7242FC1B3AD00EBEA32 /* loadCommand.cpp */,
3557+
);
3558+
path = commands;
3559+
sourceTree = "<group>";
3560+
};
35343561
94D6010F2FA9CD700066F51A /* strategies */ = {
35353562
isa = PBXGroup;
35363563
children = (
@@ -3551,6 +3578,7 @@
35513578
94DE4F772C8C3E7C00FE48FF /* include */ = {
35523579
isa = PBXGroup;
35533580
children = (
3581+
94D3A7232FC1B3A600EBEA32 /* commands */,
35543582
942EC55E2FBEF93A00CCBB5D /* backtestRunner.hpp */,
35553583
942EC55F2FBEF93A00CCBB5D /* redisLoader.hpp */,
35563584
942EC5602FBEF93A00CCBB5D /* redisRunner.hpp */,
@@ -3664,8 +3692,10 @@
36643692
buildActionMask = 2147483647;
36653693
files = (
36663694
941408AE2D59F93F000ED1F9 /* sqlManager.cpp in Sources */,
3695+
94D3A7272FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */,
36673696
9470B5A42C8C5AD0007D9CC6 /* main.cpp in Sources */,
36683697
943398252D57E53400287A2D /* jsonParser.cpp in Sources */,
3698+
94D3A72A2FC1B41500EBEA32 /* runCommand.cpp in Sources */,
36693699
942EC56A2FBEF95000CCBB5D /* backtestRunner.cpp in Sources */,
36703700
942EC56B2FBEF95000CCBB5D /* redisLoader.cpp in Sources */,
36713701
942EC56C2FBEF95000CCBB5D /* redisRunner.cpp in Sources */,
@@ -3694,10 +3724,12 @@
36943724
943398242D57E53400287A2D /* jsonParser.cpp in Sources */,
36953725
942EC5672FBEF95000CCBB5D /* backtestRunner.cpp in Sources */,
36963726
942EC5682FBEF95000CCBB5D /* redisLoader.cpp in Sources */,
3727+
94D3A7262FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */,
36973728
942EC5692FBEF95000CCBB5D /* redisRunner.cpp in Sources */,
36983729
94280BA42D2FC00200F1CF56 /* base64.cpp in Sources */,
36993730
94674B8D2D533E7800973137 /* trade.cpp in Sources */,
37003731
941B549A2D3BBADE00E3BF64 /* trading_definitions_json.cpp in Sources */,
3732+
94D3A7292FC1B41500EBEA32 /* runCommand.cpp in Sources */,
37013733
94D601102FA9CD700066F51A /* randomStrategy.cpp in Sources */,
37023734
94674B8A2D533BDA00973137 /* tradeManager.mm in Sources */,
37033735
94724A832F8B92C10029B940 /* operations.cpp in Sources */,

include/commands/loadCommand.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Backtesting Engine in C++
2+
//
3+
// (c) 2026 Ryan McCaffery | https://mccaffers.com
4+
// This code is licensed under MIT license (see LICENSE.txt for details)
5+
// ---------------------------------------
6+
7+
#pragma once
8+
9+
// Backs the `load` subcommand: reads each strategy file from disk and LPUSHes
10+
// it onto the Redis `strategy_queue` via RedisLoader.
11+
class LoadCommand {
12+
public:
13+
static int run(int argc, const char* argv[]);
14+
};

include/commands/runCommand.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Backtesting Engine in C++
2+
//
3+
// (c) 2026 Ryan McCaffery | https://mccaffers.com
4+
// This code is licensed under MIT license (see LICENSE.txt for details)
5+
// ---------------------------------------
6+
7+
#pragma once
8+
9+
// Backs the `run` subcommand: executes one strategy popped from the Redis
10+
// `strategy_queue`, or a Base64 config supplied directly on the command line.
11+
class RunCommand {
12+
public:
13+
static int run(int argc, const char* argv[]);
14+
};

source/commands/loadCommand.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Backtesting Engine in C++
2+
//
3+
// (c) 2026 Ryan McCaffery | https://mccaffers.com
4+
// This code is licensed under MIT license (see LICENSE.txt for details)
5+
// ---------------------------------------
6+
7+
#include "loadCommand.hpp"
8+
9+
#include <fstream>
10+
#include <iostream>
11+
#include <sstream>
12+
#include <string>
13+
14+
#include "redisLoader.hpp"
15+
16+
int LoadCommand::run(int argc, const char* argv[]) {
17+
if (argc < 3) {
18+
std::cerr << "Usage: " << argv[0] << " load <path> [path...]"
19+
<< std::endl;
20+
return 1;
21+
}
22+
for (int i = 2; i < argc; ++i) {
23+
const std::string path = argv[i];
24+
std::ifstream ifs(path);
25+
if (!ifs) {
26+
std::cerr << "BacktestingEngine: failed to open file: " << path
27+
<< std::endl;
28+
return 1;
29+
}
30+
std::ostringstream buffer;
31+
buffer << ifs.rdbuf();
32+
if (ifs.bad()) {
33+
std::cerr << "BacktestingEngine: failed to read file: " << path
34+
<< std::endl;
35+
return 1;
36+
}
37+
const int rc = RedisLoader::load(buffer.str(), "127.0.0.1", 6379,
38+
"strategy_queue");
39+
if (rc != 0) {
40+
return rc;
41+
}
42+
}
43+
return 0;
44+
}

source/commands/runCommand.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Backtesting Engine in C++
2+
//
3+
// (c) 2026 Ryan McCaffery | https://mccaffers.com
4+
// This code is licensed under MIT license (see LICENSE.txt for details)
5+
// ---------------------------------------
6+
7+
#include "runCommand.hpp"
8+
9+
#include <iostream>
10+
#include <string>
11+
12+
#include "backtestRunner.hpp"
13+
#include "jsonParser.hpp"
14+
#include "redisRunner.hpp"
15+
16+
namespace {
17+
18+
int runBacktestFromBase64(const std::string& questdbHost,
19+
const std::string& base64Config) {
20+
auto config = JsonParser::parseConfigurationFromBase64(base64Config);
21+
return runBacktest(questdbHost, config);
22+
}
23+
24+
} // namespace
25+
26+
int RunCommand::run(int argc, const char* argv[]) {
27+
if (argc < 3) {
28+
std::cerr << "Usage: BacktestingEngine run <questdb-host>\n"
29+
<< " BacktestingEngine run <questdb-host> <base64-config>"
30+
<< std::endl;
31+
return 1;
32+
}
33+
if (argc == 3) {
34+
return RedisRunner::run(argv[2]);
35+
}
36+
return runBacktestFromBase64(argv[2], argv[3]);
37+
}

source/main.cpp

Lines changed: 11 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -5,103 +5,26 @@
55
// ---------------------------------------
66

77
// std headers
8-
#include <fstream>
98
#include <iostream>
10-
#include <sstream>
11-
#include <string>
9+
#include <string_view>
1210

1311
// backtesting engine headers
14-
#include "backtestRunner.hpp"
15-
#include "jsonParser.hpp"
16-
#include "redisLoader.hpp"
17-
#include "redisRunner.hpp"
12+
#include "loadCommand.hpp"
13+
#include "runCommand.hpp"
1814

19-
static int runBacktest(const std::string& questdbHost,
20-
const std::string& base64Config) {
21-
auto config = JsonParser::parseConfigurationFromBase64(base64Config);
22-
return runBacktest(questdbHost, config);
23-
}
24-
25-
static void printUsage(std::ostream& out) {
26-
out << "Usage: BacktestingEngine <subcommand> [args...]\n"
27-
<< "\n"
28-
<< "Subcommands:\n"
29-
<< " load <path> [path...] Push Base64-encoded JSON strategies\n"
30-
<< " from path(s) onto Redis\n"
31-
<< " `strategy_queue`.\n"
32-
<< " run <questdb-host> Pop one Base64 strategy from the\n"
33-
<< " Redis `strategy_queue` and execute\n"
34-
<< " it.\n"
35-
<< " run <questdb-host> <base64-config>\n"
36-
<< " Decode the supplied Base64 strategy\n"
37-
<< " and execute it.\n"
38-
<< " -h, --help Show this help message."
39-
<< std::endl;
40-
}
41-
42-
// Entry point. Dispatches on argv[1] to one of the BacktestingEngine
43-
// subcommands: `load <raw-json>` is the Redis enqueue path — it LPUSHes a
44-
// base64-encoded JSON payload onto `strategy_queue` via RedisLoader, pairing
45-
// with the dequeue side handled by `RedisRunner`; `run <questdb-host>` RPOPs
46-
// the next strategy from `strategy_queue` and executes it against QuestDB,
47-
// while `run <questdb-host> <base64-config>` skips Redis and executes the
48-
// supplied Base64 strategy directly; `-h`/`--help` prints usage.
49-
int main(int argc, const char * argv[]) {
15+
int main(int argc, const char* argv[]) {
5016
if (argc < 2) {
51-
printUsage(std::cerr);
17+
std::cerr << "BacktestingEngine: missing subcommand. See README.md for usage."
18+
<< std::endl;
5219
return 1;
5320
}
5421

55-
const std::string subcommand = argv[1];
22+
const std::string_view subcommand = argv[1];
5623

57-
if (subcommand == "-h" || subcommand == "--help") {
58-
printUsage(std::cout);
59-
return 0;
60-
}
61-
62-
if (subcommand == "load") {
63-
if (argc < 3) {
64-
std::cerr << "Usage: " << argv[0] << " load <path> [path...]"
65-
<< std::endl;
66-
return 1;
67-
}
68-
for (int i = 2; i < argc; ++i) {
69-
const std::string path = argv[i];
70-
std::ifstream ifs(path);
71-
if (!ifs) {
72-
std::cerr << "BacktestingEngine: failed to open file: " << path
73-
<< std::endl;
74-
return 1;
75-
}
76-
std::ostringstream buffer;
77-
buffer << ifs.rdbuf();
78-
if (ifs.bad()) {
79-
std::cerr << "BacktestingEngine: failed to read file: " << path
80-
<< std::endl;
81-
return 1;
82-
}
83-
const int rc = RedisLoader::load(buffer.str(), "127.0.0.1", 6379,
84-
"strategy_queue");
85-
if (rc != 0) {
86-
return rc;
87-
}
88-
}
89-
return 0;
90-
}
91-
92-
if (subcommand == "run") {
93-
if (argc < 3) {
94-
std::cerr << "Usage: BacktestingEngine run <questdb-host>\n"
95-
<< " BacktestingEngine run <questdb-host> <base64-config>"
96-
<< std::endl;
97-
return 1;
98-
}
99-
if (argc == 3) {
100-
return RedisRunner::run(argv[2]);
101-
}
102-
return runBacktest(argv[2], argv[3]);
103-
}
24+
if (subcommand == "load") return LoadCommand::run(argc, argv);
25+
if (subcommand == "run") return RunCommand::run(argc, argv);
10426

105-
printUsage(std::cerr);
27+
std::cerr << "BacktestingEngine: unknown subcommand. See README.md for usage."
28+
<< std::endl;
10629
return 1;
10730
}

0 commit comments

Comments
 (0)