Skip to content

Commit 3a01f41

Browse files
committed
Refactoring to put a strategy into redis, and then a queue of runs to sweep
1 parent f157c24 commit 3a01f41

17 files changed

Lines changed: 388 additions & 139 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ TestResult.xcresult/
4242
sonarqube-generic-coverage.xml
4343
.cache/clangd/
4444
dump.rdb
45-
.infisical.json
45+
.infisical.json

backtesting-engine-cpp.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@
119119
944D0DCF2C8C3704004DD0FC /* sonar-project.properties */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "sonar-project.properties"; sourceTree = "<group>"; };
120120
944D0DD02C8C3704004DD0FC /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
121121
944D0DD32C8C3704004DD0FC /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = "<group>"; };
122+
945F475C2FD5607E00D19164 /* queueKeys.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = queueKeys.hpp; sourceTree = "<group>"; };
123+
945F475D2FD5614600D19164 /* run_configuration.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = run_configuration.hpp; sourceTree = "<group>"; };
122124
9464E5EF2FA7466900D82BAD /* symbolScale.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = symbolScale.hpp; sourceTree = "<group>"; };
123125
9464E5F02FA7467200D82BAD /* symbolScale.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = symbolScale.mm; sourceTree = "<group>"; };
124126
94674B822D533B1D00973137 /* trade.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = trade.hpp; sourceTree = "<group>"; };
@@ -1333,6 +1335,7 @@
13331335
941B548F2D3BBA3B00E3BF64 /* trading_definitions */ = {
13341336
isa = PBXGroup;
13351337
children = (
1338+
945F475D2FD5614600D19164 /* run_configuration.hpp */,
13361339
941B54972D3BBAA200E3BF64 /* configuration.hpp */,
13371340
941B54942D3BBA8300E3BF64 /* strategy.hpp */,
13381341
941B54932D3BBA7300E3BF64 /* strategy_variables.hpp */,
@@ -1502,6 +1505,7 @@
15021505
94B8C7932D3D770800E17EB6 /* utilities */ = {
15031506
isa = PBXGroup;
15041507
children = (
1508+
945F475C2FD5607E00D19164 /* queueKeys.hpp */,
15051509
943770432FD4351100317424 /* parameterSweep.hpp */,
15061510
94829FC32FCC1D1200710E6E /* env.hpp */,
15071511
942EC55D2FBEF92F00CCBB5D /* redisConnection.hpp */,

include/backtestRunner.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,20 @@
66

77
#pragma once
88
#include <string>
9+
#include <vector>
10+
#include "models/priceData.hpp"
911
#include "trading_definitions/configuration.hpp"
1012

13+
// Pulls all tick data for the run's symbols/window out of QuestDB. Expensive —
14+
// call once per run and reuse the result across that run's strategies.
15+
std::vector<PriceData> loadTicks(const std::string& questdbHost,
16+
const std::string& symbolsCsv,
17+
int lastMonths);
18+
19+
// Runs one backtest against already-loaded ticks (no QuestDB access).
20+
void runBacktestOnTicks(const std::vector<PriceData>& ticks,
21+
const trading_definitions::Configuration& config);
22+
23+
// Convenience for the direct path: loads ticks then runs a single backtest.
1124
int runBacktest(const std::string& questdbHost,
1225
const trading_definitions::Configuration& config);

include/commands/loadCommand.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
#pragma once
88

9-
// Backs the `load` subcommand: LPUSHes a strategy JSON (defined in
10-
// source/commands/loadCommand.cpp) onto the Redis `strategy_queue`.
9+
// Backs the `load` subcommand: for one RUN_ID, LPUSHes every swept strategy onto
10+
// BACKTESTING_QUEUE_STRATEGY:<RUN_ID>, then LPUSHes the run descriptor onto
11+
// BACKTESTING_QUEUE_RUN (see source/commands/loadCommand.cpp).
1112
class LoadCommand {
1213
public:
1314
static int run();

include/commands/runCommand.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
#pragma once
88

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.
9+
// Backs the `run` subcommand: drains BACKTESTING_QUEUE_RUN (loading each run's
10+
// QuestDB ticks once, then running its strategies), or runs a single Base64
11+
// Configuration supplied directly on the command line.
1112
class RunCommand {
1213
public:
1314
static int run(int argc, const char* argv[]);

include/jsonParser.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@
1313
class JsonParser {
1414
public:
1515
static trading_definitions::Configuration parseConfigurationFromBase64(const std::string& input);
16+
static trading_definitions::RunConfiguration parseRunConfigurationFromBase64(const std::string& input);
17+
static trading_definitions::Strategy parseStrategyFromBase64(const std::string& input);
1618
};

include/redisLoader.hpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,23 @@
77
#pragma once
88

99
#include <string>
10+
#include <vector>
1011

1112
// LPUSH pairs with RedisRunner's RPOP so consumers observe FIFO ordering.
1213
class RedisLoader {
1314
public:
14-
static int load(const std::string& rawJson,
15-
const std::string& redisHost = "127.0.0.1",
16-
int redisPort = 6379,
17-
const std::string& queueKey = "strategy_queue");
18-
15+
// LPUSHes a single Base64-encoded payload onto queueKey without assuming a
16+
// payload type (run descriptor or strategy).
1917
static int loadPayload(const std::string& redisHost,
2018
int redisPort,
2119
const std::string& queueKey,
2220
const std::string& rawJson);
21+
22+
// LPUSHes many payloads onto queueKey over one connection. Each payload is
23+
// Base64-encoded; order is preserved (RedisRunner's RPOP then yields them
24+
// in insertion order).
25+
static int loadPayloadBatch(const std::string& redisHost,
26+
int redisPort,
27+
const std::string& queueKey,
28+
const std::vector<std::string>& rawJsonPayloads);
2329
};

include/redisRunner.hpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
#pragma once
88
#include <string>
99

10+
// Worker entry point. Drains BACKTESTING_QUEUE_RUN: for each run it loads the
11+
// QuestDB tick data once, then drains that run's per-RUN_ID strategy list,
12+
// running every strategy against the cached ticks. Safe to launch many workers
13+
// concurrently — they peek the same run, load ticks once each, and compete on
14+
// RPOP of the shared strategy list.
1015
class RedisRunner {
1116
public:
1217
static int run(const std::string& questdbHost,
1318
const std::string& redisHost = "127.0.0.1",
14-
int redisPort = 6379,
15-
const std::string& queueKey = "strategy_queue");
19+
int redisPort = 6379);
1620
};

include/trading_definitions.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
#include "trading_definitions/strategy_variables.hpp"
1212
#include "trading_definitions/strategy.hpp"
1313
#include "trading_definitions/configuration.hpp"
14+
#include "trading_definitions/run_configuration.hpp"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
#include <string>
9+
#include <nlohmann/json.hpp>
10+
11+
namespace trading_definitions {
12+
// Run-level descriptor: the unit of QuestDB tick data shared by every strategy
13+
// in a sweep. Carried on BACKTESTING_QUEUE_RUN and linked to its strategies via
14+
// RUN_ID (see queueKeys.hpp). The runner reassembles a full Configuration from
15+
// this plus each popped Strategy.
16+
struct RunConfiguration {
17+
std::string RUN_ID;
18+
std::string SYMBOLS;
19+
int LAST_MONTHS;
20+
};
21+
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(RunConfiguration,
22+
RUN_ID,
23+
SYMBOLS,
24+
LAST_MONTHS
25+
);
26+
27+
};

0 commit comments

Comments
 (0)