style: apply cargo fmt formatting
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
3ab6daa853
commit
70b99ad870
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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={}",
|
||||
|
||||
@@ -71,7 +71,8 @@ pub fn aggregate_volume_bars_ticks<'py>(
|
||||
"price and size must be non-empty and equal length",
|
||||
));
|
||||
}
|
||||
let (ro, rh, rl, rc, rv) = ferro_ta_core::aggregation::aggregate_volume_bars_ticks(p, s, volume_threshold);
|
||||
let (ro, rh, rl, rc, rv) =
|
||||
ferro_ta_core::aggregation::aggregate_volume_bars_ticks(p, s, volume_threshold);
|
||||
Ok((
|
||||
ro.into_pyarray(py),
|
||||
rh.into_pyarray(py),
|
||||
|
||||
+21
-8
@@ -147,8 +147,8 @@ 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(|e| PyValueError::new_err(e))?;
|
||||
Ok(out.into_pyarray(py))
|
||||
}
|
||||
|
||||
@@ -202,8 +202,15 @@ pub fn backtest_core<'py>(
|
||||
validation::validate_equal_length(&[(c.len(), "close"), (s.len(), "signals")])?;
|
||||
|
||||
let cm = commission.as_ref().map(|c| &c.inner);
|
||||
let result = core_bt::backtest_core(c, s, cm, slippage_bps, initial_capital, commission_per_trade)
|
||||
.map_err(|e| PyValueError::new_err(e))?;
|
||||
let result = core_bt::backtest_core(
|
||||
c,
|
||||
s,
|
||||
cm,
|
||||
slippage_bps,
|
||||
initial_capital,
|
||||
commission_per_trade,
|
||||
)
|
||||
.map_err(|e| PyValueError::new_err(e))?;
|
||||
|
||||
Ok((
|
||||
result.positions.into_pyarray(py),
|
||||
@@ -348,8 +355,14 @@ pub fn compute_performance_metrics<'py>(
|
||||
dict.set_item("calmar", metrics.calmar)?;
|
||||
dict.set_item("max_drawdown", metrics.max_drawdown)?;
|
||||
dict.set_item("avg_drawdown", metrics.avg_drawdown)?;
|
||||
dict.set_item("max_drawdown_duration_bars", metrics.max_drawdown_duration_bars as i64)?;
|
||||
dict.set_item("avg_drawdown_duration_bars", metrics.avg_drawdown_duration_bars)?;
|
||||
dict.set_item(
|
||||
"max_drawdown_duration_bars",
|
||||
metrics.max_drawdown_duration_bars as i64,
|
||||
)?;
|
||||
dict.set_item(
|
||||
"avg_drawdown_duration_bars",
|
||||
metrics.avg_drawdown_duration_bars,
|
||||
)?;
|
||||
dict.set_item("ulcer_index", metrics.ulcer_index)?;
|
||||
dict.set_item("omega_ratio", metrics.omega_ratio)?;
|
||||
dict.set_item("win_rate", metrics.win_rate)?;
|
||||
@@ -428,8 +441,8 @@ 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(|e| PyValueError::new_err(e))?;
|
||||
|
||||
let mut entry_bars: Vec<i64> = Vec::with_capacity(trades.len());
|
||||
let mut exit_bars: Vec<i64> = Vec::with_capacity(trades.len());
|
||||
|
||||
+6
-8
@@ -27,9 +27,7 @@ use rayon::prelude::*;
|
||||
/// `result[j]` is column j (one time-series of length n_samples).
|
||||
fn numpy2d_to_columns(arr: &ndarray::ArrayView2<'_, f64>) -> Vec<Vec<f64>> {
|
||||
let (_n_samples, n_series) = arr.dim();
|
||||
(0..n_series)
|
||||
.map(|j| arr.column(j).to_vec())
|
||||
.collect()
|
||||
(0..n_series).map(|j| arr.column(j).to_vec()).collect()
|
||||
}
|
||||
|
||||
/// Convert `Vec<Vec<f64>>` (columns) back into a numpy (n_samples, n_series) array.
|
||||
@@ -320,9 +318,8 @@ pub fn batch_adx<'py>(
|
||||
let c_cols = numpy2d_to_columns(&arr_c);
|
||||
|
||||
let col_results: Vec<Vec<f64>> = py.allow_threads(|| {
|
||||
let process = |i: usize| {
|
||||
ferro_ta_core::momentum::adx(&h_cols[i], &l_cols[i], &c_cols[i], timeperiod)
|
||||
};
|
||||
let process =
|
||||
|i: usize| ferro_ta_core::momentum::adx(&h_cols[i], &l_cols[i], &c_cols[i], timeperiod);
|
||||
if parallel {
|
||||
(0..n_series).into_par_iter().map(process).collect()
|
||||
} else {
|
||||
@@ -369,8 +366,9 @@ pub fn run_close_indicators<'py>(
|
||||
.map(|r| r.map(|v| v.into_pyarray(py).unbind()).map_err(map_core_err))
|
||||
.collect()
|
||||
} else {
|
||||
let results = ferro_ta_core::batch::run_close_indicators(close_values, &names, &timeperiods)
|
||||
.map_err(map_core_err)?;
|
||||
let results =
|
||||
ferro_ta_core::batch::run_close_indicators(close_values, &names, &timeperiods)
|
||||
.map_err(map_core_err)?;
|
||||
Ok(results
|
||||
.into_iter()
|
||||
.map(|v| v.into_pyarray(py).unbind())
|
||||
|
||||
+3
-4
@@ -63,12 +63,11 @@ pub fn rolling_variance_break<'py>(
|
||||
) -> PyResult<Bound<'py, PyArray1<i8>>> {
|
||||
validation::validate_timeperiod(short_window, "short_window", 2)?;
|
||||
if long_window <= short_window {
|
||||
return Err(PyValueError::new_err(
|
||||
"long_window must be > short_window",
|
||||
));
|
||||
return Err(PyValueError::new_err("long_window must be > short_window"));
|
||||
}
|
||||
let s = series.as_slice()?;
|
||||
let result = ferro_ta_core::regime::rolling_variance_break(s, short_window, long_window, threshold);
|
||||
let result =
|
||||
ferro_ta_core::regime::rolling_variance_break(s, short_window, long_window, threshold);
|
||||
Ok(result.into_pyarray(py))
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ pub fn volume_bars<'py>(
|
||||
"All input arrays must be non-empty and have equal length",
|
||||
));
|
||||
}
|
||||
let (ro, rh, rl, rc, rv) = ferro_ta_core::resampling::volume_bars(o, h, l, c, v, volume_threshold);
|
||||
let (ro, rh, rl, rc, rv) =
|
||||
ferro_ta_core::resampling::volume_bars(o, h, l, c, v, volume_threshold);
|
||||
Ok((
|
||||
ro.into_pyarray(py),
|
||||
rh.into_pyarray(py),
|
||||
|
||||
+3
-12
@@ -209,10 +209,7 @@ impl StreamingBBands {
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"StreamingBBands(period={})",
|
||||
self.inner.period()
|
||||
)
|
||||
format!("StreamingBBands(period={})", self.inner.period())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,10 +284,7 @@ impl StreamingStoch {
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"StreamingStoch(fastk_period={})",
|
||||
self.inner.period()
|
||||
)
|
||||
format!("StreamingStoch(fastk_period={})", self.inner.period())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,10 +357,7 @@ impl StreamingSupertrend {
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"StreamingSupertrend(period={})",
|
||||
self.inner.period()
|
||||
)
|
||||
format!("StreamingSupertrend(period={})", self.inner.period())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user