Skip to content

Commit f3442f5

Browse files
committed
Merge main into feat/clear-street-cpp-adapter
2 parents 949b98b + 0592eb9 commit f3442f5

32 files changed

Lines changed: 1450 additions & 397 deletions

File tree

.agent/skills/hft_engine/SKILL.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
name: HFT Stats Engine
3+
description: Usage and architecture of the QuanuX High-Frequency Trading Statistics Engine.
4+
---
5+
6+
# HFT Stats Engine Skill
7+
8+
## Overview
9+
The **QuanuX Stats Engine** is a C++20 microservice designed for sub-microsecond signal generation. It ingests binary market data, computes rolling statistics (Welford's algorithm), and emits trading signals via a lock-free queue.
10+
11+
## Capabilities
12+
1. **Binary Ingestion**: Consumes `MARKET.BIN` NATS subjects. Payload must be a 64-byte `quanux::MarketTick` struct.
13+
2. **Persistence**: High-throughput storage to DuckDB via Appender API (no SQL overhead).
14+
3. **Signaling**: Emits `Signal` structs to an internal SPSC queue when Z-Score > 2.0.
15+
4. **Telemetry**: Logs correlation matrices to stdout every 10 seconds.
16+
17+
## Architecture
18+
- **Thread A (Producer)**:
19+
- Ingest NATS message (Zero-Copy cast).
20+
- Append to DuckDB.
21+
- Update Rolling Stats (RingBuffer).
22+
- Push to SPSC Queue (Lock-Free).
23+
- **Thread B (Consumer)**:
24+
- Spin-wait on SPSC Queue using `_mm_pause()`.
25+
- Execute Strategy Logic (currently simulated).
26+
27+
## Data Structure (`quanux::MarketTick`)
28+
Must be exactly **64 bytes** (Cache Line Aligned).
29+
```cpp
30+
struct alignas(64) MarketTick {
31+
uint64_t local_rec_ts; // 8 bytes
32+
uint64_t exchange_ts; // 8 bytes
33+
double price; // 8 bytes
34+
uint32_t size; // 4 bytes
35+
uint32_t flags; // 4 bytes
36+
uint32_t instrument_id; // 4 bytes
37+
// Implicit Padding: 4 bytes here
38+
uint64_t internal_arrival_ts;// 8 bytes
39+
uint64_t processing_start_ts;// 8 bytes
40+
uint8_t _pad[8]; // 8 bytes Explicit Padding
41+
};
42+
```
43+
44+
## Performance Benchmarks (macOS M1/M2)
45+
- **Min Latency**: 59 nanoseconds
46+
- **Avg Latency**: ~250 microseconds (OS scheduling noise)
47+
- **Throughput**: >3.2 Million msg/sec
48+
49+
## Usage
50+
### Running the Engine
51+
```bash
52+
./quanux_stats
53+
```
54+
55+
### Verifying
56+
Use the provided Python script to send compliant binary packets:
57+
```bash
58+
python3 verify_hft_engine.py
59+
```
60+
61+
### Benchmarking
62+
Run the micro-benchmark to measure internal SPSC latency:
63+
```bash
64+
./benchmark_hft_engine
65+
```
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
name: python_packaging
3+
description: Guidelines for creating robust Python extension setup.py files that survive CI environments.
4+
---
5+
# Python Packaging in QuanuX
6+
7+
When creating or modifying `setup.py` files for Python extensions (especially those using Cython, PyBind11, or Numpy), you must ensure they are robust against missing build-time dependencies.
8+
9+
## The Problem
10+
CI/CD systems (like GitHub's Dependency Submission Action) often run in environments where build dependencies like `Cython` or `numpy` are NOT installed. If `setup.py` unconditionally imports them, the dependency graph generation fails.
11+
12+
## The Solution: Robust Setup Pattern
13+
Always wrap build-time imports in `try-except` blocks. If they are missing, the script should still run successfully but yield an empty list of extensions.
14+
15+
### Template
16+
17+
```python
18+
import os
19+
from setuptools import setup, Extension
20+
21+
# 1. Wrap build dependencies
22+
try:
23+
from Cython.Build import cythonize
24+
import numpy
25+
except ImportError:
26+
cythonize = None
27+
numpy = None
28+
29+
ext_modules = []
30+
31+
# 2. Conditional Build Logic
32+
if cythonize and numpy:
33+
# Define extensions here
34+
extensions = [
35+
Extension(
36+
"my_extension",
37+
sources=["src/my_extension.pyx"],
38+
include_dirs=[numpy.get_include()],
39+
# ...
40+
)
41+
]
42+
ext_modules = cythonize(extensions, language_level=3)
43+
else:
44+
print("Build dependencies (Cython/Numpy) not found. Skipping extension build.")
45+
# This print is important for debugging but allows the script to exit with code 0
46+
47+
# 3. Setup Call
48+
setup(
49+
name="my_package",
50+
ext_modules=ext_modules, # Will be empty if deps missing
51+
# ... standard metadata ...
52+
)
53+
```
54+
55+
## Checklist
56+
- [ ] Import `Cython.Build.cythonize` inside `try-except ImportError`.
57+
- [ ] Import `numpy` inside `try-except ImportError` (if used).
58+
- [ ] Check for existence of required directories (e.g. `_deps`, `sdk`) before adding them to `include_dirs`.
59+
- [ ] Ensure `python setup.py --name` runs successfully in a bare environment.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
5+
namespace quanux {
6+
7+
/**
8+
* @brief Cache-aligned Market Tick Structure for HFT
9+
*
10+
* Designed to fit exactly into a 64-byte cache line to prevent false sharing
11+
* and optimize memory access patterns during high-frequency updates.
12+
*/
13+
struct alignas(64) MarketTick {
14+
uint64_t local_rec_ts; // 8 bytes: Nanoseconds since epoch (Packet receipt)
15+
uint64_t exchange_ts; // 8 bytes: Nanoseconds since epoch (Matching engine)
16+
double price; // 8 bytes: Execution price
17+
uint32_t size; // 4 bytes: Execution size
18+
uint32_t flags; // 4 bytes: Trade flags
19+
uint32_t instrument_id; // 4 bytes: Internal instrument mapping
20+
21+
// Internal Latency Tracking
22+
uint64_t internal_arrival_ts; // 8 bytes: Time tick entered process
23+
uint64_t processing_start_ts; // 8 bytes: Time stats engine picked it up
24+
25+
// Padding to reach 64 bytes
26+
// Offset is 56 bytes (including 4 bytes implicit padding after instrument_id)
27+
// Remaining: 8 bytes
28+
uint8_t _pad[8];
29+
};
30+
31+
static_assert(sizeof(MarketTick) == 64, "MarketTick must be exactly 64 bytes");
32+
static_assert(alignof(MarketTick) == 64, "MarketTick must be 64-byte aligned");
33+
34+
} // namespace quanux
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#pragma once
2+
3+
#include <atomic>
4+
#include <cstddef>
5+
#include <optional>
6+
#include <vector>
7+
8+
namespace quanux {
9+
10+
/**
11+
* @brief Lock-Free Single-Producer Single-Consumer Queue
12+
*
13+
* Uses a ring buffer with atomic head/tail indices.
14+
* Optimized for cache line separation to prevent false sharing between producer
15+
* and consumer.
16+
*/
17+
template <typename T> class SPSCQueue {
18+
public:
19+
explicit SPSCQueue(size_t capacity)
20+
: capacity_(capacity), data_(capacity + 1) {}
21+
22+
/**
23+
* @brief Push an item into the queue.
24+
* @return true if successful, false if full.
25+
*/
26+
bool push(const T &item) {
27+
const size_t current_tail = tail_.load(std::memory_order_relaxed);
28+
const size_t next_tail = (current_tail + 1) % data_.size();
29+
30+
if (next_tail != head_.load(std::memory_order_acquire)) {
31+
data_[current_tail] = item;
32+
tail_.store(next_tail, std::memory_order_release);
33+
return true;
34+
}
35+
return false;
36+
}
37+
38+
/**
39+
* @brief Pop an item from the queue.
40+
* @param item Reference to store the popped item.
41+
* @return true if successful, false if empty.
42+
*/
43+
bool pop(T &item) {
44+
const size_t current_head = head_.load(std::memory_order_relaxed);
45+
46+
if (current_head == tail_.load(std::memory_order_acquire)) {
47+
return false; // Empty
48+
}
49+
50+
item = data_[current_head];
51+
head_.store((current_head + 1) % data_.size(), std::memory_order_release);
52+
return true;
53+
}
54+
55+
private:
56+
size_t capacity_;
57+
std::vector<T> data_;
58+
59+
// Align head and tail to separate cache lines to prevent false sharing
60+
alignas(64) std::atomic<size_t> head_{0};
61+
alignas(64) std::atomic<size_t> tail_{0};
62+
};
63+
64+
} // namespace quanux

QuanuX-Statistics/cpp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ include_directories(../../QuanuX-Common/cpp/include)
5959
# Core Library
6060
add_library(stats_core STATIC
6161
src/stats_engine.cpp
62+
src/core/StatsEngineCore.cpp
6263
src/time_series_store.cpp
6364
)
6465
target_link_libraries(stats_core PUBLIC

0 commit comments

Comments
 (0)