use crate::Decimal;
use crate::types::error::{MMError, MMResult};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OrderBookImbalance {
pub imbalance: Decimal,
pub bid_depth: Decimal,
pub ask_depth: Decimal,
pub levels: u32,
}
impl OrderBookImbalance {
#[must_use]
pub fn new(bid_depth: Decimal, ask_depth: Decimal, levels: u32) -> Self {
let total = bid_depth + ask_depth;
let imbalance = if total > Decimal::ZERO {
(bid_depth - ask_depth) / total
} else {
Decimal::ZERO
};
Self {
imbalance,
bid_depth,
ask_depth,
levels,
}
}
#[must_use]
pub fn total_depth(&self) -> Decimal {
self.bid_depth + self.ask_depth
}
#[must_use]
pub fn is_bid_heavy(&self) -> bool {
self.imbalance > Decimal::ZERO
}
#[must_use]
pub fn is_ask_heavy(&self) -> bool {
self.imbalance < Decimal::ZERO
}
#[must_use]
pub fn abs_imbalance(&self) -> Decimal {
self.imbalance.abs()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TradeFlowImbalance {
pub buy_volume: Decimal,
pub sell_volume: Decimal,
pub net_flow: Decimal,
pub imbalance: Decimal,
}
impl TradeFlowImbalance {
#[must_use]
pub fn new(buy_volume: Decimal, sell_volume: Decimal) -> Self {
let net_flow = buy_volume - sell_volume;
let total = buy_volume + sell_volume;
let imbalance = if total > Decimal::ZERO {
net_flow / total
} else {
Decimal::ZERO
};
Self {
buy_volume,
sell_volume,
net_flow,
imbalance,
}
}
#[must_use]
pub fn total_volume(&self) -> Decimal {
self.buy_volume + self.sell_volume
}
#[must_use]
pub fn is_buy_dominated(&self) -> bool {
self.imbalance > Decimal::ZERO
}
#[must_use]
pub fn is_sell_dominated(&self) -> bool {
self.imbalance < Decimal::ZERO
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Trade {
pub price: Decimal,
pub size: Decimal,
pub is_buyer_maker: bool,
pub timestamp: u64,
}
impl Trade {
#[must_use]
pub fn new(price: Decimal, size: Decimal, is_buyer_maker: bool, timestamp: u64) -> Self {
Self {
price,
size,
is_buyer_maker,
timestamp,
}
}
#[must_use]
pub fn notional(&self) -> Decimal {
self.price * self.size
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AdaptiveSpreadConfig {
pub base_spread: Decimal,
pub max_adjustment: Decimal,
pub orderbook_sensitivity: Decimal,
pub tradeflow_sensitivity: Decimal,
}
impl AdaptiveSpreadConfig {
pub fn new(
base_spread: Decimal,
max_adjustment: Decimal,
orderbook_sensitivity: Decimal,
tradeflow_sensitivity: Decimal,
) -> MMResult<Self> {
if base_spread <= Decimal::ZERO {
return Err(MMError::InvalidConfiguration(
"base_spread must be positive".to_string(),
));
}
if max_adjustment < Decimal::ONE {
return Err(MMError::InvalidConfiguration(
"max_adjustment must be >= 1.0".to_string(),
));
}
if orderbook_sensitivity < Decimal::ZERO || orderbook_sensitivity > Decimal::ONE {
return Err(MMError::InvalidConfiguration(
"orderbook_sensitivity must be between 0.0 and 1.0".to_string(),
));
}
if tradeflow_sensitivity < Decimal::ZERO || tradeflow_sensitivity > Decimal::ONE {
return Err(MMError::InvalidConfiguration(
"tradeflow_sensitivity must be between 0.0 and 1.0".to_string(),
));
}
Ok(Self {
base_spread,
max_adjustment,
orderbook_sensitivity,
tradeflow_sensitivity,
})
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AdaptiveSpread {
pub bid_spread: Decimal,
pub ask_spread: Decimal,
pub total_spread: Decimal,
}
impl AdaptiveSpread {
#[must_use]
pub fn new(bid_spread: Decimal, ask_spread: Decimal) -> Self {
Self {
bid_spread,
ask_spread,
total_spread: bid_spread + ask_spread,
}
}
#[must_use]
pub fn symmetric(half_spread: Decimal) -> Self {
Self::new(half_spread, half_spread)
}
#[must_use]
pub fn bid_price(&self, mid_price: Decimal) -> Decimal {
mid_price * (Decimal::ONE - self.bid_spread)
}
#[must_use]
pub fn ask_price(&self, mid_price: Decimal) -> Decimal {
mid_price * (Decimal::ONE + self.ask_spread)
}
#[must_use]
pub fn skew(&self) -> Decimal {
self.ask_spread - self.bid_spread
}
#[must_use]
pub fn is_symmetric(&self) -> bool {
self.bid_spread == self.ask_spread
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AdaptiveSpreadCalculator {
config: AdaptiveSpreadConfig,
}
impl AdaptiveSpreadCalculator {
#[must_use]
pub fn new(config: AdaptiveSpreadConfig) -> Self {
Self { config }
}
#[must_use]
pub fn config(&self) -> &AdaptiveSpreadConfig {
&self.config
}
#[must_use]
pub fn calculate_orderbook_imbalance(
bid_depths: &[(Decimal, Decimal)],
ask_depths: &[(Decimal, Decimal)],
levels: u32,
) -> OrderBookImbalance {
let levels_usize = if levels == 0 {
usize::MAX
} else {
levels as usize
};
let bid_depth: Decimal = bid_depths
.iter()
.take(levels_usize)
.map(|(_, size)| *size)
.sum();
let ask_depth: Decimal = ask_depths
.iter()
.take(levels_usize)
.map(|(_, size)| *size)
.sum();
let actual_levels = bid_depths.len().min(ask_depths.len()).min(levels_usize) as u32;
OrderBookImbalance::new(bid_depth, ask_depth, actual_levels)
}
#[must_use]
pub fn calculate_weighted_orderbook_imbalance(
bid_depths: &[(Decimal, Decimal)],
ask_depths: &[(Decimal, Decimal)],
mid_price: Decimal,
levels: u32,
) -> OrderBookImbalance {
let levels_usize = if levels == 0 {
usize::MAX
} else {
levels as usize
};
let bid_depth: Decimal = bid_depths
.iter()
.take(levels_usize)
.map(|(price, size)| {
let distance = (mid_price - *price).abs() / mid_price;
let weight = Decimal::ONE / (Decimal::ONE + distance * Decimal::TEN);
*size * weight
})
.sum();
let ask_depth: Decimal = ask_depths
.iter()
.take(levels_usize)
.map(|(price, size)| {
let distance = (*price - mid_price).abs() / mid_price;
let weight = Decimal::ONE / (Decimal::ONE + distance * Decimal::TEN);
*size * weight
})
.sum();
let actual_levels = bid_depths.len().min(ask_depths.len()).min(levels_usize) as u32;
OrderBookImbalance::new(bid_depth, ask_depth, actual_levels)
}
#[must_use]
pub fn calculate_tradeflow_imbalance(
trades: &[Trade],
window_ms: u64,
current_time: u64,
) -> TradeFlowImbalance {
let cutoff = current_time.saturating_sub(window_ms);
let (buy_volume, sell_volume) = trades.iter().filter(|t| t.timestamp >= cutoff).fold(
(Decimal::ZERO, Decimal::ZERO),
|(buy, sell), trade| {
if trade.is_buyer_maker {
(buy, sell + trade.size)
} else {
(buy + trade.size, sell)
}
},
);
TradeFlowImbalance::new(buy_volume, sell_volume)
}
#[must_use]
pub fn calculate_spread(
&self,
orderbook_imbalance: &OrderBookImbalance,
tradeflow_imbalance: Option<&TradeFlowImbalance>,
) -> AdaptiveSpread {
let half_spread = self.config.base_spread / Decimal::TWO;
let mut combined_imbalance =
orderbook_imbalance.imbalance * self.config.orderbook_sensitivity;
if let Some(flow) = tradeflow_imbalance {
combined_imbalance += flow.imbalance * self.config.tradeflow_sensitivity;
}
let combined_imbalance = combined_imbalance.max(-Decimal::ONE).min(Decimal::ONE);
let adjustment_range = self.config.max_adjustment - Decimal::ONE;
let adjustment = combined_imbalance.abs() * adjustment_range;
let (bid_adjustment, ask_adjustment) = if combined_imbalance > Decimal::ZERO {
let tighten = Decimal::ONE - adjustment * Decimal::new(5, 1); let widen = Decimal::ONE + adjustment;
(tighten.max(Decimal::new(5, 1)), widen) } else if combined_imbalance < Decimal::ZERO {
let widen = Decimal::ONE + adjustment;
let tighten = Decimal::ONE - adjustment * Decimal::new(5, 1);
(widen, tighten.max(Decimal::new(5, 1)))
} else {
(Decimal::ONE, Decimal::ONE)
};
let bid_spread = half_spread * bid_adjustment;
let ask_spread = half_spread * ask_adjustment;
AdaptiveSpread::new(bid_spread, ask_spread)
}
#[must_use]
pub fn calculate_spread_with_volatility(
&self,
orderbook_imbalance: &OrderBookImbalance,
tradeflow_imbalance: Option<&TradeFlowImbalance>,
current_volatility: Decimal,
baseline_volatility: Decimal,
) -> AdaptiveSpread {
let vol_ratio = if baseline_volatility > Decimal::ZERO {
current_volatility / baseline_volatility
} else {
Decimal::ONE
};
let vol_adjustment = vol_ratio.max(Decimal::new(5, 1)).min(Decimal::TWO);
let adjusted_config = AdaptiveSpreadConfig {
base_spread: self.config.base_spread * vol_adjustment,
..self.config.clone()
};
let adjusted_calculator = AdaptiveSpreadCalculator::new(adjusted_config);
adjusted_calculator.calculate_spread(orderbook_imbalance, tradeflow_imbalance)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dec;
#[test]
fn test_orderbook_imbalance_balanced() {
let imbalance = OrderBookImbalance::new(dec!(100.0), dec!(100.0), 5);
assert_eq!(imbalance.imbalance, dec!(0.0));
assert_eq!(imbalance.total_depth(), dec!(200.0));
assert!(!imbalance.is_bid_heavy());
assert!(!imbalance.is_ask_heavy());
}
#[test]
fn test_orderbook_imbalance_bid_heavy() {
let imbalance = OrderBookImbalance::new(dec!(150.0), dec!(50.0), 5);
assert_eq!(imbalance.imbalance, dec!(0.5));
assert!(imbalance.is_bid_heavy());
assert!(!imbalance.is_ask_heavy());
}
#[test]
fn test_orderbook_imbalance_ask_heavy() {
let imbalance = OrderBookImbalance::new(dec!(50.0), dec!(150.0), 5);
assert_eq!(imbalance.imbalance, dec!(-0.5));
assert!(!imbalance.is_bid_heavy());
assert!(imbalance.is_ask_heavy());
}
#[test]
fn test_orderbook_imbalance_empty() {
let imbalance = OrderBookImbalance::new(dec!(0.0), dec!(0.0), 0);
assert_eq!(imbalance.imbalance, dec!(0.0));
}
#[test]
fn test_tradeflow_imbalance_buy_dominated() {
let flow = TradeFlowImbalance::new(dec!(100.0), dec!(50.0));
assert_eq!(flow.net_flow, dec!(50.0));
assert!(flow.is_buy_dominated());
assert!(!flow.is_sell_dominated());
}
#[test]
fn test_tradeflow_imbalance_sell_dominated() {
let flow = TradeFlowImbalance::new(dec!(50.0), dec!(100.0));
assert_eq!(flow.net_flow, dec!(-50.0));
assert!(!flow.is_buy_dominated());
assert!(flow.is_sell_dominated());
}
#[test]
fn test_trade_creation() {
let trade = Trade::new(dec!(100.0), dec!(10.0), false, 12345);
assert_eq!(trade.price, dec!(100.0));
assert_eq!(trade.size, dec!(10.0));
assert!(!trade.is_buyer_maker);
assert_eq!(trade.timestamp, 12345);
assert_eq!(trade.notional(), dec!(1000.0));
}
#[test]
fn test_config_valid() {
let config = AdaptiveSpreadConfig::new(dec!(0.001), dec!(2.0), dec!(0.5), dec!(0.3));
assert!(config.is_ok());
}
#[test]
fn test_config_invalid_base_spread() {
let config = AdaptiveSpreadConfig::new(dec!(0.0), dec!(2.0), dec!(0.5), dec!(0.3));
assert!(config.is_err());
}
#[test]
fn test_config_invalid_max_adjustment() {
let config = AdaptiveSpreadConfig::new(dec!(0.001), dec!(0.5), dec!(0.5), dec!(0.3));
assert!(config.is_err());
}
#[test]
fn test_config_invalid_sensitivity() {
let config = AdaptiveSpreadConfig::new(dec!(0.001), dec!(2.0), dec!(1.5), dec!(0.3));
assert!(config.is_err());
let config = AdaptiveSpreadConfig::new(dec!(0.001), dec!(2.0), dec!(0.5), dec!(-0.1));
assert!(config.is_err());
}
#[test]
fn test_adaptive_spread_symmetric() {
let spread = AdaptiveSpread::symmetric(dec!(0.001));
assert_eq!(spread.bid_spread, dec!(0.001));
assert_eq!(spread.ask_spread, dec!(0.001));
assert_eq!(spread.total_spread, dec!(0.002));
assert!(spread.is_symmetric());
assert_eq!(spread.skew(), dec!(0.0));
}
#[test]
fn test_adaptive_spread_asymmetric() {
let spread = AdaptiveSpread::new(dec!(0.0008), dec!(0.0012));
assert!(!spread.is_symmetric());
assert_eq!(spread.skew(), dec!(0.0004));
}
#[test]
fn test_adaptive_spread_prices() {
let spread = AdaptiveSpread::new(dec!(0.001), dec!(0.001));
let mid = dec!(100.0);
assert_eq!(spread.bid_price(mid), dec!(99.9));
assert_eq!(spread.ask_price(mid), dec!(100.1));
}
#[test]
fn test_calculate_orderbook_imbalance() {
let bids = vec![(dec!(100.0), dec!(10.0)), (dec!(99.0), dec!(20.0))];
let asks = vec![(dec!(101.0), dec!(15.0)), (dec!(102.0), dec!(25.0))];
let imbalance = AdaptiveSpreadCalculator::calculate_orderbook_imbalance(&bids, &asks, 2);
assert_eq!(imbalance.bid_depth, dec!(30.0));
assert_eq!(imbalance.ask_depth, dec!(40.0));
assert_eq!(imbalance.levels, 2);
assert!(imbalance.is_ask_heavy());
}
#[test]
fn test_calculate_orderbook_imbalance_limited_levels() {
let bids = vec![
(dec!(100.0), dec!(10.0)),
(dec!(99.0), dec!(20.0)),
(dec!(98.0), dec!(30.0)),
];
let asks = vec![
(dec!(101.0), dec!(15.0)),
(dec!(102.0), dec!(25.0)),
(dec!(103.0), dec!(35.0)),
];
let imbalance = AdaptiveSpreadCalculator::calculate_orderbook_imbalance(&bids, &asks, 1);
assert_eq!(imbalance.bid_depth, dec!(10.0));
assert_eq!(imbalance.ask_depth, dec!(15.0));
}
#[test]
fn test_calculate_tradeflow_imbalance() {
let trades = vec![
Trade::new(dec!(100.0), dec!(10.0), false, 1000), Trade::new(dec!(100.0), dec!(5.0), true, 1500), Trade::new(dec!(100.0), dec!(8.0), false, 2000), ];
let flow = AdaptiveSpreadCalculator::calculate_tradeflow_imbalance(&trades, 5000, 3000);
assert_eq!(flow.buy_volume, dec!(18.0));
assert_eq!(flow.sell_volume, dec!(5.0));
assert!(flow.is_buy_dominated());
}
#[test]
fn test_calculate_tradeflow_imbalance_window() {
let trades = vec![
Trade::new(dec!(100.0), dec!(10.0), false, 1000), Trade::new(dec!(100.0), dec!(5.0), true, 2500), Trade::new(dec!(100.0), dec!(8.0), false, 2800), ];
let flow = AdaptiveSpreadCalculator::calculate_tradeflow_imbalance(&trades, 1000, 3000);
assert_eq!(flow.buy_volume, dec!(8.0));
assert_eq!(flow.sell_volume, dec!(5.0));
}
#[test]
fn test_calculate_spread_balanced() {
let config =
AdaptiveSpreadConfig::new(dec!(0.002), dec!(2.0), dec!(0.5), dec!(0.3)).unwrap();
let calculator = AdaptiveSpreadCalculator::new(config);
let imbalance = OrderBookImbalance::new(dec!(100.0), dec!(100.0), 5);
let spread = calculator.calculate_spread(&imbalance, None);
assert!(spread.is_symmetric());
assert_eq!(spread.bid_spread, dec!(0.001));
assert_eq!(spread.ask_spread, dec!(0.001));
}
#[test]
fn test_calculate_spread_bid_heavy() {
let config =
AdaptiveSpreadConfig::new(dec!(0.002), dec!(2.0), dec!(1.0), dec!(0.0)).unwrap();
let calculator = AdaptiveSpreadCalculator::new(config);
let imbalance = OrderBookImbalance::new(dec!(200.0), dec!(0.0), 5);
let spread = calculator.calculate_spread(&imbalance, None);
assert!(spread.ask_spread > spread.bid_spread);
}
#[test]
fn test_calculate_spread_ask_heavy() {
let config =
AdaptiveSpreadConfig::new(dec!(0.002), dec!(2.0), dec!(1.0), dec!(0.0)).unwrap();
let calculator = AdaptiveSpreadCalculator::new(config);
let imbalance = OrderBookImbalance::new(dec!(0.0), dec!(200.0), 5);
let spread = calculator.calculate_spread(&imbalance, None);
assert!(spread.bid_spread > spread.ask_spread);
}
#[test]
fn test_calculate_spread_with_tradeflow() {
let config =
AdaptiveSpreadConfig::new(dec!(0.002), dec!(2.0), dec!(0.5), dec!(0.5)).unwrap();
let calculator = AdaptiveSpreadCalculator::new(config);
let orderbook = OrderBookImbalance::new(dec!(100.0), dec!(100.0), 5);
let tradeflow = TradeFlowImbalance::new(dec!(100.0), dec!(0.0));
let spread = calculator.calculate_spread(&orderbook, Some(&tradeflow));
assert!(spread.ask_spread > spread.bid_spread);
}
#[test]
fn test_calculate_spread_with_volatility() {
let config =
AdaptiveSpreadConfig::new(dec!(0.002), dec!(2.0), dec!(0.5), dec!(0.3)).unwrap();
let calculator = AdaptiveSpreadCalculator::new(config);
let imbalance = OrderBookImbalance::new(dec!(100.0), dec!(100.0), 5);
let spread_high_vol = calculator.calculate_spread_with_volatility(
&imbalance,
None,
dec!(0.02), dec!(0.01), );
let spread_normal = calculator.calculate_spread(&imbalance, None);
assert!(spread_high_vol.total_spread > spread_normal.total_spread);
}
#[cfg(feature = "serde")]
#[test]
fn test_serialization() {
let config =
AdaptiveSpreadConfig::new(dec!(0.001), dec!(2.0), dec!(0.5), dec!(0.3)).unwrap();
let json = serde_json::to_string(&config).unwrap();
let deserialized: AdaptiveSpreadConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config, deserialized);
}
}