#[cfg(test)]
#[macro_export]
macro_rules! test_pattern_parity {
($name:ident, $struct_name:ident, $talib_func:path) => {
$crate::test_pattern_parity!($name, $struct_name, $talib_func, |_, _, _, _| {});
};
($name:ident, $struct_name:ident, $talib_func:path, $fixture:expr) => {
$crate::test_pattern_parity!(
@impl $name, $struct_name, $talib_func, $fixture,
None, 0, 0, oracle_parity_is_enforced
);
};
(
$name:ident, $struct_name:ident, $talib_func:path, $fixture:expr,
oracle_exempt = $reason:expr,
max_mismatches = $max:expr,
reference_mismatches = $reference:expr
) => {
$crate::test_pattern_parity!(
@impl $name, $struct_name, $talib_func, $fixture,
Some($reason), $max, $reference, oracle_parity_is_exempted
);
};
(
@impl $name:ident, $struct_name:ident, $talib_func:path, $fixture:expr,
$exemption:expr, $max:expr, $reference:expr, $notice:ident
) => {
#[cfg(test)]
mod $name {
use super::*;
use proptest::prelude::*;
const ORACLE_EXEMPTION: Option<&str> = $exemption;
const MAX_ORACLE_MISMATCHES: usize = $max;
const REFERENCE_ORACLE_MISMATCHES: usize = $reference;
fn oracle_mismatches(o: &[f64], h: &[f64], l: &[f64], c: &[f64]) -> Vec<usize> {
let mut ind = $struct_name::new();
let streaming: Vec<f64> = (0..o.len())
.map(|i| ind.next((o[i], h[i], l[i], c[i])))
.collect();
let batch = $talib_func(o, h, l, c).expect("talib oracle failed");
streaming
.iter()
.zip(batch.iter())
.enumerate()
.filter(|(_, (s, b))| **s as i32 != **b)
.map(|(i, _)| i)
.collect()
}
fn arb_random_walk(len_range: std::ops::Range<usize>) -> impl Strategy<Value = (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>)> {
(
10.0f64..100.0f64,
prop::collection::vec(
(-2.0f64..2.0f64, 0.0f64..5.0f64),
len_range
)
).prop_map(|(start_price, steps)| {
let mut o = Vec::with_capacity(steps.len());
let mut h = Vec::with_capacity(steps.len());
let mut l = Vec::with_capacity(steps.len());
let mut c = Vec::with_capacity(steps.len());
let mut current_price = start_price;
for (drift, spread) in steps {
let open = current_price;
let close = open + drift;
let high = open.max(close) + spread;
let low = open.min(close) - spread;
o.push(open);
h.push(high);
l.push(low);
c.push(close);
current_price = close; }
(o, h, l, c)
})
}
proptest! {
#[test]
fn test_parity_long(
(mut o, mut h, mut l, mut c) in arb_random_walk(1..200)
) {
let f: fn(&mut Vec<f64>, &mut Vec<f64>, &mut Vec<f64>, &mut Vec<f64>) = $fixture;
f(&mut o, &mut h, &mut l, &mut c);
run_parity(&o, &h, &l, &c);
}
#[test]
fn test_parity_short(
(mut o, mut h, mut l, mut c) in arb_random_walk(1..30)
) {
let f: fn(&mut Vec<f64>, &mut Vec<f64>, &mut Vec<f64>, &mut Vec<f64>) = $fixture;
f(&mut o, &mut h, &mut l, &mut c);
run_parity(&o, &h, &l, &c);
}
}
#[test]
fn $notice() {
match ORACLE_EXEMPTION {
Some(reason) => {
assert!(
!reason.trim().is_empty(),
"{} declares an oracle exemption but gives no reason",
stringify!($struct_name)
);
assert!(
MAX_ORACLE_MISMATCHES > 0,
"{} declares an oracle exemption with max_mismatches = 0; \
use the non-exempt form instead",
stringify!($struct_name)
);
assert!(
REFERENCE_ORACLE_MISMATCHES <= MAX_ORACLE_MISMATCHES,
"{}: reference_mismatches ({}) exceeds max_mismatches ({})",
stringify!($struct_name),
REFERENCE_ORACLE_MISMATCHES,
MAX_ORACLE_MISMATCHES
);
println!(
"ORACLE PARITY EXEMPT: {} — exactly {} diverging bars on the \
deterministic 200k reference walk, at most {} per random run; \
batch-vs-streaming parity is still exact. Reason: {}",
stringify!($struct_name),
REFERENCE_ORACLE_MISMATCHES,
MAX_ORACLE_MISMATCHES,
reason
);
}
None => {
println!(
"ORACLE PARITY ENFORCED: {} — exact match against the oracle required.",
stringify!($struct_name)
);
}
}
}
fn run_parity(o: &[f64], h: &[f64], l: &[f64], c: &[f64]) -> bool {
let len = o.len();
if len == 0 { return false; }
let mut ind = $struct_name::new();
let streaming: Vec<f64> = (0..len)
.map(|i| ind.next((o[i], h[i], l[i], c[i])))
.collect();
let batch = $talib_func(o, h, l, c).expect("talib oracle failed");
if let Some(reason) = ORACLE_EXEMPTION {
let mismatches: Vec<usize> = streaming
.iter()
.zip(batch.iter())
.enumerate()
.filter(|(_, (s, b))| **s as i32 != **b)
.map(|(i, _)| i)
.collect();
if !mismatches.is_empty() {
println!(
"[oracle-exempt] {}: {} of {} bars diverge from the oracle \
(allowed <= {}) at {:?}. Reason: {}",
stringify!($struct_name),
mismatches.len(),
len,
MAX_ORACLE_MISMATCHES,
&mismatches[..mismatches.len().min(16)],
reason
);
}
assert!(
mismatches.len() <= MAX_ORACLE_MISMATCHES,
"{}: oracle divergence exceeded its documented bound — {} of {} bars \
differ, at most {} allowed (first offenders: {:?}). Either the native \
implementation regressed or the exemption is no longer accurate. \
Exemption reason: {}",
stringify!($struct_name),
mismatches.len(),
len,
MAX_ORACLE_MISMATCHES,
&mismatches[..mismatches.len().min(16)],
reason
);
} else {
for (i, (s, b)) in streaming.iter().zip(batch.iter()).enumerate() {
assert_eq!(*s as i32, *b, "Mismatch at index {}: streaming {}, batch {}", i, s, b);
}
}
batch.iter().any(|b| *b != 0)
}
#[test]
fn test_coverage_and_bounded_memory() {
let mut o = Vec::with_capacity(200_000);
let mut h = Vec::with_capacity(200_000);
let mut l = Vec::with_capacity(200_000);
let mut c = Vec::with_capacity(200_000);
let mut rng = 1337_u32;
let mut current_price = 50.0;
for _ in 0..200_000 {
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
let drift = ((rng >> 16) as f64 / 65536.0) * 4.0 - 2.0;
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
let spread = ((rng >> 16) as f64 / 65536.0) * 5.0;
let open = current_price;
let close = open + drift;
let high = open.max(close) + spread;
let low = open.min(close) - spread;
o.push(open);
h.push(high);
l.push(low);
c.push(close);
current_price = close;
}
let f: fn(&mut Vec<f64>, &mut Vec<f64>, &mut Vec<f64>, &mut Vec<f64>) = $fixture;
f(&mut o, &mut h, &mut l, &mut c);
let found_signal_in_base = run_parity(&o, &h, &l, &c);
if let Some(reason) = ORACLE_EXEMPTION {
let mismatches = oracle_mismatches(&o, &h, &l, &c);
assert_eq!(
mismatches.len(),
REFERENCE_ORACLE_MISMATCHES,
"{}: divergence from the oracle on the deterministic 200k reference walk \
changed from the documented {} bars to {} (at {:?}). This walk is seeded, \
so the count only moves if the implementation, the fixture, or the oracle \
changed. Re-derive the evidence before updating the number. \
Exemption reason: {}",
stringify!($struct_name),
REFERENCE_ORACLE_MISMATCHES,
mismatches.len(),
&mismatches[..mismatches.len().min(16)],
reason
);
}
assert!(found_signal_in_base, "Zero non-zero signals produced by oracle across 200,000 bars. Provide a positive fixture or fix the random walk.");
let mut ind_streaming = $struct_name::new();
let streaming_out: Vec<f64> = (0..10_000)
.map(|i| ind_streaming.next((o[i], h[i], l[i], c[i])))
.collect();
let mut ind_batch = $struct_name::new();
let inputs: Vec<(f64, f64, f64, f64)> = (0..10_000)
.map(|i| (o[i], h[i], l[i], c[i]))
.collect();
let batch_out = ind_batch.next_batch(&inputs);
for (i, (b_out, s_out)) in batch_out.iter().zip(streaming_out.iter()).enumerate() {
assert_eq!(*b_out as i32, *s_out as i32, "next_batch vs streaming mismatch at {}", i);
}
if ORACLE_EXEMPTION.is_none() {
let oracle_out = $talib_func(&o[..10_000], &h[..10_000], &l[..10_000], &c[..10_000]).unwrap();
for (i, (b_out, o_out)) in batch_out.iter().zip(oracle_out.iter()).enumerate() {
assert_eq!(*b_out as i32, *o_out, "next_batch mismatch at {}", i);
}
}
}
}
};
}