use crate::error::{CoreError, Result};
use chrono::{DateTime, Duration, Utc};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OrderSide {
Buy,
Sell,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TWAPStrategy {
pub id: Uuid,
pub token_id: i64,
pub total_quantity: Decimal,
pub side: OrderSide,
pub num_slices: usize,
pub interval_seconds: u64,
pub participation_rate: Option<Decimal>,
pub start_time: DateTime<Utc>,
pub end_time: DateTime<Utc>,
pub created_at: DateTime<Utc>,
}
impl TWAPStrategy {
pub fn new(
token_id: i64,
total_quantity: Decimal,
side: OrderSide,
duration_seconds: u64,
num_slices: usize,
) -> Result<Self> {
if num_slices == 0 {
return Err(CoreError::Validation(
"Number of slices must be > 0".to_string(),
));
}
let now = Utc::now();
let interval_seconds = duration_seconds / num_slices as u64;
Ok(Self {
id: Uuid::new_v4(),
token_id,
total_quantity,
side,
num_slices,
interval_seconds,
participation_rate: None,
start_time: now,
end_time: now + Duration::seconds(duration_seconds as i64),
created_at: now,
})
}
pub fn with_participation_rate(mut self, rate: Decimal) -> Result<Self> {
if rate <= dec!(0) || rate > dec!(100) {
return Err(CoreError::Validation(
"Participation rate must be 0-100".to_string(),
));
}
self.participation_rate = Some(rate);
Ok(self)
}
pub fn quantity_per_slice(&self) -> Decimal {
self.total_quantity / Decimal::from(self.num_slices)
}
pub fn get_schedule(&self) -> Vec<(DateTime<Utc>, Decimal)> {
let mut schedule = Vec::new();
let quantity_per_slice = self.quantity_per_slice();
for i in 0..self.num_slices {
let slice_time =
self.start_time + Duration::seconds((i as u64 * self.interval_seconds) as i64);
schedule.push((slice_time, quantity_per_slice));
}
schedule
}
pub fn adjust_for_market_volume(
&self,
market_volume: Decimal,
slice_quantity: Decimal,
) -> Decimal {
if let Some(rate) = self.participation_rate {
let max_quantity = market_volume * (rate / dec!(100));
slice_quantity.min(max_quantity)
} else {
slice_quantity
}
}
pub fn is_active(&self, current_time: DateTime<Utc>) -> bool {
current_time >= self.start_time && current_time <= self.end_time
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VWAPStrategy {
pub id: Uuid,
pub token_id: i64,
pub total_quantity: Decimal,
pub side: OrderSide,
pub vwap_periods: Vec<VWAPPeriod>,
pub target_time: DateTime<Utc>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VWAPPeriod {
pub start_time: DateTime<Utc>,
pub end_time: DateTime<Utc>,
pub avg_price: Decimal,
pub volume: Decimal,
}
impl VWAPStrategy {
pub fn new(
token_id: i64,
total_quantity: Decimal,
side: OrderSide,
target_time: DateTime<Utc>,
) -> Self {
Self {
id: Uuid::new_v4(),
token_id,
total_quantity,
side,
vwap_periods: Vec::new(),
target_time,
created_at: Utc::now(),
}
}
pub fn add_period(&mut self, period: VWAPPeriod) {
self.vwap_periods.push(period);
}
pub fn calculate_historical_vwap(&self) -> Option<Decimal> {
if self.vwap_periods.is_empty() {
return None;
}
let total_pv: Decimal = self
.vwap_periods
.iter()
.map(|p| p.avg_price * p.volume)
.sum();
let total_volume: Decimal = self.vwap_periods.iter().map(|p| p.volume).sum();
if total_volume == dec!(0) {
return None;
}
Some(total_pv / total_volume)
}
pub fn volume_weighted_schedule(&self) -> Vec<(DateTime<Utc>, Decimal)> {
if self.vwap_periods.is_empty() {
return Vec::new();
}
let total_volume: Decimal = self.vwap_periods.iter().map(|p| p.volume).sum();
if total_volume == dec!(0) {
return Vec::new();
}
self.vwap_periods
.iter()
.map(|period| {
let volume_weight = period.volume / total_volume;
let quantity = self.total_quantity * volume_weight;
(period.start_time, quantity)
})
.collect()
}
pub fn vwap_deviation(&self, execution_price: Decimal) -> Option<Decimal> {
let vwap = self.calculate_historical_vwap()?;
if vwap == dec!(0) {
return None;
}
Some(((execution_price - vwap) / vwap) * dec!(100))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImplementationShortfall {
pub id: Uuid,
pub token_id: i64,
pub total_quantity: Decimal,
pub side: OrderSide,
pub arrival_price: Decimal,
pub executions: Vec<Execution>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Execution {
pub timestamp: DateTime<Utc>,
pub quantity: Decimal,
pub price: Decimal,
}
impl ImplementationShortfall {
pub fn new(
token_id: i64,
total_quantity: Decimal,
side: OrderSide,
arrival_price: Decimal,
) -> Self {
Self {
id: Uuid::new_v4(),
token_id,
total_quantity,
side,
arrival_price,
executions: Vec::new(),
created_at: Utc::now(),
}
}
pub fn add_execution(&mut self, quantity: Decimal, price: Decimal) {
self.executions.push(Execution {
timestamp: Utc::now(),
quantity,
price,
});
}
pub fn total_executed(&self) -> Decimal {
self.executions.iter().map(|e| e.quantity).sum()
}
pub fn average_execution_price(&self) -> Option<Decimal> {
let total_quantity = self.total_executed();
if total_quantity == dec!(0) {
return None;
}
let total_cost: Decimal = self.executions.iter().map(|e| e.quantity * e.price).sum();
Some(total_cost / total_quantity)
}
pub fn calculate_shortfall(&self) -> Option<Decimal> {
let avg_price = self.average_execution_price()?;
if self.arrival_price == dec!(0) {
return None;
}
let shortfall = match self.side {
OrderSide::Buy => (avg_price - self.arrival_price) / self.arrival_price,
OrderSide::Sell => (self.arrival_price - avg_price) / self.arrival_price,
};
Some(shortfall * dec!(10000)) }
pub fn decompose_shortfall(&self, current_price: Decimal) -> ShortfallDecomposition {
let total_executed = self.total_executed();
let remaining = self.total_quantity - total_executed;
let delay_cost = if remaining > dec!(0) {
match self.side {
OrderSide::Buy => (current_price - self.arrival_price) * remaining,
OrderSide::Sell => (self.arrival_price - current_price) * remaining,
}
} else {
dec!(0)
};
let execution_cost = if let Some(avg_price) = self.average_execution_price() {
match self.side {
OrderSide::Buy => (avg_price - self.arrival_price) * total_executed,
OrderSide::Sell => (self.arrival_price - avg_price) * total_executed,
}
} else {
dec!(0)
};
ShortfallDecomposition {
delay_cost,
execution_cost,
total_cost: delay_cost + execution_cost,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShortfallDecomposition {
pub delay_cost: Decimal,
pub execution_cost: Decimal,
pub total_cost: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IcebergOrder {
pub id: Uuid,
pub token_id: i64,
pub total_quantity: Decimal,
pub visible_quantity: Decimal,
pub side: OrderSide,
pub limit_price: Decimal,
pub filled_quantity: Decimal,
pub current_slice: Decimal,
pub replenishment_strategy: ReplenishmentStrategy,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ReplenishmentStrategy {
Immediate,
RandomDelay {
min_seconds: u64,
max_seconds: u64,
},
VolumeBased {
threshold_volume: u64,
},
}
impl IcebergOrder {
pub fn new(
token_id: i64,
total_quantity: Decimal,
visible_quantity: Decimal,
side: OrderSide,
limit_price: Decimal,
replenishment_strategy: ReplenishmentStrategy,
) -> Result<Self> {
if visible_quantity > total_quantity {
return Err(CoreError::Validation(
"Visible quantity cannot exceed total quantity".to_string(),
));
}
if visible_quantity <= dec!(0) {
return Err(CoreError::Validation(
"Visible quantity must be positive".to_string(),
));
}
Ok(Self {
id: Uuid::new_v4(),
token_id,
total_quantity,
visible_quantity,
side,
limit_price,
filled_quantity: dec!(0),
current_slice: visible_quantity,
replenishment_strategy,
created_at: Utc::now(),
})
}
pub fn remaining_quantity(&self) -> Decimal {
self.total_quantity - self.filled_quantity
}
pub fn is_filled(&self) -> bool {
self.filled_quantity >= self.total_quantity
}
pub fn fill_slice(&mut self, quantity: Decimal) -> Result<()> {
if quantity > self.current_slice {
return Err(CoreError::Validation(
"Fill quantity exceeds visible slice".to_string(),
));
}
self.filled_quantity += quantity;
self.current_slice -= quantity;
if self.current_slice == dec!(0) && !self.is_filled() {
self.replenish();
}
Ok(())
}
fn replenish(&mut self) {
let remaining = self.remaining_quantity();
self.current_slice = remaining.min(self.visible_quantity);
}
pub fn detection_risk_score(&self) -> Decimal {
let visibility_ratio = self.visible_quantity / self.total_quantity;
let randomization_factor = match self.replenishment_strategy {
ReplenishmentStrategy::Immediate => dec!(1.0),
ReplenishmentStrategy::RandomDelay { .. } => dec!(0.5),
ReplenishmentStrategy::VolumeBased { .. } => dec!(0.7),
};
visibility_ratio * randomization_factor * dec!(100)
}
}
pub struct SmartExecutionManager {
twap_strategies: Vec<TWAPStrategy>,
vwap_strategies: Vec<VWAPStrategy>,
shortfall_trackers: Vec<ImplementationShortfall>,
iceberg_orders: VecDeque<IcebergOrder>,
}
impl SmartExecutionManager {
pub fn new() -> Self {
Self {
twap_strategies: Vec::new(),
vwap_strategies: Vec::new(),
shortfall_trackers: Vec::new(),
iceberg_orders: VecDeque::new(),
}
}
pub fn add_twap(&mut self, strategy: TWAPStrategy) {
self.twap_strategies.push(strategy);
}
pub fn add_vwap(&mut self, strategy: VWAPStrategy) {
self.vwap_strategies.push(strategy);
}
pub fn add_shortfall_tracker(&mut self, tracker: ImplementationShortfall) {
self.shortfall_trackers.push(tracker);
}
pub fn add_iceberg(&mut self, order: IcebergOrder) {
self.iceberg_orders.push_back(order);
}
pub fn get_active_twap(&self) -> Vec<&TWAPStrategy> {
let now = Utc::now();
self.twap_strategies
.iter()
.filter(|s| s.is_active(now))
.collect()
}
pub fn get_twap(&self, id: Uuid) -> Option<&TWAPStrategy> {
self.twap_strategies.iter().find(|s| s.id == id)
}
pub fn get_vwap(&self, id: Uuid) -> Option<&VWAPStrategy> {
self.vwap_strategies.iter().find(|s| s.id == id)
}
pub fn get_shortfall_tracker(&self, id: Uuid) -> Option<&ImplementationShortfall> {
self.shortfall_trackers.iter().find(|s| s.id == id)
}
pub fn get_shortfall_tracker_mut(&mut self, id: Uuid) -> Option<&mut ImplementationShortfall> {
self.shortfall_trackers.iter_mut().find(|s| s.id == id)
}
pub fn get_iceberg(&self, id: Uuid) -> Option<&IcebergOrder> {
self.iceberg_orders.iter().find(|o| o.id == id)
}
pub fn get_iceberg_mut(&mut self, id: Uuid) -> Option<&mut IcebergOrder> {
self.iceberg_orders.iter_mut().find(|o| o.id == id)
}
pub fn cleanup_filled_icebergs(&mut self) {
self.iceberg_orders.retain(|o| !o.is_filled());
}
}
impl Default for SmartExecutionManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_twap_strategy_creation() {
let strategy = TWAPStrategy::new(
1,
dec!(1000),
OrderSide::Buy,
3600, 10, )
.unwrap();
assert_eq!(strategy.token_id, 1);
assert_eq!(strategy.total_quantity, dec!(1000));
assert_eq!(strategy.num_slices, 10);
assert_eq!(strategy.quantity_per_slice(), dec!(100));
assert_eq!(strategy.interval_seconds, 360);
}
#[test]
fn test_twap_schedule() {
let strategy = TWAPStrategy::new(1, dec!(1000), OrderSide::Buy, 1000, 5).unwrap();
let schedule = strategy.get_schedule();
assert_eq!(schedule.len(), 5);
for (_, quantity) in &schedule {
assert_eq!(*quantity, dec!(200));
}
}
#[test]
fn test_twap_participation_rate() {
let strategy = TWAPStrategy::new(1, dec!(1000), OrderSide::Buy, 3600, 10)
.unwrap()
.with_participation_rate(dec!(20))
.unwrap();
assert_eq!(strategy.participation_rate, Some(dec!(20)));
let adjusted = strategy.adjust_for_market_volume(dec!(1000), dec!(300));
assert_eq!(adjusted, dec!(200));
}
#[test]
fn test_vwap_calculation() {
let mut strategy = VWAPStrategy::new(
1,
dec!(1000),
OrderSide::Buy,
Utc::now() + Duration::hours(1),
);
strategy.add_period(VWAPPeriod {
start_time: Utc::now(),
end_time: Utc::now() + Duration::minutes(15),
avg_price: dec!(100),
volume: dec!(500),
});
strategy.add_period(VWAPPeriod {
start_time: Utc::now() + Duration::minutes(15),
end_time: Utc::now() + Duration::minutes(30),
avg_price: dec!(110),
volume: dec!(300),
});
let vwap = strategy.calculate_historical_vwap().unwrap();
assert_eq!(vwap, dec!(103.75));
}
#[test]
fn test_implementation_shortfall() {
let mut tracker = ImplementationShortfall::new(1, dec!(1000), OrderSide::Buy, dec!(100));
tracker.add_execution(dec!(500), dec!(101));
tracker.add_execution(dec!(500), dec!(103));
assert_eq!(tracker.total_executed(), dec!(1000));
let avg_price = tracker.average_execution_price().unwrap();
assert_eq!(avg_price, dec!(102));
let shortfall = tracker.calculate_shortfall().unwrap();
assert_eq!(shortfall, dec!(200));
}
#[test]
fn test_iceberg_order() {
let mut order = IcebergOrder::new(
1,
dec!(1000),
dec!(100),
OrderSide::Buy,
dec!(50),
ReplenishmentStrategy::Immediate,
)
.unwrap();
assert_eq!(order.remaining_quantity(), dec!(1000));
assert_eq!(order.current_slice, dec!(100));
order.fill_slice(dec!(100)).unwrap();
assert_eq!(order.filled_quantity, dec!(100));
assert_eq!(order.remaining_quantity(), dec!(900));
assert_eq!(order.current_slice, dec!(100));
}
#[test]
fn test_iceberg_partial_fill() {
let mut order = IcebergOrder::new(
1,
dec!(1000),
dec!(100),
OrderSide::Buy,
dec!(50),
ReplenishmentStrategy::Immediate,
)
.unwrap();
order.fill_slice(dec!(30)).unwrap();
assert_eq!(order.filled_quantity, dec!(30));
assert_eq!(order.current_slice, dec!(70));
}
#[test]
fn test_smart_execution_manager() {
let mut manager = SmartExecutionManager::new();
let twap = TWAPStrategy::new(1, dec!(1000), OrderSide::Buy, 3600, 10).unwrap();
let twap_id = twap.id;
manager.add_twap(twap);
assert!(manager.get_twap(twap_id).is_some());
}
#[test]
fn test_shortfall_decomposition() {
let mut tracker = ImplementationShortfall::new(1, dec!(1000), OrderSide::Buy, dec!(100));
tracker.add_execution(dec!(600), dec!(102));
let decomposition = tracker.decompose_shortfall(dec!(105));
assert_eq!(decomposition.execution_cost, dec!(1200));
assert_eq!(decomposition.delay_cost, dec!(2000));
assert_eq!(decomposition.total_cost, dec!(3200));
}
}