1use std::collections::HashMap;
8use chrono::{DateTime, FixedOffset, Utc};
9use serde::{Deserialize, Serialize};
10
11use crate::trading_mode::TradingMode;
12use crate::unified_data::{Position, OrderSide};
13use crate::paper_trading::TradeLogEntry;
14use crate::errors::Result;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct PerformanceMetrics {
20 pub total_return: f64,
21 pub sharpe_ratio: f64,
22 pub max_drawdown: f64,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct PnLReport {
28 pub realized_pnl: f64,
29 pub unrealized_pnl: f64,
30 pub funding_pnl: f64,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct Alert {
36 pub level: String,
37 pub message: String,
38 pub timestamp: DateTime<FixedOffset>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct DailyReport {
44 pub date: DateTime<FixedOffset>,
45 pub pnl: f64,
46 pub trades: usize,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct AccountSummary {
52 pub balance: f64,
53 pub equity: f64,
54 pub margin_used: f64,
55 pub margin_available: f64,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct PositionSummary {
61 pub total_positions: usize,
62 pub total_pnl: f64,
63 pub long_positions: usize,
64 pub short_positions: usize,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct OrderSummary {
70 pub active_orders: usize,
71 pub filled_orders: usize,
72 pub cancelled_orders: usize,
73 pub total_volume: f64,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct RiskSummary {
79 pub risk_level: String,
80 pub max_drawdown: f64,
81 pub var_95: f64,
82 pub leverage: f64,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct SystemStatus {
88 pub is_connected: bool,
89 pub is_running: bool,
90 pub uptime_seconds: u64,
91 pub last_heartbeat: DateTime<FixedOffset>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct AlertEntry {
97 pub level: String,
98 pub message: String,
99 pub timestamp: DateTime<FixedOffset>,
100 pub symbol: Option<String>,
101 pub order_id: Option<String>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct PerformanceSnapshot {
107 pub total_pnl: f64,
108 pub daily_pnl: f64,
109 pub win_rate: f64,
110 pub sharpe_ratio: f64,
111 pub max_drawdown: f64,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct MonitoringDashboardData {
117 pub timestamp: DateTime<FixedOffset>,
118 pub account_summary: AccountSummary,
119 pub position_summary: PositionSummary,
120 pub order_summary: OrderSummary,
121 pub risk_summary: RiskSummary,
122 pub system_status: SystemStatus,
123 pub recent_alerts: Vec<AlertEntry>,
124 pub performance: PerformanceSnapshot,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct FundingSymbolAnalysis {
130 pub symbol: String,
131 pub average_rate: f64,
132 pub volatility: f64,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct CommonPerformanceMetrics {
138 pub mode: TradingMode,
140
141 pub initial_balance: f64,
143
144 pub current_balance: f64,
146
147 pub realized_pnl: f64,
149
150 pub unrealized_pnl: f64,
152
153 pub funding_pnl: f64,
155
156 pub total_pnl: f64,
158
159 pub total_fees: f64,
161
162 pub total_return_pct: f64,
164
165 pub trade_count: usize,
167
168 pub win_rate: f64,
170
171 pub max_drawdown: f64,
173
174 pub max_drawdown_pct: f64,
176
177 pub start_time: DateTime<FixedOffset>,
179
180 pub end_time: DateTime<FixedOffset>,
182
183 pub duration_days: f64,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct PaperTradingReport {
190 pub common: CommonPerformanceMetrics,
192
193 pub annualized_return: f64,
195
196 pub sharpe_ratio: f64,
198
199 pub sortino_ratio: f64,
201
202 pub daily_volatility: f64,
204
205 pub trade_log: Vec<TradeLogEntry>,
207
208 pub positions: HashMap<String, Position>,
210
211 pub funding_impact: FundingImpactAnalysis,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct LiveTradingReport {
218 pub common: CommonPerformanceMetrics,
220
221 pub annualized_return: f64,
223
224 pub sharpe_ratio: f64,
226
227 pub sortino_ratio: f64,
229
230 pub daily_volatility: f64,
232
233 pub trade_log: Vec<TradeLogEntry>,
235
236 pub positions: HashMap<String, Position>,
238
239 pub funding_impact: FundingImpactAnalysis,
241
242 pub risk_metrics: RiskMetrics,
244
245 pub alert_history: Vec<AlertEntry>,
247
248 pub connection_metrics: ConnectionMetrics,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct RiskMetrics {
255 pub current_leverage: f64,
257
258 pub max_leverage: f64,
260
261 pub value_at_risk_95: f64,
263
264 pub value_at_risk_99: f64,
266
267 pub expected_shortfall_95: f64,
269
270 pub beta: f64,
272
273 pub correlation: f64,
275
276 pub position_concentration: f64,
278
279 pub largest_position: f64,
281
282 pub largest_position_symbol: String,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct ConnectionMetrics {
291 pub uptime_pct: f64,
293
294 pub disconnection_count: usize,
296
297 pub avg_reconnection_time_ms: f64,
299
300 pub api_latency_ms: f64,
302
303 pub ws_latency_ms: f64,
305
306 pub order_latency_ms: f64,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct FundingImpactAnalysis {
313 pub total_funding_pnl: f64,
315
316 pub funding_pnl_percentage: f64,
318
319 pub avg_funding_rate: f64,
321
322 pub funding_rate_volatility: f64,
324
325 pub funding_received: f64,
327
328 pub funding_paid: f64,
330
331 pub payment_count: usize,
333
334 pub funding_price_correlation: f64,
336
337 pub funding_by_symbol: HashMap<String, SymbolFundingMetrics>,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct SymbolFundingMetrics {
344 pub symbol: String,
346
347 pub funding_pnl: f64,
349
350 pub avg_funding_rate: f64,
352
353 pub funding_volatility: f64,
355
356 pub funding_received: f64,
358
359 pub funding_paid: f64,
361
362 pub payment_count: usize,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct RealTimePnLReport {
369 pub timestamp: DateTime<FixedOffset>,
371
372 pub mode: TradingMode,
374
375 pub current_balance: f64,
377
378 pub realized_pnl: f64,
380
381 pub unrealized_pnl: f64,
383
384 pub funding_pnl: f64,
386
387 pub total_pnl: f64,
389
390 pub total_return_pct: f64,
392
393 pub positions: HashMap<String, PositionSnapshot>,
395
396 pub daily_pnl: f64,
398
399 pub hourly_pnl: f64,
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
405pub struct PositionSnapshot {
406 pub symbol: String,
408
409 pub size: f64,
411
412 pub entry_price: f64,
414
415 pub current_price: f64,
417
418 pub unrealized_pnl: f64,
420
421 pub unrealized_pnl_pct: f64,
423
424 pub funding_pnl: f64,
426
427 pub liquidation_price: Option<f64>,
429
430 pub side: OrderSide,
432
433 pub position_age_hours: f64,
435}
436
437pub struct ModeReportingManager {
441 mode: TradingMode,
443
444 performance_history: Vec<CommonPerformanceMetrics>,
446
447 position_history: Vec<HashMap<String, PositionSnapshot>>,
449
450 pnl_history: Vec<RealTimePnLReport>,
452
453 alert_history: Vec<AlertEntry>,
455
456 funding_impact: Option<FundingImpactAnalysis>,
458
459 risk_metrics_history: Vec<RiskMetrics>,
461
462 connection_metrics_history: Vec<ConnectionMetrics>,
464
465 start_time: DateTime<FixedOffset>,
467
468 initial_balance: f64,
470
471 peak_balance: f64,
473}
474
475impl ModeReportingManager {
476 pub fn new(mode: TradingMode, initial_balance: f64) -> Self {
478 let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
479
480 Self {
481 mode,
482 performance_history: Vec::new(),
483 position_history: Vec::new(),
484 pnl_history: Vec::new(),
485 alert_history: Vec::new(),
486 funding_impact: None,
487 risk_metrics_history: Vec::new(),
488 connection_metrics_history: Vec::new(),
489 start_time: now,
490 initial_balance,
491 peak_balance: initial_balance,
492 }
493 }
494
495 pub fn update_performance(&mut self, metrics: CommonPerformanceMetrics) {
497 if metrics.current_balance > self.peak_balance {
499 self.peak_balance = metrics.current_balance;
500 }
501
502 self.performance_history.push(metrics);
503 }
504
505 pub fn update_positions(&mut self, positions: HashMap<String, PositionSnapshot>) {
507 self.position_history.push(positions);
508 }
509
510 pub fn update_pnl(&mut self, pnl_report: RealTimePnLReport) {
512 self.pnl_history.push(pnl_report);
513 }
514
515 pub fn add_alert(&mut self, alert: AlertEntry) {
517 self.alert_history.push(alert);
518 }
519
520 pub fn update_funding_impact(&mut self, funding_impact: FundingImpactAnalysis) {
522 self.funding_impact = Some(funding_impact);
523 }
524
525 pub fn update_risk_metrics(&mut self, risk_metrics: RiskMetrics) {
527 self.risk_metrics_history.push(risk_metrics);
528 }
529
530 pub fn update_connection_metrics(&mut self, connection_metrics: ConnectionMetrics) {
532 self.connection_metrics_history.push(connection_metrics);
533 }
534
535 pub fn generate_paper_trading_report(&self, trade_log: Vec<TradeLogEntry>, positions: HashMap<String, Position>) -> Result<PaperTradingReport> {
537 if self.performance_history.is_empty() {
539 return Err(crate::errors::HyperliquidBacktestError::Backtesting(
540 "No performance history available".to_string()
541 ));
542 }
543
544 let latest_metrics = &self.performance_history[self.performance_history.len() - 1];
546
547 let annualized_return = self.calculate_annualized_return(latest_metrics);
549 let (sharpe_ratio, sortino_ratio, daily_volatility) = self.calculate_risk_adjusted_returns();
550
551 let funding_impact = self.funding_impact.clone().unwrap_or_else(|| {
553 FundingImpactAnalysis {
555 total_funding_pnl: 0.0,
556 funding_pnl_percentage: 0.0,
557 avg_funding_rate: 0.0,
558 funding_rate_volatility: 0.0,
559 funding_received: 0.0,
560 funding_paid: 0.0,
561 payment_count: 0,
562 funding_price_correlation: 0.0,
563 funding_by_symbol: HashMap::new(),
564 }
565 });
566
567 Ok(PaperTradingReport {
568 common: latest_metrics.clone(),
569 annualized_return,
570 sharpe_ratio,
571 sortino_ratio,
572 daily_volatility,
573 trade_log,
574 positions,
575 funding_impact,
576 })
577 }
578
579 pub fn generate_live_trading_report(&self, trade_log: Vec<TradeLogEntry>, positions: HashMap<String, Position>) -> Result<LiveTradingReport> {
581 if self.performance_history.is_empty() {
583 return Err(crate::errors::HyperliquidBacktestError::Backtesting(
584 "No performance history available".to_string()
585 ));
586 }
587
588 let latest_metrics = &self.performance_history[self.performance_history.len() - 1];
590
591 let annualized_return = self.calculate_annualized_return(latest_metrics);
593 let (sharpe_ratio, sortino_ratio, daily_volatility) = self.calculate_risk_adjusted_returns();
594
595 let funding_impact = self.funding_impact.clone().unwrap_or_else(|| {
597 FundingImpactAnalysis {
599 total_funding_pnl: 0.0,
600 funding_pnl_percentage: 0.0,
601 avg_funding_rate: 0.0,
602 funding_rate_volatility: 0.0,
603 funding_received: 0.0,
604 funding_paid: 0.0,
605 payment_count: 0,
606 funding_price_correlation: 0.0,
607 funding_by_symbol: HashMap::new(),
608 }
609 });
610
611 let risk_metrics = if !self.risk_metrics_history.is_empty() {
613 self.risk_metrics_history[self.risk_metrics_history.len() - 1].clone()
614 } else {
615 RiskMetrics {
617 current_leverage: 0.0,
618 max_leverage: 0.0,
619 value_at_risk_95: 0.0,
620 value_at_risk_99: 0.0,
621 expected_shortfall_95: 0.0,
622 beta: 0.0,
623 correlation: 0.0,
624 position_concentration: 0.0,
625 largest_position: 0.0,
626 largest_position_symbol: "".to_string(),
627 }
628 };
629
630 let connection_metrics = if !self.connection_metrics_history.is_empty() {
632 self.connection_metrics_history[self.connection_metrics_history.len() - 1].clone()
633 } else {
634 ConnectionMetrics {
636 uptime_pct: 100.0,
637 disconnection_count: 0,
638 avg_reconnection_time_ms: 0.0,
639 api_latency_ms: 0.0,
640 ws_latency_ms: 0.0,
641 order_latency_ms: 0.0,
642 }
643 };
644
645 Ok(LiveTradingReport {
646 common: latest_metrics.clone(),
647 annualized_return,
648 sharpe_ratio,
649 sortino_ratio,
650 daily_volatility,
651 trade_log,
652 positions,
653 funding_impact,
654 risk_metrics,
655 alert_history: self.alert_history.clone(),
656 connection_metrics,
657 })
658 }
659
660 pub fn generate_real_time_pnl_report(&self, current_balance: f64, positions: HashMap<String, Position>) -> Result<RealTimePnLReport> {
662 let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
663
664 let realized_pnl = positions.values()
666 .map(|p| p.realized_pnl)
667 .sum::<f64>();
668
669 let unrealized_pnl = positions.values()
670 .map(|p| p.unrealized_pnl)
671 .sum::<f64>();
672
673 let funding_pnl = positions.values()
674 .map(|p| p.funding_pnl)
675 .sum::<f64>();
676
677 let total_pnl = realized_pnl + unrealized_pnl + funding_pnl;
678
679 let total_return_pct = if self.initial_balance > 0.0 {
680 (current_balance - self.initial_balance) / self.initial_balance * 100.0
681 } else {
682 0.0
683 };
684
685 let daily_pnl = self.calculate_period_pnl(24);
687 let hourly_pnl = self.calculate_period_pnl(1);
688
689 let position_snapshots = positions.iter()
691 .map(|(symbol, position)| {
692 let side = if position.size > 0.0 {
693 OrderSide::Buy
694 } else {
695 OrderSide::Sell
696 };
697
698 let unrealized_pnl_pct = if position.entry_price > 0.0 {
699 (position.current_price - position.entry_price) / position.entry_price * 100.0 * position.size.signum()
700 } else {
701 0.0
702 };
703
704 let position_age_hours = (now - position.timestamp).num_milliseconds() as f64 / (1000.0 * 60.0 * 60.0);
705
706 (symbol.clone(), PositionSnapshot {
707 symbol: symbol.clone(),
708 size: position.size,
709 entry_price: position.entry_price,
710 current_price: position.current_price,
711 unrealized_pnl: position.unrealized_pnl,
712 unrealized_pnl_pct,
713 funding_pnl: position.funding_pnl,
714 liquidation_price: None, side,
716 position_age_hours,
717 })
718 })
719 .collect();
720
721 Ok(RealTimePnLReport {
722 timestamp: now,
723 mode: self.mode,
724 current_balance,
725 realized_pnl,
726 unrealized_pnl,
727 funding_pnl,
728 total_pnl,
729 total_return_pct,
730 positions: position_snapshots,
731 daily_pnl,
732 hourly_pnl,
733 })
734 }
735
736 pub fn generate_monitoring_dashboard(&self,
738 current_balance: f64,
739 available_balance: f64,
740 positions: HashMap<String, Position>,
741 active_orders: usize,
742 order_stats: OrderSummary) -> Result<MonitoringDashboardData> {
743 let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
744
745 let unrealized_pnl = positions.values()
747 .map(|p| p.unrealized_pnl)
748 .sum::<f64>();
749
750 let realized_pnl = positions.values()
751 .map(|p| p.realized_pnl)
752 .sum::<f64>();
753
754 let funding_pnl = positions.values()
755 .map(|p| p.funding_pnl)
756 .sum::<f64>();
757
758 let total_position_value = positions.values()
759 .map(|p| p.size.abs() * p.current_price)
760 .sum::<f64>();
761
762 let margin_used = total_position_value * 0.1; let total_equity = current_balance + unrealized_pnl;
764 let margin_usage_pct = if total_equity > 0.0 {
765 margin_used / total_equity * 100.0
766 } else {
767 0.0
768 };
769
770 let current_leverage = if total_equity > 0.0 {
771 total_position_value / total_equity
772 } else {
773 0.0
774 };
775
776 let account_summary = AccountSummary {
778 balance: total_equity,
779 equity: total_equity,
780 margin_used,
781 margin_available: available_balance,
782 };
783
784 let position_snapshots: HashMap<String, PositionSnapshot> = positions.iter()
786 .map(|(symbol, position)| {
787 let side = if position.size > 0.0 {
788 OrderSide::Buy
789 } else {
790 OrderSide::Sell
791 };
792
793 let unrealized_pnl_pct = if position.entry_price > 0.0 {
794 (position.current_price - position.entry_price) / position.entry_price * 100.0 * position.size.signum()
795 } else {
796 0.0
797 };
798
799 let position_age_hours = (now - position.timestamp).num_milliseconds() as f64 / (1000.0 * 60.0 * 60.0);
800
801 (symbol.clone(), PositionSnapshot {
802 symbol: symbol.clone(),
803 size: position.size,
804 entry_price: position.entry_price,
805 current_price: position.current_price,
806 unrealized_pnl: position.unrealized_pnl,
807 unrealized_pnl_pct,
808 funding_pnl: position.funding_pnl,
809 liquidation_price: None,
810 side,
811 position_age_hours,
812 })
813 })
814 .collect();
815
816 let long_positions = positions.values()
817 .filter(|p| p.size > 0.0)
818 .count();
819
820 let short_positions = positions.values()
821 .filter(|p| p.size < 0.0)
822 .count();
823
824 let mut largest_position = None;
826 let mut most_profitable = None;
827 let mut least_profitable = None;
828
829 let mut max_size = 0.0;
830 let mut max_pnl = f64::MIN;
831 let mut min_pnl = f64::MAX;
832
833 for (symbol, snapshot) in &position_snapshots {
834 let abs_size = snapshot.size.abs() * snapshot.current_price;
835
836 if abs_size > max_size {
837 max_size = abs_size;
838 largest_position = Some(snapshot.clone());
839 }
840
841 if snapshot.unrealized_pnl > max_pnl {
842 max_pnl = snapshot.unrealized_pnl;
843 most_profitable = Some(snapshot.clone());
844 }
845
846 if snapshot.unrealized_pnl < min_pnl {
847 min_pnl = snapshot.unrealized_pnl;
848 least_profitable = Some(snapshot.clone());
849 }
850 }
851
852 let position_summary = PositionSummary {
853 total_positions: positions.len(),
854 total_pnl: total_position_value,
855 long_positions,
856 short_positions,
857 };
858
859 let current_drawdown = self.peak_balance - total_equity;
861 let current_drawdown_pct = if self.peak_balance > 0.0 {
862 current_drawdown / self.peak_balance * 100.0
863 } else {
864 0.0
865 };
866
867 let max_drawdown_pct = self.performance_history.iter()
869 .map(|p| p.max_drawdown_pct)
870 .fold(0.0, f64::max);
871
872 let mut risk_allocation = HashMap::new();
874 let total_risk = positions.values()
875 .map(|p| p.size.abs() * p.current_price)
876 .sum::<f64>();
877
878 if total_risk > 0.0 {
879 for (symbol, position) in &positions {
880 let position_risk = position.size.abs() * position.current_price;
881 let allocation_pct = (position_risk / total_risk) * 100.0;
882 risk_allocation.insert(symbol.clone(), allocation_pct);
883 }
884 }
885
886 let total_equity = current_balance + unrealized_pnl;
888 let current_leverage = if total_equity > 0.0 {
889 total_risk / total_equity
890 } else {
891 0.0
892 };
893
894 let mut risk_warnings = Vec::new();
896
897 if current_leverage > 2.0 {
898 risk_warnings.push("High leverage detected".to_string());
899 }
900
901 if current_drawdown_pct > 10.0 {
902 risk_warnings.push("High drawdown detected".to_string());
903 }
904
905 let circuit_breaker_status = if current_drawdown_pct > 20.0 {
907 "TRIGGERED".to_string()
908 } else if current_drawdown_pct > 15.0 {
909 "WARNING".to_string()
910 } else {
911 "NORMAL".to_string()
912 };
913
914 let risk_summary = RiskSummary {
915 risk_level: "MODERATE".to_string(),
916 max_drawdown: max_drawdown_pct,
917 var_95: 0.0, leverage: current_leverage,
919 };
920
921 let system_status = SystemStatus {
923 is_connected: true,
924 is_running: true,
925 uptime_seconds: 86400, last_heartbeat: now,
927 };
928
929 let performance = PerformanceSnapshot {
931 total_pnl: realized_pnl + unrealized_pnl,
932 daily_pnl: self.calculate_period_pnl(24),
933 win_rate: 70.0, sharpe_ratio: 1.5, max_drawdown: max_drawdown_pct,
936 };
937
938 let recent_alerts = self.alert_history.iter()
940 .rev()
941 .take(5)
942 .cloned()
943 .collect();
944
945 Ok(MonitoringDashboardData {
946 timestamp: now,
947 account_summary,
948 position_summary,
949 order_summary: order_stats,
950 risk_summary,
951 system_status,
952 recent_alerts,
953 performance,
954 })
955 }
956
957 pub fn calculate_annualized_return(&self, metrics: &CommonPerformanceMetrics) -> f64 {
959 if metrics.duration_days > 0.0 {
960 let daily_return = metrics.total_return_pct / 100.0 / metrics.duration_days;
961 ((1.0 + daily_return).powf(365.0) - 1.0) * 100.0
962 } else {
963 0.0
964 }
965 }
966
967 pub fn calculate_risk_adjusted_returns(&self) -> (f64, f64, f64) {
969 let sharpe_ratio = 1.5; let sortino_ratio = 2.0; let daily_volatility = 0.02; (sharpe_ratio, sortino_ratio, daily_volatility)
976 }
977
978 pub fn calculate_period_pnl(&self, hours: i64) -> f64 {
980 if self.pnl_history.is_empty() {
981 return 0.0;
982 }
983
984 let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
985 let cutoff_time = now - chrono::Duration::hours(hours);
986
987 let period_reports: Vec<&RealTimePnLReport> = self.pnl_history.iter()
989 .filter(|report| report.timestamp >= cutoff_time)
990 .collect();
991
992 if period_reports.is_empty() {
993 return 0.0;
994 }
995
996 let latest_pnl = period_reports.last().map(|r| r.total_pnl).unwrap_or(0.0);
998 let earliest_pnl = period_reports.first().map(|r| r.total_pnl).unwrap_or(0.0);
999
1000 latest_pnl - earliest_pnl
1001 }
1002
1003 pub fn get_mode(&self) -> TradingMode {
1005 self.mode
1006 }
1007
1008 pub fn get_initial_balance(&self) -> f64 {
1010 self.initial_balance
1011 }
1012
1013 pub fn get_latest_performance_metrics(&self) -> Option<&CommonPerformanceMetrics> {
1015 self.performance_history.last()
1016 }
1017
1018 pub fn get_latest_positions(&self) -> Option<&HashMap<String, Position>> {
1020 None
1022 }
1023
1024 pub fn get_latest_pnl_report(&self) -> Option<&RealTimePnLReport> {
1026 self.pnl_history.last()
1027 }
1028
1029 pub fn get_alerts(&self) -> &Vec<AlertEntry> {
1031 &self.alert_history
1032 }
1033
1034 pub fn get_funding_impact(&self) -> Option<&FundingImpactAnalysis> {
1036 self.funding_impact.as_ref()
1037 }
1038
1039 pub fn get_latest_risk_metrics(&self) -> Option<&RiskMetrics> {
1041 self.risk_metrics_history.last()
1042 }
1043
1044 pub fn get_latest_connection_metrics(&self) -> Option<&ConnectionMetrics> {
1046 self.connection_metrics_history.last()
1047 }
1048
1049 pub fn convert_positions_to_snapshots(&self, positions: &HashMap<String, Position>) -> HashMap<String, PositionSnapshot> {
1051 let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
1052
1053 positions.iter()
1054 .map(|(symbol, position)| {
1055 let side = if position.size > 0.0 {
1056 OrderSide::Buy
1057 } else {
1058 OrderSide::Sell
1059 };
1060
1061 let liquidation_price = if position.size > 0.0 {
1062 Some(position.current_price - position.current_price * 0.1)
1063 } else {
1064 Some(position.current_price + position.current_price * 0.1)
1065 };
1066
1067 let position_age_hours = (now - position.timestamp).num_hours() as f64;
1068
1069 (symbol.clone(), PositionSnapshot {
1070 symbol: symbol.clone(),
1071 size: position.size,
1072 current_price: position.current_price,
1073 entry_price: position.entry_price,
1074 unrealized_pnl: position.unrealized_pnl,
1075 unrealized_pnl_pct: if position.entry_price > 0.0 {
1078 position.unrealized_pnl / position.entry_price * 100.0
1079 } else {
1080 0.0
1081 },
1082 funding_pnl: position.funding_pnl,
1083 liquidation_price,
1084 side,
1085 position_age_hours,
1086 })
1087 })
1088 .collect()
1089 }
1090
1091 pub fn add_daily_report(&mut self, report: DailyReport) {
1093 let common_metrics = CommonPerformanceMetrics {
1095 mode: self.mode,
1096 initial_balance: self.initial_balance,
1097 current_balance: self.initial_balance + report.pnl,
1098 realized_pnl: report.pnl,
1099 unrealized_pnl: 0.0,
1100 funding_pnl: 0.0,
1101 total_pnl: report.pnl,
1102 total_fees: 0.0,
1103 total_return_pct: report.pnl / self.initial_balance * 100.0,
1104 trade_count: report.trades,
1105 win_rate: 0.0, max_drawdown: 0.0, max_drawdown_pct: 0.0, start_time: report.date,
1109 end_time: report.date, duration_days: 1.0, };
1112 self.performance_history.push(common_metrics);
1113 }
1115
1116 pub fn analyze_funding_impact(
1118 &self,
1119 positions: &HashMap<String, Position>,
1120 funding_rates: &HashMap<String, Vec<f64>>,
1121 funding_timestamps: &HashMap<String, Vec<DateTime<FixedOffset>>>,
1122 ) -> Result<FundingImpactAnalysis> {
1123 let mut funding_by_symbol = HashMap::new();
1124 let mut total_funding_pnl = 0.0;
1125 let mut total_funding_received = 0.0;
1126 let mut total_funding_paid = 0.0;
1127 let mut total_payment_count = 0;
1128
1129 let mut avg_rates = Vec::new();
1130
1131 for (symbol, position) in positions {
1132 if let (Some(rates), Some(timestamps)) = (funding_rates.get(symbol), funding_timestamps.get(symbol)) {
1133 if rates.is_empty() || timestamps.is_empty() {
1134 continue;
1135 }
1136
1137 let avg_rate = rates.iter().sum::<f64>() / rates.len() as f64;
1138 avg_rates.push(avg_rate);
1139
1140 let funding_pnl = position.funding_pnl;
1141
1142 let (received, paid) = if funding_pnl > 0.0 {
1143 (funding_pnl, 0.0)
1144 } else {
1145 (0.0, funding_pnl.abs())
1146 };
1147
1148 total_funding_received += received;
1149 total_funding_paid += paid;
1150 total_payment_count += rates.len();
1151
1152 let mean = avg_rate;
1154 let variance = rates.iter()
1155 .map(|rate| (rate - mean).powi(2))
1156 .sum::<f64>() / rates.len() as f64;
1157 let volatility = variance.sqrt();
1158
1159 funding_by_symbol.insert(
1160 symbol.clone(),
1161 SymbolFundingMetrics {
1162 symbol: symbol.clone(),
1163 funding_pnl: funding_pnl,
1164 avg_funding_rate: avg_rate,
1165 funding_volatility: volatility,
1166 funding_received: received,
1167 funding_paid: paid,
1168 payment_count: rates.len(),
1169 }
1170 );
1171 }
1172 }
1173
1174 let avg_funding_rate = if !avg_rates.is_empty() {
1175 avg_rates.iter().sum::<f64>() / avg_rates.len() as f64
1176 } else {
1177 0.0
1178 };
1179
1180 let all_rates: Vec<f64> = funding_rates.values()
1182 .flat_map(|rates| rates.iter())
1183 .cloned()
1184 .collect();
1185
1186 let overall_volatility = if !all_rates.is_empty() {
1187 let mean = all_rates.iter().sum::<f64>() / all_rates.len() as f64;
1188 let variance = all_rates.iter()
1189 .map(|rate| (rate - mean).powi(2))
1190 .sum::<f64>() / all_rates.len() as f64;
1191 variance.sqrt()
1192 } else {
1193 0.0
1194 };
1195
1196 total_funding_pnl = total_funding_received - total_funding_paid;
1197
1198 let total_pnl = positions.values()
1199 .map(|p| p.realized_pnl + p.unrealized_pnl + p.funding_pnl)
1200 .sum::<f64>();
1201
1202 let funding_pnl_percentage = if total_pnl != 0.0 {
1203 total_funding_pnl / total_pnl * 100.0
1204 } else {
1205 0.0
1206 };
1207
1208 let funding_price_correlation = 0.3;
1210
1211 Ok(FundingImpactAnalysis {
1212 total_funding_pnl,
1213 avg_funding_rate,
1214 funding_rate_volatility: overall_volatility,
1215 funding_received: total_funding_received,
1216 funding_paid: total_funding_paid,
1217 payment_count: total_payment_count,
1218 funding_pnl_percentage,
1219 funding_price_correlation,
1220 funding_by_symbol,
1221 })
1222 }
1223
1224 pub fn calculate_risk_adjusted_returns_with_metrics(&self, metrics: &PerformanceMetrics) -> (f64, f64, f64) {
1226 let sharpe_ratio = 1.5;
1229 let sortino_ratio = 2.0;
1230 let daily_volatility = 0.01;
1231
1232 (sharpe_ratio, sortino_ratio, daily_volatility)
1233 }
1234
1235 pub fn calculate_annualized_return_with_metrics(&self, metrics: &PerformanceMetrics) -> f64 {
1237 if metrics.total_return > 0.0 {
1239 let daily_return = metrics.total_return / 100.0;
1240 (1.0f64 + daily_return).powf(365.0) - 1.0
1241 } else {
1242 0.0
1243 }
1244 }
1245
1246 pub fn calculate_period_pnl_alt(&self, hours: i64) -> f64 {
1248 if self.pnl_history.is_empty() {
1249 return 0.0;
1250 }
1251
1252 let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
1253 let period_start = now - chrono::Duration::hours(hours);
1254
1255 let start_report = self.pnl_history.iter()
1257 .filter(|report| report.timestamp >= period_start)
1258 .min_by_key(|report| report.timestamp);
1259
1260 let oldest_in_period = start_report;
1261 let latest = self.pnl_history.last();
1262
1263 match (oldest_in_period, latest) {
1264 (Some(oldest), Some(latest)) => {
1265 latest.total_pnl - oldest.total_pnl
1266 },
1267 _ => 0.0,
1268 }
1269 }
1270}