use crate::types::Order;
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Reconciliation {
pub appeared: Vec<String>,
pub vanished: Vec<String>,
pub still_open: Vec<String>,
}
impl Reconciliation {
#[must_use]
pub fn has_divergence(&self) -> bool {
!self.appeared.is_empty() || !self.vanished.is_empty()
}
}
#[must_use]
pub fn reconcile_orders(local_open_ids: &[String], venue_open: &[Order]) -> Reconciliation {
let local: HashSet<&str> = local_open_ids.iter().map(String::as_str).collect();
let venue: HashSet<&str> = venue_open.iter().map(|o| o.id.as_str()).collect();
let mut appeared: Vec<String> = venue.difference(&local).map(|s| (*s).to_string()).collect();
let mut vanished: Vec<String> = local.difference(&venue).map(|s| (*s).to_string()).collect();
let mut still_open: Vec<String> = local
.intersection(&venue)
.map(|s| (*s).to_string())
.collect();
appeared.sort();
vanished.sort();
still_open.sort();
Reconciliation {
appeared,
vanished,
still_open,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbol::Symbol;
use crate::types::{OrderSide, OrderStatus, OrderType};
use rust_decimal_macros::dec;
fn order(id: &str) -> Order {
Order {
id: id.to_string(),
client_order_id: None,
symbol: Symbol::new("BTC", "USDT"),
side: OrderSide::Buy,
order_type: OrderType::Limit,
status: OrderStatus::New,
quantity: dec!(1),
filled_quantity: dec!(0),
price: Some(dec!(100)),
average_price: None,
}
}
#[test]
fn identical_views_have_no_divergence() {
let local = vec!["a".to_string(), "b".to_string()];
let venue = vec![order("a"), order("b")];
let rec = reconcile_orders(&local, &venue);
assert!(!rec.has_divergence());
assert_eq!(rec.still_open, vec!["a", "b"]);
assert_eq!(rec.appeared, Vec::<String>::new());
assert_eq!(rec.vanished, Vec::<String>::new());
}
#[test]
fn detects_vanished_and_appeared() {
let local = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let venue = vec![order("b"), order("c"), order("d")];
let rec = reconcile_orders(&local, &venue);
assert!(rec.has_divergence());
assert_eq!(rec.vanished, vec!["a"]);
assert_eq!(rec.appeared, vec!["d"]);
assert_eq!(rec.still_open, vec!["b", "c"]);
}
#[test]
fn empty_inputs() {
let rec = reconcile_orders(&[], &[]);
assert!(!rec.has_divergence());
assert_eq!(rec, Reconciliation::default());
}
}