Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Dotfiles and system files
.idea/
.vscode/
.cache/
.DS_Store

build/
Expand Down
25 changes: 20 additions & 5 deletions CMakeLists.txt
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
cmake_minimum_required(VERSION 3.14)

# Set the C++ standard
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")

project(hft_sim)

Expand All @@ -21,6 +22,7 @@ FetchContent_Declare(
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(googletest)
include(GoogleTest)

# Net library
set(NET_SOURCES
Expand All @@ -32,6 +34,15 @@ set(NET_SOURCES
add_library(hsnet STATIC ${NET_SOURCES})
target_include_directories(hsnet PUBLIC ${CMAKE_SOURCE_DIR}/include/net)

# Enable ARM CRC intrinsics on Apple Silicon (M1/M2) for hsnet only
if(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm|aarch64)")
message(STATUS "Enabling ARM CRC intrinsics for hsnet on Apple Silicon")
# Request an ARM architecture that supports CRC instructions
target_compile_options(hsnet PRIVATE -march=armv8-a+crc)
target_compile_definitions(hsnet PRIVATE __ARM_FEATURE_CRC32=1)
endif()


# Add the source files for the main executable
set(SOURCES
main.cpp
Expand Down Expand Up @@ -87,16 +98,18 @@ set(TEST_SOURCES
tests/test_crc32c.cpp
tests/test_reordering_buffer.cpp
tests/test_udp_transport.cpp
tests/test_memory_pool.cpp
)

# Function to add a Google Test executable (commented out for now)
# Function to add a Google Test executable
function(add_gtest_test TEST_NAME TEST_SOURCE)
add_executable(${TEST_NAME} ${TEST_SOURCE} src/OrderBook.cpp src/Order.cpp util/Logger.cpp)
set_target_properties(${TEST_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TEST_OUTPUT_DIR})
set_target_properties(${TEST_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TEST_OUTPUT_DIR})
target_include_directories(${TEST_NAME} PRIVATE
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/util)
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/util)
target_link_libraries(${TEST_NAME} PRIVATE gtest gtest_main hsnet)
gtest_discover_tests(${TEST_NAME} PRIVATE)
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gtest_discover_tests call at line 112 incorrectly uses the PRIVATE keyword which is not a valid argument for this function. This should be removed as gtest_discover_tests doesn't accept visibility specifiers.

Suggested change
gtest_discover_tests(${TEST_NAME} PRIVATE)
gtest_discover_tests(${TEST_NAME})

Copilot uses AI. Check for mistakes.
add_test(NAME ${TEST_NAME} COMMAND ${TEST_OUTPUT_DIR}/${TEST_NAME})
message("Writing test executable: ${TEST_NAME} with source: ${TEST_SOURCE} to ${TEST_OUTPUT_DIR}")
endfunction()
Expand All @@ -105,6 +118,8 @@ add_gtest_test(test_full_orders tests/test_full_orders.cpp)
add_gtest_test(test_cancel_order tests/test_cancel_order.cpp)
add_gtest_test(test_crc32c tests/test_crc32c.cpp)
add_gtest_test(test_reordering_buffer tests/test_reordering_buffer.cpp)
add_gtest_test(test_lockfree_queue tests/test_lockfree_queue.cpp)
add_gtest_test(test_memory_pool tests/test_memory_pool.cpp)


#foreach(TEST_SRC ${TEST_SOURCES})
Expand Down
31 changes: 29 additions & 2 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,39 @@ The simulator allows users to test various trading strategies, analyze market da
- Order cancellation
- Feed publishing and subscription
- Trade execution simulation
- Lock-free multi-producer single-consumer (MPSC) queue for high-performance message passing
- Basic logging functionality


## Things that can be improved
## Things to work on / TODOs
- Performance optimizations (order matching and execution)
- Price representation as floats (should be a "Money" type)
- More sophisticated order types (e.g., stop-loss, iceberg orders)
- Enhanced logging and monitoring capabilities

# Potential future features
- Integration with real market data feeds
- Support for additional asset classes (e.g., options, futures)
- Advanced analytics and reporting tools
-
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The incomplete bullet point on line 27 creates malformed documentation. This empty bullet point should either be completed with actual content or removed entirely.

Suggested change
-

Copilot uses AI. Check for mistakes.

# Getting Started

## Prerequisites
- C++17 or later
- CMake 3.20 or later
- A C++ compiler that supports C++17 (e.g., GCC, Clang, MSVC)
## Testing
To run all tests, use the following command after building the project:

```bash
cd build/
ctest
```


## References
While I was working on the lock-free MPSC queue, I found the following resources helpful:
- [Lock-Free Queue Implementation](https://people.cs.pitt.edu/~jacklange/teaching/cs2510-f17/implementing_lock_free.pdf)
- [Lock-Free MPMC](https://www.linuxjournal.com/content/lock-free-multi-producer-multi-consumer-queue-ring-buffer)
- [Lock-Free MPMC](https://www.linuxjournal.com/content/lock-free-multi-producer-multi-consumer-queue-ring-buffer)

69 changes: 69 additions & 0 deletions include/MemoryPool.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H
#include <iostream>
#include <cstddef>
#include <cstdint>
#include <memory>

template <typename T>
class MemoryPool {
public:
explicit MemoryPool(size_t pool_size = 1024): pool_size_(pool_size) {
pool_ = std::make_unique<Slot[]>(pool_size_);
init_free_list();
};

~MemoryPool() {};

template<typename... Args>
T* allocate(Args&&... args) {
if(!head)
return nullptr;
Slot* curr = head;
head = head->next;
return new (&curr->data) T(std::forward<Args>(args)...); // Pool exhausted
};

/**
* Marks a pooled object slot as free so it can be reused.
* @param obj Pointer previously obtained from this pool's allocate(). Must point
* into the memory block returned by pool_.get().
* See also: allocate()
*/
void deallocate(T* obj) {

if(!obj)
return;
obj->~T();

Slot* curr = reinterpret_cast<Slot*>(obj);
// Set the current slot to the top of the free list
curr->next = head;
head = curr;
};

private:
// Storage
union Slot {
T data;
Slot* next;
};
std::unique_ptr<Slot[]> pool_;
// free list
Slot* head;
// In-use buffer
bool* in_use_;
Comment on lines +54 to +55
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The member variable in_use_ is declared but never initialized or used. This creates unnecessary memory overhead and should be removed. The MemoryPool relies on the free list for tracking allocation state, making this variable redundant.

Suggested change
// In-use buffer
bool* in_use_;

Copilot uses AI. Check for mistakes.
size_t pool_size_;

void init_free_list() {
std::cout << "Initializing free list..." << std::endl;
for (size_t i = 0; i < pool_size_ - 1; ++i) {
pool_[i].next = &pool_[i + 1];
}
pool_[pool_size_ - 1].next = nullptr;
head = &pool_[0];
}
};


#endif
Empty file modified include/net/Crc32c.h
100644 → 100755
Empty file.
47 changes: 26 additions & 21 deletions include/net/ReorderingBuffer.h
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,58 @@
#include <vector>

namespace hsnet {

using data_t = std::vector<uint8_t>;
// Simple reordering buffer that ensures in-sequence delivery
class ReorderingBuffer {
public:
explicit ReorderingBuffer(size_t max_size = 1024);

// Add a packet with sequence number, returns true if it was added
bool add(uint64_t sequence, std::vector<uint8_t> data, uint32_t stream_id = 0);
bool add(uint64_t sequence, data_t data);

// Get next in-sequence packet, returns empty if none available
std::optional<std::pair<std::vector<uint8_t>, uint32_t>> get_next();
std::optional<data_t> get_next();

// Check if we have any packets ready for delivery
bool has_ready() const;

// Get the next expected sequence number
uint64_t next_expected() const { return next_seq_; }

uint64_t next_expected() const {
return next_seq_;
}

// Get statistics
size_t size() const { return count_; }
size_t max_size() const { return max_size_; }

size_t size() const {
return count_;
}
size_t max_size() const {
return max_size_;
}

// Clear the buffer (useful for reset scenarios)
void clear();

private:
struct Packet {
uint64_t sequence;
std::vector<uint8_t> data;
uint32_t stream_id;
data_t data;
bool valid;
Packet() : sequence(0), stream_id(0), valid(false) {}
Packet(uint64_t seq, std::vector<uint8_t> d, uint32_t sid) : sequence(seq), data(std::move(d)), stream_id(sid), valid(true) {}

Packet() : sequence(0), valid(false) {}
Packet(uint64_t seq, data_t d) : sequence(seq), data(std::move(d)), valid(true) {}
};

std::vector<Packet> buffer_;
size_t max_size_;
uint64_t next_seq_;
size_t head_;
size_t count_;

// Helper to find packet in buffer
size_t find_packet(uint64_t sequence) const;

// Helper to advance head pointer
void advance_head();
};

} // namespace hsnet
} // namespace hsnet
Loading