hyperliquid_backtest/
indicators.rs

1//! Indicators and analysis tools for funding rates and market data
2
3use std::collections::VecDeque;
4use serde::{Deserialize, Serialize};
5
6/// Direction of funding rate
7#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
8pub enum FundingDirection {
9    /// Positive funding rate (longs pay shorts)
10    Positive,
11    /// Negative funding rate (shorts pay longs)
12    Negative,
13    /// Neutral funding rate (close to zero)
14    Neutral,
15}
16
17impl FundingDirection {
18    /// Determine direction from funding rate value
19    pub fn from_rate(rate: f64) -> Self {
20        if rate > 0.0 {
21            FundingDirection::Positive
22        } else if rate < 0.0 {
23            FundingDirection::Negative
24        } else {
25            FundingDirection::Neutral
26        }
27    }
28}
29
30/// Funding rate volatility analysis
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct FundingVolatility {
33    /// Standard deviation of funding rates
34    pub std_dev: f64,
35    /// Coefficient of variation
36    pub cv: f64,
37    /// Is volatility high compared to historical average
38    pub is_high: bool,
39    /// Percentile of current volatility in historical distribution
40    pub percentile: f64,
41}
42
43/// Calculate funding rate volatility
44pub fn calculate_funding_volatility(rates: &[f64]) -> f64 {
45    if rates.len() <= 1 {
46        return 0.0;
47    }
48    
49    let mean = rates.iter().sum::<f64>() / rates.len() as f64;
50    let variance = rates.iter()
51        .map(|&r| (r - mean).powi(2))
52        .sum::<f64>() / (rates.len() - 1) as f64;
53    
54    variance.sqrt()
55}
56
57/// Funding rate momentum analysis
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct FundingMomentum {
60    /// Direction of momentum
61    pub direction: FundingDirection,
62    /// Strength of momentum (0.0 to 1.0)
63    pub strength: f64,
64    /// Rate of change
65    pub rate_of_change: f64,
66    /// Is momentum accelerating
67    pub is_accelerating: bool,
68}
69
70/// Calculate funding rate momentum
71pub fn calculate_funding_momentum(rates: &[f64]) -> f64 {
72    if rates.len() <= 1 {
73        return 0.0;
74    }
75    
76    // Simple linear regression slope
77    let n = rates.len() as f64;
78    let indices: Vec<f64> = (0..rates.len()).map(|i| i as f64).collect();
79    
80    let sum_x: f64 = indices.iter().sum();
81    let sum_y: f64 = rates.iter().sum();
82    let sum_xy: f64 = indices.iter().zip(rates.iter()).map(|(&x, &y)| x * y).sum();
83    let sum_xx: f64 = indices.iter().map(|&x| x * x).sum();
84    
85    let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
86    
87    slope
88}
89
90/// Funding cycle analysis
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct FundingCycle {
93    /// Period of the cycle in hours
94    pub period_hours: usize,
95    /// Strength of the cycle pattern (0.0 to 1.0)
96    pub strength: f64,
97    /// Is the cycle statistically significant
98    pub is_significant: bool,
99    /// Phase of the cycle (0.0 to 1.0)
100    pub current_phase: f64,
101}
102
103/// Funding rate anomaly detection
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct FundingAnomaly {
106    /// Is the current rate anomalous
107    pub is_anomaly: bool,
108    /// How many standard deviations from the mean
109    pub deviation: f64,
110    /// Direction of the anomaly
111    pub direction: FundingDirection,
112    /// Potential cause of the anomaly
113    pub potential_cause: String,
114}
115
116/// Funding arbitrage opportunity analysis
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct FundingArbitrageOpportunity {
119    /// Is there an arbitrage opportunity
120    pub is_arbitrage: bool,
121    /// Direction of the opportunity
122    pub direction: FundingDirection,
123    /// Annualized yield of the opportunity
124    pub annualized_yield: f64,
125    /// Payment per contract
126    pub payment_per_contract: f64,
127}
128
129/// Calculate funding arbitrage opportunity
130pub fn calculate_funding_arbitrage(funding_rate: f64, price: f64) -> FundingArbitrageOpportunity {
131    let direction = FundingDirection::from_rate(funding_rate);
132    let is_arbitrage = funding_rate.abs() > 0.0001; // Threshold for arbitrage
133    let annualized_yield = funding_rate.abs() * 3.0 * 365.25; // 3 funding periods per day
134    let payment_per_contract = funding_rate.abs() * price;
135    
136    FundingArbitrageOpportunity {
137        is_arbitrage,
138        direction,
139        annualized_yield,
140        payment_per_contract,
141    }
142}
143
144/// Correlation between funding rate and price
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct FundingPriceCorrelation {
147    /// Correlation coefficient (-1.0 to 1.0)
148    pub coefficient: f64,
149    /// Is the correlation statistically significant
150    pub is_significant: bool,
151    /// Relationship description
152    pub relationship: String,
153    /// Time lag with strongest correlation
154    pub optimal_lag: i32,
155}
156
157impl FundingPriceCorrelation {
158    /// Calculate correlation between funding rates and prices
159    pub fn calculate(funding_rates: &[f64], prices: &[f64]) -> Self {
160        if funding_rates.len() != prices.len() || funding_rates.is_empty() {
161            return Self {
162                coefficient: 0.0,
163                is_significant: false,
164                relationship: "Unknown".to_string(),
165                optimal_lag: 0,
166            };
167        }
168        
169        let n = funding_rates.len() as f64;
170        let sum_x: f64 = funding_rates.iter().sum();
171        let sum_y: f64 = prices.iter().sum();
172        let sum_xy: f64 = funding_rates.iter().zip(prices.iter()).map(|(&x, &y)| x * y).sum();
173        let sum_xx: f64 = funding_rates.iter().map(|&x| x * x).sum();
174        let sum_yy: f64 = prices.iter().map(|&y| y * y).sum();
175        
176        let numerator = n * sum_xy - sum_x * sum_y;
177        let denominator = ((n * sum_xx - sum_x * sum_x) * (n * sum_yy - sum_y * sum_y)).sqrt();
178        
179        let coefficient = if denominator != 0.0 {
180            numerator / denominator
181        } else {
182            0.0
183        };
184        
185        let is_significant = coefficient.abs() > 0.5;
186        let relationship = if coefficient > 0.7 {
187            "Positive".to_string()
188        } else if coefficient < -0.7 {
189            "Negative".to_string()
190        } else {
191            "Weak".to_string()
192        };
193        
194        Self {
195            coefficient,
196            is_significant,
197            relationship,
198            optimal_lag: 0, // Would require more complex analysis
199        }
200    }
201}
202
203/// Open interest data and analysis
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct OpenInterestData {
206    /// Current open interest
207    pub current_oi: f64,
208    /// Change in open interest
209    pub change: OpenInterestChange,
210    /// Long/short ratio
211    pub long_short_ratio: f64,
212    /// Is open interest at historical high
213    pub is_at_high: bool,
214}
215
216/// Open interest change analysis
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct OpenInterestChange {
219    /// Absolute change in open interest
220    pub absolute_change: f64,
221    /// Percentage change in open interest
222    pub percentage_change: f64,
223    /// USD value of the change
224    pub usd_value_change: f64,
225    /// Is open interest increasing
226    pub is_increasing: bool,
227    /// Is open interest decreasing
228    pub is_decreasing: bool,
229}
230
231impl OpenInterestChange {
232    /// Create a new OpenInterestChange
233    pub fn new(prev_oi: f64, curr_oi: f64, price: f64) -> Self {
234        let absolute_change = curr_oi - prev_oi;
235        let percentage_change = if prev_oi > 0.0 {
236            absolute_change / prev_oi
237        } else {
238            0.0
239        };
240        let usd_value_change = absolute_change * price;
241        
242        Self {
243            absolute_change,
244            percentage_change,
245            usd_value_change,
246            is_increasing: absolute_change > 0.0,
247            is_decreasing: absolute_change < 0.0,
248        }
249    }
250}
251
252/// Liquidation data and analysis
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct LiquidationData {
255    /// Total liquidation amount
256    pub total_amount: f64,
257    /// Long liquidations
258    pub long_liquidations: f64,
259    /// Short liquidations
260    pub short_liquidations: f64,
261    /// Impact on market
262    pub impact: LiquidationImpact,
263}
264
265/// Liquidation market impact analysis
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct LiquidationImpact {
268    /// Liquidation as percentage of open interest
269    pub liquidation_percentage: f64,
270    /// USD value of liquidations
271    pub usd_value: f64,
272    /// Price impact percentage
273    pub price_impact: f64,
274    /// Is the liquidation significant
275    pub is_significant: bool,
276}
277
278impl LiquidationImpact {
279    /// Create a new LiquidationImpact
280    pub fn new(liquidation_amount: f64, open_interest: f64, price: f64, price_impact: f64) -> Self {
281        let liquidation_percentage = if open_interest > 0.0 {
282            liquidation_amount / open_interest
283        } else {
284            0.0
285        };
286        let usd_value = liquidation_amount * price;
287        let is_significant = liquidation_percentage > 0.05 || price_impact.abs() > 0.01;
288        
289        Self {
290            liquidation_percentage,
291            usd_value,
292            price_impact,
293            is_significant,
294        }
295    }
296}
297
298/// Basis indicator for futures vs spot
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct BasisIndicator {
301    /// Basis percentage
302    pub basis: f64,
303    /// Annualized basis
304    pub annualized_basis: f64,
305    /// Absolute basis amount
306    pub basis_amount: f64,
307    /// Is the basis widening
308    pub is_widening: bool,
309}
310
311/// Calculate basis indicator
312pub fn calculate_basis_indicator(spot_price: f64, futures_price: f64, days_to_expiry: f64) -> BasisIndicator {
313    let basis_amount = futures_price - spot_price;
314    let basis = basis_amount / spot_price;
315    let annualized_basis = basis * (365.0 / days_to_expiry);
316    
317    BasisIndicator {
318        basis,
319        annualized_basis,
320        basis_amount,
321        is_widening: false, // Would need historical data to determine
322    }
323}
324
325/// Funding rate prediction
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct FundingPrediction {
328    /// Expected funding rate
329    pub expected_rate: f64,
330    /// Direction of the prediction
331    pub direction: FundingDirection,
332    /// Confidence in the prediction (0.0 to 1.0)
333    pub confidence: f64,
334    /// Time horizon of the prediction in hours
335    pub horizon_hours: u32,
336}
337
338/// Configuration for funding rate prediction
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct FundingPredictionConfig {
341    /// Number of periods to look back for analysis
342    pub lookback_periods: usize,
343    /// Weight for volatility in prediction
344    pub volatility_weight: f64,
345    /// Weight for momentum in prediction
346    pub momentum_weight: f64,
347    /// Weight for basis in prediction
348    pub basis_weight: f64,
349    /// Weight for correlation in prediction
350    pub correlation_weight: f64,
351}
352
353impl Default for FundingPredictionConfig {
354    fn default() -> Self {
355        Self {
356            lookback_periods: 48,
357            volatility_weight: 0.2,
358            momentum_weight: 0.3,
359            basis_weight: 0.3,
360            correlation_weight: 0.2,
361        }
362    }
363}
364
365/// Funding rate prediction model
366pub trait FundingPredictionModel {
367    /// Add a new funding rate observation
368    fn add_observation(&mut self, rate: f64);
369    
370    /// Predict the next funding rate
371    fn predict(&self) -> FundingPrediction;
372    
373    /// Get the volatility of funding rates
374    fn get_volatility(&self) -> f64;
375    
376    /// Get the momentum of funding rates
377    fn get_momentum(&self) -> f64;
378    
379    /// Detect funding cycle
380    fn detect_funding_cycle(&self) -> FundingCycle;
381    
382    /// Detect funding anomaly
383    fn detect_anomaly(&self) -> FundingAnomaly;
384    
385    /// Calculate correlation with another predictor
386    fn correlation_with(&self, other: &dyn FundingPredictionModel) -> f64;
387}
388
389/// Funding rate predictor implementation
390pub struct FundingRatePredictor {
391    /// Configuration for the predictor
392    config: FundingPredictionConfig,
393    /// Historical funding rates
394    rates: VecDeque<f64>,
395}
396
397impl FundingRatePredictor {
398    /// Create a new FundingRatePredictor
399    pub fn new(config: FundingPredictionConfig) -> Self {
400        let capacity = config.lookback_periods;
401        Self {
402            config,
403            rates: VecDeque::with_capacity(capacity),
404        }
405    }
406}
407
408impl FundingPredictionModel for FundingRatePredictor {
409    fn add_observation(&mut self, rate: f64) {
410        if self.rates.len() >= self.config.lookback_periods {
411            self.rates.pop_front();
412        }
413        self.rates.push_back(rate);
414    }
415    
416    fn predict(&self) -> FundingPrediction {
417        if self.rates.is_empty() {
418            return FundingPrediction {
419                expected_rate: 0.0,
420                direction: FundingDirection::Neutral,
421                confidence: 0.0,
422                horizon_hours: 8,
423            };
424        }
425        
426        // Simple prediction based on recent trend
427        let rates: Vec<f64> = self.rates.iter().copied().collect();
428        let momentum = calculate_funding_momentum(&rates);
429        let volatility = calculate_funding_volatility(&rates);
430        
431        // Last observed rate
432        let last_rate = *self.rates.back().unwrap();
433        
434        // Predict next rate based on momentum
435        let expected_rate = last_rate + momentum;
436        let direction = FundingDirection::from_rate(expected_rate);
437        
438        // Higher confidence with lower volatility and stronger momentum
439        let confidence = 0.5 + 0.3 * (momentum.abs() / (volatility + 0.0001)).min(1.0);
440        
441        FundingPrediction {
442            expected_rate,
443            direction,
444            confidence,
445            horizon_hours: 8,
446        }
447    }
448    
449    fn get_volatility(&self) -> f64 {
450        let rates: Vec<f64> = self.rates.iter().copied().collect();
451        calculate_funding_volatility(&rates)
452    }
453    
454    fn get_momentum(&self) -> f64 {
455        let rates: Vec<f64> = self.rates.iter().copied().collect();
456        calculate_funding_momentum(&rates)
457    }
458    
459    fn detect_funding_cycle(&self) -> FundingCycle {
460        // Simple cycle detection (in a real implementation, would use FFT or autocorrelation)
461        FundingCycle {
462            period_hours: 8, // Typical funding period
463            strength: 0.7,
464            is_significant: true,
465            current_phase: 0.5,
466        }
467    }
468    
469    fn detect_anomaly(&self) -> FundingAnomaly {
470        if self.rates.len() < 2 {
471            return FundingAnomaly {
472                is_anomaly: false,
473                deviation: 0.0,
474                direction: FundingDirection::Neutral,
475                potential_cause: "Insufficient data".to_string(),
476            };
477        }
478        
479        let rates: Vec<f64> = self.rates.iter().copied().collect();
480        let mean = rates.iter().sum::<f64>() / rates.len() as f64;
481        let std_dev = calculate_funding_volatility(&rates);
482        
483        let last_rate = *self.rates.back().unwrap();
484        let deviation = if std_dev > 0.0 {
485            (last_rate - mean) / std_dev
486        } else {
487            0.0
488        };
489        
490        let is_anomaly = deviation.abs() > 3.0; // 3 sigma rule
491        let direction = FundingDirection::from_rate(last_rate);
492        
493        FundingAnomaly {
494            is_anomaly,
495            deviation: deviation.abs(),
496            direction,
497            potential_cause: if is_anomaly {
498                "Significant market event".to_string()
499            } else {
500                "Normal market conditions".to_string()
501            },
502        }
503    }
504    
505    fn correlation_with(&self, _other: &dyn FundingPredictionModel) -> f64 {
506        // Simplified implementation - return a default correlation
507        0.5
508    }
509}