Skip to content

Commit 7ea4952

Browse files
authored
Adding elasticsearch client (#42)
* Adding elasticsearch client * updated build * Fixing some of the code smells * Updated trade tracking to post to elasticsearch and switched to using env variables with Infisical * fixed references in xcode
1 parent 3be1db3 commit 7ea4952

23 files changed

Lines changed: 412 additions & 49 deletions

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
with:
8080
fetch-depth: 0
8181
- name: Install dependencies
82-
run: sudo apt install -y libssl-dev libpq-dev
82+
run: sudo apt install -y libssl-dev libpq-dev libcurl4-openssl-dev
8383
# Ubuntu's apt Boost (1.83 on 24.04) predates Boost.Redis, which was added
8484
# in Boost 1.84, so <boost/redis.hpp> is absent. Install a newer Boost.
8585
# Boost.Redis/Asio are header-only, so we only need the headers plus the

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,13 @@ endif()
7070
find_package(Boost REQUIRED CONFIG)
7171
find_package(OpenSSL REQUIRED)
7272
find_package(Threads REQUIRED)
73+
find_package(CURL REQUIRED)
7374
target_link_libraries(BacktestingEngineLib PUBLIC
7475
Boost::headers
7576
OpenSSL::SSL
7677
OpenSSL::Crypto
77-
Threads::Threads)
78+
Threads::Threads
79+
CURL::libcurl)
7880

7981
add_subdirectory(external/boost-decimal)
8082
target_link_libraries(BacktestingEngineLib PUBLIC Boost::decimal)

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,28 @@ Xcode - Library Path
8787

8888
`bash ./scripts/build.sh`
8989

