Skip to content

Commit 0b05da3

Browse files
authored
- SQL injection fix: symbols from Redis payloads are validated against the canonical symbol_scale table before query interpolation; unknown symbols are rejected with a clear error. (#46)
- JSON parse errors no longer swallowed: parse failures propagate with byte-offset detail instead of continuing into a misleading downstream exception; leftover debug prints removed. - Thread-safety: timestamp-parse date cache moved from function-local statics to caller-owned state (safe if tick loading moves onto the thread pool); unchecked from_chars price parses now throw instead of producing garbage values (also covers NULL DB columns). - Dead code removed: scaffolding files (serviceA, configManager, empty trade.cpp), unused base64/timestamp utilities (one with a ms/µs conversion bug), printResults; Xcode project references scrubbed. - Build fixes: main.cpp no longer compiled into both the static lib and the executable; macOS-only xcrun guarded by if(APPLE); libomp located via brew --prefix (fixes Intel Macs); duplicate find_package(OpenMP) removed; global include_directories converted to target-scoped. - Minor: redundant PnL recomputation in Reporting::collect removed; unnecessary heap allocation of TradeManager replaced with a stack local.
1 parent 4c54837 commit 0b05da3

31 files changed

Lines changed: 617 additions & 429 deletions

CMakeLists.txt

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
cmake_minimum_required(VERSION 3.30)
22

33
# CMAKE_OSX_SYSROOT is a macOS-specific setting that specifies the SDK path.
4-
# This is ignored on non-Apple platforms, so it's safe to include in cross-platform builds.
5-
execute_process(
6-
COMMAND xcrun --show-sdk-path
7-
OUTPUT_VARIABLE CMAKE_OSX_SYSROOT
8-
OUTPUT_STRIP_TRAILING_WHITESPACE
9-
)
4+
if(APPLE)
5+
execute_process(
6+
COMMAND xcrun --show-sdk-path
7+
OUTPUT_VARIABLE CMAKE_OSX_SYSROOT
8+
OUTPUT_STRIP_TRAILING_WHITESPACE
9+
)
10+
endif()
1011

1112
project(BacktestingEngine)
1213

@@ -34,8 +35,19 @@ set(CMAKE_MESSAGE_LOG_LEVEL "WARNING")
3435
# Build libpqxx from source
3536
add_subdirectory(external/libpqxx EXCLUDE_FROM_ALL)
3637

37-
# Include directories
38-
include_directories(
38+
# Collect all .cpp files in the src directory
39+
file(GLOB_RECURSE SOURCES "source/*.cpp")
40+
41+
# main.cpp belongs to the executable only — keeping it in the library too
42+
# means it gets compiled twice and the lib carries a competing main().
43+
list(REMOVE_ITEM SOURCES ${CMAKE_SOURCE_DIR}/source/main.cpp)
44+
45+
# Create a library of your project's code
46+
add_library(BacktestingEngineLib STATIC ${SOURCES})
47+
48+
# Include directories — PUBLIC so they propagate to the executable (and any
49+
# other consumer) through target_link_libraries.
50+
target_include_directories(BacktestingEngineLib PUBLIC
3951
${CMAKE_SOURCE_DIR}/include
4052
${CMAKE_SOURCE_DIR}/include/commands
4153
${CMAKE_SOURCE_DIR}/include/utilities
@@ -47,22 +59,23 @@ include_directories(
4759
${CMAKE_SOURCE_DIR}/external
4860
)
4961

50-
# Collect all .cpp files in the src directory
51-
file(GLOB_RECURSE SOURCES "source/*.cpp")
52-
53-
# Create a library of your project's code
54-
add_library(BacktestingEngineLib STATIC ${SOURCES})
55-
5662
# Configure OpenMP. On Apple, provide Homebrew libomp hints before discovery.
63+
# brew --prefix resolves the right location on both Apple Silicon
64+
# (/opt/homebrew) and Intel (/usr/local).
5765
if(APPLE)
66+
execute_process(
67+
COMMAND brew --prefix libomp
68+
OUTPUT_VARIABLE LIBOMP_PREFIX
69+
OUTPUT_STRIP_TRAILING_WHITESPACE
70+
)
5871
set(OpenMP_C_FLAGS "-Xclang -fopenmp")
5972
set(OpenMP_CXX_FLAGS "-Xclang -fopenmp")
6073
set(OpenMP_C_LIB_NAMES "omp")
6174
set(OpenMP_CXX_LIB_NAMES "omp")
62-
set(OpenMP_omp_LIBRARY /opt/homebrew/opt/libomp/lib/libomp.dylib)
63-
find_package(OpenMP REQUIRED)
64-
target_include_directories(BacktestingEngineLib PRIVATE /opt/homebrew/opt/libomp/include)
75+
set(OpenMP_omp_LIBRARY ${LIBOMP_PREFIX}/lib/libomp.dylib)
76+
target_include_directories(BacktestingEngineLib PRIVATE ${LIBOMP_PREFIX}/include)
6577
endif()
78+
find_package(OpenMP REQUIRED)
6679

6780
# Boost (Homebrew) — Boost.Redis is header-only but its implementation is
6881
# compiled into one dedicated TU via <boost/redis/src.hpp> (source/
@@ -82,7 +95,6 @@ target_link_libraries(BacktestingEngineLib PUBLIC
8295
add_subdirectory(external/boost-decimal)
8396
target_link_libraries(BacktestingEngineLib PUBLIC Boost::decimal)
8497

85-
find_package(OpenMP REQUIRED)
8698
target_link_libraries(BacktestingEngineLib PUBLIC pqxx OpenMP::OpenMP_CXX)
8799

88100
# Main executable

backtesting-engine-cpp.xcodeproj/project.pbxproj

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10-
940A61132C92CE210083FEB8 /* configManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 940A61112C92CE210083FEB8 /* configManager.cpp */; };
11-
940A61142C92CE210083FEB8 /* configManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 940A61112C92CE210083FEB8 /* configManager.cpp */; };
12-
940A61172C92CE960083FEB8 /* serviceA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 940A61152C92CE960083FEB8 /* serviceA.cpp */; };
13-
940A61182C92CE960083FEB8 /* serviceA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 940A61152C92CE960083FEB8 /* serviceA.cpp */; };
1410
941408AE2D59F93F000ED1F9 /* sqlManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 941408AD2D59F93F000ED1F9 /* sqlManager.cpp */; };
1511
941408AF2D59F93F000ED1F9 /* sqlManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 941408AD2D59F93F000ED1F9 /* sqlManager.cpp */; };
1612
941B549A2D3BBADE00E3BF64 /* trading_definitions_json.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 941B54992D3BBADD00E3BF64 /* trading_definitions_json.cpp */; };
@@ -41,8 +37,6 @@
4137
94674B872D533B4000973137 /* tradeManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94674B852D533B4000973137 /* tradeManager.cpp */; };
4238
94674B882D533B4000973137 /* tradeManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94674B852D533B4000973137 /* tradeManager.cpp */; };
4339
94674B8A2D533BDA00973137 /* tradeManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 94674B892D533BDA00973137 /* tradeManager.mm */; };
44-
94674B8D2D533E7800973137 /* trade.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94674B8B2D533E7800973137 /* trade.cpp */; };
45-
94674B8E2D533E7800973137 /* trade.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94674B8B2D533E7800973137 /* trade.cpp */; };
4640
946EFF7E2FB9F44E008D9647 /* reporting.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 946EFF7D2FB9F44E008D9647 /* reporting.cpp */; };
4741
946EFF7F2FB9F44E008D9647 /* reporting.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 946EFF7D2FB9F44E008D9647 /* reporting.cpp */; };
4842
9470B5A42C8C5AD0007D9CC6 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9470B5A32C8C5AD0007D9CC6 /* main.cpp */; };
@@ -75,10 +69,6 @@
7569

