#![cfg(feature = "providers")]
use rangebar::providers::exness::{
ExnessFetcher, ExnessInstrument, ExnessRangeBarBuilder, ExnessTick, ValidationStrictness,
};
use std::collections::HashMap;
#[tokio::test]
#[ignore] async fn test_all_10_instruments_consistency() {
println!("\n=== Multi-Instrument Consistency Test ===\n");
let mut results: HashMap<&str, InstrumentResult> = HashMap::new();
for instrument in ExnessInstrument::all() {
println!("\n--- Testing {} ---", instrument);
let fetcher = ExnessFetcher::for_instrument(*instrument);
let ticks = match fetcher.fetch_month(2024, 6).await {
Ok(t) => t,
Err(e) => {
panic!(
"\n=== FETCH FAILED ===\n\
Instrument: {}\n\
Error: {:?}\n",
instrument, e
);
}
};
println!(" Fetched {} ticks", ticks.len());
validate_temporal_integrity(&ticks, instrument.symbol());
let (price_min, price_max) = instrument.price_range();
validate_price_range(&ticks, price_min, price_max, instrument.symbol());
let zero_pct = calculate_zero_spread_pct(&ticks, instrument.spread_tolerance());
println!(" Zero spread: {:.1}%", zero_pct);
assert!(
zero_pct >= 80.0,
"{}: Only {:.1}% zero spread (expected >80%)",
instrument,
zero_pct
);
let mut bar_counts = Vec::new();
for threshold in [2, 5, 10] {
let mut builder = ExnessRangeBarBuilder::for_instrument(
*instrument,
threshold,
ValidationStrictness::Strict,
)
.expect("Builder failed");
let count = ticks
.iter()
.filter_map(|t| builder.process_tick(t).ok().flatten())
.count();
bar_counts.push(count);
}
assert!(
bar_counts[0] >= bar_counts[1],
"{}: 0.2bps ({} bars) should produce >= bars than 0.5bps ({} bars)",
instrument,
bar_counts[0],
bar_counts[1]
);
assert!(
bar_counts[1] >= bar_counts[2],
"{}: 0.5bps ({} bars) should produce >= bars than 1.0bps ({} bars)",
instrument,
bar_counts[1],
bar_counts[2]
);
results.insert(
instrument.symbol(),
InstrumentResult {
ticks: ticks.len(),
zero_spread_pct: zero_pct,
bar_counts,
},
);
println!(
" Bars: {} / {} / {} (0.2/0.5/1.0 bps)",
results[instrument.symbol()].bar_counts[0],
results[instrument.symbol()].bar_counts[1],
results[instrument.symbol()].bar_counts[2]
);
println!(" ✅ {} validated", instrument);
}
println!("\n=== Multi-Instrument Summary ===\n");
println!(
"{:<10} {:>12} {:>10} {:>10} {:>10} {:>10}",
"Symbol", "Ticks", "Zero%", "0.2bps", "0.5bps", "1.0bps"
);
println!("{}", "-".repeat(65));
for instrument in ExnessInstrument::all() {
let r = &results[instrument.symbol()];
println!(
"{:<10} {:>12} {:>9.1}% {:>10} {:>10} {:>10}",
instrument.symbol(),
r.ticks,
r.zero_spread_pct,
r.bar_counts[0],
r.bar_counts[1],
r.bar_counts[2]
);
}
println!("\n✅ All 10 instruments validated successfully");
}
#[tokio::test]
#[ignore]
async fn test_jpy_pairs_price_scale() {
println!("\n=== JPY Pairs Price Scale Test ===\n");
let jpy_pairs = [
ExnessInstrument::USDJPY,
ExnessInstrument::EURJPY,
ExnessInstrument::GBPJPY,
];
for instrument in jpy_pairs {
assert!(
instrument.is_jpy_pair(),
"{} should be identified as JPY pair",
instrument
);
let fetcher = ExnessFetcher::for_instrument(instrument);
let ticks = fetcher
.fetch_month(2024, 6)
.await
.expect(&format!("{} fetch failed", instrument));
let (min, max) = instrument.price_range();
for tick in &ticks {
assert!(
tick.bid >= min && tick.bid <= max,
"{}: price {} outside expected range [{}, {}]",
instrument,
tick.bid,
min,
max
);
}
println!(" ✅ {} prices in range [{}, {}]", instrument, min, max);
}
}
#[tokio::test]
#[ignore]
async fn test_cross_pairs() {
println!("\n=== Cross Pairs Test ===\n");
let cross_pairs = [ExnessInstrument::EURGBP];
for instrument in cross_pairs {
let fetcher = ExnessFetcher::for_instrument(instrument);
let ticks = fetcher
.fetch_month(2024, 6)
.await
.expect(&format!("{} fetch failed", instrument));
validate_temporal_integrity(&ticks, instrument.symbol());
let (min, max) = instrument.price_range();
validate_price_range(&ticks, min, max, instrument.symbol());
println!(" ✅ {} validated ({} ticks)", instrument, ticks.len());
}
}
struct InstrumentResult {
ticks: usize,
zero_spread_pct: f64,
bar_counts: Vec<usize>,
}
fn validate_temporal_integrity(ticks: &[ExnessTick], symbol: &str) {
for i in 1..ticks.len() {
assert!(
ticks[i].timestamp_ms >= ticks[i - 1].timestamp_ms,
"{}: Temporal violation at tick {}: {} < {}",
symbol,
i,
ticks[i].timestamp_ms,
ticks[i - 1].timestamp_ms
);
}
}
fn validate_price_range(ticks: &[ExnessTick], min: f64, max: f64, symbol: &str) {
for tick in ticks {
assert!(
tick.bid >= min && tick.bid <= max,
"{}: price {} outside valid range [{}, {}]",
symbol,
tick.bid,
min,
max
);
}
}
fn calculate_zero_spread_pct(ticks: &[ExnessTick], spread_tolerance: f64) -> f64 {
let zero_spreads = ticks
.iter()
.filter(|t| (t.ask - t.bid).abs() < spread_tolerance)
.count();
(zero_spreads as f64 / ticks.len() as f64) * 100.0
}