Skip to content

Commit def3a6e

Browse files
committed
Feat: Complete Wiring, NATS Integration, and Deep IDE Support
- Implemented 'quanux.backtest.run' agent tool for orchestrating backtests. - Enabled NATS loopback: Backtester publishes 'SIM', Node consumes 'SIM'. - Updated 'execution-node' with NATS subscription logic. - Added 'NatsBridge::subscribe' for inbound telemetry/data. - Created 'CMakePresets.json' for unified CLion/VS/VSCode builds. - Added 'scripts/generate_xcode.sh' and 'scripts/setup_spyder.py'. - Updated VSCode 'c_cpp_properties.json' and 'tasks.json' for multi-root workspace. - Documented maintenance procedures in 'server/skills/project_maintenance/SKILL.md'. - Updated README and Walkthrough with verification steps.
1 parent e9364ac commit def3a6e

15 files changed

Lines changed: 366 additions & 27 deletions

File tree

CMakePresets.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"version": 3,
3+
"cmakeMinimumRequired": {
4+
"major": 3,
5+
"minor": 20,
6+
"patch": 0
7+
},
8+
"configurePresets": [
9+
{
10+
"name": "default",
11+
"displayName": "Default Config",
12+
"description": "Default build using Ninja or Unix Makefiles",
13+
"generator": "Unix Makefiles",
14+
"binaryDir": "${sourceDir}/build",
15+
"cacheVariables": {
16+
"CMAKE_BUILD_TYPE": "Debug",
17+
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
18+
}
19+
},
20+
{
21+
"name": "execution-node",
22+
"displayName": "Execution Node",
23+
"description": "Configure the Execution Node",
24+
"binaryDir": "${sourceDir}/execution-node/cpp/build",
25+
"generator": "Unix Makefiles",
26+
"cacheVariables": {
27+
"CMAKE_BUILD_TYPE": "Debug",
28+
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
29+
}
30+
},
31+
{
32+
"name": "backtester",
33+
"displayName": "Backtesting Engine",
34+
"description": "Configure the Backtesting Engine",
35+
"binaryDir": "${sourceDir}/QuanuX-Backtesting-Engine/cpp/build",
36+
"generator": "Unix Makefiles",
37+
"cacheVariables": {
38+
"CMAKE_BUILD_TYPE": "Debug",
39+
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
40+
}
41+
}
42+
],
43+
"buildPresets": [
44+
{
45+
"name": "build-node",
46+
"displayName": "Build Execution Node",
47+
"configurePreset": "default",
48+
"targets": [
49+
"quanux_node"
50+
]
51+
},
52+
{
53+
"name": "build-backtest",
54+
"displayName": "Build Backtester",
55+
"configurePreset": "default",
56+
"targets": [
57+
"quanux_backtest"
58+
]
59+
}
60+
]
61+
}

QuanuX-Backtesting-Engine/cpp/include/engine/BacktestRunner.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44

55
namespace quanux::engine {
66

7+
struct BacktestConfig {
8+
bool enable_nats = false;
9+
std::string nats_url = "nats://localhost:4222";
10+
};
11+
712
class BacktestRunner {
813
public:
9-
void run();
14+
void run(const BacktestConfig &config);
1015
};
1116

1217
} // namespace quanux::engine

QuanuX-Backtesting-Engine/cpp/include/engine/DbnPipeFeeder.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ class DbnDecoder;
1111

