Skip to content
Merged
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
10 changes: 5 additions & 5 deletions benchmarks/bench_matching_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ static void BM_MatchingEngine_NoRisk_NoGateway(benchmark::State& state) {
Pool pool;
std::vector<Handle> owned;
NullGateway gateway(g_gateway_pool);
MatchingEngine engine("AAPL", nullptr, &gateway);
MatchingEngine engine("AAPL", nullptr, &gateway, nullptr);

const int N = state.range(0);
std::vector<Order*> orders;
Expand Down Expand Up @@ -142,7 +142,7 @@ static void BM_MatchingEngine_WithRisk_NoGateway(benchmark::State& state) {
std::vector<Handle> owned;
RiskManager risk;
NullGateway gateway(g_gateway_pool);
MatchingEngine engine("AAPL", &risk, &gateway);
MatchingEngine engine("AAPL", &risk, &gateway, nullptr);

const int N = state.range(0);
std::vector<Order*> orders;
Expand Down Expand Up @@ -172,7 +172,7 @@ static void BM_MatchingEngine_NoRisk_RealGateway(benchmark::State& state) {
Pool pool;
std::vector<Handle> owned;
RealGateway gateway(g_gateway_pool);
MatchingEngine engine("AAPL", nullptr, &gateway);
MatchingEngine engine("AAPL", nullptr, &gateway, nullptr);

const int N = state.range(0);
std::vector<Order*> orders;
Expand Down Expand Up @@ -203,7 +203,7 @@ static void BM_MatchingEngine_FullPipeline(benchmark::State& state) {
std::vector<Handle> owned;
RiskManager risk;
RealGateway gateway(g_gateway_pool);
MatchingEngine engine("AAPL", &risk, &gateway);
MatchingEngine engine("AAPL", &risk, &gateway, nullptr);

const int N = state.range(0);
std::vector<Order*> orders;
Expand Down Expand Up @@ -234,7 +234,7 @@ static void BM_MatchingEngine_Throughput(benchmark::State& state) {
std::vector<Handle> owned;
RiskManager risk;
RealGateway gateway(g_gateway_pool);
MatchingEngine engine("AAPL", &risk, &gateway);
MatchingEngine engine("AAPL", &risk, &gateway, nullptr);

const int N = 1000;
std::vector<Order*> orders;
Expand Down
7 changes: 5 additions & 2 deletions include/velox/matching/matching_engine.hpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#pragma once
#include <atomic>
#include "velox/book/order_book.hpp"
#include "velox/matching/order.hpp"
#include "lockfree/spsc_queue.hpp"
#include "lockfree/pool.hpp"
#include "velox/risk/risk_manager.hpp"
#include "velox/gateway/execution_gateway.hpp"
#include "velox/risk/position_manager.hpp"
#include "velox/matching/order.hpp"

namespace velox {

Expand All @@ -15,7 +16,8 @@ class MatchingEngine {

MatchingEngine(const char* symbol,
RiskManager* risk_manager,
ExecutionGateway* gateway);
ExecutionGateway* gateway,
PositionManager* position_manager);

~MatchingEngine();

Expand All @@ -40,6 +42,7 @@ class MatchingEngine {
OrderBook m_order_book;
RiskManager* m_risk_manager;
ExecutionGateway* m_gateway;
PositionManager* m_position_manager;

OrderQueue m_incoming_orders;

Expand Down
36 changes: 17 additions & 19 deletions include/velox/risk/position_manager.hpp
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <unordered_map>
#include <string>
#include "velox/matching/order.hpp"

namespace velox {

struct Position {
std::atomic<int64_t> net_position{0}; // Total bought - total sold
std::atomic<int64_t> realized_pnl{0}; // Locked-in P&L
std::atomic<int64_t> avg_entry_price{0}; // Average entry price
std::atomic<uint32_t> total_bought{0};
std::atomic<uint32_t> total_sold{0};
};

class PositionManager {
public:
PositionManager();
~PositionManager();

// Update position after fill
void update_position(const Order* order, uint32_t fill_quantity);
// Update position after fill (called by Execution Gateway)
void update_position(const Order* order, uint32_t fill_quantity, int64_t fill_price);

// Get current position for symbol
int64_t get_position(const char* symbol) const;

// P&L tracking
void record_fill(const Order* order, uint32_t fill_quantity, int64_t fill_price);
int64_t get_realized_pnl() const;
int64_t get_unrealized_pnl(int64_t current_price) const;
int64_t get_realized_pnl(const char* symbol) const;
int64_t get_unrealized_pnl(const char* symbol, int64_t current_price) const;
int64_t get_total_pnl(const char* symbol, int64_t current_price) const;

// Reset for testing
void reset();

// Hash symbol
uint32_t hash_symbol(const char* symbol) const;

private:
struct Position {
std::atomic<int64_t> net_position{0};
std::atomic<int64_t> realized_pnl{0};
std::atomic<int64_t> avg_entry_price{0};
std::atomic<uint32_t> total_bought{0};
std::atomic<uint32_t> total_sold{0};
};

Position m_positions[256];
std::atomic<int64_t> m_total_realized_pnl{0};
void update_average_price(Position& pos, uint32_t quantity, int64_t price);
std::unordered_map<std::string, Position> m_positions;
void update_realized_pnl(Position& pos, int64_t price);
};

}
15 changes: 12 additions & 3 deletions src/matching/matching_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ namespace velox {

MatchingEngine::MatchingEngine(const char* symbol,
RiskManager* risk_manager,
ExecutionGateway* gateway)
ExecutionGateway* gateway,
PositionManager* positiion_manager)
: m_order_book(symbol),
m_risk_manager(risk_manager),
m_gateway(gateway) {}
m_gateway(gateway),
m_position_manager(positiion_manager) {}

MatchingEngine::~MatchingEngine() = default;

Expand Down Expand Up @@ -73,7 +75,14 @@ void MatchingEngine::send_fill(Order* order, uint32_t fill_quantity, int64_t fil
(void)fill_quantity;
(void)fill_price;

m_gateway->send_order(order);
if (m_gateway) {
m_gateway->send_order(order);
}

// Update position manager
if (m_position_manager) {
m_position_manager->update_position(order, fill_quantity, fill_price);
}
}

bool MatchingEngine::check_risk(const Order* order) const {
Expand Down
111 changes: 62 additions & 49 deletions src/risk/position_manager.cpp
Original file line number Diff line number Diff line change
@@ -1,77 +1,90 @@
#include "velox/risk/position_manager.hpp"
#include <cstring>
#include <cmath>

namespace velox {

PositionManager::PositionManager() = default;
PositionManager::~PositionManager() = default;

void PositionManager::update_position(const Order* order, uint32_t fill_quantity) {
if (!order) return;

uint32_t idx = hash_symbol(order->symbol);
auto& pos = m_positions[idx];
void PositionManager::update_position(const Order* order, uint32_t fill_quantity, int64_t fill_price) {
if (!order || fill_quantity == 0) return;

std::string symbol(order->symbol);

// Create default position if symbol doesn't exist in system
auto& pos = m_positions[symbol];

if (order->is_buy()) {
int64_t new_pos = pos.net_position.load() + static_cast<int64_t>(fill_quantity);
pos.net_position.store(new_pos, std::memory_order_release);
pos.total_bought.fetch_add(fill_quantity, std::memory_order_release);
update_average_price(pos, fill_quantity, order->price);
} else {
int64_t new_pos = pos.net_position.load() - static_cast<int64_t>(fill_quantity);
pos.net_position.store(new_pos, std::memory_order_release);
// Buy: increase position, update average entry price
int64_t old_position = pos.net_position.load(std::memory_order_acquire);
int64_t old_avg = pos.avg_entry_price.load(std::memory_order_acquire);
uint64_t old_bought = pos.total_bought.load(std::memory_order_acquire);

int64_t new_position = old_position + fill_quantity;
uint64_t new_bought = old_bought + fill_quantity;

// Weighted average price
int64_t new_avg = (old_avg * old_bought + fill_price * fill_quantity) / new_bought;

pos.net_position.store(new_position, std::memory_order_release);
pos.avg_entry_price.store(new_avg, std::memory_order_release);
pos.total_bought.store(new_bought, std::memory_order_release);
}
else {
// Sell: decrease position, realize P&L
int64_t old_position = pos.net_position.load(std::memory_order_acquire);
int64_t old_avg = pos.avg_entry_price.load(std::memory_order_acquire);

// Realized P&L = (sell_price - avg_price) * quantity
int64_t pnl = (fill_price - old_avg) * fill_quantity;
update_realized_pnl(pos, pnl);

int64_t new_position = old_position - fill_quantity;
pos.net_position.store(new_position, std::memory_order_release);
pos.total_sold.fetch_add(fill_quantity, std::memory_order_release);
}
}

int64_t PositionManager::get_position(const char* symbol) const {
uint32_t idx = hash_symbol(symbol);
return m_positions[idx].net_position.load(std::memory_order_acquire);
void PositionManager::update_realized_pnl(Position& pos, int64_t pnl) {
pos.realized_pnl.fetch_add(pnl, std::memory_order_release);
}

void PositionManager::record_fill(const Order* order, uint32_t fill_quantity, int64_t fill_price) {
update_position(order, fill_quantity);
// TODO: Calculate P&L
}

int64_t PositionManager::get_realized_pnl() const {
return m_total_realized_pnl.load(std::memory_order_acquire);
int64_t PositionManager::get_position(const char* symbol) const {
auto it = m_positions.find(symbol);
if (it == m_positions.end()) return 0;
return it->second.net_position.load(std::memory_order_acquire);
}

int64_t PositionManager::get_unrealized_pnl(int64_t current_price) const {
// TODO: Calculate unrealized P&L based on positions
return 0;
int64_t PositionManager::get_realized_pnl(const char* symbol) const {
auto it = m_positions.find(symbol);
if (it == m_positions.end()) return 0;
return it->second.realized_pnl.load(std::memory_order_acquire);
}

void PositionManager::reset() {
for (auto& pos : m_positions) {
pos.net_position.store(0, std::memory_order_release);
pos.realized_pnl.store(0, std::memory_order_release);
pos.avg_entry_price.store(0, std::memory_order_release);
pos.total_bought.store(0, std::memory_order_release);
pos.total_sold.store(0, std::memory_order_release);
int64_t PositionManager::get_unrealized_pnl(const char* symbol, int64_t current_price) const {
auto it = m_positions.find(symbol);
if (it == m_positions.end()) return 0;

int64_t position = it->second.net_position.load(std::memory_order_acquire);
if (position == 0) return 0;

int64_t avg_price = it->second.avg_entry_price.load(std::memory_order_acquire);
if (position > 0) {
// Long position: (current - avg) * position
return (current_price - avg_price) * position;
} else {
// Short position: (avg - current) * (-position)
return (avg_price - current_price) * (-position);
}
m_total_realized_pnl.store(0, std::memory_order_release);
}

uint32_t PositionManager::hash_symbol(const char* symbol) const {
uint32_t hash = 0;
while (*symbol) {
hash = hash * 31 + static_cast<uint32_t>(*symbol++);
}
return hash % 256;
int64_t PositionManager::get_total_pnl(const char* symbol, int64_t current_price) const {
return get_realized_pnl(symbol) + get_unrealized_pnl(symbol, current_price);
}

void PositionManager::update_average_price(Position& pos, uint32_t quantity, int64_t price) {
int64_t total_bought = pos.total_bought.load();
int64_t old_avg = pos.avg_entry_price.load();

if (total_bought == 0) {
pos.avg_entry_price.store(price, std::memory_order_release);
} else {
int64_t new_avg = (old_avg * total_bought + price * quantity) / (total_bought + quantity);
pos.avg_entry_price.store(new_avg, std::memory_order_release);
}
void PositionManager::reset() {
m_positions.clear();
}

}
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ add_executable(velox_tests
test_execution_gateway.cpp
test_feed_handler.cpp
test_book_snapshot.cpp
test_position_manager.cpp
)

target_link_libraries(velox_tests
Expand Down
2 changes: 1 addition & 1 deletion tests/test_matching_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class MatchingEngineTest : public ::testing::Test {
pool = std::make_unique<lockfree::ObjectPool<Order, 10000>>();
risk = std::make_unique<RiskManager>();
gateway = std::make_unique<ExecutionGateway>();
engine = std::make_unique<MatchingEngine>("AAPL", risk.get(), gateway.get());
engine = std::make_unique<MatchingEngine>("AAPL", risk.get(), gateway.get(), nullptr);
}

Order* create_order(uint64_t id, OrderSide side, int64_t price, uint32_t qty) {
Expand Down
Loading
Loading