1use std::collections::VecDeque;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
8pub enum FundingDirection {
9 Positive,
11 Negative,
13 Neutral,
15}
16
17impl FundingDirection {
18 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#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct FundingVolatility {
33 pub std_dev: f64,
35 pub cv: f64,
37 pub is_high: bool,
39 pub percentile: f64,
41}
42
43pub 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#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct FundingMomentum {
60 pub direction: FundingDirection,
62 pub strength: f64,
64 pub rate_of_change: f64,
66 pub is_accelerating: bool,
68}
69
70pub fn calculate_funding_momentum(rates: &[f64]) -> f64 {
72 if rates.len() <= 1 {
73 return 0.0;
74 }
75
76 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#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct FundingCycle {
93 pub period_hours: usize,
95 pub strength: f64,
97 pub is_significant: bool,
99 pub current_phase: f64,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct FundingAnomaly {
106 pub is_anomaly: bool,
108 pub deviation: f64,
110 pub direction: FundingDirection,
112 pub potential_cause: String,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct FundingArbitrageOpportunity {
119 pub is_arbitrage: bool,
121 pub direction: FundingDirection,
123 pub annualized_yield: f64,
125 pub payment_per_contract: f64,
127}
128
129pub 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; let annualized_yield = funding_rate.abs() * 3.0 * 365.25; 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#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct FundingPriceCorrelation {
147 pub coefficient: f64,
149 pub is_significant: bool,
151 pub relationship: String,
153 pub optimal_lag: i32,
155}
156
157impl FundingPriceCorrelation {
158 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, }
200 }
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct OpenInterestData {
206 pub current_oi: f64,
208 pub change: OpenInterestChange,
210 pub long_short_ratio: f64,
212 pub is_at_high: bool,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct OpenInterestChange {
219 pub absolute_change: f64,
221 pub percentage_change: f64,
223 pub usd_value_change: f64,
225 pub is_increasing: bool,
227 pub is_decreasing: bool,
229}
230
231impl OpenInterestChange {
232 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#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct LiquidationData {
255 pub total_amount: f64,
257 pub long_liquidations: f64,
259 pub short_liquidations: f64,
261 pub impact: LiquidationImpact,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct LiquidationImpact {
268 pub liquidation_percentage: f64,
270 pub usd_value: f64,
272 pub price_impact: f64,
274 pub is_significant: bool,
276}
277
278impl LiquidationImpact {
279 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#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct BasisIndicator {
301 pub basis: f64,
303 pub annualized_basis: f64,
305 pub basis_amount: f64,
307 pub is_widening: bool,
309}
310
311pub 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, }
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct FundingPrediction {
328 pub expected_rate: f64,
330 pub direction: FundingDirection,
332 pub confidence: f64,
334 pub horizon_hours: u32,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct FundingPredictionConfig {
341 pub lookback_periods: usize,
343 pub volatility_weight: f64,
345 pub momentum_weight: f64,
347 pub basis_weight: f64,
349 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
365pub trait FundingPredictionModel {
367 fn add_observation(&mut self, rate: f64);
369
370 fn predict(&self) -> FundingPrediction;
372
373 fn get_volatility(&self) -> f64;
375
376 fn get_momentum(&self) -> f64;
378
379 fn detect_funding_cycle(&self) -> FundingCycle;
381
382 fn detect_anomaly(&self) -> FundingAnomaly;
384
385 fn correlation_with(&self, other: &dyn FundingPredictionModel) -> f64;
387}
388
389pub struct FundingRatePredictor {
391 config: FundingPredictionConfig,
393 rates: VecDeque<f64>,
395}
396
397impl FundingRatePredictor {
398 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 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 let last_rate = *self.rates.back().unwrap();
433
434 let expected_rate = last_rate + momentum;
436 let direction = FundingDirection::from_rate(expected_rate);
437
438 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 FundingCycle {
462 period_hours: 8, 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; 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 0.5
508 }
509}