1212
namespace quanux::engine {
1313

14+
// Forward declare NatsReplayer
15+
class NatsReplayer;
16+
1417
class DbnPipeFeeder {
1518
SimulatedExchange *exchange_;
19+
NatsReplayer *replayer_ = nullptr;
1620
std::unique_ptr<databento::DbnDecoder> decoder_;
1721
// Buffer for reading from stdin
1822
std::vector<uint8_t> read_buffer_;
1923

2024
public:
21-
explicit DbnPipeFeeder(SimulatedExchange *exchange);
25+
DbnPipeFeeder(SimulatedExchange *exchange, NatsReplayer *replayer = nullptr);
2226
~DbnPipeFeeder();
2327

2428
// Run the feeder loop (blocking)

QuanuX-Backtesting-Engine/cpp/src/engine/BacktestRunner.cpp

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,27 @@
77

88
namespace quanux::engine {
99

10-
void BacktestRunner::run() {
10+
void BacktestRunner::run(const BacktestConfig &config) {
1111
std::cout << "BacktestRunner: Initializing Simulation..." << std::endl;
1212

1313
// 1. Init Components
14-
DuckDBFeeder feeder; // Assume default or passed args
1514
SimulatedExchange exchange;
16-
NatsReplayer replayer; // Connects to localhost:4222
15+
std::unique_ptr<NatsReplayer> replayer = nullptr;
16+
17+
if (config.enable_nats) {
18+
std::cout << "BacktestRunner: NATS Replay Enabled (" << config.nats_url
19+
<< ")" << std::endl;
20+
replayer = std::make_unique<NatsReplayer>(config.nats_url);
21+
}
1722

1823
// 2. Load Strategy (TODO: Dynamic loading)
1924
std::cout << "BacktestRunner: Loading Strategy..." << std::endl;
2025

2126
// 3. Run Loop
22-
// For now, defaulting to Pipe Feeder as requested by user
23-
// In production, we'd check flags or isatty(STDIN_FILENO)
24-
2527
std::cout << "BacktestRunner: Starting Pipe Feeder (Stdin)..." << std::endl;
26-
DbnPipeFeeder pipe_feeder(&exchange);
28+
DbnPipeFeeder pipe_feeder(&exchange, replayer.get());
2729
pipe_feeder.run();
2830

29-
/* Replay Logic (Disabled in favor of Pipe for this demo)
30-
std::cout << "BacktestRunner: Starting Replay..." << std::endl;
31-
for (int i = 0; i < 100; i++) {
32-
uint64_t now = 1000000 + i * 1000;
33-
double price = 5000.0 + (i % 10);
34-
exchange.on_market_data(i, (int64_t)(price * 1000000), 10, true, true);
35-
replayer.publish_tick("ES", now, price, 10, true);
36-
std::this_thread::sleep_for(std::chrono::milliseconds(10));
37-
}
38-
*/
39-
4031
std::cout << "BacktestRunner: Simulation Complete." << std::endl;
4132
}
4233

QuanuX-Backtesting-Engine/cpp/src/engine/DbnPipeFeeder.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "engine/DbnPipeFeeder.h"
2+
#include "engine/NatsReplayer.h"
23
#include <databento/dbn.hpp>
34
#include <databento/dbn_decoder.hpp>
45
#include <databento/ireadable.hpp> // Ensure this matches include path
@@ -36,8 +37,9 @@ class StdinReadable : public databento::IReadable {
3637
}
3738
};
3839

39-
DbnPipeFeeder::DbnPipeFeeder(SimulatedExchange *exchange)
40-
: exchange_(exchange) {
40+
DbnPipeFeeder::DbnPipeFeeder(SimulatedExchange *exchange,
41+
NatsReplayer *replayer)
42+
: exchange_(exchange), replayer_(replayer) {
4143
// Transfer ownership of StdinReadable to DbnDecoder
4244
decoder_ = std::make_unique<databento::DbnDecoder>(
4345
std::make_unique<StdinReadable>());
@@ -60,11 +62,17 @@ void DbnPipeFeeder::run() {
6062
reinterpret_cast<const databento::MboMsg *>(&record->Header());
6163

6264
bool is_bid = (mbo->side == databento::Side::Bid);
63-
bool is_add = (mbo->action == databento::Action::Add);
65+
// bool is_add = (mbo->action == databento::Action::Add);
6466

6567
if (mbo->action == databento::Action::Add) {
6668
exchange_->on_market_data(mbo->order_id, mbo->price, mbo->size, is_bid,
6769
true);
70+
if (replayer_) {
71+
// Re-broadcast add (TODO: Use proper symbol from metadata)
72+
replayer_->publish_tick(
73+
"SIM", mbo->hd.ts_event.time_since_epoch().count(),
74+
(double)mbo->price / 1000000000.0, mbo->size, is_bid);
75+
}
6876
} else if (mbo->action == databento::Action::Cancel) {
6977
exchange_->on_market_data(mbo->order_id, 0, 0, is_bid, false);
7078
}
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
#include "engine/BacktestRunner.h"
22
#include <iostream>
33

4+
#include <string>
5+
46
int main(int argc, char **argv) {
57
std::cout << "Starting QuanuX Backtester..." << std::endl;
68

9+
quanux::engine::BacktestConfig config;
10+
11+
for (int i = 1; i < argc; ++i) {
12+
std::string arg = argv[i];
13+
if (arg == "--nats" || arg == "--replay") {
14+
config.enable_nats = true;
15+
} else if (arg == "--nats-url" && i + 1 < argc) {
16+
config.nats_url = argv[++i];
17+
}
18+
}
19+
720
quanux::engine::BacktestRunner runner;
8-
runner.run();
21+
runner.run(config);
922

1023
return 0;
1124
}

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,15 @@ pip install -r requirements.txt
158158
pnpm install
159159
```
160160

161+
### 3. Development Environment Setup
162+
QuanuX supports a **Polyglot Development Experience**:
163+
* **VSCode**: Native support via `.vscode` configuration (C++, Python, React).
164+
* **CLion / Visual Studio**: Standardized `CMakePresets.json` for C++ components.
165+
* **Xcode**: Helpers to generate `.xcodeproj` files.
166+
* **Spyder**: Helpers to initialize `.spyproject` configuration.
167+
168+
To configure your preferred IDE, check `server/skills/project_maintenance/SKILL.md` or use the helper scripts in `scripts/`.
169+
161170
### 4. Configuration
162171

163172
QuanuX uses the OS Keyring to securely store API keys.

execution-node/cpp/include/nats_bridge.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#pragma once
2+
#include <functional>
23
#include <nats.h>
34
#include <string>
45

@@ -14,4 +15,8 @@ class NatsBridge {
1415
void publish_trade(uint64_t order_id, double price, double quantity);
1516
void publish_market_data(const std::string &symbol, double price, double size,
1617
bool is_trade);
18+
19+
// Subscription
20+
using MsgCallback = std::function<void(const std::string &)>;
21+
void subscribe(const std::string &subject, MsgCallback cb);
1722
};

execution-node/cpp/include/order_gateway.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
#include "quanux/common/StrategyInterface.h"
44
#include "risk_engine.h"
55
#include <atomic>
6-
#include <mutex>
7-
#include <vector>
86

97
using namespace quanux::common;
108

execution-node/cpp/src/engine.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,39 @@ void Engine::run() {
9898
// TODO: Pin thread to core 0
9999

100100
// Start Market Data Feed (if needed to trigger it)
101-
market_data_engine_.subscribe("ES.c.0");
101+
if (std::getenv("QUANUX_NATS_FEED")) {
102+
std::cout << "[Engine] Enabling NATS Feed Consumption..." << std::endl;
103+
nats_bridge_.subscribe("SIM", [this](const std::string &msg) {
104+
// Minimal parsing of: {"symbol": "SIM", "price": 4800.5, "size": 1,
105+
// "type": "trade" ...}
106+
// TODO: Use real JSON parser.
107+
try {
108+
// Quick find
109+
auto p_pos = msg.find("\"price\": ");
110+
auto s_pos = msg.find("\"size\": ");
111+
auto t_pos = msg.find("\"type\": \"");
112+
113+
if (p_pos != std::string::npos && s_pos != std::string::npos) {
114+
double price = std::stod(msg.substr(p_pos + 9));
115+
double size = std::stod(msg.substr(s_pos + 8));
116+
bool is_trade = (msg.find("trade") != std::string::npos);
117+
118+
MarketUpdate update = {
119+
.timestamp = 0, // todo
120+
.instrument_id = 999, // SIM ID
121+
.price = price,
122+
.size = size,
123+
.is_trade = is_trade,
124+
.side = 1 // unknown
125+
};
126+
market_data_engine_.on_update(update);
127+
}
128+
} catch (...) {
129+
}
130+
});
131+
} else {
132+
market_data_engine_.subscribe("ES.c.0");
133+
}
102134

103135
// Core Loop
104136
bool running = true;

0 commit comments

Comments
 (0)