hyperliquid_backtest/
csv_export.rs

1//! CSV export functionality for Hyperliquid backtesting
2
3use crate::data::HyperliquidData;
4use crate::errors::{HyperliquidBacktestError, Result};
5use crate::backtest::{HyperliquidBacktest, FundingPayment};
6use std::fs::File;
7use std::io::Write;
8
9/// Export funding payments to CSV
10pub fn export_funding_payments_to_csv(payments: &[FundingPayment], file_path: &str) -> Result<()> {
11    let mut file = File::create(file_path)
12        .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to create file {}: {}", file_path, e)))?;
13    
14    // Write header
15    writeln!(file, "timestamp,funding_rate,position_size,price,payment_amount")
16        .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to write header: {}", e)))?;
17    
18    // Write payment data
19    for payment in payments {
20        let timestamp = payment.timestamp.format("%Y-%m-%d %H:%M:%S").to_string();
21        
22        writeln!(
23            file,
24            "{},{:.8},{},{:.2},{:.2}",
25            timestamp,
26            payment.funding_rate,
27            payment.position_size,
28            payment.mark_price,
29            payment.payment_amount
30        ).map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to write payment data: {}", e)))?;
31    }
32    
33    Ok(())
34}
35
36/// Export funding rate history to CSV
37pub fn export_funding_rate_history(data: &HyperliquidData, file_path: &str) -> Result<()> {
38    let mut file = File::create(file_path)
39        .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to create file {}: {}", file_path, e)))?;
40    
41    // Write header
42    writeln!(file, "timestamp,funding_rate")
43        .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to write header: {}", e)))?;
44    
45    // Write funding rate data
46    for i in 0..data.datetime.len() {
47        let timestamp = data.datetime[i].format("%Y-%m-%d %H:%M:%S").to_string();
48        let funding_rate = data.funding_rates[i];
49        
50        // Only include non-NaN funding rates
51        if !funding_rate.is_nan() {
52            writeln!(
53                file,
54                "{},{:.8}",
55                timestamp,
56                funding_rate
57            ).map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to write funding rate data: {}", e)))?;
58        }
59    }
60    
61    Ok(())
62}
63
64/// Enhanced CSV export trait for HyperliquidBacktest
65pub trait EnhancedCsvExport {
66    /// Export backtest results to CSV
67    fn export_to_csv(&self, file_path: &str) -> Result<()>;
68    
69    /// Export backtest results with extended data to CSV
70    fn export_to_csv_extended(&self, file_path: &str, include_funding: bool, include_trading: bool, include_total: bool) -> Result<()>;
71}
72
73/// Extended CSV export trait for HyperliquidBacktest
74pub trait EnhancedCsvExportExt {
75    /// Export backtest results with funding data to CSV
76    fn export_to_csv(&self, file_path: &str) -> Result<()>;
77    
78    /// Export backtest results with extended data to CSV
79    fn export_to_csv_extended(&self, file_path: &str, include_funding: bool, include_trading: bool, include_total: bool) -> Result<()>;
80}
81
82impl EnhancedCsvExport for HyperliquidBacktest {
83    fn export_to_csv(&self, file_path: &str) -> Result<()> {
84        let mut file = File::create(file_path)
85            .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to create file {}: {}", file_path, e)))?;
86        
87        // Write header
88        writeln!(file, "timestamp,price,position,equity,funding_rate,funding_pnl")
89            .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to write header: {}", e)))?;
90        
91        // Write data rows
92        for i in 0..self.data().len() {
93            let timestamp = self.data().datetime[i].format("%Y-%m-%d %H:%M:%S").to_string();
94            let price = self.data().close[i];
95            let position = 0.0; // Placeholder, would be populated from actual position data
96            let equity = self.initial_capital() + self.funding_pnl[i]; // Simplified equity calculation
97            let funding_rate = if i < self.data().funding_rates.len() {
98                self.data().funding_rates[i]
99            } else {
100                f64::NAN
101            };
102            
103            writeln!(
104                file,
105                "{},{:.2},{:.2},{:.2},{:.8},{:.2}",
106                timestamp,
107                price,
108                position,
109                equity,
110                if funding_rate.is_nan() { 0.0 } else { funding_rate },
111                self.funding_pnl[i]
112            ).map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to write data row: {}", e)))?;
113        }
114        
115        Ok(())
116    }
117    
118    fn export_to_csv_extended(&self, file_path: &str, include_funding: bool, include_trading: bool, include_total: bool) -> Result<()> {
119        let mut file = File::create(file_path)
120            .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to create file {}: {}", file_path, e)))?;
121        
122        // Build header based on included data
123        let mut header = String::from("timestamp,price,position,equity");
124        
125        if include_funding {
126            header.push_str(",funding_rate,funding_pnl");
127        }
128        
129        if include_trading {
130            header.push_str(",trading_pnl");
131        }
132        
133        if include_total {
134            header.push_str(",total_pnl");
135        }
136        
137        // Write header
138        writeln!(file, "{}", header)
139            .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to write header: {}", e)))?;
140        
141        // Write data rows
142        for i in 0..self.data().len() {
143            let timestamp = self.data().datetime[i].format("%Y-%m-%d %H:%M:%S").to_string();
144            let price = self.data().close[i];
145            let position = 0.0; // Placeholder, would be populated from actual position data
146            let equity = self.initial_capital() + self.funding_pnl[i]; // Simplified equity calculation
147            
148            let mut row = format!("{},{:.2},{:.2},{:.2}", timestamp, price, position, equity);
149            
150            if include_funding {
151                let funding_rate = if i < self.data().funding_rates.len() {
152                    self.data().funding_rates[i]
153                } else {
154                    f64::NAN
155                };
156                
157                row.push_str(&format!(",{:.8},{:.2}", 
158                    if funding_rate.is_nan() { 0.0 } else { funding_rate },
159                    self.funding_pnl[i]
160                ));
161            }
162            
163            if include_trading {
164                row.push_str(&format!(",{:.2}", self.trading_pnl[i]));
165            }
166            
167            if include_total {
168                let total_pnl = self.funding_pnl[i] + self.trading_pnl[i];
169                row.push_str(&format!(",{:.2}", total_pnl));
170            }
171            
172            writeln!(file, "{}", row)
173                .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to write data row: {}", e)))?;
174        }
175        
176        Ok(())
177    }
178}
179
180impl EnhancedCsvExportExt for HyperliquidBacktest {
181    fn export_to_csv(&self, file_path: &str) -> Result<()> {
182        <Self as EnhancedCsvExport>::export_to_csv(self, file_path)
183    }
184    
185    fn export_to_csv_extended(&self, file_path: &str, include_funding: bool, include_trading: bool, include_total: bool) -> Result<()> {
186        <Self as EnhancedCsvExport>::export_to_csv_extended(self, file_path, include_funding, include_trading, include_total)
187    }
188}
189
190/// Strategy comparison data for multiple backtests
191pub struct StrategyComparisonData {
192    /// List of backtests to compare
193    pub strategies: Vec<HyperliquidBacktest>,
194}
195
196impl StrategyComparisonData {
197    /// Export strategy comparison data to CSV
198    pub fn export_to_csv(&self, file_path: &str) -> Result<()> {
199        let mut file = File::create(file_path)
200            .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to create file {}: {}", file_path, e)))?;
201        
202        // Build header with strategy names
203        let mut header = String::from("timestamp,price");
204        
205        for strategy in &self.strategies {
206            let strategy_name = strategy.strategy_name().replace(" ", "_");
207            header.push_str(&format!(",{}_position,{}_equity", strategy_name, strategy_name));
208        }
209        
210        // Write header
211        writeln!(file, "{}", header)
212            .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to write header: {}", e)))?;
213        
214        // Ensure all strategies have the same data length
215        if self.strategies.is_empty() {
216            return Ok(());
217        }
218        
219        let data_len = self.strategies[0].data().len();
220        
221        // Write data rows
222        for i in 0..data_len {
223            let timestamp = self.strategies[0].data().datetime[i].format("%Y-%m-%d %H:%M:%S").to_string();
224            let price = self.strategies[0].data().close[i];
225            
226            let mut row = format!("{},{:.2}", timestamp, price);
227            
228            for strategy in &self.strategies {
229                let position = 0.0; // Placeholder, would be populated from actual position data
230                let equity = strategy.initial_capital() + 
231                    (if i < strategy.funding_pnl.len() { strategy.funding_pnl[i] } else { 0.0 }) +
232                    (if i < strategy.trading_pnl.len() { strategy.trading_pnl[i] } else { 0.0 });
233                
234                row.push_str(&format!(",{:.2},{:.2}", position, equity));
235            }
236            
237            writeln!(file, "{}", row)
238                .map_err(|e| HyperliquidBacktestError::data_conversion(format!("Failed to write data row: {}", e)))?;
239        }
240        
241        Ok(())
242    }
243}