use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TradeDirection {
Buy,
Sell,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExecutionError {
InsufficientCapital { required: u64, available: u64 },
DrawdownExceeded { loss_percentage: f32 },
RiskExceeded { reason: String },
CapacityExceeded { reason: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TradeResult {
Success { pnl: i64, fees: u64 },
Loss { loss: u64, fees: u64, drawdown: f32 },
Rejected { error: ExecutionError, energy_consumed: u64 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Trade {
pub id: u64,
pub timestamp: DateTime<Utc>,
pub direction: TradeDirection,
pub asset: String,
pub size: u64,
pub entry_price: f32,
pub exit_price: Option<f32>,
pub leverage: f32,
pub fee_percentage: f32,
pub fees_paid: u64,
}
impl Trade {
pub fn new(
id: u64,
direction: TradeDirection,
asset: String,
size: u64,
entry_price: f32,
leverage: f32,
fee_percentage: f32,
) -> Self {
Trade {
id,
timestamp: Utc::now(),
direction,
asset,
size,
entry_price,
exit_price: None,
leverage,
fee_percentage,
fees_paid: 0,
}
}
pub fn close(&mut self, exit_price: f32) -> (i64, u64) {
self.exit_price = Some(exit_price);
let entry_value = (self.size as f32 * self.entry_price * self.leverage) as i64;
let exit_value = (self.size as f32 * exit_price * self.leverage) as i64;
let pnl = match self.direction {
TradeDirection::Buy => exit_value - entry_value,
TradeDirection::Sell => entry_value - exit_value,
};
let fees = ((pnl.abs() as f32) * (self.fee_percentage / 100.0)) as u64;
self.fees_paid = fees;
(pnl, fees)
}
pub fn calculate_drawdown(&self) -> Option<f32> {
self.exit_price.map(|exit_price| {
let price_change = (exit_price - self.entry_price) / self.entry_price;
let drawdown = match self.direction {
TradeDirection::Buy => -price_change,
TradeDirection::Sell => price_change,
};
drawdown * 100.0
})
}
}
pub struct TradeOperation {
pub trade: Trade,
pub fee_cost: u64,
pub capital_requirement: u64,
}
impl TradeOperation {
pub fn validate(&self, available_capital: u64, max_leverage: f32) -> Result<(), ExecutionError> {
if self.trade.leverage > max_leverage {
return Err(ExecutionError::RiskExceeded {
reason: format!("Leverage {} exceeds max {}", self.trade.leverage, max_leverage),
});
}
if self.capital_requirement > available_capital {
return Err(ExecutionError::InsufficientCapital {
required: self.capital_requirement,
available: available_capital,
});
}
Ok(())
}
pub fn execute(&mut self, exit_price: f32) -> TradeResult {
let (pnl, fees) = self.trade.close(exit_price);
self.fee_cost = fees;
match self.trade.calculate_drawdown() {
Some(drawdown) if drawdown > 5.0 => {
TradeResult::Loss {
loss: drawdown.abs() as u64,
fees,
drawdown,
}
}
Some(drawdown) if pnl < 0 => {
TradeResult::Loss {
loss: pnl.abs() as u64,
fees,
drawdown,
}
}
_ if pnl > 0 => {
TradeResult::Success {
pnl,
fees,
}
}
_ => {
TradeResult::Loss {
loss: 0,
fees,
drawdown: 0.0,
}
}
}
}
pub fn total_energy_cost(&self) -> u64 {
self.capital_requirement + self.fee_cost
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trade_creation() {
let trade = Trade::new(1, TradeDirection::Buy, "BTC".to_string(), 1, 50000.0, 1.0, 0.1);
assert_eq!(trade.direction, TradeDirection::Buy);
assert_eq!(trade.asset, "BTC");
}
#[test]
fn test_trade_pnl_calculation() {
let mut trade = Trade::new(1, TradeDirection::Buy, "BTC".to_string(), 1, 50000.0, 1.0, 0.1);
let (pnl, fees) = trade.close(51000.0);
assert!(pnl > 0); assert!(fees > 0); }
}