use crate::data::HyperliquidData;
use crate::errors::Result;
use crate::tests::mock_data::generate_mock_data;
use chrono::{FixedOffset, TimeZone};
use std::collections::HashMap;
#[test]
fn test_data_conversion_roundtrip() {
let symbol = "BTC";
let hours = 24;
let with_funding = true;
let data = generate_mock_data(symbol, hours, with_funding, false);
let rs_data = data.to_rs_backtester_data();
assert_eq!(rs_data.ticker, data.symbol);
assert_eq!(rs_data.datetime.len(), data.datetime.len());
assert_eq!(rs_data.open.len(), data.open.len());
assert_eq!(rs_data.high.len(), data.high.len());
assert_eq!(rs_data.low.len(), data.low.len());
assert_eq!(rs_data.close.len(), data.close.len());
assert_eq!(rs_data.volume.len(), data.volume.len());
for i in 0..data.len() {
assert_eq!(rs_data.datetime[i], data.datetime[i]);
assert_eq!(rs_data.open[i], data.open[i]);
assert_eq!(rs_data.high[i], data.high[i]);
assert_eq!(rs_data.low[i], data.low[i]);
assert_eq!(rs_data.close[i], data.close[i]);
assert_eq!(rs_data.volume[i], data.volume[i]);
}
}
#[test]
fn test_string_to_float_conversion() {
let test_cases = vec![
(0.0, "0"),
(1.0, "1"),
(-1.0, "-1"),
(3.14159, "3.14159"),
(0.0001, "0.0001"),
(-0.0001, "-0.0001"),
(1000000.0, "1000000"),
];
for (value, string) in test_cases {
let formatted = format!("{}", value);
let parsed: f64 = formatted.parse().unwrap();
assert!((parsed - value).abs() < 0.0001);
let parsed_string: f64 = string.parse().unwrap();
assert!((parsed_string - value).abs() < 0.0001);
}
}
#[test]
fn test_timestamp_conversion() {
let test_cases = vec![
1000000000, 1500000000, 1600000000, 1700000000, ];
for timestamp in test_cases {
let tz = FixedOffset::east_opt(0).unwrap();
let dt = tz.timestamp_opt(timestamp, 0).unwrap();
let converted_timestamp = dt.timestamp();
assert_eq!(timestamp, converted_timestamp);
}
}
#[test]
fn test_data_validation() -> Result<()> {
let valid_data = generate_mock_data("BTC", 24, true, false);
assert!(valid_data.validate_all_data().is_ok());
let invalid_data_map = crate::tests::mock_data::generate_invalid_data();
let empty_data = &invalid_data_map["empty"];
assert!(empty_data.validate_all_data().is_ok());
let mismatched_data = &invalid_data_map["mismatched_lengths"];
assert!(mismatched_data.validate_all_data().is_err());
let invalid_high_low = &invalid_data_map["invalid_high_low"];
assert!(invalid_high_low.validate_all_data().is_err());
let non_chronological = &invalid_data_map["non_chronological"];
assert!(non_chronological.validate_all_data().is_err());
Ok(())
}
#[test]
fn test_fetch_parameters_validation() {
let result = HyperliquidData::validate_fetch_parameters(
"BTC", "1h", 1640995200, 1641081600
);
assert!(result.is_ok());
let result = HyperliquidData::validate_fetch_parameters(
"", "1h", 1640995200, 1641081600
);
assert!(result.is_err());
let result = HyperliquidData::validate_fetch_parameters(
"BTC", "2h", 1640995200, 1641081600
);
assert!(result.is_err());
let result = HyperliquidData::validate_fetch_parameters(
"BTC", "1h", 1641081600, 1640995200
);
assert!(result.is_err());
let future_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() + 100000;
let result = HyperliquidData::validate_fetch_parameters(
"BTC", "1h", future_time, future_time + 3600
);
assert!(result.is_err());
let start_time = 1640995200;
let max_range = crate::data::HyperliquidDataFetcher::max_time_range_for_interval("1h");
let end_time = start_time + max_range + 3600;
let result = HyperliquidData::validate_fetch_parameters(
"BTC", "1h", start_time, end_time
);
assert!(result.is_err());
}
#[test]
fn test_hyperliquid_data_constructors() -> Result<()> {
let symbol = "BTC";
let datetime = vec![
FixedOffset::east_opt(0).unwrap().timestamp_opt(1640995200, 0).unwrap(),
FixedOffset::east_opt(0).unwrap().timestamp_opt(1640998800, 0).unwrap(),
];
let open = vec![100.0, 101.0];
let high = vec![105.0, 106.0];
let low = vec![95.0, 96.0];
let close = vec![102.0, 103.0];
let volume = vec![1000.0, 1100.0];
let funding_rates = vec![0.0001, -0.0001];
let data1 = HyperliquidData::with_ohlc_data(
symbol.to_string(),
datetime.clone(),
open.clone(),
high.clone(),
low.clone(),
close.clone(),
volume.clone(),
)?;
assert_eq!(data1.symbol, symbol);
assert_eq!(data1.datetime, datetime);
assert_eq!(data1.open, open);
assert_eq!(data1.high, high);
assert_eq!(data1.low, low);
assert_eq!(data1.close, close);
assert_eq!(data1.volume, volume);
assert!(data1.funding_rates.iter().all(|&r| r.is_nan()));
let data2 = HyperliquidData::with_ohlc_and_funding_data(
symbol.to_string(),
datetime.clone(),
open.clone(),
high.clone(),
low.clone(),
close.clone(),
volume.clone(),
funding_rates.clone(),
)?;
assert_eq!(data2.symbol, symbol);
assert_eq!(data2.datetime, datetime);
assert_eq!(data2.open, open);
assert_eq!(data2.high, high);
assert_eq!(data2.low, low);
assert_eq!(data2.close, close);
assert_eq!(data2.volume, volume);
assert_eq!(data2.funding_rates, funding_rates);
let mut short_open = open.clone();
short_open.pop();
let result = HyperliquidData::with_ohlc_data(
symbol.to_string(),
datetime.clone(),
short_open,
high.clone(),
low.clone(),
close.clone(),
volume.clone(),
);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_funding_statistics_calculation() {
let mut data = generate_mock_data("BTC", 24, false, false);
data.funding_rates = vec![
0.0001, 0.0002, 0.0003, -0.0001, -0.0002,
0.0001, 0.0002, 0.0003, -0.0001, -0.0002,
0.0001, 0.0002, 0.0003, -0.0001, -0.0002,
0.0001, 0.0002, 0.0003, -0.0001, -0.0002,
0.0001, 0.0002, 0.0003, -0.0001,
];
let stats = data.calculate_funding_statistics();
assert_eq!(stats.total_periods, 24);
assert_eq!(stats.positive_periods, 16);
assert_eq!(stats.negative_periods, 8);
let expected_avg = data.funding_rates.iter().sum::<f64>() / 24.0;
assert!((stats.average_rate - expected_avg).abs() < 0.0000001);
assert_eq!(stats.min_rate, -0.0002);
assert_eq!(stats.max_rate, 0.0003);
let empty_data = HyperliquidData {
symbol: "BTC".to_string(),
datetime: Vec::new(),
open: Vec::new(),
high: Vec::new(),
low: Vec::new(),
close: Vec::new(),
volume: Vec::new(),
funding_rates: Vec::new(),
};
let empty_stats = empty_data.calculate_funding_statistics();
assert_eq!(empty_stats.total_periods, 0);
assert_eq!(empty_stats.positive_periods, 0);
assert_eq!(empty_stats.negative_periods, 0);
assert_eq!(empty_stats.average_rate, 0.0);
assert_eq!(empty_stats.volatility, 0.0);
}
#[test]
fn test_get_funding_rate_at() {
let mut data = generate_mock_data("BTC", 24, false, false);
for i in 0..24 {
if i % 8 == 0 {
data.funding_rates[i] = 0.0001 * (i as f64 / 8.0 + 1.0);
} else {
data.funding_rates[i] = f64::NAN;
}
}
for i in 0..3 {
let timestamp = data.datetime[i * 8];
let rate = data.get_funding_rate_at(timestamp);
assert!(rate.is_some());
assert_eq!(rate.unwrap(), 0.0001 * (i as f64 + 1.0));
}
for i in 1..8 {
let timestamp = data.datetime[i];
let rate = data.get_funding_rate_at(timestamp);
assert!(rate.is_none());
}
let unknown_timestamp = FixedOffset::east_opt(0).unwrap()
.timestamp_opt(1650000000, 0).unwrap();
let rate = data.get_funding_rate_at(unknown_timestamp);
assert!(rate.is_none());
}
#[test]
fn test_popular_trading_pairs() {
let pairs = HyperliquidData::popular_trading_pairs();
assert!(!pairs.is_empty());
assert!(pairs.contains(&"BTC"));
assert!(pairs.contains(&"ETH"));
assert!(HyperliquidData::is_popular_pair("BTC"));
assert!(HyperliquidData::is_popular_pair("ETH"));
assert!(!HyperliquidData::is_popular_pair("UNKNOWN"));
}
#[test]
fn test_supported_intervals() {
let intervals = crate::data::HyperliquidDataFetcher::supported_intervals();
assert!(!intervals.is_empty());
assert!(intervals.contains(&"1m"));
assert!(intervals.contains(&"1h"));
assert!(intervals.contains(&"1d"));
assert!(crate::data::HyperliquidDataFetcher::is_interval_supported("1m"));
assert!(crate::data::HyperliquidDataFetcher::is_interval_supported("1h"));
assert!(crate::data::HyperliquidDataFetcher::is_interval_supported("1d"));
assert!(!crate::data::HyperliquidDataFetcher::is_interval_supported("2h"));
}
#[test]
fn test_max_time_range_for_interval() {
let range_1m = crate::data::HyperliquidDataFetcher::max_time_range_for_interval("1m");
let range_1h = crate::data::HyperliquidDataFetcher::max_time_range_for_interval("1h");
let range_1d = crate::data::HyperliquidDataFetcher::max_time_range_for_interval("1d");
assert!(range_1m < range_1h);
assert!(range_1h < range_1d);
let range_unknown = crate::data::HyperliquidDataFetcher::max_time_range_for_interval("unknown");
assert_eq!(range_unknown, 365 * 24 * 3600); }