use rangebar_core::{FixedPoint, RangeBar, RangeBarProcessor};
fn validate_breach_consistency_invariant(
bars: &[RangeBar],
threshold_decimal_bps: u32,
) -> Result<(), String> {
const BASIS_POINTS_SCALE: i64 = 100_000;
for (i, bar) in bars.iter().enumerate() {
let open_val = bar.open.0;
let threshold_delta = (open_val * threshold_decimal_bps as i64) / BASIS_POINTS_SCALE;
let upper_threshold = open_val + threshold_delta;
let lower_threshold = open_val - threshold_delta;
let high_val = bar.high.0;
let low_val = bar.low.0;
let close_val = bar.close.0;
let high_breached = high_val >= upper_threshold;
let low_breached = low_val <= lower_threshold;
if high_breached && close_val != high_val {
return Err(format!(
"Breach Consistency Invariant VIOLATION at bar {}: \
High breached (high={} >= upper_threshold={}), \
but close={} != high. \
Bar: open={}, high={}, low={}, close={}, threshold={}bps ({}×0.1bps)",
i,
FixedPoint(high_val),
FixedPoint(upper_threshold),
FixedPoint(close_val),
bar.open,
bar.high,
bar.low,
bar.close,
threshold_decimal_bps as f64 / 10.0,
threshold_decimal_bps
));
}
if low_breached && close_val != low_val {
return Err(format!(
"Breach Consistency Invariant VIOLATION at bar {}: \
Low breached (low={} <= lower_threshold={}), \
but close={} != low. \
Bar: open={}, high={}, low={}, close={}, threshold={}bps ({}×0.1bps)",
i,
FixedPoint(low_val),
FixedPoint(lower_threshold),
FixedPoint(close_val),
bar.open,
bar.high,
bar.low,
bar.close,
threshold_decimal_bps as f64 / 10.0,
threshold_decimal_bps
));
}
if high_val < open_val {
return Err(format!(
"Sanity check FAILED at bar {}: high < open (high={}, open={})",
i,
FixedPoint(high_val),
FixedPoint(open_val)
));
}
if low_val > open_val {
return Err(format!(
"Sanity check FAILED at bar {}: low > open (low={}, open={})",
i,
FixedPoint(low_val),
FixedPoint(open_val)
));
}
if high_val < close_val {
return Err(format!(
"Sanity check FAILED at bar {}: high < close (high={}, close={})",
i,
FixedPoint(high_val),
FixedPoint(close_val)
));
}
if low_val > close_val {
return Err(format!(
"Sanity check FAILED at bar {}: low > close (low={}, close={})",
i,
FixedPoint(low_val),
FixedPoint(close_val)
));
}
}
Ok(())
}
#[cfg(test)]
mod invariant_tests {
use super::*;
use rangebar_core::test_utils::{AggTradeBuilder, generators, scenarios};
#[test]
fn test_invariant_exact_breach_upward() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = scenarios::exact_breach_upward(threshold_decimal_bps);
let bars = processor.process_agg_trade_records(&trades).unwrap();
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Exact upward breach should satisfy invariant");
}
#[test]
fn test_invariant_exact_breach_downward() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = scenarios::exact_breach_downward(threshold_decimal_bps);
let bars = processor.process_agg_trade_records(&trades).unwrap();
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Exact downward breach should satisfy invariant");
}
#[test]
fn test_invariant_large_gap() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = scenarios::large_gap_sequence();
let bars = processor.process_agg_trade_records(&trades).unwrap();
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Large gap scenario should satisfy invariant");
}
#[test]
fn test_invariant_single_breach() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = scenarios::single_breach_sequence(threshold_decimal_bps);
let bars = processor.process_agg_trade_records(&trades).unwrap();
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Single breach scenario should satisfy invariant");
}
#[test]
fn test_invariant_massive_realistic_dataset() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = generators::create_massive_realistic_dataset(1_000_000);
let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Processed {} trades into {} bars (threshold={}bps)",
trades.len(),
bars.len(),
threshold_decimal_bps as f64 / 10.0
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Massive realistic dataset should satisfy invariant");
}
#[test]
fn test_invariant_multiple_thresholds() {
let thresholds = vec![2, 10, 100, 250, 1000];
let trades = generators::create_massive_realistic_dataset(100_000);
for threshold_decimal_bps in thresholds {
let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let bars = processor.process_agg_trade_records(&trades).unwrap();
validate_breach_consistency_invariant(&bars, threshold_decimal_bps).unwrap_or_else(
|err| {
panic!(
"Invariant violation at threshold {}bps: {}",
threshold_decimal_bps as f64 / 10.0,
err
)
},
);
println!(
"✓ Threshold {}bps: {} bars generated, all satisfy invariant",
threshold_decimal_bps as f64 / 10.0,
bars.len()
);
}
}
#[test]
fn test_invariant_multi_day_boundaries() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = generators::create_multi_day_boundary_dataset(7); let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Multi-day dataset: {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Multi-day boundary dataset should satisfy invariant");
}
#[test]
fn test_invariant_volatile_conditions() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let base_time = 1609459200000; let trades = generators::create_volatile_day_data(base_time, 10_000);
let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Volatile day: {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Volatile market conditions should satisfy invariant");
}
#[test]
fn test_invariant_stable_conditions() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let base_time = 1609459200000; let trades = generators::create_stable_day_data(base_time, 10_000);
let bars = processor.process_agg_trade_records(&trades).unwrap();
println!("Stable day: {} trades → {} bars", trades.len(), bars.len());
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Stable market conditions should satisfy invariant");
}
#[test]
fn test_invariant_trending_conditions() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let base_time = 1609459200000; let trades = generators::create_trending_day_data(base_time, 10_000);
let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Trending day: {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Trending market conditions should satisfy invariant");
}
#[test]
fn test_invariant_high_frequency() {
let threshold_decimal_bps = 100; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = generators::create_high_frequency_data(10); let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"High-frequency (10ms): {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("High-frequency data should satisfy invariant");
}
#[test]
fn test_invariant_low_frequency() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = generators::create_low_frequency_data(60_000); let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Low-frequency (1min): {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Low-frequency data should satisfy invariant");
}
#[test]
fn test_invariant_mixed_frequency() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = generators::create_mixed_frequency_data();
let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Mixed-frequency: {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Mixed-frequency data should satisfy invariant");
}
#[test]
fn test_invariant_rapid_threshold_hits() {
let threshold_decimal_bps = 50; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = generators::create_rapid_threshold_hit_data();
let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Rapid threshold hits: {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Rapid threshold hits should satisfy invariant");
}
#[test]
fn test_invariant_precision_limits() {
let threshold_decimal_bps = 1; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = generators::create_precision_limit_data();
let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Precision limits (0.1bps): {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Precision limit data should satisfy invariant");
}
#[test]
fn test_invariant_volume_extremes() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = generators::create_volume_extreme_data();
let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Volume extremes: {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Volume extreme data should satisfy invariant");
}
#[test]
fn test_invariant_timestamp_edges() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = generators::create_timestamp_edge_data();
let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Timestamp edges: {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Timestamp edge data should satisfy invariant");
}
#[test]
fn test_invariant_floating_point_stress() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = generators::create_floating_point_stress_data();
let bars = processor.process_agg_trade_records(&trades).unwrap();
println!(
"Floating-point stress: {} trades → {} bars",
trades.len(),
bars.len()
);
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Floating-point stress data should satisfy invariant");
}
#[test]
fn test_invariant_custom_oscillation() {
let threshold_decimal_bps = 250; let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let trades = AggTradeBuilder::new()
.with_base_price(50000.0)
.with_base_timestamp(1609459200000)
.add_trade(1, 1.0, 1000) .add_trade(2, 1.002, 2000) .add_trade(3, 0.998, 3000) .add_trade(4, 1.002, 4000) .add_trade(5, 0.998, 5000) .add_trade(6, 1.0025, 6000) .build();
let bars = processor.process_agg_trade_records(&trades).unwrap();
assert_eq!(bars.len(), 1, "Should create exactly 1 bar on exact breach");
validate_breach_consistency_invariant(&bars, threshold_decimal_bps)
.expect("Custom oscillation should satisfy invariant");
}
#[test]
fn test_invariant_boundary_thresholds() {
let boundary_thresholds = vec![
1, 100_000, ];
let trades = generators::create_massive_realistic_dataset(10_000);
for threshold_decimal_bps in boundary_thresholds {
let mut processor = RangeBarProcessor::new(threshold_decimal_bps).unwrap();
let bars = processor.process_agg_trade_records(&trades).unwrap();
validate_breach_consistency_invariant(&bars, threshold_decimal_bps).unwrap_or_else(
|err| {
panic!(
"Invariant violation at boundary threshold {}bps: {}",
threshold_decimal_bps as f64 / 10.0,
err
)
},
);
println!(
"✓ Boundary threshold {}bps: {} bars, all valid",
threshold_decimal_bps as f64 / 10.0,
bars.len()
);
}
}
}