use crate::orderbook::error::OrderBookError;
use dashmap::DashSet;
use pricelevel::{Id, OrderType, PegReferenceType, Side};
use tracing::trace;
#[derive(Debug, Default)]
pub struct SpecialOrderTracker {
pegged_orders: DashSet<Id>,
trailing_stop_orders: DashSet<Id>,
}
impl SpecialOrderTracker {
pub fn new() -> Self {
Self {
pegged_orders: DashSet::new(),
trailing_stop_orders: DashSet::new(),
}
}
pub fn register_pegged_order(&self, order_id: Id) {
self.pegged_orders.insert(order_id);
trace!("Registered pegged order {} for re-pricing", order_id);
}
pub fn register_trailing_stop(&self, order_id: Id) {
self.trailing_stop_orders.insert(order_id);
trace!("Registered trailing stop order {} for re-pricing", order_id);
}
pub fn unregister_pegged_order(&self, order_id: &Id) {
self.pegged_orders.remove(order_id);
trace!("Unregistered pegged order {} from re-pricing", order_id);
}
pub fn unregister_trailing_stop(&self, order_id: &Id) {
self.trailing_stop_orders.remove(order_id);
trace!(
"Unregistered trailing stop order {} from re-pricing",
order_id
);
}
pub fn pegged_order_count(&self) -> usize {
self.pegged_orders.len()
}
pub fn trailing_stop_count(&self) -> usize {
self.trailing_stop_orders.len()
}
pub fn pegged_order_ids(&self) -> Vec<Id> {
self.pegged_orders.iter().map(|r| *r).collect()
}
pub fn trailing_stop_ids(&self) -> Vec<Id> {
self.trailing_stop_orders.iter().map(|r| *r).collect()
}
pub fn clear(&self) {
self.pegged_orders.clear();
self.trailing_stop_orders.clear();
}
}
#[derive(Debug, Clone, Default)]
pub struct RepricingResult {
pub pegged_orders_repriced: usize,
pub trailing_stops_repriced: usize,
pub failed_orders: Vec<(Id, String)>,
}
pub fn calculate_pegged_price(
reference_type: PegReferenceType,
offset: i64,
_side: Side,
best_bid: Option<u128>,
best_ask: Option<u128>,
mid_price: Option<u128>,
last_trade: Option<u128>,
) -> Option<u128> {
let reference_price = match reference_type {
PegReferenceType::BestBid => best_bid?,
PegReferenceType::BestAsk => best_ask?,
PegReferenceType::MidPrice => mid_price?,
PegReferenceType::LastTrade => last_trade?,
};
let new_price = if offset >= 0 {
reference_price.saturating_add(offset as u128)
} else {
reference_price.saturating_sub((-offset) as u128)
};
Some(new_price.max(1))
}
pub fn calculate_trailing_stop_price(
side: Side,
current_stop_price: u128,
trail_amount: u64,
last_reference_price: u128,
current_market_price: u128,
) -> Option<(u128, u128)> {
let trail = trail_amount as u128;
match side {
Side::Sell => {
if current_market_price > last_reference_price {
let new_stop_price = current_market_price.saturating_sub(trail);
if new_stop_price > current_stop_price {
return Some((new_stop_price, current_market_price));
}
}
}
Side::Buy => {
if current_market_price < last_reference_price {
let new_stop_price = current_market_price.saturating_add(trail);
if new_stop_price < current_stop_price {
return Some((new_stop_price, current_market_price));
}
}
}
}
None
}
pub trait RepricingOperations<T> {
fn reprice_pegged_orders(&self) -> Result<usize, OrderBookError>;
fn reprice_trailing_stops(&self) -> Result<usize, OrderBookError>;
fn reprice_special_orders(&self) -> Result<RepricingResult, OrderBookError>;
fn should_trigger_trailing_stop(
&self,
order: &OrderType<T>,
current_market_price: u128,
) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_pegged_price_best_bid() {
let price = calculate_pegged_price(
PegReferenceType::BestBid,
5,
Side::Buy,
Some(100),
Some(105),
Some(102),
Some(101),
);
assert_eq!(price, Some(105)); }
#[test]
fn test_calculate_pegged_price_best_ask() {
let price = calculate_pegged_price(
PegReferenceType::BestAsk,
-3,
Side::Sell,
Some(100),
Some(105),
Some(102),
Some(101),
);
assert_eq!(price, Some(102)); }
#[test]
fn test_calculate_pegged_price_mid_price() {
let price = calculate_pegged_price(
PegReferenceType::MidPrice,
0,
Side::Buy,
Some(100),
Some(110),
Some(105),
Some(103),
);
assert_eq!(price, Some(105)); }
#[test]
fn test_calculate_pegged_price_last_trade() {
let price = calculate_pegged_price(
PegReferenceType::LastTrade,
2,
Side::Buy,
Some(100),
Some(105),
Some(102),
Some(101),
);
assert_eq!(price, Some(103)); }
#[test]
fn test_calculate_pegged_price_missing_reference() {
let price = calculate_pegged_price(
PegReferenceType::BestBid,
5,
Side::Buy,
None, Some(105),
Some(102),
Some(101),
);
assert_eq!(price, None);
}
#[test]
fn test_calculate_pegged_price_negative_offset_floor() {
let price = calculate_pegged_price(
PegReferenceType::BestBid,
-200, Side::Buy,
Some(100),
Some(105),
Some(102),
Some(101),
);
assert_eq!(price, Some(1)); }
#[test]
fn test_trailing_stop_sell_market_rises() {
let result = calculate_trailing_stop_price(
Side::Sell,
95, 5, 100, 110, );
assert_eq!(result, Some((105, 110))); }
#[test]
fn test_trailing_stop_sell_market_falls() {
let result = calculate_trailing_stop_price(
Side::Sell,
95, 5, 100, 90, );
assert_eq!(result, None); }
#[test]
fn test_trailing_stop_buy_market_falls() {
let result = calculate_trailing_stop_price(
Side::Buy,
105, 5, 100, 90, );
assert_eq!(result, Some((95, 90))); }
#[test]
fn test_trailing_stop_buy_market_rises() {
let result = calculate_trailing_stop_price(
Side::Buy,
105, 5, 100, 110, );
assert_eq!(result, None); }
#[test]
fn test_special_order_tracker() {
let tracker = SpecialOrderTracker::new();
let id1 = Id::from_u64(1);
let id2 = Id::from_u64(2);
let id3 = Id::from_u64(3);
tracker.register_pegged_order(id1);
tracker.register_pegged_order(id2);
tracker.register_trailing_stop(id3);
assert_eq!(tracker.pegged_order_count(), 2);
assert_eq!(tracker.trailing_stop_count(), 1);
tracker.unregister_pegged_order(&id1);
assert_eq!(tracker.pegged_order_count(), 1);
tracker.clear();
assert_eq!(tracker.pegged_order_count(), 0);
assert_eq!(tracker.trailing_stop_count(), 0);
}
}