use std::collections::HashMap;
use chrono::{DateTime, FixedOffset, Utc};
use serde::{Deserialize, Serialize};
use crate::trading_mode::TradingMode;
use crate::unified_data::{Position, OrderSide};
use crate::paper_trading::TradeLogEntry;
use crate::errors::Result;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceMetrics {
pub total_return: f64,
pub sharpe_ratio: f64,
pub max_drawdown: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PnLReport {
pub realized_pnl: f64,
pub unrealized_pnl: f64,
pub funding_pnl: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Alert {
pub level: String,
pub message: String,
pub timestamp: DateTime<FixedOffset>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DailyReport {
pub date: DateTime<FixedOffset>,
pub pnl: f64,
pub trades: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountSummary {
pub balance: f64,
pub equity: f64,
pub margin_used: f64,
pub margin_available: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PositionSummary {
pub total_positions: usize,
pub total_pnl: f64,
pub long_positions: usize,
pub short_positions: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderSummary {
pub active_orders: usize,
pub filled_orders: usize,
pub cancelled_orders: usize,
pub total_volume: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskSummary {
pub risk_level: String,
pub max_drawdown: f64,
pub var_95: f64,
pub leverage: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemStatus {
pub is_connected: bool,
pub is_running: bool,
pub uptime_seconds: u64,
pub last_heartbeat: DateTime<FixedOffset>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertEntry {
pub level: String,
pub message: String,
pub timestamp: DateTime<FixedOffset>,
pub symbol: Option<String>,
pub order_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceSnapshot {
pub total_pnl: f64,
pub daily_pnl: f64,
pub win_rate: f64,
pub sharpe_ratio: f64,
pub max_drawdown: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitoringDashboardData {
pub timestamp: DateTime<FixedOffset>,
pub account_summary: AccountSummary,
pub position_summary: PositionSummary,
pub order_summary: OrderSummary,
pub risk_summary: RiskSummary,
pub system_status: SystemStatus,
pub recent_alerts: Vec<AlertEntry>,
pub performance: PerformanceSnapshot,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FundingSymbolAnalysis {
pub symbol: String,
pub average_rate: f64,
pub volatility: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommonPerformanceMetrics {
pub mode: TradingMode,
pub initial_balance: f64,
pub current_balance: f64,
pub realized_pnl: f64,
pub unrealized_pnl: f64,
pub funding_pnl: f64,
pub total_pnl: f64,
pub total_fees: f64,
pub total_return_pct: f64,
pub trade_count: usize,
pub win_rate: f64,
pub max_drawdown: f64,
pub max_drawdown_pct: f64,
pub start_time: DateTime<FixedOffset>,
pub end_time: DateTime<FixedOffset>,
pub duration_days: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaperTradingReport {
pub common: CommonPerformanceMetrics,
pub annualized_return: f64,
pub sharpe_ratio: f64,
pub sortino_ratio: f64,
pub daily_volatility: f64,
pub trade_log: Vec<TradeLogEntry>,
pub positions: HashMap<String, Position>,
pub funding_impact: FundingImpactAnalysis,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiveTradingReport {
pub common: CommonPerformanceMetrics,
pub annualized_return: f64,
pub sharpe_ratio: f64,
pub sortino_ratio: f64,
pub daily_volatility: f64,
pub trade_log: Vec<TradeLogEntry>,
pub positions: HashMap<String, Position>,
pub funding_impact: FundingImpactAnalysis,
pub risk_metrics: RiskMetrics,
pub alert_history: Vec<AlertEntry>,
pub connection_metrics: ConnectionMetrics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskMetrics {
pub current_leverage: f64,
pub max_leverage: f64,
pub value_at_risk_95: f64,
pub value_at_risk_99: f64,
pub expected_shortfall_95: f64,
pub beta: f64,
pub correlation: f64,
pub position_concentration: f64,
pub largest_position: f64,
pub largest_position_symbol: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionMetrics {
pub uptime_pct: f64,
pub disconnection_count: usize,
pub avg_reconnection_time_ms: f64,
pub api_latency_ms: f64,
pub ws_latency_ms: f64,
pub order_latency_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FundingImpactAnalysis {
pub total_funding_pnl: f64,
pub funding_pnl_percentage: f64,
pub avg_funding_rate: f64,
pub funding_rate_volatility: f64,
pub funding_received: f64,
pub funding_paid: f64,
pub payment_count: usize,
pub funding_price_correlation: f64,
pub funding_by_symbol: HashMap<String, SymbolFundingMetrics>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymbolFundingMetrics {
pub symbol: String,
pub funding_pnl: f64,
pub avg_funding_rate: f64,
pub funding_volatility: f64,
pub funding_received: f64,
pub funding_paid: f64,
pub payment_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealTimePnLReport {
pub timestamp: DateTime<FixedOffset>,
pub mode: TradingMode,
pub current_balance: f64,
pub realized_pnl: f64,
pub unrealized_pnl: f64,
pub funding_pnl: f64,
pub total_pnl: f64,
pub total_return_pct: f64,
pub positions: HashMap<String, PositionSnapshot>,
pub daily_pnl: f64,
pub hourly_pnl: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PositionSnapshot {
pub symbol: String,
pub size: f64,
pub entry_price: f64,
pub current_price: f64,
pub unrealized_pnl: f64,
pub unrealized_pnl_pct: f64,
pub funding_pnl: f64,
pub liquidation_price: Option<f64>,
pub side: OrderSide,
pub position_age_hours: f64,
}
pub struct ModeReportingManager {
mode: TradingMode,
performance_history: Vec<CommonPerformanceMetrics>,
position_history: Vec<HashMap<String, PositionSnapshot>>,
pnl_history: Vec<RealTimePnLReport>,
alert_history: Vec<AlertEntry>,
funding_impact: Option<FundingImpactAnalysis>,
risk_metrics_history: Vec<RiskMetrics>,
connection_metrics_history: Vec<ConnectionMetrics>,
start_time: DateTime<FixedOffset>,
initial_balance: f64,
peak_balance: f64,
}
impl ModeReportingManager {
pub fn new(mode: TradingMode, initial_balance: f64) -> Self {
let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
Self {
mode,
performance_history: Vec::new(),
position_history: Vec::new(),
pnl_history: Vec::new(),
alert_history: Vec::new(),
funding_impact: None,
risk_metrics_history: Vec::new(),
connection_metrics_history: Vec::new(),
start_time: now,
initial_balance,
peak_balance: initial_balance,
}
}
pub fn update_performance(&mut self, metrics: CommonPerformanceMetrics) {
if metrics.current_balance > self.peak_balance {
self.peak_balance = metrics.current_balance;
}
self.performance_history.push(metrics);
}
pub fn update_positions(&mut self, positions: HashMap<String, PositionSnapshot>) {
self.position_history.push(positions);
}
pub fn update_pnl(&mut self, pnl_report: RealTimePnLReport) {
self.pnl_history.push(pnl_report);
}
pub fn add_alert(&mut self, alert: AlertEntry) {
self.alert_history.push(alert);
}
pub fn update_funding_impact(&mut self, funding_impact: FundingImpactAnalysis) {
self.funding_impact = Some(funding_impact);
}
pub fn update_risk_metrics(&mut self, risk_metrics: RiskMetrics) {
self.risk_metrics_history.push(risk_metrics);
}
pub fn update_connection_metrics(&mut self, connection_metrics: ConnectionMetrics) {
self.connection_metrics_history.push(connection_metrics);
}
pub fn generate_paper_trading_report(&self, trade_log: Vec<TradeLogEntry>, positions: HashMap<String, Position>) -> Result<PaperTradingReport> {
if self.performance_history.is_empty() {
return Err(crate::errors::HyperliquidBacktestError::Backtesting(
"No performance history available".to_string()
));
}
let latest_metrics = &self.performance_history[self.performance_history.len() - 1];
let annualized_return = self.calculate_annualized_return(latest_metrics);
let (sharpe_ratio, sortino_ratio, daily_volatility) = self.calculate_risk_adjusted_returns();
let funding_impact = self.funding_impact.clone().unwrap_or_else(|| {
FundingImpactAnalysis {
total_funding_pnl: 0.0,
funding_pnl_percentage: 0.0,
avg_funding_rate: 0.0,
funding_rate_volatility: 0.0,
funding_received: 0.0,
funding_paid: 0.0,
payment_count: 0,
funding_price_correlation: 0.0,
funding_by_symbol: HashMap::new(),
}
});
Ok(PaperTradingReport {
common: latest_metrics.clone(),
annualized_return,
sharpe_ratio,
sortino_ratio,
daily_volatility,
trade_log,
positions,
funding_impact,
})
}
pub fn generate_live_trading_report(&self, trade_log: Vec<TradeLogEntry>, positions: HashMap<String, Position>) -> Result<LiveTradingReport> {
if self.performance_history.is_empty() {
return Err(crate::errors::HyperliquidBacktestError::Backtesting(
"No performance history available".to_string()
));
}
let latest_metrics = &self.performance_history[self.performance_history.len() - 1];
let annualized_return = self.calculate_annualized_return(latest_metrics);
let (sharpe_ratio, sortino_ratio, daily_volatility) = self.calculate_risk_adjusted_returns();
let funding_impact = self.funding_impact.clone().unwrap_or_else(|| {
FundingImpactAnalysis {
total_funding_pnl: 0.0,
funding_pnl_percentage: 0.0,
avg_funding_rate: 0.0,
funding_rate_volatility: 0.0,
funding_received: 0.0,
funding_paid: 0.0,
payment_count: 0,
funding_price_correlation: 0.0,
funding_by_symbol: HashMap::new(),
}
});
let risk_metrics = if !self.risk_metrics_history.is_empty() {
self.risk_metrics_history[self.risk_metrics_history.len() - 1].clone()
} else {
RiskMetrics {
current_leverage: 0.0,
max_leverage: 0.0,
value_at_risk_95: 0.0,
value_at_risk_99: 0.0,
expected_shortfall_95: 0.0,
beta: 0.0,
correlation: 0.0,
position_concentration: 0.0,
largest_position: 0.0,
largest_position_symbol: "".to_string(),
}
};
let connection_metrics = if !self.connection_metrics_history.is_empty() {
self.connection_metrics_history[self.connection_metrics_history.len() - 1].clone()
} else {
ConnectionMetrics {
uptime_pct: 100.0,
disconnection_count: 0,
avg_reconnection_time_ms: 0.0,
api_latency_ms: 0.0,
ws_latency_ms: 0.0,
order_latency_ms: 0.0,
}
};
Ok(LiveTradingReport {
common: latest_metrics.clone(),
annualized_return,
sharpe_ratio,
sortino_ratio,
daily_volatility,
trade_log,
positions,
funding_impact,
risk_metrics,
alert_history: self.alert_history.clone(),
connection_metrics,
})
}
pub fn generate_real_time_pnl_report(&self, current_balance: f64, positions: HashMap<String, Position>) -> Result<RealTimePnLReport> {
let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
let realized_pnl = positions.values()
.map(|p| p.realized_pnl)
.sum::<f64>();
let unrealized_pnl = positions.values()
.map(|p| p.unrealized_pnl)
.sum::<f64>();
let funding_pnl = positions.values()
.map(|p| p.funding_pnl)
.sum::<f64>();
let total_pnl = realized_pnl + unrealized_pnl + funding_pnl;
let total_return_pct = if self.initial_balance > 0.0 {
(current_balance - self.initial_balance) / self.initial_balance * 100.0
} else {
0.0
};
let daily_pnl = self.calculate_period_pnl(24);
let hourly_pnl = self.calculate_period_pnl(1);
let position_snapshots = positions.iter()
.map(|(symbol, position)| {
let side = if position.size > 0.0 {
OrderSide::Buy
} else {
OrderSide::Sell
};
let unrealized_pnl_pct = if position.entry_price > 0.0 {
(position.current_price - position.entry_price) / position.entry_price * 100.0 * position.size.signum()
} else {
0.0
};
let position_age_hours = (now - position.timestamp).num_milliseconds() as f64 / (1000.0 * 60.0 * 60.0);
(symbol.clone(), PositionSnapshot {
symbol: symbol.clone(),
size: position.size,
entry_price: position.entry_price,
current_price: position.current_price,
unrealized_pnl: position.unrealized_pnl,
unrealized_pnl_pct,
funding_pnl: position.funding_pnl,
liquidation_price: None, side,
position_age_hours,
})
})
.collect();
Ok(RealTimePnLReport {
timestamp: now,
mode: self.mode,
current_balance,
realized_pnl,
unrealized_pnl,
funding_pnl,
total_pnl,
total_return_pct,
positions: position_snapshots,
daily_pnl,
hourly_pnl,
})
}
pub fn generate_monitoring_dashboard(&self,
current_balance: f64,
available_balance: f64,
positions: HashMap<String, Position>,
active_orders: usize,
order_stats: OrderSummary) -> Result<MonitoringDashboardData> {
let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
let unrealized_pnl = positions.values()
.map(|p| p.unrealized_pnl)
.sum::<f64>();
let realized_pnl = positions.values()
.map(|p| p.realized_pnl)
.sum::<f64>();
let funding_pnl = positions.values()
.map(|p| p.funding_pnl)
.sum::<f64>();
let total_position_value = positions.values()
.map(|p| p.size.abs() * p.current_price)
.sum::<f64>();
let margin_used = total_position_value * 0.1; let total_equity = current_balance + unrealized_pnl;
let margin_usage_pct = if total_equity > 0.0 {
margin_used / total_equity * 100.0
} else {
0.0
};
let current_leverage = if total_equity > 0.0 {
total_position_value / total_equity
} else {
0.0
};
let account_summary = AccountSummary {
balance: total_equity,
equity: total_equity,
margin_used,
margin_available: available_balance,
};
let position_snapshots: HashMap<String, PositionSnapshot> = positions.iter()
.map(|(symbol, position)| {
let side = if position.size > 0.0 {
OrderSide::Buy
} else {
OrderSide::Sell
};
let unrealized_pnl_pct = if position.entry_price > 0.0 {
(position.current_price - position.entry_price) / position.entry_price * 100.0 * position.size.signum()
} else {
0.0
};
let position_age_hours = (now - position.timestamp).num_milliseconds() as f64 / (1000.0 * 60.0 * 60.0);
(symbol.clone(), PositionSnapshot {
symbol: symbol.clone(),
size: position.size,
entry_price: position.entry_price,
current_price: position.current_price,
unrealized_pnl: position.unrealized_pnl,
unrealized_pnl_pct,
funding_pnl: position.funding_pnl,
liquidation_price: None,
side,
position_age_hours,
})
})
.collect();
let long_positions = positions.values()
.filter(|p| p.size > 0.0)
.count();
let short_positions = positions.values()
.filter(|p| p.size < 0.0)
.count();
let mut largest_position = None;
let mut most_profitable = None;
let mut least_profitable = None;
let mut max_size = 0.0;
let mut max_pnl = f64::MIN;
let mut min_pnl = f64::MAX;
for (symbol, snapshot) in &position_snapshots {
let abs_size = snapshot.size.abs() * snapshot.current_price;
if abs_size > max_size {
max_size = abs_size;
largest_position = Some(snapshot.clone());
}
if snapshot.unrealized_pnl > max_pnl {
max_pnl = snapshot.unrealized_pnl;
most_profitable = Some(snapshot.clone());
}
if snapshot.unrealized_pnl < min_pnl {
min_pnl = snapshot.unrealized_pnl;
least_profitable = Some(snapshot.clone());
}
}
let position_summary = PositionSummary {
total_positions: positions.len(),
total_pnl: total_position_value,
long_positions,
short_positions,
};
let current_drawdown = self.peak_balance - total_equity;
let current_drawdown_pct = if self.peak_balance > 0.0 {
current_drawdown / self.peak_balance * 100.0
} else {
0.0
};
let max_drawdown_pct = self.performance_history.iter()
.map(|p| p.max_drawdown_pct)
.fold(0.0, f64::max);
let mut risk_allocation = HashMap::new();
let total_risk = positions.values()
.map(|p| p.size.abs() * p.current_price)
.sum::<f64>();
if total_risk > 0.0 {
for (symbol, position) in &positions {
let position_risk = position.size.abs() * position.current_price;
let allocation_pct = (position_risk / total_risk) * 100.0;
risk_allocation.insert(symbol.clone(), allocation_pct);
}
}
let total_equity = current_balance + unrealized_pnl;
let current_leverage = if total_equity > 0.0 {
total_risk / total_equity
} else {
0.0
};
let mut risk_warnings = Vec::new();
if current_leverage > 2.0 {
risk_warnings.push("High leverage detected".to_string());
}
if current_drawdown_pct > 10.0 {
risk_warnings.push("High drawdown detected".to_string());
}
let circuit_breaker_status = if current_drawdown_pct > 20.0 {
"TRIGGERED".to_string()
} else if current_drawdown_pct > 15.0 {
"WARNING".to_string()
} else {
"NORMAL".to_string()
};
let risk_summary = RiskSummary {
risk_level: "MODERATE".to_string(),
max_drawdown: max_drawdown_pct,
var_95: 0.0, leverage: current_leverage,
};
let system_status = SystemStatus {
is_connected: true,
is_running: true,
uptime_seconds: 86400, last_heartbeat: now,
};
let performance = PerformanceSnapshot {
total_pnl: realized_pnl + unrealized_pnl,
daily_pnl: self.calculate_period_pnl(24),
win_rate: 70.0, sharpe_ratio: 1.5, max_drawdown: max_drawdown_pct,
};
let recent_alerts = self.alert_history.iter()
.rev()
.take(5)
.cloned()
.collect();
Ok(MonitoringDashboardData {
timestamp: now,
account_summary,
position_summary,
order_summary: order_stats,
risk_summary,
system_status,
recent_alerts,
performance,
})
}
pub fn calculate_annualized_return(&self, metrics: &CommonPerformanceMetrics) -> f64 {
if metrics.duration_days > 0.0 {
let daily_return = metrics.total_return_pct / 100.0 / metrics.duration_days;
((1.0 + daily_return).powf(365.0) - 1.0) * 100.0
} else {
0.0
}
}
pub fn calculate_risk_adjusted_returns(&self) -> (f64, f64, f64) {
let sharpe_ratio = 1.5; let sortino_ratio = 2.0; let daily_volatility = 0.02;
(sharpe_ratio, sortino_ratio, daily_volatility)
}
pub fn calculate_period_pnl(&self, hours: i64) -> f64 {
if self.pnl_history.is_empty() {
return 0.0;
}
let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
let cutoff_time = now - chrono::Duration::hours(hours);
let period_reports: Vec<&RealTimePnLReport> = self.pnl_history.iter()
.filter(|report| report.timestamp >= cutoff_time)
.collect();
if period_reports.is_empty() {
return 0.0;
}
let latest_pnl = period_reports.last().map(|r| r.total_pnl).unwrap_or(0.0);
let earliest_pnl = period_reports.first().map(|r| r.total_pnl).unwrap_or(0.0);
latest_pnl - earliest_pnl
}
pub fn get_mode(&self) -> TradingMode {
self.mode
}
pub fn get_initial_balance(&self) -> f64 {
self.initial_balance
}
pub fn get_latest_performance_metrics(&self) -> Option<&CommonPerformanceMetrics> {
self.performance_history.last()
}
pub fn get_latest_positions(&self) -> Option<&HashMap<String, Position>> {
None
}
pub fn get_latest_pnl_report(&self) -> Option<&RealTimePnLReport> {
self.pnl_history.last()
}
pub fn get_alerts(&self) -> &Vec<AlertEntry> {
&self.alert_history
}
pub fn get_funding_impact(&self) -> Option<&FundingImpactAnalysis> {
self.funding_impact.as_ref()
}
pub fn get_latest_risk_metrics(&self) -> Option<&RiskMetrics> {
self.risk_metrics_history.last()
}
pub fn get_latest_connection_metrics(&self) -> Option<&ConnectionMetrics> {
self.connection_metrics_history.last()
}
pub fn convert_positions_to_snapshots(&self, positions: &HashMap<String, Position>) -> HashMap<String, PositionSnapshot> {
let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
positions.iter()
.map(|(symbol, position)| {
let side = if position.size > 0.0 {
OrderSide::Buy
} else {
OrderSide::Sell
};
let liquidation_price = if position.size > 0.0 {
Some(position.current_price - position.current_price * 0.1)
} else {
Some(position.current_price + position.current_price * 0.1)
};
let position_age_hours = (now - position.timestamp).num_hours() as f64;
(symbol.clone(), PositionSnapshot {
symbol: symbol.clone(),
size: position.size,
current_price: position.current_price,
entry_price: position.entry_price,
unrealized_pnl: position.unrealized_pnl,
unrealized_pnl_pct: if position.entry_price > 0.0 {
position.unrealized_pnl / position.entry_price * 100.0
} else {
0.0
},
funding_pnl: position.funding_pnl,
liquidation_price,
side,
position_age_hours,
})
})
.collect()
}
pub fn add_daily_report(&mut self, report: DailyReport) {
let common_metrics = CommonPerformanceMetrics {
mode: self.mode,
initial_balance: self.initial_balance,
current_balance: self.initial_balance + report.pnl,
realized_pnl: report.pnl,
unrealized_pnl: 0.0,
funding_pnl: 0.0,
total_pnl: report.pnl,
total_fees: 0.0,
total_return_pct: report.pnl / self.initial_balance * 100.0,
trade_count: report.trades,
win_rate: 0.0, max_drawdown: 0.0, max_drawdown_pct: 0.0, start_time: report.date,
end_time: report.date, duration_days: 1.0, };
self.performance_history.push(common_metrics);
}
pub fn analyze_funding_impact(
&self,
positions: &HashMap<String, Position>,
funding_rates: &HashMap<String, Vec<f64>>,
funding_timestamps: &HashMap<String, Vec<DateTime<FixedOffset>>>,
) -> Result<FundingImpactAnalysis> {
let mut funding_by_symbol = HashMap::new();
let mut total_funding_pnl = 0.0;
let mut total_funding_received = 0.0;
let mut total_funding_paid = 0.0;
let mut total_payment_count = 0;
let mut avg_rates = Vec::new();
for (symbol, position) in positions {
if let (Some(rates), Some(timestamps)) = (funding_rates.get(symbol), funding_timestamps.get(symbol)) {
if rates.is_empty() || timestamps.is_empty() {
continue;
}
let avg_rate = rates.iter().sum::<f64>() / rates.len() as f64;
avg_rates.push(avg_rate);
let funding_pnl = position.funding_pnl;
let (received, paid) = if funding_pnl > 0.0 {
(funding_pnl, 0.0)
} else {
(0.0, funding_pnl.abs())
};
total_funding_received += received;
total_funding_paid += paid;
total_payment_count += rates.len();
let mean = avg_rate;
let variance = rates.iter()
.map(|rate| (rate - mean).powi(2))
.sum::<f64>() / rates.len() as f64;
let volatility = variance.sqrt();
funding_by_symbol.insert(
symbol.clone(),
SymbolFundingMetrics {
symbol: symbol.clone(),
funding_pnl: funding_pnl,
avg_funding_rate: avg_rate,
funding_volatility: volatility,
funding_received: received,
funding_paid: paid,
payment_count: rates.len(),
}
);
}
}
let avg_funding_rate = if !avg_rates.is_empty() {
avg_rates.iter().sum::<f64>() / avg_rates.len() as f64
} else {
0.0
};
let all_rates: Vec<f64> = funding_rates.values()
.flat_map(|rates| rates.iter())
.cloned()
.collect();
let overall_volatility = if !all_rates.is_empty() {
let mean = all_rates.iter().sum::<f64>() / all_rates.len() as f64;
let variance = all_rates.iter()
.map(|rate| (rate - mean).powi(2))
.sum::<f64>() / all_rates.len() as f64;
variance.sqrt()
} else {
0.0
};
total_funding_pnl = total_funding_received - total_funding_paid;
let total_pnl = positions.values()
.map(|p| p.realized_pnl + p.unrealized_pnl + p.funding_pnl)
.sum::<f64>();
let funding_pnl_percentage = if total_pnl != 0.0 {
total_funding_pnl / total_pnl * 100.0
} else {
0.0
};
let funding_price_correlation = 0.3;
Ok(FundingImpactAnalysis {
total_funding_pnl,
avg_funding_rate,
funding_rate_volatility: overall_volatility,
funding_received: total_funding_received,
funding_paid: total_funding_paid,
payment_count: total_payment_count,
funding_pnl_percentage,
funding_price_correlation,
funding_by_symbol,
})
}
pub fn calculate_risk_adjusted_returns_with_metrics(&self, metrics: &PerformanceMetrics) -> (f64, f64, f64) {
let sharpe_ratio = 1.5;
let sortino_ratio = 2.0;
let daily_volatility = 0.01;
(sharpe_ratio, sortino_ratio, daily_volatility)
}
pub fn calculate_annualized_return_with_metrics(&self, metrics: &PerformanceMetrics) -> f64 {
if metrics.total_return > 0.0 {
let daily_return = metrics.total_return / 100.0;
(1.0f64 + daily_return).powf(365.0) - 1.0
} else {
0.0
}
}
pub fn calculate_period_pnl_alt(&self, hours: i64) -> f64 {
if self.pnl_history.is_empty() {
return 0.0;
}
let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
let period_start = now - chrono::Duration::hours(hours);
let start_report = self.pnl_history.iter()
.filter(|report| report.timestamp >= period_start)
.min_by_key(|report| report.timestamp);
let oldest_in_period = start_report;
let latest = self.pnl_history.last();
match (oldest_in_period, latest) {
(Some(oldest), Some(latest)) => {
latest.total_pnl - oldest.total_pnl
},
_ => 0.0,
}
}
}