use crate::prelude::*;
use chrono::{DateTime, FixedOffset};
use std::collections::HashMap;
use serde_json;
#[tokio::test]
async fn test_hyperliquid_data_structure_compatibility() {
let data = create_regression_test_data();
assert!(!data.ticker.is_empty());
assert!(data.ticker.len() <= 10);
assert!(!data.datetime.is_empty());
assert!(!data.open.is_empty());
assert!(!data.high.is_empty());
assert!(!data.low.is_empty());
assert!(!data.close.is_empty());
assert!(!data.volume.is_empty());
assert!(!data.funding_rates.is_empty());
assert!(!data.funding_timestamps.is_empty());
let len = data.datetime.len();
assert_eq!(data.open.len(), len, "Open prices length mismatch");
assert_eq!(data.high.len(), len, "High prices length mismatch");
assert_eq!(data.low.len(), len, "Low prices length mismatch");
assert_eq!(data.close.len(), len, "Close prices length mismatch");
assert_eq!(data.volume.len(), len, "Volume length mismatch");
for i in 0..len {
assert!(data.high[i] >= data.low[i], "High < Low at index {}", i);
assert!(data.high[i] >= data.open[i], "High < Open at index {}", i);
assert!(data.high[i] >= data.close[i], "High < Close at index {}", i);
assert!(data.low[i] <= data.open[i], "Low > Open at index {}", i);
assert!(data.low[i] <= data.close[i], "Low > Close at index {}", i);
assert!(data.volume[i] >= 0.0, "Negative volume at index {}", i);
}
}
#[tokio::test]
async fn test_rs_backtester_integration_compatibility() {
let hyperliquid_data = create_regression_test_data();
let rs_data = hyperliquid_data.to_rs_backtester_data();
assert_eq!(rs_data.datetime.len(), hyperliquid_data.datetime.len());
assert_eq!(rs_data.open.len(), hyperliquid_data.open.len());
assert_eq!(rs_data.high.len(), hyperliquid_data.high.len());
assert_eq!(rs_data.low.len(), hyperliquid_data.low.len());
assert_eq!(rs_data.close.len(), hyperliquid_data.close.len());
assert_eq!(rs_data.volume.len(), hyperliquid_data.volume.len());
use rs_backtester::prelude::*;
let strategies = vec![
strategies::sma_cross(10, 20),
strategies::rsi_strategy(14, 30.0, 70.0),
strategies::bollinger_bands(20, 2.0),
];
for strategy in strategies {
let backtest = Backtest::new(
rs_data.clone(),
strategy,
10000.0,
Commission::default(),
);
assert_eq!(backtest.initial_capital, 10000.0);
assert!(backtest.data.close.len() > 0);
}
}
#[tokio::test]
async fn test_hyperliquid_backtest_compatibility() {
let data = create_regression_test_data();
let strategy = enhanced_sma_cross(10, 20, 0.3);
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let enhanced_report = backtest.enhanced_report();
assert!(enhanced_report.total_return.is_finite());
assert!(enhanced_report.max_drawdown.is_finite());
assert!(enhanced_report.sharpe_ratio.is_finite() || enhanced_report.sharpe_ratio.is_nan());
let funding_report = backtest.funding_report();
assert!(funding_report.total_funding_paid >= 0.0);
assert!(funding_report.total_funding_received >= 0.0);
assert!(!funding_report.funding_payments.is_empty());
let mut csv_buffer = Vec::new();
backtest.enhanced_csv_export(&mut csv_buffer).unwrap();
assert!(!csv_buffer.is_empty());
let csv_string = String::from_utf8(csv_buffer).unwrap();
assert!(csv_string.contains("timestamp"));
assert!(csv_string.contains("close"));
assert!(csv_string.contains("funding_rate"));
}
#[tokio::test]
async fn test_commission_structure_compatibility() {
let default_commission = HyperliquidCommission::default();
assert!(default_commission.maker_rate >= 0.0);
assert!(default_commission.taker_rate >= 0.0);
assert!(default_commission.taker_rate >= default_commission.maker_rate);
let custom_commission = HyperliquidCommission {
maker_rate: 0.0001,
taker_rate: 0.0003,
funding_enabled: true,
};
let data = create_regression_test_data();
let strategy = enhanced_sma_cross(10, 20, 0.0);
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
custom_commission,
);
backtest.calculate_with_funding();
let report = backtest.enhanced_report();
assert!(report.total_return.is_finite());
}
#[tokio::test]
async fn test_strategy_interface_compatibility() {
let data = create_regression_test_data();
let strategies = vec![
("Basic SMA", enhanced_sma_cross(10, 20, 0.0)),
("Funding Aware SMA", enhanced_sma_cross(15, 35, 0.3)),
("High Funding Weight", enhanced_sma_cross(5, 25, 0.8)),
("Funding Arbitrage", funding_arbitrage_strategy(0.001, Default::default())),
("Conservative Arbitrage", funding_arbitrage_strategy(0.0005, Default::default())),
];
for (name, strategy) in strategies {
let mut backtest = HyperliquidBacktest::new(
data.clone(),
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let report = backtest.enhanced_report();
assert!(report.total_return.is_finite(), "Strategy '{}' produced invalid return", name);
assert!(report.max_drawdown.is_finite(), "Strategy '{}' produced invalid drawdown", name);
}
}
#[tokio::test]
async fn test_error_handling_compatibility() {
let error_cases = vec![
HyperliquidBacktestError::DataConversion("Test conversion error".to_string()),
HyperliquidBacktestError::InvalidTimeRange { start: 100, end: 50 },
HyperliquidBacktestError::UnsupportedInterval("invalid".to_string()),
HyperliquidBacktestError::Backtesting("Test backtest error".to_string()),
];
for error in error_cases {
let error_string = error.to_string();
assert!(!error_string.is_empty());
assert!(error_string.len() > 10); }
}
#[tokio::test]
async fn test_funding_rate_compatibility() {
let data = create_regression_test_data();
for (i, timestamp) in data.datetime.iter().enumerate() {
let funding_rate = data.get_funding_rate_at(*timestamp);
if i < data.funding_timestamps.len() {
assert!(funding_rate.is_some(), "Missing funding rate for timestamp {}", i);
let rate = funding_rate.unwrap();
assert!(rate.is_finite(), "Invalid funding rate at timestamp {}", i);
assert!(rate.abs() < 1.0, "Unrealistic funding rate {} at timestamp {}", rate, i);
}
}
if data.funding_timestamps.len() > 1 {
let mid_time = data.datetime[data.datetime.len() / 2];
let funding_rate = data.get_funding_rate_at(mid_time);
assert!(funding_rate.is_some() || funding_rate.is_none());
}
}
#[tokio::test]
async fn test_serialization_compatibility() {
let data = create_regression_test_data();
let commission = HyperliquidCommission::default();
let commission_json = serde_json::to_string(&commission);
assert!(commission_json.is_ok() || commission_json.is_err());
let small_data = create_small_regression_data();
let large_data = create_large_regression_data();
assert!(small_data.datetime.len() < large_data.datetime.len());
assert_eq!(small_data.datetime.len(), small_data.close.len());
assert_eq!(large_data.datetime.len(), large_data.close.len());
}
#[tokio::test]
async fn test_performance_regression() {
use std::time::Instant;
let data = create_regression_test_data();
let start = Instant::now();
let _rs_data = data.to_rs_backtester_data();
let conversion_time = start.elapsed();
assert!(conversion_time.as_millis() < 1000, "Data conversion too slow: {:?}", conversion_time);
let start = Instant::now();
let strategy = enhanced_sma_cross(10, 20, 0.3);
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let backtest_time = start.elapsed();
assert!(backtest_time.as_secs() < 10, "Backtesting too slow: {:?}", backtest_time);
}
#[tokio::test]
async fn test_memory_usage_regression() {
use memory_stats::memory_stats;
let initial_memory = memory_stats().map(|stats| stats.physical_mem).unwrap_or(0);
for i in 0..5 {
let data = create_regression_test_data();
let strategy = enhanced_sma_cross(10 + i, 20 + i * 2, 0.1 + i as f64 * 0.1);
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let _report = backtest.enhanced_report();
drop(backtest);
}
if let Some(final_stats) = memory_stats() {
let final_memory = final_stats.physical_mem;
let growth = final_memory.saturating_sub(initial_memory);
let max_allowed_growth = initial_memory / 10;
assert!(growth < max_allowed_growth,
"Excessive memory growth: {} bytes (limit: {} bytes)", growth, max_allowed_growth);
}
}
#[tokio::test]
async fn test_api_version_compatibility() {
let data = create_regression_test_data();
assert!(!data.ticker.is_empty());
assert!(!data.datetime.is_empty());
let rs_data = data.to_rs_backtester_data();
assert!(!rs_data.close.is_empty());
if !data.funding_timestamps.is_empty() {
let funding_rate = data.get_funding_rate_at(data.datetime[0]);
assert!(funding_rate.is_some() || funding_rate.is_none());
}
let strategy = enhanced_sma_cross(10, 20, 0.3);
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let enhanced_report = backtest.enhanced_report();
assert!(enhanced_report.total_return.is_finite());
let funding_report = backtest.funding_report();
assert!(funding_report.total_funding_paid >= 0.0);
let mut csv_buffer = Vec::new();
let csv_result = backtest.enhanced_csv_export(&mut csv_buffer);
assert!(csv_result.is_ok());
}
#[tokio::test]
async fn test_backward_compatibility() {
let test_cases = vec![
("minimal_data", create_minimal_compatibility_data()),
("extended_data", create_extended_compatibility_data()),
("legacy_format", create_legacy_format_data()),
];
for (case_name, data) in test_cases {
info!("Testing backward compatibility case: {}", case_name);
let rs_data = data.to_rs_backtester_data();
assert_eq!(rs_data.datetime.len(), data.datetime.len(),
"Data length mismatch for {}", case_name);
let strategy = enhanced_sma_cross(5, 15, 0.2);
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let report = backtest.enhanced_report();
assert!(report.total_return.is_finite(),
"Invalid return for compatibility case: {}", case_name);
}
}
#[tokio::test]
async fn test_forward_compatibility() {
let mut data = create_regression_test_data();
let rs_data = data.to_rs_backtester_data();
assert_eq!(rs_data.close.len(), data.close.len());
let strategy = enhanced_sma_cross(10, 20, 0.3);
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let report = backtest.enhanced_report();
assert!(report.total_return.is_finite());
}
#[tokio::test]
async fn test_api_breaking_changes() {
let data = create_regression_test_data();
assert_eq!(data.ticker.len(), 3); assert!(!data.datetime.is_empty());
assert!(!data.close.is_empty());
let _rs_data: rs_backtester::prelude::Data = data.to_rs_backtester_data();
let timestamp = data.datetime[0];
let _funding_rate: Option<f64> = data.get_funding_rate_at(timestamp);
let strategy = enhanced_sma_cross(10, 20, 0.3);
let commission = HyperliquidCommission::default();
let _backtest = HyperliquidBacktest::new(data, strategy, 10000.0, commission);
}
#[tokio::test]
async fn test_data_structure_fields() {
let data = create_regression_test_data();
let _ticker: &String = &data.ticker;
let _datetime: &Vec<DateTime<FixedOffset>> = &data.datetime;
let _open: &Vec<f64> = &data.open;
let _high: &Vec<f64> = &data.high;
let _low: &Vec<f64> = &data.low;
let _close: &Vec<f64> = &data.close;
let _volume: &Vec<f64> = &data.volume;
let _funding_rates: &Vec<f64> = &data.funding_rates;
let _funding_timestamps: &Vec<DateTime<FixedOffset>> = &data.funding_timestamps;
assert_eq!(data.datetime.len(), data.open.len());
assert_eq!(data.datetime.len(), data.high.len());
assert_eq!(data.datetime.len(), data.low.len());
assert_eq!(data.datetime.len(), data.close.len());
assert_eq!(data.datetime.len(), data.volume.len());
assert_eq!(data.funding_rates.len(), data.funding_timestamps.len());
}
#[tokio::test]
async fn test_commission_structure_evolution() {
let commission_configs = vec![
("default", HyperliquidCommission::default()),
("zero_fees", HyperliquidCommission {
maker_rate: 0.0,
taker_rate: 0.0,
funding_enabled: true,
}),
("high_fees", HyperliquidCommission {
maker_rate: 0.001,
taker_rate: 0.002,
funding_enabled: true,
}),
("no_funding", HyperliquidCommission {
maker_rate: 0.0002,
taker_rate: 0.0005,
funding_enabled: false,
}),
];
let data = create_regression_test_data();
let strategy = enhanced_sma_cross(10, 20, 0.3);
for (config_name, commission) in commission_configs {
let mut backtest = HyperliquidBacktest::new(
data.clone(),
strategy.clone(),
10000.0,
commission,
);
backtest.calculate_with_funding();
let report = backtest.enhanced_report();
assert!(report.total_return.is_finite(),
"Invalid return for commission config: {}", config_name);
info!("Commission config '{}' processed successfully", config_name);
}
}
#[tokio::test]
async fn test_strategy_interface_evolution() {
let data = create_regression_test_data();
let strategy_configs = vec![
("minimal_sma", enhanced_sma_cross(2, 5, 0.0)),
("standard_sma", enhanced_sma_cross(10, 20, 0.3)),
("long_period_sma", enhanced_sma_cross(50, 200, 0.1)),
("high_funding_weight", enhanced_sma_cross(10, 20, 0.9)),
("conservative_arbitrage", funding_arbitrage_strategy(0.0001, Default::default())),
("aggressive_arbitrage", funding_arbitrage_strategy(0.01, Default::default())),
];
for (strategy_name, strategy) in strategy_configs {
let mut backtest = HyperliquidBacktest::new(
data.clone(),
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let report = backtest.enhanced_report();
assert!(report.total_return.is_finite(),
"Strategy '{}' produced invalid results", strategy_name);
info!("Strategy '{}' interface compatibility verified", strategy_name);
}
}
fn create_regression_test_data() -> HyperliquidData {
let size = 1000;
let datetime: Vec<DateTime<FixedOffset>> = (0..size)
.map(|i| {
DateTime::from_timestamp(1640995200 + i as i64 * 3600, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
})
.collect();
let prices: Vec<f64> = (0..size)
.map(|i| 47000.0 + (i as f64 * 0.1).sin() * 100.0)
.collect();
HyperliquidData {
ticker: "BTC".to_string(),
datetime: datetime.clone(),
open: prices.iter().enumerate().map(|(i, p)| {
if i == 0 { *p } else { prices[i-1] }
}).collect(),
high: prices.iter().map(|p| p + 25.0).collect(),
low: prices.iter().map(|p| p - 25.0).collect(),
close: prices,
volume: vec![100.0; size],
funding_rates: (0..size).map(|i| 0.0001 + (i as f64 * 0.01).sin() * 0.0001).collect(),
funding_timestamps: datetime,
}
}
fn create_small_regression_data() -> HyperliquidData {
let size = 10;
let datetime: Vec<DateTime<FixedOffset>> = (0..size)
.map(|i| {
DateTime::from_timestamp(1640995200 + i as i64 * 3600, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
})
.collect();
let prices = vec![47000.0, 47100.0, 47050.0, 47200.0, 47150.0, 47300.0, 47250.0, 47400.0, 47350.0, 47500.0];
HyperliquidData {
ticker: "BTC".to_string(),
datetime: datetime.clone(),
open: prices.iter().enumerate().map(|(i, p)| {
if i == 0 { *p } else { prices[i-1] }
}).collect(),
high: prices.iter().map(|p| p + 10.0).collect(),
low: prices.iter().map(|p| p - 10.0).collect(),
close: prices,
volume: vec![100.0; size],
funding_rates: vec![0.0001; size],
funding_timestamps: datetime,
}
}
fn create_large_regression_data() -> HyperliquidData {
let size = 10000;
let datetime: Vec<DateTime<FixedOffset>> = (0..size)
.map(|i| {
DateTime::from_timestamp(1640995200 + i as i64 * 3600, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
})
.collect();
let prices: Vec<f64> = (0..size)
.map(|i| 47000.0 + (i as f64 * 0.01).sin() * 500.0)
.collect();
HyperliquidData {
ticker: "BTC".to_string(),
datetime: datetime.clone(),
open: prices.iter().enumerate().map(|(i, p)| {
if i == 0 { *p } else { prices[i-1] }
}).collect(),
high: prices.iter().map(|p| p + 50.0).collect(),
low: prices.iter().map(|p| p - 50.0).collect(),
close: prices,
volume: vec![100.0; size],
funding_rates: (0..size).map(|i| 0.0001 + (i as f64 * 0.001).sin() * 0.0001).collect(),
funding_timestamps: datetime,
}
}
fn create_minimal_compatibility_data() -> HyperliquidData {
let size = 5;
let datetime: Vec<DateTime<FixedOffset>> = (0..size)
.map(|i| {
DateTime::from_timestamp(1640995200 + i as i64 * 3600, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
})
.collect();
HyperliquidData {
ticker: "BTC".to_string(),
datetime: datetime.clone(),
open: vec![47000.0, 47100.0, 47050.0, 47200.0, 47150.0],
high: vec![47050.0, 47150.0, 47100.0, 47250.0, 47200.0],
low: vec![46950.0, 47050.0, 47000.0, 47150.0, 47100.0],
close: vec![47020.0, 47080.0, 47180.0, 47170.0, 47190.0],
volume: vec![100.0; size],
funding_rates: vec![0.0001; size],
funding_timestamps: datetime,
}
}
fn create_extended_compatibility_data() -> HyperliquidData {
let size = 100;
let datetime: Vec<DateTime<FixedOffset>> = (0..size)
.map(|i| {
DateTime::from_timestamp(1640995200 + i as i64 * 3600, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
})
.collect();
let prices: Vec<f64> = (0..size)
.map(|i| 47000.0 + (i as f64 * 0.2).sin() * 200.0)
.collect();
HyperliquidData {
ticker: "ETH".to_string(),
datetime: datetime.clone(),
open: prices.iter().enumerate().map(|(i, p)| {
if i == 0 { *p } else { prices[i-1] }
}).collect(),
high: prices.iter().map(|p| p + 30.0).collect(),
low: prices.iter().map(|p| p - 30.0).collect(),
close: prices,
volume: (0..size).map(|i| 150.0 + (i as f64 * 0.1).cos() * 30.0).collect(),
funding_rates: (0..size).map(|i| 0.00015 + (i as f64 * 0.02).sin() * 0.00005).collect(),
funding_timestamps: datetime,
}
}
fn create_legacy_format_data() -> HyperliquidData {
let size = 50;
let datetime: Vec<DateTime<FixedOffset>> = (0..size)
.map(|i| {
DateTime::from_timestamp(1640995200 + i as i64 * 3600, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
})
.collect();
let base_price = 150.0; let prices: Vec<f64> = (0..size)
.map(|i| base_price + (i as f64).sin() * 5.0)
.collect();
HyperliquidData {
ticker: "SOL".to_string(),
datetime: datetime.clone(),
open: prices.iter().enumerate().map(|(i, p)| {
if i == 0 { *p } else { prices[i-1] }
}).collect(),
high: prices.iter().map(|p| p + 2.0).collect(),
low: prices.iter().map(|p| p - 2.0).collect(),
close: prices,
volume: vec![75.0; size], funding_rates: vec![0.0001; size], funding_timestamps: datetime,
}
} Test comprehensive API compatibility across versions
#[tokio::test]
async fn test_comprehensive_api_compatibility() {
let data = create_regression_test_data();
assert!(!data.ticker.is_empty());
assert!(!data.datetime.is_empty());
assert!(!data.open.is_empty());
assert!(!data.high.is_empty());
assert!(!data.low.is_empty());
assert!(!data.close.is_empty());
assert!(!data.volume.is_empty());
assert!(!data.funding_rates.is_empty());
assert!(!data.funding_timestamps.is_empty());
let rs_data = data.to_rs_backtester_data();
assert_eq!(rs_data.datetime.len(), data.datetime.len());
let funding_rate = data.get_funding_rate_at(data.datetime[0]);
assert!(funding_rate.is_some() || funding_rate.is_none());
let strategy = enhanced_sma_cross(10, 20, 0.3);
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let enhanced_report = backtest.enhanced_report();
let funding_report = backtest.funding_report();
assert!(enhanced_report.total_return.is_finite());
assert!(funding_report.total_funding_paid >= 0.0);
assert!(funding_report.total_funding_received >= 0.0);
let mut csv_buffer = Vec::new();
let csv_result = backtest.enhanced_csv_export(&mut csv_buffer);
assert!(csv_result.is_ok());
assert!(!csv_buffer.is_empty());
}
#[tokio::test]
async fn test_performance_regression_detection() {
use std::time::Instant;
let data = create_regression_test_data();
let start = Instant::now();
let _rs_data = data.to_rs_backtester_data();
let conversion_time = start.elapsed();
assert!(conversion_time.as_millis() < 100,
"Data conversion performance regression: {:?}", conversion_time);
let start = Instant::now();
let strategy = enhanced_sma_cross(10, 20, 0.3);
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let backtest_time = start.elapsed();
assert!(backtest_time.as_secs() < 5,
"Backtesting performance regression: {:?}", backtest_time);
}
#[tokio::test]
async fn test_memory_usage_regression_detection() {
use memory_stats::memory_stats;
let initial_memory = memory_stats().map(|stats| stats.physical_mem).unwrap_or(0);
for i in 0..5 {
let data = create_regression_test_data();
let strategy = enhanced_sma_cross(10 + i, 20 + i * 2, 0.1 + i as f64 * 0.1);
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
let _report = backtest.enhanced_report();
drop(backtest);
}
if let Some(final_stats) = memory_stats() {
let final_memory = final_stats.physical_mem;
let growth = final_memory.saturating_sub(initial_memory);
let max_allowed_growth = initial_memory / 20;
assert!(growth < max_allowed_growth,
"Memory usage regression detected: {} bytes growth", growth);
}
}
#[tokio::test]
async fn test_edge_case_regression_prevention() {
let edge_cases = vec![
("single_point", create_single_point_data()),
("identical_prices", create_identical_price_data()),
("extreme_values", create_extreme_value_data()),
];
for (case_name, data_result) in edge_cases {
match data_result {
Ok(data) => {
let conversion_result = std::panic::catch_unwind(|| {
data.to_rs_backtester_data()
});
match conversion_result {
Ok(rs_data) => {
assert!(!rs_data.close.is_empty(), "Edge case {} produced empty data", case_name);
let strategy = enhanced_sma_cross(2, 5, 0.1);
let backtest_result = std::panic::catch_unwind(|| {
let mut backtest = HyperliquidBacktest::new(
data,
strategy,
10000.0,
HyperliquidCommission::default(),
);
backtest.calculate_with_funding();
backtest.enhanced_report()
});
match backtest_result {
Ok(report) => {
assert!(report.total_return.is_finite() || report.total_return.is_nan(),
"Edge case {} produced invalid report", case_name);
}
Err(_) => {
info!("Edge case {} failed backtesting as expected", case_name);
}
}
}
Err(_) => {
info!("Edge case {} failed conversion as expected", case_name);
}
}
}
Err(error) => {
info!("Edge case {} failed data creation: {}", case_name, error);
}
}
}
}
fn create_single_point_data() -> Result<HyperliquidData, &'static str> {
let datetime = vec![
DateTime::from_timestamp(1640995200, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
];
Ok(HyperliquidData {
ticker: "BTC".to_string(),
datetime: datetime.clone(),
open: vec![47000.0],
high: vec![47010.0],
low: vec![46990.0],
close: vec![47005.0],
volume: vec![100.0],
funding_rates: vec![0.0001],
funding_timestamps: datetime,
})
}
fn create_identical_price_data() -> Result<HyperliquidData, &'static str> {
let size = 100;
let datetime: Vec<DateTime<FixedOffset>> = (0..size)
.map(|i| {
DateTime::from_timestamp(1640995200 + i as i64 * 3600, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
})
.collect();
let price = 47000.0;
Ok(HyperliquidData {
ticker: "BTC".to_string(),
datetime: datetime.clone(),
open: vec![price; size],
high: vec![price; size],
low: vec![price; size],
close: vec![price; size],
volume: vec![100.0; size],
funding_rates: vec![0.0001; size],
funding_timestamps: datetime,
})
}
fn create_extreme_value_data() -> Result<HyperliquidData, &'static str> {
let size = 50;
let datetime: Vec<DateTime<FixedOffset>> = (0..size)
.map(|i| {
DateTime::from_timestamp(1640995200 + i as i64 * 3600, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
})
.collect();
let prices: Vec<f64> = (0..size)
.map(|i| {
if i % 10 == 0 { 1000000.0 } else { 0.01 } })
.collect();
Ok(HyperliquidData {
ticker: "BTC".to_string(),
datetime: datetime.clone(),
open: prices.clone(),
high: prices.iter().map(|p| p * 1.1).collect(),
low: prices.iter().map(|p| p * 0.9).collect(),
close: prices,
volume: vec![100.0; size],
funding_rates: (0..size).map(|i| {
if i % 5 == 0 { 0.1 } else { -0.1 } }).collect(),
funding_timestamps: datetime,
})
}