#![allow(clippy::panic, clippy::expect_used)]
use crate::{Dec, OrderBook, PriceAmount, dec};
use rand::{RngExt, SeedableRng, rngs::SmallRng};
use rand_distr::{Distribution, Exp, Normal, NormalError};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Side {
Buy,
Sell,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PriceAmountSide {
pub price_amount: PriceAmount,
pub side: Side,
}
#[derive(Debug)]
#[allow(dead_code)] pub struct OrderBookTopBuilder {
mid: Dec,
bid_spread: Dec,
ask_spread: Dec,
bid_amount: Dec,
ask_amount: Dec,
sequence_number: u64,
}
#[derive(Debug)]
pub struct OrderBookBuilder {
book: OrderBook,
tick_size: Dec,
lot_size: Dec,
spread: Dec,
amount: Dec,
arrival_rate: Dec,
rng: SmallRng,
}
#[derive(Default, Debug, Clone)]
pub struct OrderBookEvents {
pub trades: Vec<PriceAmountSide>,
pub cancels: Vec<PriceAmountSide>,
pub new_orders: Vec<PriceAmountSide>,
}
impl Default for OrderBookBuilder {
fn default() -> Self {
Self::new()
}
}
impl OrderBookBuilder {
pub fn new() -> Self {
Self {
book: OrderBook::default(),
tick_size: dec!(0.01),
lot_size: dec!(0.01),
spread: dec!(0.01),
amount: dec!(10),
arrival_rate: Dec::ONE,
rng: SmallRng::seed_from_u64(0),
}
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.rng = SmallRng::seed_from_u64(seed);
self
}
pub fn with_bid(mut self, price: Dec, amount: Dec) -> Self {
self.book.bids.set_price_amount(price, amount);
self
}
pub fn with_ask(mut self, price: Dec, amount: Dec) -> Self {
self.book.asks.set_price_amount(price, amount);
self
}
pub fn with_tick_size(mut self, tick_size: Dec) -> Self {
if tick_size <= Dec::ZERO {
panic!("Tick size must be positive");
}
self.tick_size = tick_size;
self
}
pub fn with_lot_size(mut self, lot_size: Dec) -> Self {
if lot_size <= Dec::ZERO {
panic!("Lot size must be positive");
}
self.lot_size = lot_size;
self
}
pub fn with_spread(mut self, spread: Dec) -> Self {
if spread <= Dec::ZERO {
panic!("Spread must be positive");
}
self.spread = spread;
self
}
pub fn with_amount(mut self, amount: Dec) -> Self {
if amount <= Dec::ZERO {
panic!("Amount must be positive");
}
self.amount = amount;
self
}
pub fn with_arrival_rate(mut self, arrival_rate: Dec) -> Self {
if arrival_rate <= Dec::ZERO {
panic!("Arrival rate must be positive");
}
self.arrival_rate = arrival_rate;
self
}
pub fn build(self) -> OrderBook {
self.book
}
pub fn simulate(&mut self, mid: Dec, levels: usize) -> (OrderBook, OrderBookEvents) {
let mut events = self.deplete();
let half_spread = self.simulate_half_spread();
let bid = (mid - half_spread).round_to_step(self.tick_size);
let ask = (mid + half_spread).round_to_step(self.tick_size);
events.trades = self.trades(bid, ask);
events.new_orders = self.add_levels(bid, ask, levels);
(self.book.clone(), events)
}
pub fn simulate_spread(&mut self) -> Dec {
let jitter: i128 = self.rng.random_range(50..150);
ceil_to_step(scale_percent(self.spread, jitter), self.tick_size)
}
pub fn simulate_half_spread(&mut self) -> Dec {
Dec::from_raw(self.simulate_spread().into_raw() / 2)
}
pub fn simulate_amount(&mut self) -> Dec {
let factor: i128 = self.rng.random_range(10..1_000);
ceil_to_step(scale_percent(self.amount, factor), self.lot_size)
}
pub fn simulate_distance(&mut self) -> Dec {
let rate = self.arrival_rate.to_f64() / self.spread.to_f64();
let dist = Exp::new(rate).expect("rate must be positive");
let distance = Dec::from_f64(dist.sample(&mut self.rng)).unwrap_or(self.spread);
ceil_to_step(distance.max(self.tick_size), self.tick_size)
}
pub fn deplete(&mut self) -> OrderBookEvents {
let mut events = OrderBookEvents::default();
let Some(mid) = self.book.mid_price() else {
return events;
};
let Some(spread) = self.book.spread() else {
return events;
};
let lambda = self.arrival_rate.to_f64() / spread.to_f64();
let depleted: Vec<Dec> = self
.book
.bids
.iter()
.filter_map(|level| {
let distance = (mid - level.price).to_f64();
let depletion_chance = (-lambda * distance).exp();
let roll: f64 = self.rng.random_range(0.0..1.0);
if roll < depletion_chance {
events.cancels.push(PriceAmountSide {
price_amount: *level,
side: Side::Buy,
});
Some(level.price)
} else {
None
}
})
.collect();
for price in depleted {
self.book.bids.set_price_amount(price, Dec::ZERO);
}
let depleted: Vec<Dec> = self
.book
.asks
.iter()
.filter_map(|level| {
let distance = (level.price - mid).to_f64();
let depletion_chance = (-lambda * distance).exp();
let roll: f64 = self.rng.random_range(0.0..1.0);
if roll < depletion_chance {
events.cancels.push(PriceAmountSide {
price_amount: *level,
side: Side::Sell,
});
Some(level.price)
} else {
None
}
})
.collect();
for price in depleted {
self.book.asks.set_price_amount(price, Dec::ZERO);
}
events
}
fn trades(&mut self, bid: Dec, ask: Dec) -> Vec<PriceAmountSide> {
let mut trades = Vec::new();
loop {
if let Some(level) = self.book.bids.best().copied()
&& level.price > bid
{
trades.push(PriceAmountSide {
price_amount: level,
side: Side::Sell,
});
self.book.bids.set_price_amount(level.price, Dec::ZERO);
} else {
break;
}
}
loop {
if let Some(level) = self.book.asks.best().copied()
&& level.price < ask
{
trades.push(PriceAmountSide {
price_amount: level,
side: Side::Buy,
});
self.book.asks.set_price_amount(level.price, Dec::ZERO);
} else {
break;
}
}
trades
}
fn add_levels(&mut self, bid: Dec, ask: Dec, levels: usize) -> Vec<PriceAmountSide> {
let mut new_orders = Vec::new();
if self.book.bids.best_price() < Some(bid) {
let amount = self.simulate_amount();
self.book.bids.set_price_amount(bid, amount);
self.book.bids.trim(levels);
}
if self.book.asks.best_price() > Some(ask) {
let amount = self.simulate_amount();
self.book.asks.set_price_amount(ask, amount);
self.book.asks.trim(levels);
}
while self.book.bids.len() < levels {
let price = bid - self.simulate_distance();
let amount = self.simulate_amount();
self.book.bids.set_price_amount(price, amount);
new_orders.push(PriceAmountSide {
price_amount: PriceAmount { price, amount },
side: Side::Buy,
});
}
while self.book.asks.len() < levels {
let price = ask + self.simulate_distance();
let amount = self.simulate_amount();
self.book.asks.set_price_amount(price, amount);
new_orders.push(PriceAmountSide {
price_amount: PriceAmount { price, amount },
side: Side::Sell,
});
}
new_orders
}
}
fn scale_percent(value: Dec, percent: i128) -> Dec {
Dec::from_raw(value.into_raw() * percent / 100)
}
fn ceil_to_step(value: Dec, step: Dec) -> Dec {
let (value, step) = (value.into_raw(), step.into_raw());
let steps = value.div_euclid(step) + i128::from(value.rem_euclid(step) != 0);
Dec::from_raw(steps * step)
}
#[derive(Debug)]
pub struct RandomWalk {
steps: usize,
rng: SmallRng,
}
impl RandomWalk {
pub fn new(steps: usize) -> Self {
Self {
steps,
rng: SmallRng::seed_from_u64(0),
}
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.rng = SmallRng::seed_from_u64(seed);
self
}
pub fn lognormal(
mut self,
start: f64,
drift: f64,
volatility: f64,
) -> Result<Vec<f64>, NormalError> {
let dt = 1.0 / self.steps.max(1) as f64;
let step = Normal::new(
(drift - volatility * volatility / 2.0) * dt,
volatility * dt.sqrt(),
)?;
let mut price = start;
Ok((0..self.steps)
.map(|_| {
price *= step.sample(&mut self.rng).exp();
price
})
.collect())
}
}