7670
/* Begin PBXFileReference section */
7771
9409A61B2FAA6411002C30FF /* strategy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = strategy.hpp; sourceTree = "<group>"; };
78-
940A61112C92CE210083FEB8 /* configManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = configManager.cpp; sourceTree = "<group>"; };
79-
940A61122C92CE210083FEB8 /* configManager.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = configManager.hpp; sourceTree = "<group>"; };
80-
940A61152C92CE960083FEB8 /* serviceA.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = serviceA.cpp; sourceTree = "<group>"; };
81-
940A61162C92CE960083FEB8 /* serviceA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = serviceA.hpp; sourceTree = "<group>"; };
8272
941408AD2D59F93F000ED1F9 /* sqlManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sqlManager.cpp; sourceTree = "<group>"; };
8373
941408B02D59F954000ED1F9 /* sqlManager.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = sqlManager.hpp; sourceTree = "<group>"; };
8474
941B54902D3BBA4900E3BF64 /* ohlc_variables.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ohlc_variables.hpp; sourceTree = "<group>"; };
@@ -126,7 +116,6 @@
126116
94674B832D533B2F00973137 /* tradeManager.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = tradeManager.hpp; sourceTree = "<group>"; };
127117
94674B852D533B4000973137 /* tradeManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tradeManager.cpp; sourceTree = "<group>"; };
128118
94674B892D533BDA00973137 /* tradeManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = tradeManager.mm; sourceTree = "<group>"; };
129-
94674B8B2D533E7800973137 /* trade.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = trade.cpp; sourceTree = "<group>"; };
130119
94674BA02D533B2F00973137 /* exitRules.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = exitRules.hpp; sourceTree = "<group>"; };
131120
94674BA12F8B92C10029B940 /* reviewStopAndLimit.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = reviewStopAndLimit.hpp; sourceTree = "<group>"; };
132121
94685CCE2D384A8B00863D04 /* json.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json.hpp; sourceTree = "<group>"; };
@@ -1455,7 +1444,6 @@
14551444
94674B8C2D533E7800973137 /* models */ = {
14561445
isa = PBXGroup;
14571446
children = (
1458-
94674B8B2D533E7800973137 /* trade.cpp */,
14591447
);
14601448
path = models;
14611449
sourceTree = "<group>";
@@ -1484,8 +1472,6 @@
14841472
942FDDDF2FC5C8B20096F318 /* tradingResults.cpp */,
14851473
942EC5662FBEF95000CCBB5D /* redisRunner.cpp */,
14861474
9470B5A32C8C5AD0007D9CC6 /* main.cpp */,
1487-
940A61112C92CE210083FEB8 /* configManager.cpp */,
1488-
940A61152C92CE960083FEB8 /* serviceA.cpp */,
14891475
94CD8B9F2D2E8CE500041BBA /* databaseConnection.cpp */,
14901476
941408AD2D59F93F000ED1F9 /* sqlManager.cpp */,
14911477
94724A822F8B92C10029B940 /* operations.cpp */,
@@ -3641,10 +3627,8 @@
36413627
942EC55E2FBEF93A00CCBB5D /* backtestRunner.hpp */,
36423628
942EC55F2FBEF93A00CCBB5D /* redisLoader.hpp */,
36433629
942EC5602FBEF93A00CCBB5D /* redisRunner.hpp */,
3644-
940A61162C92CE960083FEB8 /* serviceA.hpp */,
36453630
943398222D57E52900287A2D /* jsonParser.hpp */,
36463631
942FDDDD2FC5C8A30096F318 /* tradingResults.hpp */,
3647-
940A61122C92CE210083FEB8 /* configManager.hpp */,
36483632
941408B02D59F954000ED1F9 /* sqlManager.hpp */,
36493633
94724A852F8B92E30029B940 /* operations.hpp */,
36503634
94CD8B9E2D2E8CE500041BBA /* databaseConnection.hpp */,
@@ -3757,16 +3741,13 @@
37573741
94829FC52FCC1D1A00710E6E /* env.cpp in Sources */,
37583742
942EC56C2FBEF95000CCBB5D /* redisRunner.cpp in Sources */,
37593743
94280BA32D2FC00200F1CF56 /* base64.cpp in Sources */,
3760-
94674B8E2D533E7800973137 /* trade.cpp in Sources */,
37613744
943770402FD42FC000317424 /* elasticClient.cpp in Sources */,
37623745
941B549B2D3BBADE00E3BF64 /* trading_definitions_json.cpp in Sources */,
37633746
946EFF7E2FB9F44E008D9647 /* reporting.cpp in Sources */,
37643747
94674B872D533B4000973137 /* tradeManager.cpp in Sources */,
37653748
94CD8BA02D2E8CE500041BBA /* databaseConnection.cpp in Sources */,
37663749
942EC5622FBEF94700CCBB5D /* redisConnection.cpp in Sources */,
3767-
940A61132C92CE210083FEB8 /* configManager.cpp in Sources */,
37683750
94724A842F8B92C10029B940 /* operations.cpp in Sources */,
3769-
940A61172C92CE960083FEB8 /* serviceA.cpp in Sources */,
37703751
94D601112FA9CD700066F51A /* randomStrategy.cpp in Sources */,
37713752
);
37723753
runOnlyForDeploymentPostprocessing = 0;
@@ -3785,23 +3766,20 @@
37853766
94D3A7262FC1B3AD00EBEA32 /* loadCommand.cpp in Sources */,
37863767
942EC5692FBEF95000CCBB5D /* redisRunner.cpp in Sources */,
37873768
94280BA42D2FC00200F1CF56 /* base64.cpp in Sources */,
3788-
94674B8D2D533E7800973137 /* trade.cpp in Sources */,
37893769
943770462FD4396F00317424 /* boostRedisImpl.cpp in Sources */,
37903770
941B549A2D3BBADE00E3BF64 /* trading_definitions_json.cpp in Sources */,
37913771
94D3A7292FC1B41500EBEA32 /* runCommand.cpp in Sources */,
37923772
94D601102FA9CD700066F51A /* randomStrategy.cpp in Sources */,
37933773
94674B8A2D533BDA00973137 /* tradeManager.mm in Sources */,
37943774
94829FC62FCC1D1A00710E6E /* env.cpp in Sources */,
37953775
94724A832F8B92C10029B940 /* operations.cpp in Sources */,
3796-
940A61182C92CE960083FEB8 /* serviceA.cpp in Sources */,
37973776
94674B882D533B4000973137 /* tradeManager.cpp in Sources */,
37983777
946EFF7F2FB9F44E008D9647 /* reporting.cpp in Sources */,
37993778
943398272D57E54000287A2D /* jsonParser.mm in Sources */,
38003779
942FDDE22FC5C8B20096F318 /* tradingResults.cpp in Sources */,
38013780
9437703F2FD42FC000317424 /* elasticClient.cpp in Sources */,
38023781
9470B5B62C8C5BFD007D9CC6 /* main.cpp in Sources */,
38033782
94364CB62D416D8D00F35B55 /* db.mm in Sources */,
3804-
940A61142C92CE210083FEB8 /* configManager.cpp in Sources */,
38053783
);
38063784
runOnlyForDeploymentPostprocessing = 0;
38073785
};

