use crate::interbar_types::TradeSnapshot;
use libm;
const GARMAN_KLASS_COEFFICIENT: f64 = 0.386_294_361_119_890_6;
#[inline]
fn normalize_moments(m2_sum: f64, m3_sum: f64, m4_sum: f64, n: f64) -> (f64, f64) {
let n_inv = 1.0 / n;
let m2 = m2_sum * n_inv;
let m3 = m3_sum * n_inv;
let m4 = m4_sum * n_inv;
let sigma = m2.sqrt();
if sigma < f64::EPSILON {
return (0.0, 0.0); }
let sigma2 = sigma * sigma;
let sigma3 = sigma2 * sigma;
let sigma4 = sigma2 * sigma2;
let skewness = m3 / sigma3;
let kurtosis = m4 / sigma4 - 3.0;
(skewness, kurtosis)
}
#[inline]
pub fn compute_kyle_lambda(lookback: &[&TradeSnapshot]) -> f64 {
#[cfg(feature = "simd-kyle-lambda")]
{
super::simd::compute_kyle_lambda_simd(lookback)
}
#[cfg(not(feature = "simd-kyle-lambda"))]
{
compute_kyle_lambda_scalar(lookback)
}
}
#[cfg(any(not(feature = "simd-kyle-lambda"), test, feature = "test-utils"))]
#[inline]
pub fn compute_kyle_lambda_scalar(lookback: &[&TradeSnapshot]) -> f64 {
let n = lookback.len();
if n < 2 {
return 0.0;
}
let first_price = lookback[0].price.to_f64();
let last_price = lookback[n - 1].price.to_f64();
let (buy_vol, sell_vol) = if n > 500 {
lookback.iter().step_by(5).fold((0.0, 0.0), |acc, t| {
if t.is_buyer_maker {
(acc.0, acc.1 + t.volume.to_f64())
} else {
(acc.0 + t.volume.to_f64(), acc.1)
}
})
} else {
super::accumulate_buy_sell_branchless(lookback)
};
let total_vol = buy_vol + sell_vol;
let first_price_abs = first_price.abs();
if buy_vol >= total_vol - f64::EPSILON {
return if first_price_abs > f64::EPSILON {
(last_price - first_price) / first_price
} else {
0.0
};
} else if sell_vol >= total_vol - f64::EPSILON {
return if first_price_abs > f64::EPSILON {
-((last_price - first_price) / first_price)
} else {
0.0
};
}
let normalized_imbalance = if total_vol > f64::EPSILON {
(buy_vol - sell_vol) / total_vol
} else {
0.0
};
let imbalance_abs = normalized_imbalance.abs();
if imbalance_abs <= f64::EPSILON {
return 0.0; }
let imbalance_valid = 1.0; let price_valid = if first_price_abs > f64::EPSILON {
1.0
} else {
0.0
};
let both_valid = imbalance_valid * price_valid;
let price_change = if first_price_abs > f64::EPSILON {
(last_price - first_price) / first_price
} else {
0.0
};
if both_valid > 0.0 {
price_change / normalized_imbalance
} else {
0.0
}
}
#[inline]
pub fn compute_burstiness(lookback: &[&TradeSnapshot]) -> f64 {
#[cfg(feature = "simd-burstiness")]
{
super::simd::compute_burstiness_simd(lookback)
}
#[cfg(not(feature = "simd-burstiness"))]
{
compute_burstiness_scalar(lookback)
}
}
#[cfg(any(not(feature = "simd-burstiness"), test, feature = "test-utils"))]
#[inline]
pub fn compute_burstiness_scalar(lookback: &[&TradeSnapshot]) -> f64 {
if lookback.len() < 2 {
return 0.0;
}
let mut mean = 0.0;
let mut m2 = 0.0; let mut count = 0.0;
for i in 1..lookback.len() {
let delta_t = (lookback[i].timestamp - lookback[i - 1].timestamp) as f64;
count += 1.0;
let delta = delta_t - mean;
mean += delta / count;
let delta2 = delta_t - mean;
m2 += delta * delta2;
}
let variance = m2 / count;
let sigma = variance.sqrt();
let denominator = sigma + mean;
let numerator = sigma - mean;
numerator / denominator.max(f64::EPSILON)
}
#[inline]
pub fn compute_volume_moments(lookback: &[&TradeSnapshot]) -> (f64, f64) {
let n = lookback.len() as f64;
if n < 3.0 {
return (0.0, 0.0);
}
let n_inv = 1.0 / n;
let sum_vol = lookback.iter().fold(0.0, |acc, t| acc + t.volume.to_f64());
let mu = sum_vol * n_inv;
let (m2, m3, m4) = lookback.iter().fold((0.0, 0.0, 0.0), |(m2, m3, m4), t| {
let v = t.volume.to_f64();
let d = v - mu;
let d2 = d * d;
(m2 + d2, m3 + d2 * d, m4 + d2 * d2)
});
normalize_moments(m2, m3, m4, n)
}
#[inline]
pub fn compute_volume_moments_cached(volumes: &[f64]) -> (f64, f64) {
let n = volumes.len() as f64;
if n < 3.0 {
return (0.0, 0.0);
}
let sum_vol: f64 = volumes.iter().sum();
compute_volume_moments_with_mean(volumes, sum_vol / n)
}
#[inline]
pub fn compute_volume_moments_with_mean(volumes: &[f64], mu: f64) -> (f64, f64) {
let n = volumes.len() as f64;
if n < 3.0 {
return (0.0, 0.0);
}
let (m2, m3, m4) = volumes.iter().fold((0.0, 0.0, 0.0), |(m2, m3, m4), &v| {
let d = v - mu;
let d2 = d * d;
(m2 + d2, m3 + d2 * d, m4 + d2 * d2)
});
normalize_moments(m2, m3, m4, n)
}
#[inline]
pub fn compute_garman_klass(lookback: &[&TradeSnapshot]) -> f64 {
if lookback.is_empty() {
return 0.0;
}
let count = lookback.len();
let open_price = lookback[0].price.to_f64();
let close_price = lookback[count - 1].price.to_f64();
let (low_price, high_price) = lookback.iter().fold((f64::MAX, f64::MIN), |acc, t| {
let p = t.price.to_f64();
(acc.0.min(p), acc.1.max(p))
});
compute_garman_klass_with_ohlc(open_price, high_price, low_price, close_price)
}
#[inline]
pub fn compute_garman_klass_with_ohlc(open: f64, high: f64, low: f64, close: f64) -> f64 {
if open <= f64::EPSILON || low <= f64::EPSILON || high <= f64::EPSILON {
return 0.0;
}
let log_hl = libm::log(high / low);
let log_co = libm::log(close / open);
let variance = 0.5 * (log_hl * log_hl) - GARMAN_KLASS_COEFFICIENT * (log_co * log_co);
if variance > 0.0 { variance.sqrt() } else { 0.0 }
}