Skip to content

Commit ada8e28

Browse files
authored
Added performance metrics and some refactoring (#52)
1 parent 1519951 commit ada8e28

35 files changed

Lines changed: 531 additions & 174 deletions

‎CMakeLists.txt‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,10 @@ find_package(OpenMP REQUIRED)
195195
string(APPEND CMAKE_CXX_FLAGS " ${OpenMP_CXX_FLAGS}")
196196

197197
# Boost (Homebrew) — Boost.Redis is header-only but its implementation is
198-
# compiled into one dedicated TU via <boost/redis/src.hpp> (source/
199-
# boostRedisImpl.cpp), which isolates its macro/SSL pollution from the rest of
200-
# the build. It pulls in Boost.Asio's SSL layer, which needs OpenSSL.
198+
# compiled into one dedicated TU via <boost/redis/src.hpp>
199+
# (source/shared/redis/connection/boostRedisImpl.cpp), which isolates its
200+
# macro/SSL pollution from the rest of the build. It pulls in Boost.Asio's SSL
201+
# layer, which needs OpenSSL.
201202
find_package(Boost REQUIRED CONFIG)
202203
find_package(OpenSSL REQUIRED)
203204
find_package(Threads REQUIRED)

‎source/load/loadCommand.cppm‎

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,50 +14,22 @@ module;
1414
#include "shared/utilities/env.hpp"
1515
#include "shared/utilities/parameterSweep.hpp"
1616
#include "shared/utilities/queueKeys.hpp"
17-
#include "shared/redis/redisLoader.hpp"
18-
#include "shared/tradingDefinitions/runConfiguration.hpp" // RunConfiguration (json conversion)
19-
#include "shared/tradingDefinitions/strategy.hpp"
17+
#include "shared/redis/producer/redisLoader.hpp"
18+
#include "shared/tradingDefinitions/config/runConfiguration.hpp" // RunConfiguration -> json
19+
#include "shared/tradingDefinitions/strategy.hpp" // Strategy -> json (makeStrategy result)
2020

2121
export module loadCommand;
2222

2323
import std; // replaces <print>, <ranges>, <string>, <vector>
2424
import randomStrategySweep; // buildRandomStrategySweep
2525
import runConfigurationBuilder; // makeRunConfiguration
26+
import makeStrategy; // sweep::makeStrategy
2627

2728
export class LoadCommand {
2829
public:
2930
static int run();
3031
};
3132

32-
using tradingDefinitions::Strategy;
33-
34-
Strategy makeStrategy(const sweep::Combination& combo) {
35-
using namespace tradingDefinitions;
36-
37-
return Strategy{
38-
// Each parameter combination gets its own UUID so a single backtest
39-
// result is uniquely identifiable and traceable back to its inputs.
40-
.UUID = boost::uuids::to_string(boost::uuids::random_generator()()),
41-
.TRADING_VARIABLES = TradingVariables{
42-
.STRATEGY = "RandomStrategy",
43-
.STOP_DISTANCE_IN_PIPS = combo.getInt("STOP_DISTANCE_IN_PIPS"),
44-
.LIMIT_DISTANCE_IN_PIPS = combo.getInt("LIMIT_DISTANCE_IN_PIPS"),
45-
.TRADING_SIZE = 1,
46-
},
47-
.OHLC_VARIABLES = {
48-
OHLCVariables{
49-
// Only read OHLC params when the sweep actually registers them;
50-
// they default to 0 otherwise (see buildRandomStrategySweep).
51-
.OHLC_COUNT = combo.has("OHLC_COUNT") ? combo.getInt("OHLC_COUNT") : 0,
52-
.OHLC_MINUTES = combo.has("OHLC_MINUTES") ? combo.getInt("OHLC_MINUTES") : 0,
53-
},
54-
},
55-
.STRATEGY_VARIABLES = StrategyVariables{
56-
.OHLC_RSI_VARIABLES = OHLCRSIVariables{.RSI_LONG = 60, .RSI_SHORT = 40},
57-
},
58-
};
59-
}
60-
6133
int LoadCommand::run() {
6234

6335
// One RUN_ID identifies the whole sweep; each combination becomes its own
@@ -79,7 +51,7 @@ int LoadCommand::run() {
7951
const auto strategyPayloads = combinations
8052
| std::views::transform([](const sweep::Combination& combo) {
8153
// Pin to JSON explicitly to trigger implicit Strategy conversion
82-
const nlohmann::json j = makeStrategy(combo);
54+
const nlohmann::json j = sweep::makeStrategy(combo);
8355
return j.dump();
8456
})
8557
| std::ranges::to<std::vector>();

‎source/load/makeStrategy.cppm‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
module;
8+
9+
#include <boost/uuid/random_generator.hpp>
10+
#include <boost/uuid/uuid_io.hpp>
11+
12+
#include "shared/utilities/parameterSweep.hpp" // sweep::Combination
13+
#include "shared/tradingDefinitions/strategy.hpp"
14+
15+
export module makeStrategy;
16+
17+
import std;
18+
19+
export namespace sweep {
20+
21+
// Map one swept parameter combination onto a concrete Strategy. Each
22+
// combination gets its own UUID so a single backtest result is uniquely
23+
// identifiable and traceable back to its inputs.
24+
tradingDefinitions::Strategy makeStrategy(const sweep::Combination& combo) {
25+
using namespace tradingDefinitions;
26+
27+
return Strategy{
28+
.UUID = boost::uuids::to_string(boost::uuids::random_generator()()),
29+
.TRADING_VARIABLES = TradingVariables{
30+
.STRATEGY = "RandomStrategy",
31+
.STOP_DISTANCE_IN_PIPS = combo.getInt("STOP_DISTANCE_IN_PIPS"),
32+
.LIMIT_DISTANCE_IN_PIPS = combo.getInt("LIMIT_DISTANCE_IN_PIPS"),
33+
.TRADING_SIZE = 1,
34+
},
35+
.OHLC_VARIABLES = {
36+
OHLCVariables{
37+
// Only read OHLC params when the sweep actually registers them;
38+
// they default to 0 otherwise (see buildRandomStrategySweep).
39+
.OHLC_COUNT = combo.has("OHLC_COUNT") ? combo.getInt("OHLC_COUNT") : 0,
40+
.OHLC_MINUTES = combo.has("OHLC_MINUTES") ? combo.getInt("OHLC_MINUTES") : 0,
41+
},
42+
},
43+
.STRATEGY_VARIABLES = StrategyVariables{
44+
.OHLC_RSI_VARIABLES = OHLCRSIVariables{.RSI_LONG = 60, .RSI_SHORT = 40},
45+
},
46+
};
47+
}
48+
49+
} // namespace sweep

‎source/load/sweep/runConfigurationBuilder.cppm‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module;
99
#include <boost/decimal.hpp>
1010
#include <boost/decimal/literals.hpp>
1111

12-
#include "shared/tradingDefinitions/runConfiguration.hpp"
12+
#include "shared/tradingDefinitions/config/runConfiguration.hpp"
1313

1414
export module runConfigurationBuilder;
1515

‎source/main.cpp‎

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,24 @@
44
// This code is licensed under MIT license (see LICENSE.txt for details)
55
// ---------------------------------------
66

7-
import std; // replaces <iostream>, <string_view>
7+
import std;
88

99
// backtesting engine headers
1010
import loadCommand;
1111
import runCommand;
1212

13-
// Entry point
1413
int main(const int argc, const char* argv[]) {
1514

16-
if (argc < 2) {
17-
std::cerr << "BacktestingEngine: missing a subcommand. See README.md for usage"
18-
<< std::endl;
19-
return 1;
20-
}
15+
if (argc < 2) {
16+
std::println(std::cerr, "Error: missing subcommand");
17+
return 1;
18+
}
2119

22-
const std::string_view subcommand = argv[1];
20+
const std::string_view subcommand = argv[1];
2321

24-
// Two paths, load or run
25-
if (subcommand == "load") return LoadCommand::run();
26-
if (subcommand == "run") return RunCommand::run(argc, argv);
22+
if (subcommand == "load") return LoadCommand::run();
23+
if (subcommand == "run") return RunCommand::run(argc, argv);
2724

28-
std::cerr << "BacktestingEngine: unknown subcommand. See README.md for usage."
29-
<< std::endl;
30-
31-
return 1;
32-
}
25+
std::println(std::cerr, "Error: unknown subcommand '{}'.", subcommand);
26+
return 1;
27+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
module;
88

99
#include "shared/utilities/env.hpp"
10-
#include "shared/tradingDefinitions/configuration.hpp"
10+
#include "shared/tradingDefinitions/config/configuration.hpp"
1111

1212
export module backtestRunner;
1313

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// This code is licensed under MIT license (see LICENSE.txt for details)
55
// ---------------------------------------
66

7-
#include "shared/redis/runnerBridge.hpp"
7+
#include "run/execution/runnerBridge.hpp"
88

99
import std;
1010
import priceData; // PriceData
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#pragma once
77
#include <memory>
88
#include <string>
9-
#include "shared/tradingDefinitions/configuration.hpp"
9+
#include "shared/tradingDefinitions/config/configuration.hpp"
1010

1111
// Opaque handle to a run's loaded tick data, defined in runnerBridge.cpp.
1212
//

‎source/run/operations.cppm‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
module;
88

99
#include "shared/utilities/backtestLog.hpp"
10-
#include "shared/tradingDefinitions/configuration.hpp"
10+
#include "shared/tradingDefinitions/config/configuration.hpp"
1111
#include "run/reporting/tradingResults.hpp"
1212

1313
export module operations;
@@ -76,7 +76,7 @@ void Operations::run(const std::vector<PriceData>& ticks,
7676
trading::runTicks(tradeManager, *strategy, ticks,
7777
config.STRATEGY.TRADING_VARIABLES, riskLimits);
7878

79-
ResultsSummary::summarise(tradeManager);
79+
ResultsSummary::summarise(tradeManager, config);
8080

8181
// Elapsed backtest time for this run, measured from the top of run(). The
8282
// Elasticsearch PUT below is deliberately excluded so the duration reflects
@@ -128,7 +128,7 @@ void Operations::run(const std::vector<PriceData>& ticks,
128128
durationSeconds,
129129
reason.str(),
130130
config,
131-
ResultsSummary::collect(tradeManager),
131+
ResultsSummary::collect(tradeManager, config),
132132
};
133133
ElasticClient::putTradingFailure(failure);
134134
} else {
@@ -137,7 +137,7 @@ void Operations::run(const std::vector<PriceData>& ticks,
137137
TradingResults::nowIsoUtc(),
138138
durationSeconds,
139139
config,
140-
ResultsSummary::collect(tradeManager),
140+
ResultsSummary::collect(tradeManager, config),
141141
};
142142
ElasticClient::putTradingResults(results);
143143
}

0 commit comments

Comments
 (0)