90+
### Environment variables
91+
92+
The engine reads its connection configuration from the environment. The following variables are **required**`scripts/run.sh` validates them up front and aborts if any are missing or empty:
93+
94+
| Variable | Used for |
95+
| --- | --- |
96+
| `ELASTIC_HOST` | Elasticsearch base URL that trading results are PUT to (e.g. `https://elastic.example.com:9200`) |
97+
| `ELASTIC_USER` | Elasticsearch HTTP basic-auth username |
98+
| `ELASTIC_USER_PASSWORD` | Elasticsearch HTTP basic-auth password |
99+
| `REDIS_HOST` | Redis host for the `strategy_queue` list |
100+
101+
I manage these secrets with [Infisical](https://infisical.com/), which injects them into the process environment at runtime, so I run the engine with:
102+
103+
```
104+
infisical run -- sh ./scripts/run.sh
105+
```
106+
107+
If you're not using Infisical, export the variables yourself (e.g. via your shell profile or a sourced `.env`) before invoking the script.
108+
90109
### Run via terminal
91110

92-
`bash ./scripts/run.sh` builds the project, then, if `redis-cli ping` reaches a local Redis, enqueues an inline JSON strategy via `load` and executes it via `run localhost`. If Redis is unreachable the script prints a message and exits cleanly (see `scripts/run.sh:22-25`), so first-time users without Redis still get a clear signal.
111+
`bash ./scripts/run.sh` builds the project, then, if `redis-cli ping` reaches a local Redis, enqueues an inline JSON strategy via `load` and executes it via `run localhost`. If Redis is unreachable the script prints a message and exits cleanly (see `scripts/run.sh:22-25`), so first-time users without Redis still get a clear signal. The script requires the [environment variables](#environment-variables) listed above; with Infisical that becomes `infisical run -- sh ./scripts/run.sh`.
93112

94113
The `BacktestingEngine` binary exposes a subcommand CLI:
95114

backtesting-engine-cpp.xcodeproj/project.pbxproj

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
942EC56A2FBEF95000CCBB5D /* backtestRunner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 942EC5642FBEF95000CCBB5D /* backtestRunner.cpp */; };
2626
942EC56B2FBEF95000CCBB5D /* redisLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 942EC5652FBEF95000CCBB5D /* redisLoader.cpp */; };
2727
942EC56C2FBEF95000CCBB5D /* redisRunner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 942EC5662FBEF95000CCBB5D /* redisRunner.cpp */; };
28+
942FDDE02FC5C8B20096F318 /* tradingResults.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 942FDDDF2FC5C8B20096F318 /* tradingResults.cpp */; };
29+
942FDDE12FC5C8B20096F318 /* elasticClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 942FDDDE2FC5C8B20096F318 /* elasticClient.cpp */; };
30+
942FDDE22FC5C8B20096F318 /* tradingResults.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 942FDDDF2FC5C8B20096F318 /* tradingResults.cpp */; };
31+
942FDDE32FC5C8B20096F318 /* elasticClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 942FDDDE2FC5C8B20096F318 /* elasticClient.cpp */; };
32+
942FDDE52FC5C9D30096F318 /* libcurl.4.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 942FDDE42FC5C9D30096F318 /* libcurl.4.tbd */; };
33+
942FDDE62FC5C9DB0096F318 /* libcurl.4.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 942FDDE42FC5C9D30096F318 /* libcurl.4.tbd */; };
2834
943398242D57E53400287A2D /* jsonParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 943398232D57E53400287A2D /* jsonParser.cpp */; };
2935
943398252D57E53400287A2D /* jsonParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 943398232D57E53400287A2D /* jsonParser.cpp */; };
3036
943398272D57E54000287A2D /* jsonParser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 943398262D57E54000287A2D /* jsonParser.mm */; };
@@ -41,6 +47,8 @@
4147
9470B5B62C8C5BFD007D9CC6 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9470B5A32C8C5AD0007D9CC6 /* main.cpp */; };
4248
94724A832F8B92C10029B940 /* operations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94724A822F8B92C10029B940 /* operations.cpp */; };
4349
94724A842F8B92C10029B940 /* operations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94724A822F8B92C10029B940 /* operations.cpp */; };
50+
94829FC52FCC1D1A00710E6E /* env.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94829FC42FCC1D1A00710E6E /* env.cpp */; };
51+
94829FC62FCC1D1A00710E6E /* env.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94829FC42FCC1D1A00710E6E /* env.cpp */; };
4452
94CD8BA02D2E8CE500041BBA /* databaseConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */; };
4553
94CD8BA12D2E8CE500041BBA /* databaseConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */; };
4654
94D3A7262FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94D3A7242FC1B3AD00EBEA32 /* loadCommand.cpp */; };
@@ -90,6 +98,11 @@
9098
942EC5642FBEF95000CCBB5D /* backtestRunner.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = backtestRunner.cpp; sourceTree = "<group>"; };
9199
942EC5652FBEF95000CCBB5D /* redisLoader.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = redisLoader.cpp; sourceTree = "<group>"; };
92100
942EC5662FBEF95000CCBB5D /* redisRunner.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = redisRunner.cpp; sourceTree = "<group>"; };
101+
942FDDDC2FC5C8950096F318 /* elasticClient.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = elasticClient.hpp; sourceTree = "<group>"; };
102+
942FDDDD2FC5C8A30096F318 /* tradingResults.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = tradingResults.hpp; sourceTree = "<group>"; };
103+
942FDDDE2FC5C8B20096F318 /* elasticClient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = elasticClient.cpp; sourceTree = "<group>"; };
104+
942FDDDF2FC5C8B20096F318 /* tradingResults.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tradingResults.cpp; sourceTree = "<group>"; };
105+
942FDDE42FC5C9D30096F318 /* libcurl.4.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcurl.4.tbd; path = usr/lib/libcurl.4.tbd; sourceTree = SDKROOT; };
93106
943398222D57E52900287A2D /* jsonParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsonParser.hpp; sourceTree = "<group>"; };
94107
943398232D57E53400287A2D /* jsonParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = jsonParser.cpp; sourceTree = "<group>"; };
95108
943398262D57E54000287A2D /* jsonParser.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = jsonParser.mm; sourceTree = "<group>"; };
@@ -119,6 +132,8 @@
119132
9470B5AC2C8C5B99007D9CC6 /* tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
120133
94724A822F8B92C10029B940 /* operations.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = operations.cpp; sourceTree = "<group>"; };
121134
94724A852F8B92E30029B940 /* operations.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = operations.hpp; sourceTree = "<group>"; };
135+
94829FC32FCC1D1200710E6E /* env.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = env.hpp; sourceTree = "<group>"; };
136+
94829FC42FCC1D1A00710E6E /* env.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = env.cpp; sourceTree = "<group>"; };
122137
948A9CCD2C906A5600E23669 /* CONVENTIONS.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONVENTIONS.md; sourceTree = "<group>"; };
123138
94BBA4512D2EA2640010E04D /* build.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build.sh; sourceTree = "<group>"; };
124139
94C331A02FA899A8006BD690 /* decimal_json.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = decimal_json.hpp; sourceTree = "<group>"; };
@@ -1296,13 +1311,15 @@
12961311
isa = PBXFrameworksBuildPhase;
12971312
buildActionMask = 2147483647;
12981313
files = (
1314+
942FDDE62FC5C9DB0096F318 /* libcurl.4.tbd in Frameworks */,
12991315
);
13001316
runOnlyForDeploymentPostprocessing = 0;
13011317
};
13021318
9470B5A92C8C5B99007D9CC6 /* Frameworks */ = {
13031319
isa = PBXFrameworksBuildPhase;
13041320
buildActionMask = 2147483647;
13051321
files = (
1322+
942FDDE52FC5C9D30096F318 /* libcurl.4.tbd in Frameworks */,
13061323
);
13071324
runOnlyForDeploymentPostprocessing = 0;
13081325
};
@@ -1333,6 +1350,7 @@
13331350
94280BA72D2FC29F00F1CF56 /* utilities */ = {
13341351
isa = PBXGroup;
13351352
children = (
1353+
94829FC42FCC1D1A00710E6E /* env.cpp */,
13361354
942EC5612FBEF94700CCBB5D /* redisConnection.cpp */,
13371355
94280BA22D2FC00200F1CF56 /* base64.cpp */,
13381356
943398232D57E53400287A2D /* jsonParser.cpp */,
@@ -1436,6 +1454,8 @@
14361454
94280BA72D2FC29F00F1CF56 /* utilities */,
14371455
942EC5642FBEF95000CCBB5D /* backtestRunner.cpp */,
14381456
942EC5652FBEF95000CCBB5D /* redisLoader.cpp */,
1457+
942FDDDE2FC5C8B20096F318 /* elasticClient.cpp */,
1458+
942FDDDF2FC5C8B20096F318 /* tradingResults.cpp */,
14391459
942EC5662FBEF95000CCBB5D /* redisRunner.cpp */,
14401460
9470B5A32C8C5AD0007D9CC6 /* main.cpp */,
14411461
940A61112C92CE210083FEB8 /* configManager.cpp */,
@@ -1461,6 +1481,7 @@
14611481
94B8C7932D3D770800E17EB6 /* utilities */ = {
14621482
isa = PBXGroup;
14631483
children = (
1484+
94829FC32FCC1D1200710E6E /* env.hpp */,
14641485
942EC55D2FBEF92F00CCBB5D /* redisConnection.hpp */,
14651486
94C331A02FA899A8006BD690 /* decimal_json.hpp */,
14661487
94280BA12D2FC00200F1CF56 /* base64.hpp */,
@@ -2218,6 +2239,7 @@
22182239
94CD87342D2D2EE100041BBA /* Frameworks */ = {
22192240
isa = PBXGroup;
22202241
children = (
2242+
942FDDE42FC5C9D30096F318 /* libcurl.4.tbd */,
22212243
94CD8B982D2DCDD800041BBA /* libpqxx-7.10.a */,
22222244
94CD8B9A2D2DCF6E00041BBA /* libpqxx-7.10.a */,
22232245
94CD8B712D2D34C800041BBA /* config */,
@@ -3579,17 +3601,19 @@
35793601
isa = PBXGroup;
35803602
children = (
35813603
94D3A7232FC1B3A600EBEA32 /* commands */,
3582-
942EC55E2FBEF93A00CCBB5D /* backtestRunner.hpp */,
3583-
942EC55F2FBEF93A00CCBB5D /* redisLoader.hpp */,
3584-
942EC5602FBEF93A00CCBB5D /* redisRunner.hpp */,
3585-
94D601132FA9CD890066F51A /* strategies */,
3586-
94674B842D533B2F00973137 /* trading */,
35873604
942966D72D48E84100532862 /* models */,
3605+
94674B842D533B2F00973137 /* trading */,
3606+
94D601132FA9CD890066F51A /* strategies */,
35883607
94B8C7932D3D770800E17EB6 /* utilities */,
35893608
941B548F2D3BBA3B00E3BF64 /* trading_definitions */,
35903609
941B549C2D3BBFB900E3BF64 /* trading_definitions.hpp */,
3610+
942EC55E2FBEF93A00CCBB5D /* backtestRunner.hpp */,
3611+
942EC55F2FBEF93A00CCBB5D /* redisLoader.hpp */,
3612+
942EC5602FBEF93A00CCBB5D /* redisRunner.hpp */,
35913613
940A61162C92CE960083FEB8 /* serviceA.hpp */,
3614+
942FDDDC2FC5C8950096F318 /* elasticClient.hpp */,
35923615
943398222D57E52900287A2D /* jsonParser.hpp */,
3616+
942FDDDD2FC5C8A30096F318 /* tradingResults.hpp */,
35933617
940A61122C92CE210083FEB8 /* configManager.hpp */,
35943618
941408B02D59F954000ED1F9 /* sqlManager.hpp */,
35953619
94724A852F8B92E30029B940 /* operations.hpp */,
@@ -3695,9 +3719,12 @@
36953719
94D3A7272FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */,
36963720
9470B5A42C8C5AD0007D9CC6 /* main.cpp in Sources */,
36973721
943398252D57E53400287A2D /* jsonParser.cpp in Sources */,
3722+
942FDDE02FC5C8B20096F318 /* tradingResults.cpp in Sources */,
3723+
942FDDE12FC5C8B20096F318 /* elasticClient.cpp in Sources */,
36983724
94D3A72A2FC1B41500EBEA32 /* runCommand.cpp in Sources */,
36993725
942EC56A2FBEF95000CCBB5D /* backtestRunner.cpp in Sources */,
37003726
942EC56B2FBEF95000CCBB5D /* redisLoader.cpp in Sources */,
3727+
94829FC52FCC1D1A00710E6E /* env.cpp in Sources */,
37013728
942EC56C2FBEF95000CCBB5D /* redisRunner.cpp in Sources */,
37023729
94280BA32D2FC00200F1CF56 /* base64.cpp in Sources */,
37033730
94674B8E2D533E7800973137 /* trade.cpp in Sources */,
@@ -3732,11 +3759,14 @@
37323759
94D3A7292FC1B41500EBEA32 /* runCommand.cpp in Sources */,
37333760
94D601102FA9CD700066F51A /* randomStrategy.cpp in Sources */,
37343761
94674B8A2D533BDA00973137 /* tradeManager.mm in Sources */,
3762+
94829FC62FCC1D1A00710E6E /* env.cpp in Sources */,
37353763
94724A832F8B92C10029B940 /* operations.cpp in Sources */,
37363764
940A61182C92CE960083FEB8 /* serviceA.cpp in Sources */,
37373765
94674B882D533B4000973137 /* tradeManager.cpp in Sources */,
37383766
946EFF7F2FB9F44E008D9647 /* reporting.cpp in Sources */,
37393767
943398272D57E54000287A2D /* jsonParser.mm in Sources */,
3768+
942FDDE22FC5C8B20096F318 /* tradingResults.cpp in Sources */,
3769+
942FDDE32FC5C8B20096F318 /* elasticClient.cpp in Sources */,
37403770
9470B5B62C8C5BFD007D9CC6 /* main.cpp in Sources */,
37413771
94364CB62D416D8D00F35B55 /* db.mm in Sources */,
37423772
940A61142C92CE210083FEB8 /* configManager.cpp in Sources */,

include/elasticClient.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
#include "tradingResults.hpp"
10+
11+
// Minimal Elasticsearch HTTP client — PUT-only, for indexing TradingResults.
12+
// Host is read from $ELASTIC_HOST (default http://localhost:9200) with optional
13+
// HTTP basic auth from $ELASTIC_USER / $ELASTIC_USER_PASSWORD; docs land in
14+
// index "trading_results" with a freshly generated UUID per put.
15+
class ElasticClient {
16+
public:
17+
static int putTradingResults(const TradingResults& results);
18+
};

include/strategies/strategy.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#include "models/trade.hpp"
1212

1313
// Forward declaration. We only refer to `TradeManager` by reference in
14-
// this header, so the compiler doesn't need its full definition here
14+
// this header, so the compiler doesn't need its full definition here,
1515
// just to know "it's a class". Pulling in the full header would drag
1616
// every TradeManager dependency into every strategy. C# doesn't really
1717
// have an equivalent because it resolves types at the assembly level
@@ -25,7 +25,7 @@ class TradeManager;
2525
// - This is the C++ equivalent of a C# `interface`. C++ has no
2626
// dedicated `interface` keyword, so you express it as a class
2727
// whose methods are all pure virtual (the `= 0` suffix). The
28-
// `I` prefix is borrowed from C# C++ has no fixed convention,
28+
// `I` prefix is borrowed from C#, C++ has no fixed convention,
2929
// but it's a useful hint because C++ classes routinely mix
3030
// virtual and concrete methods, so "is this an interface?" isn't
3131
// always obvious from the keyword alone.
@@ -34,7 +34,7 @@ class TradeManager;
3434
// destructor would skip the derived destructor and leak resources.
3535
// C# handles this for you; in C++ you opt in.
3636
// - `= 0` makes a method pure virtual, which makes the class
37-
// abstract you cannot instantiate `IStrategy` directly, only
37+
// abstract, you cannot instantiate `IStrategy` directly, only
3838
// concrete subclasses. Same behaviour as a C# interface.
3939
// - Derived classes annotate their implementations with `override`
4040
// (see `RandomStrategy`). It's optional in C++ but catches
@@ -52,7 +52,7 @@ class IStrategy {
5252
// reference so strategies can both inspect open positions
5353
// (`tradeManager.getActiveTrades()`) and act on them
5454
// (`closeTrade`, future scale/adjust hooks). A reference signals
55-
// "borrow, don't own" the strategy must not delete it. The C#
55+
// "borrow, don't own", the strategy must not delete it. The C#
5656
// analogue is just passing the manager as a parameter; C# has no
5757
// distinction between reference and pointer so the by-ref nature
5858
// is implicit there.

include/trading/reporting.hpp

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

77
#pragma once
88
#include "tradeManager.hpp"
9+
#include "tradingResults.hpp"
910

1011
class Reporting {
1112

1213
public:
14+
static TradingResultsStats collect(const TradeManager& tradeManager);
1315
static void summarise(const TradeManager& tradeManager);
1416
};

include/tradingResults.hpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
#include <cstddef>
10+
#include <optional>
11+
#include <string>
12+
13+
#include <boost/decimal.hpp>
14+
#include <nlohmann/json.hpp>
15+
16+
#include "utilities/decimal_json.hpp"
17+
#include "trading_definitions.hpp"
18+
19+
// Per-run summary mirroring what Reporting::summarise prints today.
20+
struct TradingResultsStats {
21+
boost::decimal::decimal64_t finalPnl;
22+
std::size_t tradesOpened;
23+
std::size_t tradesClosed;
24+
std::size_t openedLong;
25+
std::size_t openedShort;
26+
std::size_t closedLong;
27+
std::size_t closedShort;
28+
std::size_t winners;
29+
std::size_t losers;
30+
std::size_t breakeven;
31+
std::optional<boost::decimal::decimal64_t> avgPnl;
32+
};
33+
34+
// Wire shape pushed to Elasticsearch: the input Configuration plus the run's
35+
// output stats. Serialises the timestamp as `@timestamp` for Kibana.
36+
struct TradingResults {
37+
std::string RUN_ID;
38+
std::string timestamp;
39+
trading_definitions::Configuration config;
40+
TradingResultsStats results;
41+
42+
static std::string nowIsoUtc();
43+
};
44+
45+
void to_json(nlohmann::json& j, const TradingResultsStats& s);
46+
void to_json(nlohmann::json& j, const TradingResults& r);

include/utilities/env.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
#include <string>
10+
11+
namespace env {
12+
13+
// Returns $name, or `fallback` when the variable is unset or empty.
14+
std::string getOr(const char* name, std::string fallback);
15+
16+
} // namespace env

scripts/run.sh

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
#!/bin/bash
22
# This executes the run script
33

4+
required_vars=(ELASTIC_HOST ELASTIC_USER ELASTIC_USER_PASSWORD REDIS_HOST)
5+
missing=()
6+
for var in "${required_vars[@]}"; do
7+
[[ -z "${!var}" ]] && missing+=("$var")
8+
done
9+
if [[ ${#missing[@]} -gt 0 ]]; then
10+
echo "Error: missing required environment variables: ${missing[*]}"
11+
exit 1
12+
fi
13+
414
current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
515

616
# Build the source code
@@ -24,10 +34,6 @@ if ! redis-cli -h localhost ping >/dev/null 2>&1; then
2434
exit 0
2535
fi
2636

27-
if ! ./"$BUILD_DIR/$EXECUTABLE_NAME" load; then
28-
exit 1
29-
fi
30-
3137
start_time=$(date +%s%N)
3238
# Invoke the `run` subcommand: BacktestingEngine pops a Base64-encoded
3339
# strategy off the Redis `strategy_queue` and executes it against the

0 commit comments

Comments
 (0)