diff --git a/crates/ferro_ta_core/src/alerts.rs b/crates/ferro_ta_core/src/alerts.rs index ac933f4..62d3475 100644 --- a/crates/ferro_ta_core/src/alerts.rs +++ b/crates/ferro_ta_core/src/alerts.rs @@ -22,14 +22,10 @@ pub fn check_threshold(series: &[f64], level: f64, direction: i32) -> Vec { if prev.is_nan() || curr.is_nan() { continue; } - if direction == 1 { - if prev <= level && curr > level { - out[i] = 1; - } - } else if direction == -1 { - if prev >= level && curr < level { - out[i] = 1; - } + if (direction == 1 && prev <= level && curr > level) + || (direction == -1 && prev >= level && curr < level) + { + out[i] = 1; } } out diff --git a/crates/ferro_ta_core/src/backtest.rs b/crates/ferro_ta_core/src/backtest.rs index 5d99b55..7b9c33f 100644 --- a/crates/ferro_ta_core/src/backtest.rs +++ b/crates/ferro_ta_core/src/backtest.rs @@ -1565,6 +1565,7 @@ pub struct MultiAssetBacktestResult { /// `weights_2d`: row-major (n_assets, n_bars) /// /// Callers must transpose from (n_bars, n_assets) if needed. +#[allow(clippy::too_many_arguments)] pub fn backtest_multi_asset_core( close_2d: &[Vec], weights_2d: &[Vec], @@ -1585,6 +1586,7 @@ pub fn backtest_multi_asset_core( // Apply portfolio constraints per bar let mut constrained: Vec> = weights_2d.to_vec(); + #[allow(clippy::needless_range_loop)] if max_asset_weight != 1.0 || max_gross_exposure > 0.0 || max_net_exposure > 0.0 { for i in 0..n_bars { // 1. Clamp per-asset weight @@ -1635,6 +1637,7 @@ pub fn backtest_multi_asset_core( // Portfolio return = sum of per-asset strategy returns let mut portfolio_returns = vec![0.0_f64; n_bars]; + #[allow(clippy::needless_range_loop)] for i in 0..n_bars { let mut s = 0.0_f64; for j in 0..n_assets { diff --git a/crates/ferro_ta_core/src/batch.rs b/crates/ferro_ta_core/src/batch.rs index 353647b..24f55c5 100644 --- a/crates/ferro_ta_core/src/batch.rs +++ b/crates/ferro_ta_core/src/batch.rs @@ -272,6 +272,7 @@ pub fn batch_atr( /// Apply Stochastic to each set of (high, low, close) columns. /// Returns `(slowk_columns, slowd_columns)`. +#[allow(clippy::type_complexity)] pub fn batch_stoch( high: &[Vec], low: &[Vec], diff --git a/crates/ferro_ta_core/src/extended.rs b/crates/ferro_ta_core/src/extended.rs index 44a47c4..5706800 100644 --- a/crates/ferro_ta_core/src/extended.rs +++ b/crates/ferro_ta_core/src/extended.rs @@ -231,13 +231,7 @@ pub fn supertrend( // Direction and output only from index timeperiod (warmup = 0, NaN) if i >= timeperiod { let prev_dir = direction[i - 1]; - direction[i] = if prev_dir == 0 { - if close[i] > upper_band[i] { - 1 - } else { - -1 - } - } else if prev_dir == -1 { + direction[i] = if prev_dir == 0 || prev_dir == -1 { if close[i] > upper_band[i] { 1 } else { @@ -464,6 +458,7 @@ pub fn chandelier_exit( /// /// # Returns /// `(tenkan, kijun, senkou_a, senkou_b, chikou)` arrays. +#[allow(clippy::type_complexity)] pub fn ichimoku( high: &[f64], low: &[f64], @@ -534,6 +529,7 @@ pub fn ichimoku( /// /// # Returns /// `(pivot, r1, s1, r2, s2)` arrays. Index 0 is always `NaN` (no previous bar). +#[allow(clippy::type_complexity)] pub fn pivot_points( high: &[f64], low: &[f64], diff --git a/crates/ferro_ta_core/src/portfolio.rs b/crates/ferro_ta_core/src/portfolio.rs index 610511e..83b7671 100644 --- a/crates/ferro_ta_core/src/portfolio.rs +++ b/crates/ferro_ta_core/src/portfolio.rs @@ -165,6 +165,7 @@ pub fn correlation_matrix(data: &[Vec]) -> Vec> { assert!(n_assets > 0, "data must contain at least one asset column"); let n_bars = data[0].len(); assert!(n_bars >= 2, "data must have at least 2 rows (bars)"); + #[allow(clippy::needless_range_loop)] for j in 1..n_assets { assert!( data[j].len() == n_bars, @@ -190,6 +191,7 @@ pub fn correlation_matrix(data: &[Vec]) -> Vec> { // Build correlation matrix (exploit symmetry: compute each pair once) let mut result = vec![vec![0.0_f64; n_assets]; n_assets]; + #[allow(clippy::needless_range_loop)] for j1 in 0..n_assets { result[j1][j1] = 1.0; for j2 in (j1 + 1)..n_assets { @@ -332,6 +334,7 @@ pub fn compose_weighted(data: &[Vec], weights: &[f64]) -> Vec { return vec![]; } let n_bars = data[0].len(); + #[allow(clippy::needless_range_loop)] for j in 1..n_sigs { assert!( data[j].len() == n_bars, diff --git a/src/backtest/mod.rs b/src/backtest/mod.rs index 09013ce..5d4997a 100644 --- a/src/backtest/mod.rs +++ b/src/backtest/mod.rs @@ -147,8 +147,7 @@ pub fn sma_crossover_signals<'py>( validation::validate_timeperiod(fast, "fast", 1)?; validation::validate_timeperiod(slow, "slow", 1)?; let prices = close.as_slice()?; - let out = - core_bt::sma_crossover_signals(prices, fast, slow).map_err(|e| PyValueError::new_err(e))?; + let out = core_bt::sma_crossover_signals(prices, fast, slow).map_err(PyValueError::new_err)?; Ok(out.into_pyarray(py)) } @@ -166,7 +165,7 @@ pub fn macd_crossover_signals<'py>( validation::validate_timeperiod(signalperiod, "signalperiod", 1)?; let prices = close.as_slice()?; let out = core_bt::macd_crossover_signals(prices, fastperiod, slowperiod, signalperiod) - .map_err(|e| PyValueError::new_err(e))?; + .map_err(PyValueError::new_err)?; Ok(out.into_pyarray(py)) } @@ -210,7 +209,7 @@ pub fn backtest_core<'py>( initial_capital, commission_per_trade, ) - .map_err(|e| PyValueError::new_err(e))?; + .map_err(PyValueError::new_err)?; Ok(( result.positions.into_pyarray(py), @@ -314,7 +313,7 @@ pub fn backtest_ohlcv_core<'py>( let lp_opt: Option<&[f64]> = limit_prices.as_ref().and_then(|lp| lp.as_slice().ok()); let result = core_bt::backtest_ohlcv_core(o, h, l, c, s, &config, lp_opt) - .map_err(|e| PyValueError::new_err(e))?; + .map_err(PyValueError::new_err)?; Ok(( result.positions.into_pyarray(py), @@ -344,7 +343,7 @@ pub fn compute_performance_metrics<'py>( let br = benchmark_returns.as_ref().and_then(|b| b.as_slice().ok()); let metrics = core_bt::compute_performance_metrics(r, eq, periods_per_year, risk_free_rate, br) - .map_err(|e| PyValueError::new_err(e))?; + .map_err(PyValueError::new_err)?; let dict = PyDict::new(py); dict.set_item("total_return", metrics.total_return)?; @@ -441,8 +440,7 @@ pub fn extract_trades_ohlcv<'py>( (l.len(), "low"), ])?; - let trades = - core_bt::extract_trades_ohlcv(pos, fp, h, l).map_err(|e| PyValueError::new_err(e))?; + let trades = core_bt::extract_trades_ohlcv(pos, fp, h, l).map_err(PyValueError::new_err)?; let mut entry_bars: Vec = Vec::with_capacity(trades.len()); let mut exit_bars: Vec = Vec::with_capacity(trades.len()); @@ -535,6 +533,7 @@ pub fn backtest_multi_asset_core<'py>( // Apply portfolio constraints first via the core function's logic. // Apply constraints + #[allow(clippy::needless_range_loop)] if max_asset_weight != 1.0 || max_gross_exposure > 0.0 || max_net_exposure > 0.0 { for i in 0..n_bars { if max_asset_weight < f64::INFINITY && max_asset_weight > 0.0 { @@ -703,7 +702,7 @@ pub fn walk_forward_indices<'py>( step_bars: usize, ) -> PyResult>> { let folds = core_bt::walk_forward_indices(n_bars, train_bars, test_bars, anchored, step_bars) - .map_err(|e| PyValueError::new_err(e))?; + .map_err(PyValueError::new_err)?; let n_folds = folds.len(); let mut arr = Array2::::zeros((n_folds, 4)); @@ -722,12 +721,12 @@ pub fn walk_forward_indices<'py>( #[pyfunction] pub fn kelly_fraction(win_rate: f64, avg_win: f64, avg_loss: f64) -> PyResult { - core_bt::kelly_fraction(win_rate, avg_win, avg_loss).map_err(|e| PyValueError::new_err(e)) + core_bt::kelly_fraction(win_rate, avg_win, avg_loss).map_err(PyValueError::new_err) } #[pyfunction] pub fn half_kelly_fraction(win_rate: f64, avg_win: f64, avg_loss: f64) -> PyResult { - core_bt::half_kelly_fraction(win_rate, avg_win, avg_loss).map_err(|e| PyValueError::new_err(e)) + core_bt::half_kelly_fraction(win_rate, avg_win, avg_loss).map_err(PyValueError::new_err) } // --------------------------------------------------------------------------- diff --git a/src/streaming/mod.rs b/src/streaming/mod.rs index 3e3959e..a0b3ab5 100644 --- a/src/streaming/mod.rs +++ b/src/streaming/mod.rs @@ -298,13 +298,19 @@ pub struct StreamingVWAP { inner: core::StreamingVWAP, } +impl Default for StreamingVWAP { + fn default() -> Self { + Self { + inner: core::StreamingVWAP::new(), + } + } +} + #[pymethods] impl StreamingVWAP { #[new] pub fn new() -> Self { - Self { - inner: core::StreamingVWAP::new(), - } + Self::default() } /// Add a new bar (high, low, close, volume) and return cumulative VWAP.