hyperliquid_backtest/
utils.rs1use chrono::{DateTime, FixedOffset, TimeZone, Utc};
4use crate::errors::{HyperliquidBacktestError, Result};
5
6pub mod time {
8 use super::*;
9
10 pub fn unix_to_datetime(timestamp: u64) -> Result<DateTime<FixedOffset>> {
12 let datetime = DateTime::from_timestamp(timestamp as i64, 0)
13 .ok_or_else(|| HyperliquidBacktestError::DataConversion(
14 format!("Invalid timestamp: {}", timestamp)
15 ))?;
16
17 Ok(datetime.with_timezone(&FixedOffset::east_opt(0).unwrap()))
18 }
19
20 pub fn datetime_to_unix(dt: DateTime<FixedOffset>) -> u64 {
22 dt.timestamp() as u64
23 }
24
25 pub fn validate_time_range(start: u64, end: u64) -> Result<()> {
27 if start >= end {
28 return Err(HyperliquidBacktestError::InvalidTimeRange { start, end });
29 }
30 Ok(())
31 }
32
33 pub fn current_timestamp() -> u64 {
35 Utc::now().timestamp() as u64
36 }
37}
38
39pub mod validation {
41 use super::*;
42
43 pub fn validate_ohlc_data(
45 open: &[f64],
46 high: &[f64],
47 low: &[f64],
48 close: &[f64],
49 ) -> Result<()> {
50 let len = open.len();
51 if high.len() != len || low.len() != len || close.len() != len {
52 return Err(HyperliquidBacktestError::DataConversion(
53 "OHLC arrays have different lengths".to_string()
54 ));
55 }
56
57 for i in 0..len {
58 if high[i] < low[i] {
59 return Err(HyperliquidBacktestError::DataConversion(
60 format!("High price {} is less than low price {} at index {}",
61 high[i], low[i], i)
62 ));
63 }
64
65 if open[i] < 0.0 || high[i] < 0.0 || low[i] < 0.0 || close[i] < 0.0 {
66 return Err(HyperliquidBacktestError::DataConversion(
67 format!("Negative price found at index {}", i)
68 ));
69 }
70 }
71
72 Ok(())
73 }
74
75 pub fn validate_funding_rates(rates: &[f64]) -> Result<()> {
77 for (i, &rate) in rates.iter().enumerate() {
78 if rate.is_nan() || rate.is_infinite() {
79 return Err(HyperliquidBacktestError::DataConversion(
80 format!("Invalid funding rate at index {}: {}", i, rate)
81 ));
82 }
83
84 if rate.abs() > 0.01 {
86 eprintln!("Warning: Unusually high funding rate at index {}: {:.4}%",
87 i, rate * 100.0);
88 }
89 }
90
91 Ok(())
92 }
93
94 pub fn validate_interval(interval: &str) -> Result<()> {
96 match interval {
97 "1m" | "5m" | "15m" | "30m" | "1h" | "4h" | "1d" => Ok(()),
98 _ => Err(HyperliquidBacktestError::UnsupportedInterval(interval.to_string())),
99 }
100 }
101}
102
103pub mod conversion {
105 use super::*;
106
107 pub fn string_to_f64(s: &str) -> Result<f64> {
109 s.parse::<f64>()
110 .map_err(|_| HyperliquidBacktestError::DataConversion(
111 format!("Failed to parse '{}' as f64", s)
112 ))
113 }
114
115 pub fn string_to_u64(s: &str) -> Result<u64> {
117 s.parse::<u64>()
118 .map_err(|_| HyperliquidBacktestError::DataConversion(
119 format!("Failed to parse '{}' as u64", s)
120 ))
121 }
122
123 pub fn f64_to_string(value: f64, precision: usize) -> String {
125 format!("{:.prec$}", value, prec = precision)
126 }
127}
128
129pub mod math {
131 pub fn sma(data: &[f64], period: usize) -> Vec<f64> {
133 if period == 0 || data.len() < period {
134 return Vec::new();
135 }
136
137 let mut result = Vec::with_capacity(data.len() - period + 1);
138
139 for i in period - 1..data.len() {
140 let sum: f64 = data[i - period + 1..=i].iter().sum();
141 result.push(sum / period as f64);
142 }
143
144 result
145 }
146
147 pub fn ema(data: &[f64], period: usize) -> Vec<f64> {
149 if data.is_empty() || period == 0 {
150 return Vec::new();
151 }
152
153 let alpha = 2.0 / (period as f64 + 1.0);
154 let mut result = Vec::with_capacity(data.len());
155
156 result.push(data[0]);
158
159 for i in 1..data.len() {
160 let ema_value = alpha * data[i] + (1.0 - alpha) * result[i - 1];
161 result.push(ema_value);
162 }
163
164 result
165 }
166
167 pub fn std_dev(data: &[f64]) -> f64 {
169 if data.len() < 2 {
170 return 0.0;
171 }
172
173 let mean = data.iter().sum::<f64>() / data.len() as f64;
174 let variance = data.iter()
175 .map(|x| (x - mean).powi(2))
176 .sum::<f64>() / (data.len() - 1) as f64;
177
178 variance.sqrt()
179 }
180
181 pub fn lerp(x0: f64, y0: f64, x1: f64, y1: f64, x: f64) -> f64 {
183 if (x1 - x0).abs() < f64::EPSILON {
184 return y0;
185 }
186
187 y0 + (y1 - y0) * (x - x0) / (x1 - x0)
188 }
189}
190
191pub mod csv_utils {
193 use super::*;
194 use std::path::Path;
195
196 pub fn write_csv<P: AsRef<Path>>(
198 path: P,
199 headers: &[&str],
200 data: &[Vec<String>],
201 ) -> Result<()> {
202 let mut writer = csv::Writer::from_path(path)?;
203
204 writer.write_record(headers)?;
206
207 for row in data {
209 writer.write_record(row)?;
210 }
211
212 writer.flush()?;
213 Ok(())
214 }
215
216 pub fn read_csv<P: AsRef<Path>>(path: P) -> Result<(Vec<String>, Vec<Vec<String>>)> {
218 let mut reader = csv::Reader::from_path(path)?;
219
220 let headers = reader.headers()?.iter().map(|s| s.to_string()).collect();
222
223 let mut data = Vec::new();
225 for result in reader.records() {
226 let record = result?;
227 let row: Vec<String> = record.iter().map(|s| s.to_string()).collect();
228 data.push(row);
229 }
230
231 Ok((headers, data))
232 }
233}