chore: prepare v1.1.0 release
Update version numbers across Rust, Python, and documentation files to 1.1.0. Enhance the .gitignore to include macOS dSYM files and plans directory. Introduce new dependencies in the Rust core library and update the README to reflect recent performance benchmarks and backtesting engine capabilities. Add new artifacts to the benchmarks manifest and improve documentation for the backtesting engine API.
This commit is contained in:
+13
-147
@@ -1,18 +1,9 @@
|
||||
//! Resampling — OHLCV resampling and multi-timeframe helpers.
|
||||
//!
|
||||
//! Provides volume-bar resampling and OHLCV aggregation primitives.
|
||||
//! Time-based resampling (pandas rule strings) is handled in the Python layer;
|
||||
//! this module provides the compute-heavy parts that benefit from Rust.
|
||||
//!
|
||||
//! # Functions
|
||||
//! - `volume_bars` — Aggregate ticks/bars into bars of fixed volume size.
|
||||
//! - `ohlcv_agg` — Aggregate an array of OHLCV bars given bar-index labels.
|
||||
//! Resampling — OHLCV resampling (thin PyO3 wrapper over ferro_ta_core::resampling).
|
||||
|
||||
use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
|
||||
use pyo3::exceptions::PyValueError;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
/// Return type for functions that return five OHLCV 1-D arrays.
|
||||
type Ohlcv5<'py> = (
|
||||
Bound<'py, PyArray1<f64>>,
|
||||
Bound<'py, PyArray1<f64>>,
|
||||
@@ -21,30 +12,7 @@ type Ohlcv5<'py> = (
|
||||
Bound<'py, PyArray1<f64>>,
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// volume_bars
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Aggregate OHLCV data into volume bars of a fixed volume threshold.
|
||||
///
|
||||
/// Each output bar accumulates input bars until `volume_threshold` units of
|
||||
/// volume have been consumed. The resulting bar has:
|
||||
/// - open = first open of the group
|
||||
/// - high = max high of the group
|
||||
/// - low = min low of the group
|
||||
/// - close = last close of the group
|
||||
/// - volume = sum of volumes (approximately `volume_threshold`)
|
||||
///
|
||||
/// Returns five 1-D arrays: (open, high, low, close, volume).
|
||||
///
|
||||
/// Parameters
|
||||
/// ----------
|
||||
/// open, high, low, close, volume : 1-D float64 arrays (equal length)
|
||||
/// volume_threshold : float — target volume per bar (must be > 0)
|
||||
///
|
||||
/// Returns
|
||||
/// -------
|
||||
/// Tuple of five 1-D float64 arrays (open, high, low, close, volume).
|
||||
#[pyfunction]
|
||||
#[pyo3(signature = (open, high, low, close, volume, volume_threshold))]
|
||||
pub fn volume_bars<'py>(
|
||||
@@ -70,76 +38,17 @@ pub fn volume_bars<'py>(
|
||||
"All input arrays must be non-empty and have equal length",
|
||||
));
|
||||
}
|
||||
|
||||
let mut out_open: Vec<f64> = Vec::new();
|
||||
let mut out_high: Vec<f64> = Vec::new();
|
||||
let mut out_low: Vec<f64> = Vec::new();
|
||||
let mut out_close: Vec<f64> = Vec::new();
|
||||
let mut out_vol: Vec<f64> = Vec::new();
|
||||
|
||||
let mut bar_open = o[0];
|
||||
let mut bar_high = h[0];
|
||||
let mut bar_low = l[0];
|
||||
let mut bar_close = c[0];
|
||||
let mut bar_vol = v[0];
|
||||
|
||||
for i in 1..n {
|
||||
bar_high = bar_high.max(h[i]);
|
||||
bar_low = bar_low.min(l[i]);
|
||||
bar_close = c[i];
|
||||
bar_vol += v[i];
|
||||
|
||||
if bar_vol >= volume_threshold {
|
||||
out_open.push(bar_open);
|
||||
out_high.push(bar_high);
|
||||
out_low.push(bar_low);
|
||||
out_close.push(bar_close);
|
||||
out_vol.push(bar_vol);
|
||||
// Start new bar
|
||||
if i + 1 < n {
|
||||
bar_open = o[i + 1];
|
||||
bar_high = h[i + 1];
|
||||
bar_low = l[i + 1];
|
||||
bar_close = c[i + 1];
|
||||
bar_vol = v[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Push any remaining partial bar
|
||||
if bar_vol > 0.0 && out_vol.last().is_none_or(|&last| last != bar_vol) {
|
||||
out_open.push(bar_open);
|
||||
out_high.push(bar_high);
|
||||
out_low.push(bar_low);
|
||||
out_close.push(bar_close);
|
||||
out_vol.push(bar_vol);
|
||||
}
|
||||
|
||||
let (ro, rh, rl, rc, rv) = ferro_ta_core::resampling::volume_bars(o, h, l, c, v, volume_threshold);
|
||||
Ok((
|
||||
out_open.into_pyarray(py),
|
||||
out_high.into_pyarray(py),
|
||||
out_low.into_pyarray(py),
|
||||
out_close.into_pyarray(py),
|
||||
out_vol.into_pyarray(py),
|
||||
ro.into_pyarray(py),
|
||||
rh.into_pyarray(py),
|
||||
rl.into_pyarray(py),
|
||||
rc.into_pyarray(py),
|
||||
rv.into_pyarray(py),
|
||||
))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ohlcv_agg
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Aggregate OHLCV bars by integer group labels.
|
||||
///
|
||||
/// Given OHLCV arrays and a `labels` array of non-negative integers (same
|
||||
/// length), groups consecutive bars with the same label and computes:
|
||||
/// - open = first open of the group
|
||||
/// - high = max high of the group
|
||||
/// - low = min low of the group
|
||||
/// - close = last close of the group
|
||||
/// - volume = sum of volumes
|
||||
///
|
||||
/// `labels` must be non-decreasing (groups are contiguous).
|
||||
///
|
||||
/// Returns five 1-D arrays: (open, high, low, close, volume).
|
||||
#[pyfunction]
|
||||
#[pyo3(signature = (open, high, low, close, volume, labels))]
|
||||
pub fn ohlcv_agg<'py>(
|
||||
@@ -163,59 +72,16 @@ pub fn ohlcv_agg<'py>(
|
||||
"All input arrays must be non-empty and have equal length",
|
||||
));
|
||||
}
|
||||
|
||||
let mut out_open: Vec<f64> = Vec::new();
|
||||
let mut out_high: Vec<f64> = Vec::new();
|
||||
let mut out_low: Vec<f64> = Vec::new();
|
||||
let mut out_close: Vec<f64> = Vec::new();
|
||||
let mut out_vol: Vec<f64> = Vec::new();
|
||||
|
||||
let mut cur_label = lbl[0];
|
||||
let mut bar_open = o[0];
|
||||
let mut bar_high = h[0];
|
||||
let mut bar_low = l[0];
|
||||
let mut bar_close = c[0];
|
||||
let mut bar_vol = v[0];
|
||||
|
||||
for i in 1..n {
|
||||
if lbl[i] != cur_label {
|
||||
out_open.push(bar_open);
|
||||
out_high.push(bar_high);
|
||||
out_low.push(bar_low);
|
||||
out_close.push(bar_close);
|
||||
out_vol.push(bar_vol);
|
||||
cur_label = lbl[i];
|
||||
bar_open = o[i];
|
||||
bar_high = h[i];
|
||||
bar_low = l[i];
|
||||
bar_close = c[i];
|
||||
bar_vol = v[i];
|
||||
} else {
|
||||
bar_high = bar_high.max(h[i]);
|
||||
bar_low = bar_low.min(l[i]);
|
||||
bar_close = c[i];
|
||||
bar_vol += v[i];
|
||||
}
|
||||
}
|
||||
out_open.push(bar_open);
|
||||
out_high.push(bar_high);
|
||||
out_low.push(bar_low);
|
||||
out_close.push(bar_close);
|
||||
out_vol.push(bar_vol);
|
||||
|
||||
let (ro, rh, rl, rc, rv) = ferro_ta_core::resampling::ohlcv_agg(o, h, l, c, v, lbl);
|
||||
Ok((
|
||||
out_open.into_pyarray(py),
|
||||
out_high.into_pyarray(py),
|
||||
out_low.into_pyarray(py),
|
||||
out_close.into_pyarray(py),
|
||||
out_vol.into_pyarray(py),
|
||||
ro.into_pyarray(py),
|
||||
rh.into_pyarray(py),
|
||||
rl.into_pyarray(py),
|
||||
rc.into_pyarray(py),
|
||||
rv.into_pyarray(py),
|
||||
))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Register
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(volume_bars, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(ohlcv_agg, m)?)?;
|
||||
|
||||
Reference in New Issue
Block a user