use std::collections::VecDeque;
use crate::bar_indicators::indicator_value::IndicatorValue;
use crate::bar_indicators::liquidation_consumer::LiquidationConsumer;
use crate::core::types::Liquidation;
#[derive(Clone)]
pub struct LiquidationVolumeVelocity {
window_ms: i64,
events: VecDeque<(i64, f64)>,
total_value: f64,
last_velocity: f64,
}
impl LiquidationVolumeVelocity {
pub fn new(window_ms: i64) -> Self {
Self {
window_ms: window_ms.max(1),
events: VecDeque::new(),
total_value: 0.0,
last_velocity: 0.0,
}
}
fn evict(&mut self, now: i64) {
while let Some(&(ts, val)) = self.events.front() {
if now - ts > self.window_ms {
self.events.pop_front();
self.total_value -= val;
} else {
break;
}
}
if self.total_value < 0.0 {
self.total_value = 0.0;
}
}
}
impl LiquidationConsumer for LiquidationVolumeVelocity {
fn update_liquidation(&mut self, liq: &Liquidation) -> IndicatorValue {
let qv = liq.quote_value();
self.events.push_back((liq.timestamp, qv));
self.total_value += qv;
self.evict(liq.timestamp);
let window_minutes = self.window_ms as f64 / 60_000.0;
self.last_velocity = self.total_value / window_minutes;
IndicatorValue::Single(self.last_velocity)
}
fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.last_velocity)
}
fn reset(&mut self) {
self.events.clear();
self.total_value = 0.0;
self.last_velocity = 0.0;
}
fn is_ready(&self) -> bool {
!self.events.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::types::TradeSide;
fn liq(ts: i64, price: f64, qty: f64) -> Liquidation {
Liquidation { symbol: String::new(), side: TradeSide::Buy, price, quantity: qty, timestamp: ts, value: None }
}
#[test]
fn zero_initially() {
let lvv = LiquidationVolumeVelocity::new(60_000);
assert!(!lvv.is_ready());
assert_eq!(lvv.value(), IndicatorValue::Single(0.0));
}
#[test]
fn single_event_velocity() {
let mut lvv = LiquidationVolumeVelocity::new(60_000);
lvv.update_liquidation(&liq(0, 30_000.0, 1.0));
assert!(lvv.is_ready());
if let IndicatorValue::Single(v) = lvv.value() {
assert!((v - 30_000.0).abs() < 1e-6, "v={v}");
} else {
panic!("expected Single");
}
}
#[test]
fn two_events_sum() {
let mut lvv = LiquidationVolumeVelocity::new(60_000);
lvv.update_liquidation(&liq(0, 10_000.0, 1.0));
lvv.update_liquidation(&liq(1_000, 10_000.0, 1.0));
if let IndicatorValue::Single(v) = lvv.value() {
assert!((v - 20_000.0).abs() < 1e-6, "v={v}");
}
}
#[test]
fn old_events_evicted() {
let mut lvv = LiquidationVolumeVelocity::new(10_000);
lvv.update_liquidation(&liq(0, 30_000.0, 1.0));
lvv.update_liquidation(&liq(15_000, 5_000.0, 1.0));
let window_minutes = 10_000.0_f64 / 60_000.0;
let expected = 5_000.0 / window_minutes;
if let IndicatorValue::Single(v) = lvv.value() {
assert!((v - expected).abs() < 1e-3, "v={v}, expected={expected}");
}
}
#[test]
fn reset_clears_state() {
let mut lvv = LiquidationVolumeVelocity::new(60_000);
lvv.update_liquidation(&liq(0, 30_000.0, 1.0));
lvv.reset();
assert!(!lvv.is_ready());
assert_eq!(lvv.value(), IndicatorValue::Single(0.0));
}
}