use bot_core::{
now_ms, ClientOrderId, ExchangeOrderId, InstrumentId, LiveOrder, OrderSide, OrderStatus, Price,
Qty, TradeId,
};
use std::collections::{HashMap, HashSet};
pub struct OrderManager {
orders: HashMap<ClientOrderId, LiveOrder>,
exchange_id_map: HashMap<ExchangeOrderId, ClientOrderId>,
seen_trades: HashSet<String>,
seen_trades_order: Vec<String>,
max_seen_trades: usize,
}
impl OrderManager {
pub fn new() -> Self {
Self {
orders: HashMap::new(),
exchange_id_map: HashMap::new(),
seen_trades: HashSet::new(),
seen_trades_order: Vec::new(),
max_seen_trades: 10000,
}
}
pub fn get(&self, client_id: &ClientOrderId) -> Option<&LiveOrder> {
self.orders.get(client_id)
}
pub fn get_mut(&mut self, client_id: &ClientOrderId) -> Option<&mut LiveOrder> {
self.orders.get_mut(client_id)
}
pub fn contains(&self, client_id: &ClientOrderId) -> bool {
self.orders.contains_key(client_id)
}
pub fn create_order(
&mut self,
client_id: ClientOrderId,
instrument: InstrumentId,
side: OrderSide,
price: Price,
qty: Qty,
) -> &LiveOrder {
let order = LiveOrder {
client_id: client_id.clone(),
exchange_order_id: None,
instrument,
side,
price,
requested_qty: qty,
filled_qty: Qty::new(rust_decimal::Decimal::ZERO),
avg_fill_px: None,
status: OrderStatus::New,
ts_created: now_ms(),
ts_last_update: now_ms(),
};
self.orders.insert(client_id.clone(), order);
self.orders.get(&client_id).unwrap()
}
pub fn accept_order(
&mut self,
client_id: &ClientOrderId,
exchange_order_id: Option<ExchangeOrderId>,
) -> bool {
if let Some(order) = self.orders.get_mut(client_id) {
order.status = OrderStatus::Accepted;
order.ts_last_update = now_ms();
if let Some(eid) = exchange_order_id {
order.exchange_order_id = Some(eid.clone());
self.exchange_id_map.insert(eid, client_id.clone());
}
true
} else {
false
}
}
pub fn reject_order(&mut self, client_id: &ClientOrderId) -> Option<LiveOrder> {
if let Some(mut order) = self.orders.remove(client_id) {
order.status = OrderStatus::Rejected;
order.ts_last_update = now_ms();
if let Some(ref eid) = order.exchange_order_id {
self.exchange_id_map.remove(eid);
}
Some(order)
} else {
None
}
}
pub fn apply_fill(
&mut self,
client_id: &ClientOrderId,
trade_id: &TradeId,
fill_qty: Qty,
fill_px: Price,
) -> bool {
if self.seen_trades.contains(&trade_id.0) {
return false;
}
self.seen_trades.insert(trade_id.0.clone());
self.seen_trades_order.push(trade_id.0.clone());
if self.seen_trades_order.len() > self.max_seen_trades {
if let Some(old) = self.seen_trades_order.first().cloned() {
self.seen_trades.remove(&old);
self.seen_trades_order.remove(0);
}
}
if let Some(order) = self.orders.get_mut(client_id) {
order.filled_qty += fill_qty;
let old_notional = order.avg_fill_px.map(|p| p.0).unwrap_or_default()
* (order.filled_qty.0 - fill_qty.0);
let new_notional = fill_px.0 * fill_qty.0;
let total_qty = order.filled_qty.0;
if total_qty > rust_decimal::Decimal::ZERO {
order.avg_fill_px = Some(Price((old_notional + new_notional) / total_qty));
}
if order.filled_qty >= order.requested_qty {
order.status = OrderStatus::Filled;
} else {
order.status = OrderStatus::PartiallyFilled;
}
order.ts_last_update = now_ms();
true
} else {
false
}
}
pub fn cancel_order(&mut self, client_id: &ClientOrderId) -> Option<LiveOrder> {
if let Some(mut order) = self.orders.remove(client_id) {
order.status = OrderStatus::Canceled;
order.ts_last_update = now_ms();
if let Some(ref eid) = order.exchange_order_id {
self.exchange_id_map.remove(eid);
}
Some(order)
} else {
None
}
}
pub fn is_complete(&self, client_id: &ClientOrderId) -> bool {
self.orders
.get(client_id)
.map(|o| o.is_complete())
.unwrap_or(false)
}
pub fn remove_terminal(&mut self, client_id: &ClientOrderId) -> Option<LiveOrder> {
if let Some(order) = self.orders.get(client_id) {
if order.status.is_terminal() {
let order = self.orders.remove(client_id)?;
if let Some(ref eid) = order.exchange_order_id {
self.exchange_id_map.remove(eid);
}
return Some(order);
}
}
None
}
pub fn client_id_from_exchange_id(
&self,
exchange_id: &ExchangeOrderId,
) -> Option<&ClientOrderId> {
self.exchange_id_map.get(exchange_id)
}
pub fn is_trade_seen(&self, trade_id: &TradeId) -> bool {
self.seen_trades.contains(&trade_id.0)
}
pub fn order_count(&self) -> usize {
self.orders.len()
}
}
impl Default for OrderManager {
fn default() -> Self {
Self::new()
}
}