style: apply cargo fmt formatting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Pratik Bhadane
2026-04-01 20:12:19 +05:30
co-authored by Claude Sonnet 4.6
parent 3ab6daa853
commit 70b99ad870
17 changed files with 128 additions and 128 deletions
+11 -17
View File
@@ -21,14 +21,13 @@ type Ohlcv5AndLabels = (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>, Vec<i6
///
/// # Panics
/// Panics if `ticks_per_bar == 0`, arrays are empty, or lengths differ.
pub fn aggregate_tick_bars(
price: &[f64],
size: &[f64],
ticks_per_bar: usize,
) -> Ohlcv5 {
pub fn aggregate_tick_bars(price: &[f64], size: &[f64], ticks_per_bar: usize) -> Ohlcv5 {
assert!(ticks_per_bar >= 1, "ticks_per_bar must be >= 1");
let n = price.len();
assert!(n > 0 && size.len() == n, "price and size must be non-empty and equal length");
assert!(
n > 0 && size.len() == n,
"price and size must be non-empty and equal length"
);
let n_bars = n.div_ceil(ticks_per_bar);
let mut out_open = Vec::with_capacity(n_bars);
@@ -71,14 +70,13 @@ pub fn aggregate_tick_bars(
///
/// # Panics
/// Panics if `volume_threshold <= 0`, arrays are empty, or lengths differ.
pub fn aggregate_volume_bars_ticks(
price: &[f64],
size: &[f64],
volume_threshold: f64,
) -> Ohlcv5 {
pub fn aggregate_volume_bars_ticks(price: &[f64], size: &[f64], volume_threshold: f64) -> Ohlcv5 {
assert!(volume_threshold > 0.0, "volume_threshold must be > 0");
let n = price.len();
assert!(n > 0 && size.len() == n, "price and size must be non-empty and equal length");
assert!(
n > 0 && size.len() == n,
"price and size must be non-empty and equal length"
);
let mut out_open: Vec<f64> = Vec::new();
let mut out_high: Vec<f64> = Vec::new();
@@ -140,11 +138,7 @@ pub fn aggregate_volume_bars_ticks(
///
/// # Panics
/// Panics if arrays are empty or have unequal lengths.
pub fn aggregate_time_bars(
price: &[f64],
size: &[f64],
labels: &[i64],
) -> Ohlcv5AndLabels {
pub fn aggregate_time_bars(price: &[f64], size: &[f64], labels: &[i64]) -> Ohlcv5AndLabels {
let n = price.len();
assert!(
n > 0 && size.len() == n && labels.len() == n,
+5 -1
View File
@@ -28,7 +28,11 @@ use std::collections::HashMap;
pub fn trade_stats(pnl: &[f64], hold_bars: &[f64]) -> (f64, f64, f64, f64, f64) {
let n = pnl.len();
assert!(n > 0, "pnl must be non-empty");
assert_eq!(n, hold_bars.len(), "pnl and hold_bars must have equal length");
assert_eq!(
n,
hold_bars.len(),
"pnl and hold_bars must have equal length"
);
let mut wins: Vec<f64> = Vec::new();
let mut losses: Vec<f64> = Vec::new();
+28 -13
View File
@@ -287,7 +287,12 @@ pub struct StreamingSummary {
// ---------------------------------------------------------------------------
/// RSI threshold strategy: +1 when RSI <= oversold, -1 when RSI >= overbought, 0 otherwise.
pub fn rsi_threshold_signals(close: &[f64], timeperiod: usize, oversold: f64, overbought: f64) -> Vec<f64> {
pub fn rsi_threshold_signals(
close: &[f64],
timeperiod: usize,
oversold: f64,
overbought: f64,
) -> Vec<f64> {
let rsi = crate::momentum::rsi(close, timeperiod);
rsi.iter()
.map(|&v| {
@@ -1128,8 +1133,7 @@ pub fn compute_performance_metrics(
}
let mean_r: f64 = valid_r.iter().sum::<f64>() / n_valid as f64;
let variance: f64 =
valid_r.iter().map(|&v| (v - mean_r).powi(2)).sum::<f64>() / n_valid as f64;
let variance: f64 = valid_r.iter().map(|&v| (v - mean_r).powi(2)).sum::<f64>() / n_valid as f64;
let std_r = variance.sqrt();
let downside_sq_sum: f64 = valid_r
@@ -1253,13 +1257,19 @@ pub fn compute_performance_metrics(
a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
});
let p5 = pct_r[idx_5];
let worst_bar = pct_r[..=idx_5].iter().copied().fold(f64::INFINITY, f64::min);
let worst_bar = pct_r[..=idx_5]
.iter()
.copied()
.fold(f64::INFINITY, f64::min);
// Find 95th percentile in the remaining upper partition
pct_r[idx_5..].select_nth_unstable_by(idx_95 - idx_5, |a, b| {
a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
});
let p95 = pct_r[idx_95];
let best_bar = pct_r[idx_95..].iter().copied().fold(f64::NEG_INFINITY, f64::max);
let best_bar = pct_r[idx_95..]
.iter()
.copied()
.fold(f64::NEG_INFINITY, f64::max);
let tail_ratio = if p5.abs() > 0.0 {
p95.abs() / p5.abs()
} else {
@@ -1451,10 +1461,8 @@ pub fn extract_trades_ohlcv(
}
} else {
if trade_entry_price > 0.0 {
let unreal_high =
trade_dir * (high[i] - trade_entry_price) / trade_entry_price;
let unreal_low =
trade_dir * (low[i] - trade_entry_price) / trade_entry_price;
let unreal_high = trade_dir * (high[i] - trade_entry_price) / trade_entry_price;
let unreal_low = trade_dir * (low[i] - trade_entry_price) / trade_entry_price;
let bar_best = unreal_high.max(unreal_low);
let bar_worst = unreal_high.min(unreal_low);
if bar_best > trade_mfe {
@@ -1615,8 +1623,12 @@ pub fn backtest_multi_asset_core(
// Per-asset backtests
let asset_strategy_returns: Vec<Vec<f64>> = (0..n_assets)
.map(|j| {
let (_, strat_rets, _) =
single_asset_backtest(&close_2d[j], &constrained[j], commission_per_trade, slippage_bps);
let (_, strat_rets, _) = single_asset_backtest(
&close_2d[j],
&constrained[j],
commission_per_trade,
slippage_bps,
);
strat_rets
})
.collect();
@@ -1757,7 +1769,9 @@ pub fn walk_forward_indices(
}
if folds.is_empty() {
return Err("No complete folds fit within n_bars with the given train/test sizes".to_string());
return Err(
"No complete folds fit within n_bars with the given train/test sizes".to_string(),
);
}
Ok(folds)
@@ -2018,7 +2032,8 @@ mod tests {
let signals: Vec<f64> = vec![0.0, 1.0, 1.0, 1.0, 0.0, -1.0, -1.0, 0.0, 0.0, 0.0];
let config = BacktestConfig::default();
let result = backtest_ohlcv_core(&open, &high, &low, &close, &signals, &config, None).unwrap();
let result =
backtest_ohlcv_core(&open, &high, &low, &close, &signals, &config, None).unwrap();
assert_eq!(result.equity.len(), n);
// Equity should be positive
assert!(*result.equity.last().unwrap() > 0.0);
+14 -16
View File
@@ -46,11 +46,7 @@ fn validate_hlc_columns(
return Ok((0, 0));
}
let n = high[0].len();
for (idx, (h, (l, c))) in high
.iter()
.zip(low.iter().zip(close.iter()))
.enumerate()
{
for (idx, (h, (l, c))) in high.iter().zip(low.iter().zip(close.iter())).enumerate() {
if h.len() != n || l.len() != n || c.len() != n {
return Err(format!(
"column {idx}: high len={}, low len={}, close len={} — must all be {n}",
@@ -358,24 +354,26 @@ fn compute_close_indicator(
.collect()),
"LINEARREG" => {
let last_x = (timeperiod - 1) as f64;
Ok(rolling_linreg_apply(close, timeperiod, |slope, intercept| {
intercept + slope * last_x
}))
Ok(rolling_linreg_apply(
close,
timeperiod,
|slope, intercept| intercept + slope * last_x,
))
}
"LINEARREG_SLOPE" => Ok(rolling_linreg_apply(close, timeperiod, |slope, _| slope)),
"LINEARREG_INTERCEPT" => {
Ok(rolling_linreg_apply(close, timeperiod, |_, intercept| {
intercept
}))
}
"LINEARREG_INTERCEPT" => Ok(rolling_linreg_apply(close, timeperiod, |_, intercept| {
intercept
})),
"LINEARREG_ANGLE" => Ok(rolling_linreg_apply(close, timeperiod, |slope, _| {
slope.atan() * 180.0 / std::f64::consts::PI
})),
"TSF" => {
let forecast_x = timeperiod as f64;
Ok(rolling_linreg_apply(close, timeperiod, |slope, intercept| {
intercept + slope * forecast_x
}))
Ok(rolling_linreg_apply(
close,
timeperiod,
|slope, intercept| intercept + slope * forecast_x,
))
}
_ => Err(format!(
"unsupported close indicator for grouped execution: {name}"
+4 -4
View File
@@ -72,11 +72,11 @@ mod tests {
fn test_mark_session_boundaries() {
let ns_per_day: i64 = 86_400_000_000_000;
let ts = vec![
0, // day 0
ns_per_day / 2, // day 0
ns_per_day, // day 1
0, // day 0
ns_per_day / 2, // day 0
ns_per_day, // day 1
ns_per_day + ns_per_day / 2, // day 1
ns_per_day * 2, // day 2
ns_per_day * 2, // day 2
];
let result = mark_session_boundaries(&ts);
assert_eq!(result, vec![0, 2, 4]);
+4 -15
View File
@@ -215,16 +215,14 @@ pub fn supertrend(
let lower_basic = hl2 - multiplier * atr[i];
// Adjust lower band
lower_band[i] = if lower_basic > lower_band[i - 1] || close[i - 1] < lower_band[i - 1]
{
lower_band[i] = if lower_basic > lower_band[i - 1] || close[i - 1] < lower_band[i - 1] {
lower_basic
} else {
lower_band[i - 1]
};
// Adjust upper band
upper_band[i] = if upper_basic < upper_band[i - 1] || close[i - 1] > upper_band[i - 1]
{
upper_band[i] = if upper_basic < upper_band[i - 1] || close[i - 1] > upper_band[i - 1] {
upper_basic
} else {
upper_band[i - 1]
@@ -269,11 +267,7 @@ pub fn supertrend(
///
/// # Returns
/// `(upper, middle, lower)` arrays.
pub fn donchian(
high: &[f64],
low: &[f64],
timeperiod: usize,
) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
pub fn donchian(high: &[f64], low: &[f64], timeperiod: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let n = high.len();
let mut upper = vec![f64::NAN; n];
let mut lower = vec![f64::NAN; n];
@@ -305,12 +299,7 @@ pub fn donchian(
///
/// Values near 100 indicate a choppy market; near 0 indicates trending.
/// The first `timeperiod` values are `NaN`.
pub fn choppiness_index(
high: &[f64],
low: &[f64],
close: &[f64],
timeperiod: usize,
) -> Vec<f64> {
pub fn choppiness_index(high: &[f64], low: &[f64], close: &[f64], timeperiod: usize) -> Vec<f64> {
let n = high.len();
let mut result = vec![f64::NAN; n];
if timeperiod < 1 || n <= timeperiod {
+6 -3
View File
@@ -1,6 +1,5 @@
//! Momentum indicators.
/// Compute the Relative Strength Index (RSI).
///
/// Returns values in the range `[0, 100]`. Uses Wilder's smoothing method
@@ -119,8 +118,12 @@ pub fn stoch(
for j in (win_start + 1)..=i {
let h = high[j];
let l = low[j];
if h > hh { hh = h; }
if l < ll { ll = l; }
if h > hh {
hh = h;
}
if l < ll {
ll = l;
}
}
let range = hh - ll;
fastk_valid[i - fastk_start] = if range != 0.0 {
+16 -15
View File
@@ -73,7 +73,10 @@ pub fn beta_full(asset_returns: &[f64], benchmark_returns: &[f64]) -> f64 {
cov += da * db;
var_b += db * db;
}
assert!(var_b != 0.0, "benchmark_returns has zero variance; cannot compute beta");
assert!(
var_b != 0.0,
"benchmark_returns has zero variance; cannot compute beta"
);
cov / var_b
}
@@ -225,7 +228,11 @@ pub fn relative_strength(asset_returns: &[f64], benchmark_returns: &[f64]) -> Ve
for i in 0..n {
cum_a *= 1.0 + asset_returns[i];
cum_b *= 1.0 + benchmark_returns[i];
result[i] = if cum_b == 0.0 { f64::NAN } else { cum_a / cum_b };
result[i] = if cum_b == 0.0 {
f64::NAN
} else {
cum_a / cum_b
};
}
result
}
@@ -243,7 +250,10 @@ pub fn spread(a: &[f64], b: &[f64], hedge: f64) -> Vec<f64> {
n > 0 && b.len() == n,
"a and b must be non-empty and equal length"
);
a.iter().zip(b.iter()).map(|(&x, &y)| x - hedge * y).collect()
a.iter()
.zip(b.iter())
.map(|(&x, &y)| x - hedge * y)
.collect()
}
// ---------------------------------------------------------------------------
@@ -358,10 +368,7 @@ mod tests {
#[test]
fn test_portfolio_volatility_identity_cov() {
// Identity covariance, equal weights => sqrt(sum(w_i^2))
let cov = vec![
vec![1.0, 0.0],
vec![0.0, 1.0],
];
let cov = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
let w = vec![0.5, 0.5];
let vol = portfolio_volatility(&cov, &w);
// w' I w = 0.25 + 0.25 = 0.5, sqrt = 0.7071...
@@ -378,10 +385,7 @@ mod tests {
#[test]
fn test_portfolio_volatility_correlated() {
// Fully correlated: cov = [[0.04, 0.04], [0.04, 0.04]]
let cov = vec![
vec![0.04, 0.04],
vec![0.04, 0.04],
];
let cov = vec![vec![0.04, 0.04], vec![0.04, 0.04]];
let w = vec![0.5, 0.5];
// w' Σ w = 0.04, sqrt = 0.2
let vol = portfolio_volatility(&cov, &w);
@@ -594,10 +598,7 @@ mod tests {
#[test]
fn test_compose_weighted_basic() {
let data = vec![
vec![1.0, 2.0, 3.0],
vec![4.0, 5.0, 6.0],
];
let data = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
let weights = vec![0.3, 0.7];
let cw = compose_weighted(&data, &weights);
// bar 0: 1*0.3 + 4*0.7 = 3.1
+1 -6
View File
@@ -54,12 +54,7 @@ pub fn regime_combined(
/// Detect structural breaks using a CUSUM (cumulative sum) approach.
///
/// `window` must be >= 2. Returns `Vec<i8>`: `1` at break bars, `0` elsewhere.
pub fn detect_breaks_cusum(
series: &[f64],
window: usize,
threshold: f64,
slack: f64,
) -> Vec<i8> {
pub fn detect_breaks_cusum(series: &[f64], window: usize, threshold: f64, slack: f64) -> Vec<i8> {
let n = series.len();
let mut out = vec![0i8; n];
if n < window || window < 2 {
+1 -2
View File
@@ -197,8 +197,7 @@ mod tests {
#[test]
fn test_volume_bars_single_element() {
let (ro, rh, rl, rc, rv) =
volume_bars(&[10.0], &[12.0], &[8.0], &[11.0], &[50.0], 100.0);
let (ro, rh, rl, rc, rv) = volume_bars(&[10.0], &[12.0], &[8.0], &[11.0], &[50.0], 100.0);
assert_eq!(rv.len(), 1);
assert!((rv[0] - 50.0).abs() < 1e-10);
assert!((ro[0] - 10.0).abs() < 1e-10);
+1 -2
View File
@@ -932,8 +932,7 @@ mod tests {
if i + 1 < period {
assert!(streaming[i].is_nan(), "bar {} should be NaN", i);
} else {
let batch: f64 =
data[i + 1 - period..=i].iter().sum::<f64>() / period as f64;
let batch: f64 = data[i + 1 - period..=i].iter().sum::<f64>() / period as f64;
assert!(
approx_eq(streaming[i], batch, 1e-10),
"bar {}: streaming={} batch={}",