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
7 changes: 7 additions & 0 deletions include/velox/core/symbol_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ class SymbolEngine {
m_book.set_market_price(bid, ask);
}

// Drain each engine
void drain() {
for (int i = 0; i < 100; ++i) {
m_engine.run_match_cycle();
}
}

private:
OrderBook m_book;
MatchingEngine m_engine;
Expand Down
15 changes: 9 additions & 6 deletions include/velox/sim/env/market_sim.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@ class MarketSimulator {
using PriceCallback = std::function<void(SymbolEngine&, int64_t bid, int64_t ask)>;

MarketSimulator(double tick_intervals_ms = 100.0,
int64_t initial_price = 10000,
const std::unordered_map<std::string, int64_t>& initial_prices = {},
int64_t volatility = 50,
int64_t min_spread = 5,
int64_t max_spread = 50,
uint32_t resting_quantity = 500) :
m_tick_interval(std::chrono::milliseconds(static_cast<int>(tick_intervals_ms))),
m_initial_price(initial_price),
m_initial_prices(initial_prices),
m_default_initial_price(10000),
m_volatility(volatility),
m_min_spread(min_spread),
m_max_spread(max_spread),
Expand All @@ -76,9 +77,13 @@ class MarketSimulator {
void update_resting_orders(SymbolEngine& engine, int64_t bid_price, int64_t ask_price);
void clear_resting_orders();

// Initialize initial price per share
int64_t get_initial_price(const std::string& symbol) const;

private:
std::chrono::milliseconds m_tick_interval;
int64_t m_initial_price;
std::unordered_map<std::string, int64_t> m_initial_prices;
int64_t m_default_initial_price;
int64_t m_volatility;
int64_t m_min_spread;
int64_t m_max_spread;
Expand All @@ -104,9 +109,7 @@ class MarketSimulator {
// Per-symbol resting orders
std::unordered_map<std::string, SymbolOrders> m_symbol_orders;




// Track active orders
std::unordered_map<std::string, std::unique_ptr<ActiveOrders>> m_active_orders;

// Shared pool for simulator orders
Expand Down
12 changes: 10 additions & 2 deletions include/velox/sim/strategy/bot_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace bot {
class BotManager {
public:
using OrderQueue = lockfree::SPSCQueue<Order, 65536>;
using CancelQueue = lockfree::SPSCQueue<uint64_t, 65536>;

BotManager();
~BotManager();
Expand All @@ -22,9 +23,13 @@ class BotManager {
// Called by snapshot thread to distribute snapshots to bots
void on_snapshot(const BookSnapshot& snapshot);

// Called by matching engine thread to consume orders
bool pop_order(Order& order);
// Called by matching engine thread to consume orders (per-symbol)
bool pop_order_for_symbol(const std::string& symbol, Order& order);
void push_order(const Order& order);

// Cancel queue (orders cancelled by bots)
void push_cancel(uint64_t order_id);
bool pop_cancel(uint64_t& order_id);

// Statistics
size_t bot_count() const { return m_bots.size(); }
Expand All @@ -34,6 +39,9 @@ class BotManager {
std::vector<std::unique_ptr<TradingBot>> m_bots;
std::unordered_map<std::string, std::vector<TradingBot*>> m_bots_by_symbol;
OrderQueue m_order_queue;
CancelQueue m_cancel_queue;
// Per-symbol order queues (single snapshot thread will both produce and consume for that symbol)
std::unordered_map<std::string, std::unique_ptr<OrderQueue>> m_order_queues;
};

}
Expand Down
159 changes: 127 additions & 32 deletions include/velox/sim/strategy/bots.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
#include "velox/book/book_snapshot.hpp"
#include <random>

/*
* NOTE: Each bot can only have 3 active orders at once to prevent flooding
*/
namespace velox {
namespace bot {

Expand All @@ -12,7 +15,7 @@ class BotManager;
class TradingBot {
public:
TradingBot(const std::string& name, const std::string& symbol)
: m_name(name), m_symbol(symbol) {}
: m_name(name), m_symbol(symbol), m_gen(m_rd()), m_dist(0.0, 1.0) {}

virtual ~TradingBot() = default;

Expand All @@ -28,6 +31,14 @@ class TradingBot {

void submit_order(const Order& order);

protected:
// RNG for stochastic behavior
std::random_device m_rd;
std::mt19937 m_gen;
std::uniform_real_distribution<> m_dist;

double rand01() { return m_dist(m_gen); }

std::string m_symbol;
private:
std::string m_name;
Expand Down Expand Up @@ -124,6 +135,12 @@ class SpreadBot : public TradingBot {
, m_quantity(quantity) {}

void on_snapshot(const BookSnapshot& snapshot) override {
// Expire tracked open orders TTL
for (auto it = m_open_order_ttls.begin(); it != m_open_order_ttls.end();) {
if (--(*it) <= 0) it = m_open_order_ttls.erase(it);
else ++it;
}

if (m_last_spread == 0) {
m_last_spread = snapshot.spread;
return;
Expand All @@ -132,7 +149,7 @@ class SpreadBot : public TradingBot {
int64_t spread_change = snapshot.spread - m_last_spread;

if (spread_change > m_threshold) {
// Spread widened buy
// Spread widened -> buy
Order order;
order.order_id = ++m_order_id;
order.side = OrderSide::BUY;
Expand All @@ -141,10 +158,13 @@ class SpreadBot : public TradingBot {
order.remaining_quantity = m_quantity;
order.type = OrderType::LIMIT;
std::strncpy(order.symbol, snapshot.symbol, 7);
submit_order(order);
if (m_open_order_ttls.size() < m_max_open_orders) {
submit_order(order);
m_open_order_ttls.push_back(m_order_ttl);
}
}
else if (spread_change < -m_threshold) {
// Spread narrowed sell
// Spread narrowed -> sell
Order order;
order.order_id = ++m_order_id;
order.side = OrderSide::SELL;
Expand All @@ -153,7 +173,10 @@ class SpreadBot : public TradingBot {
order.remaining_quantity = m_quantity;
order.type = OrderType::LIMIT;
std::strncpy(order.symbol, snapshot.symbol, 7);
submit_order(order);
if (m_open_order_ttls.size() < m_max_open_orders) {
submit_order(order);
m_open_order_ttls.push_back(m_order_ttl);
}
}

m_last_spread = snapshot.spread;
Expand All @@ -164,6 +187,11 @@ class SpreadBot : public TradingBot {
uint32_t m_quantity;
int64_t m_last_spread = 0;
uint64_t m_order_id = 10000;

// Safety limits
size_t m_max_open_orders = 3;
int m_order_ttl = 1000; // Snapshots until considered expired
std::vector<int> m_open_order_ttls;
};

// Random Walk Bot: random buy/sell decisions
Expand All @@ -179,33 +207,59 @@ class RandomWalkBot : public TradingBot {
, m_dist(0.0, 1.0) {}

void on_snapshot(const BookSnapshot& snapshot) override {
double r = m_dist(m_gen);

// Decrement TTLs and remove expired
for (auto it = m_open_order_ttls.begin(); it != m_open_order_ttls.end();) {
if (--(*it) <= 0) it = m_open_order_ttls.erase(it);
else ++it;
}

double r = rand01();

// Normal limit orders, obey per-bot open-order limit
if (r < m_buy_prob) {
Order order;
order.order_id = ++m_order_id;
order.side = OrderSide::BUY;
order.price = snapshot.best_ask;
order.quantity = m_quantity;
order.remaining_quantity = m_quantity;
order.type = OrderType::LIMIT;
std::strncpy(order.symbol, snapshot.symbol, 7);
/*
std::cout << "[RandomWalkBot " << name() << "] SUBMITTING BUY order at "
<< order.price << std::endl; */
submit_order(order);
if (m_open_order_ttls.size() < m_max_open_orders) {
Order order;
order.order_id = ++m_order_id;
order.side = OrderSide::BUY;
order.price = snapshot.best_ask;
order.quantity = m_quantity;
order.remaining_quantity = m_quantity;
order.type = OrderType::LIMIT;
std::strncpy(order.symbol, snapshot.symbol, 7);
submit_order(order);
m_open_order_ttls.push_back(m_order_ttl);
}
}
else if (r < m_buy_prob + m_sell_prob) {
Order order;
order.order_id = ++m_order_id;
order.side = OrderSide::SELL;
order.price = snapshot.best_bid;
order.quantity = m_quantity;
order.remaining_quantity = m_quantity;
order.type = OrderType::LIMIT;
std::strncpy(order.symbol, snapshot.symbol, 7);
submit_order(order);
if (m_open_order_ttls.size() < m_max_open_orders) {
Order order;
order.order_id = ++m_order_id;
order.side = OrderSide::SELL;
order.price = snapshot.best_bid;
order.quantity = m_quantity;
order.remaining_quantity = m_quantity;
order.type = OrderType::LIMIT;
std::strncpy(order.symbol, snapshot.symbol, 7);
submit_order(order);
m_open_order_ttls.push_back(m_order_ttl);
}
}

// Rare spike: large market order with cooldown
else if (rand01() < m_spike_prob && m_spike_cooldown == 0 && m_open_order_ttls.empty()) {
Order spike;
spike.order_id = ++m_order_id;
spike.side = (rand01() < 0.5) ? OrderSide::BUY : OrderSide::SELL;
spike.type = OrderType::MARKET;
spike.quantity = m_quantity * (3 + (m_gen() % 5));
spike.remaining_quantity = spike.quantity;
std::strncpy(spike.symbol, snapshot.symbol, 7);
submit_order(spike);
m_open_order_ttls.push_back(m_spike_ttl);
m_spike_cooldown = m_spike_cooldown_default;
}

if (m_spike_cooldown > 0) --m_spike_cooldown;
}

private:
Expand All @@ -216,6 +270,15 @@ class RandomWalkBot : public TradingBot {
std::mt19937 m_gen;
std::uniform_real_distribution<> m_dist;
uint64_t m_order_id = 20000;
// Safety controls
size_t m_max_open_orders = 3;
int m_order_ttl = 1000; // snapshots until considered expired
std::vector<int> m_open_order_ttls;
// Spike controls
double m_spike_prob = 0.01;
int m_spike_cooldown = 0;
int m_spike_cooldown_default = 2000;
int m_spike_ttl = 1500;
};

// Mean Reversion Bot: trades when price deviates from mean
Expand All @@ -229,6 +292,12 @@ class MeanReversionBot : public TradingBot {
, m_quantity(quantity) {}

void on_snapshot(const BookSnapshot& snapshot) override {
// expire tracked open orders TTL
for (auto it = m_open_order_ttls.begin(); it != m_open_order_ttls.end();) {
if (--(*it) <= 0) it = m_open_order_ttls.erase(it);
else ++it;
}

// Record price
m_prices.push_back(snapshot.mid_price);
if (m_prices.size() > m_lookback) {
Expand All @@ -254,7 +323,10 @@ class MeanReversionBot : public TradingBot {
order.remaining_quantity = m_quantity;
order.type = OrderType::LIMIT;
std::strncpy(order.symbol, snapshot.symbol, 7);
submit_order(order);
if (m_open_order_ttls.size() < m_max_open_orders) {
submit_order(order);
m_open_order_ttls.push_back(m_order_ttl);
}
}
else if (z_score < -m_z_threshold) {
// Price too low → buy
Expand All @@ -266,7 +338,10 @@ class MeanReversionBot : public TradingBot {
order.remaining_quantity = m_quantity;
order.type = OrderType::LIMIT;
std::strncpy(order.symbol, snapshot.symbol, 7);
submit_order(order);
if (m_open_order_ttls.size() < m_max_open_orders) {
submit_order(order);
m_open_order_ttls.push_back(m_order_ttl);
}
}
}

Expand All @@ -291,6 +366,10 @@ class MeanReversionBot : public TradingBot {
uint32_t m_quantity;
std::vector<double> m_prices;
uint64_t m_order_id = 30000;
// Safety controls
size_t m_max_open_orders = 3;
int m_order_ttl = 1000;
std::vector<int> m_open_order_ttls;
};

// Momentum Bot: Trades based on price momentum
Expand All @@ -303,6 +382,12 @@ class MomentumBot : public TradingBot {
, m_quantity(quantity) {}

void on_snapshot(const BookSnapshot& snapshot) override {
// Expire TTLs for tracked open orders
for (auto it = m_open_order_ttls.begin(); it != m_open_order_ttls.end();) {
if (--(*it) <= 0) it = m_open_order_ttls.erase(it);
else ++it;
}

if (m_prev_price == 0) {
m_prev_price = snapshot.mid_price;
return;
Expand All @@ -321,7 +406,10 @@ class MomentumBot : public TradingBot {
order.remaining_quantity = m_quantity;
order.type = OrderType::LIMIT;
std::strncpy(order.symbol, snapshot.symbol, 7);
submit_order(order);
if (m_open_order_ttls.size() < m_max_open_orders) {
submit_order(order);
m_open_order_ttls.push_back(m_order_ttl);
}
}
else if (change < -m_momentum_threshold) {
// Downward momentum → sell
Expand All @@ -333,7 +421,10 @@ class MomentumBot : public TradingBot {
order.remaining_quantity = m_quantity;
order.type = OrderType::LIMIT;
std::strncpy(order.symbol, snapshot.symbol, 7);
submit_order(order);
if (m_open_order_ttls.size() < m_max_open_orders) {
submit_order(order);
m_open_order_ttls.push_back(m_order_ttl);
}
}
}

Expand All @@ -342,6 +433,10 @@ class MomentumBot : public TradingBot {
uint32_t m_quantity;
int64_t m_prev_price = 0;
uint64_t m_order_id = 40000;
// Safety controls
size_t m_max_open_orders = 3;
int m_order_ttl = 1000;
std::vector<int> m_open_order_ttls;
};

}
Expand Down
Loading
Loading