include/configManager.hpp

Lines changed: 0 additions & 27 deletions
This file was deleted.

include/databaseConnection.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ class DatabaseConnection {
1818
const std::string& user = "admin",
1919
const std::string& password = "");
2020

21-
void printResults(const std::vector<PriceData>& results) const;
2221
std::vector<PriceData> executeQuery(const std::string& query) const;
2322

2423
const std::string& getConnectionString() const {

include/elasticClient.hpp

Lines changed: 0 additions & 18 deletions
This file was deleted.

include/models/trade.hpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,25 @@ struct Trade {
4040
// Pip-PnL = price difference * scalingFactor * size (sign flipped for SHORT).
4141
boost::decimal::decimal64_t pnl;
4242

43+
// Mark-to-market state while the trade is open, maintained by
44+
// TradeManager: the most recent close-side price seen for this symbol and
45+
// the floating PnL at that price. Feeds the account loss limit, and
46+
// lastMarkPrice is the liquidation price if the run is cut off.
47+
// floatingPnl is zeroed on close — its value has been realized into pnl.
48+
boost::decimal::decimal64_t lastMarkPrice;
49+
boost::decimal::decimal64_t floatingPnl;
50+
51+
// True when the close was forced by the account loss limit (liquidation
52+
// at the last marked price) rather than earned via SL/TP or strategy
53+
// logic — lets reporting separate forced closes from organic ones.
54+
bool liquidated = false;
55+
4356
// Default constructor
4457
Trade() : entryPrice(0), entryBid(0), entryAsk(0), size(0), direction(Direction::LONG),
4558
scalingFactor(0), stopDistancePips(0), limitDistancePips(0),
4659
exitReferencePrice(0),
4760
closePrice(0), pnl(0),
61+
lastMarkPrice(0), floatingPnl(0),
4862
openTime(std::chrono::system_clock::now()) {}
4963

5064
// Copy constructor
@@ -65,7 +79,9 @@ struct Trade {
6579
stopDistancePips(0),
6680
limitDistancePips(0),
6781
exitReferencePrice(0),
68-
pnl(0) {
82+
pnl(0),
83+
lastMarkPrice(0),
84+
floatingPnl(0) {
6985
}
7086

7187
};

include/reporting/elasticClient.hpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88

99
#include "tradingResults.hpp"
1010

11-
// Minimal Elasticsearch HTTP client — PUT-only, for indexing TradingResults.
11+
// Minimal Elasticsearch HTTP client — PUT-only, for indexing run outcomes.
1212
// 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.
13+
// HTTP basic auth from $ELASTIC_USER / $ELASTIC_USER_PASSWORD. Completed runs
14+
// land in index "trading_results"; runs cut off early (loss limit) land in
15+
// "trading_failures". Each doc gets a freshly generated UUID.
1516
class ElasticClient {
1617
public:
1718
static int putTradingResults(const TradingResults& results);
19+
static int putTradingFailure(const TradingFailure& failure);
1820
};

include/serviceA.hpp

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)