Simulation Loop
Writing a QR simulation from scratch
Barebones Example
The simplest possible simulation — load parameters, init book, loop:
#include "orderbook.h"
#include "qr_model.h"
using namespace qr;
int main() {
std::string params_path = "data/PFE/qr_params";
// Load estimated parameters
QueueDistributions dists(params_path + "/invariant_distributions_qmax100.csv");
QRParams params(params_path);
SizeDistributions size_dists(params_path + "/size_distrib.csv");
// Initialise order book (4 levels per side)
OrderBook lob(dists, 4);
lob.init({1516, 1517, 1518, 1519}, // bid prices
{4, 1, 10, 5}, // bid volumes
{1520, 1521, 1522, 1523}, // ask prices
{6, 17, 22, 23}); // ask volumes
// Create model (wraps book + params + size distributions)
QRModel model(&lob, params, size_dists);
// Simulate 1 hour (in nanoseconds)
int64_t duration = int64_t(3600) * 1'000'000'000;
int64_t time = 0;
while (time < duration) {
Order order = model.sample_order(time);
// Always sample dt after the order — dt distribution may depend on the event
int64_t dt = model.sample_dt(model.last_event());
time += dt;
order.ts = time;
lob.process(order); // apply to book
}
}That’s the entire simulation. model.sample_order() reads the current book state (imbalance, spread), samples an event and a size, and returns a concrete Order. model.sample_dt() draws an inter-arrival time. The book handles the rest.
What sample_order Does
Internally, sample_order performs these steps:
- Read \((\text{Imb}, n)\) from the book
- Look up the
StateParamsfor that state — a list of events with probabilities - Draw from the categorical distribution → gives an
Event(type, side, queue) - Draw a size from the empirical size distribution for that event, scaled by MES
- Compute the price from the event and current book prices:
| Event | Price |
|---|---|
| Add at \(q_{-1}\) | best_bid |
| Add at \(q_{-2}\) | best_bid - 1 |
| Add at \(q_1\) | best_ask |
| Add at \(q_2\) | best_ask + 1 |
| Cancel | same as Add |
| Trade at \(q_{-1}\) | best_bid (sell into bid) |
| Trade at \(q_1\) | best_ask (buy into ask) |
| CreateBid | best_bid + 1 |
| CreateAsk | best_ask - 1 |
Inter-Arrival Time
By default, \(\Delta t\) is sampled from an exponential distribution: \(\Delta t \sim \text{Exp}(\Lambda(\text{Imb}, n))\).
You can replace it with any distribution by implementing the DeltaT interface and passing it to the model:
class DeltaT {
public:
virtual ~DeltaT() = default;
virtual int64_t sample(int imb_bin, int spread, const Event& event,
std::mt19937_64& rng) const = 0;
};The interface receives the current state and the sampled event, so your distribution can condition on anything. For example, the codebase includes a 5-component Gaussian mixture in \(\log_{10}\) space:
MixtureDeltaT delta_t(params_path + "/delta_t_gmm.csv");
QRModel model(&lob, params, size_dists, delta_t);Adding Bias
To inject a signal, call model.bias(b) before sample_order. This scales trade probabilities while leaving non-trade events unchanged:
while (time < duration) {
double b = impact->bias_factor();
model.bias(b); // bias trades
Order order = model.sample_order(time);
int64_t dt = model.sample_dt(model.last_event());
time += dt;
order.ts = time;
impact->step(time); // decay
lob.process(order);
// Update impact accumulator after trades
if (order.type == OrderType::Trade && !order.rejected) {
impact->add_trade(order.side, order.size);
}
}Any source of bias works — market impact, alpha signal, or your own:
double bias = impact->bias_factor() - alpha->scale() * alpha->value();
model.bias(bias);Recording Output
To capture the simulation output, record the book state before each event and the order metadata after:
std::vector<Fill> fills;
while (time < duration) {
Order order = model.sample_order(time);
int64_t dt = model.sample_dt(model.last_event());
time += dt;
order.ts = time;
// Snapshot book state before processing
double mid = (lob.best_bid() + lob.best_ask()) / 2.0;
double imb = lob.imbalance();
fills.clear();
lob.process(order, &fills);
// Now you have: mid, imb (before), order (after), fills
}The fills vector gives you per-level execution details for trades. See Order Book — Fill Recording for the format.