diff --git a/CHANGELOG.md b/CHANGELOG.md index f2f9b794..dddbc54d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- **Python: zero third-party dependencies — NumPy is no longer required** + (breaking). `pip install wickra` now pulls nothing else. Batch inputs accept any + sequence or buffer of numbers (`array.array`, `memoryview`, a NumPy array, or a + plain `list`); single-output `batch(...)` now returns a stdlib `array.array('d')` + and multi-output indicators return a buffer-protocol `Matrix` (with `.shape`, + integer-row and `[i, j]` element access, `.tolist()`) instead of 1-D / 2-D NumPy + arrays. Both expose the buffer protocol, so `numpy.asarray(result)` still wraps a + 1-D result zero-copy when NumPy is installed — it is now an optional extra + (`pip install wickra[numpy]`). Streaming `update(...)` is unchanged, and results + are numerically identical. The other nine languages were already dependency-free. + +### Removed +- **Python: `numpy` runtime dependency** (see *Changed*). NumPy moves to the + optional `numpy`/`test`/`bench` extras. + ### Fixed - **Binance kline feed: add the missing `3d` and `1M` intervals.** The `live-binance` `Interval` enum was missing three-day (`3d`) and one-month (`1M`) diff --git a/Cargo.lock b/Cargo.lock index 3ee2b581..9be5cb53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,12 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "bytes" version = "1.11.1" @@ -716,16 +722,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "matrixmultiply" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" -dependencies = [ - "autocfg", - "rawpointer", -] - [[package]] name = "memchr" version = "2.8.0" @@ -827,21 +823,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "ndarray" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" -dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "portable-atomic", - "portable-atomic-util", - "rawpointer", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -851,24 +832,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -901,22 +864,6 @@ dependencies = [ "syn", ] -[[package]] -name = "numpy" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5b15d63a5ff39e378daed0e1340d3a5964703ea9712eb09a0dc66fade996f4" -dependencies = [ - "libc", - "ndarray", - "num-complex", - "num-integer", - "num-traits", - "pyo3", - "pyo3-build-config", - "rustc-hash", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -1034,15 +981,6 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" -[[package]] -name = "portable-atomic-util" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" -dependencies = [ - "portable-atomic", -] - [[package]] name = "potential_utf" version = "0.1.5" @@ -1214,12 +1152,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - [[package]] name = "rayon" version = "1.12.0" @@ -1269,12 +1201,6 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - [[package]] name = "rustix" version = "1.1.4" @@ -1934,7 +1860,7 @@ dependencies = [ name = "wickra-python" version = "0.9.2" dependencies = [ - "numpy", + "bytemuck", "pyo3", "tokio", "wickra-core", diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index f6121395..be6dcea7 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -24,6 +24,8 @@ workspace = true wickra-core = { workspace = true } wickra-data = { workspace = true, features = ["live-binance"] } pyo3 = { workspace = true } -numpy = { workspace = true } +# Reinterprets `&[f64]` as bytes when building stdlib `array.array('d')` results, +# keeping the binding free of any NumPy runtime dependency. +bytemuck = "1" # Drives the async Binance feed behind the blocking, GIL-releasing poll. tokio = { version = "1", features = ["rt", "net", "time"] } diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml index 56e793dd..709a86d2 100644 --- a/bindings/python/pyproject.toml +++ b/bindings/python/pyproject.toml @@ -25,11 +25,17 @@ classifiers = [ "Topic :: Office/Business :: Financial :: Investment", "Topic :: Scientific/Engineering :: Mathematics", ] -dependencies = [ - "numpy>=1.22", -] +# Zero third-party runtime dependencies: batch results are stdlib `array.array` +# objects (and a buffer-protocol `Matrix` for multi-output indicators), so a plain +# `pip install wickra` pulls nothing else. +dependencies = [] [project.optional-dependencies] +# Optional NumPy interop. Results expose the buffer protocol, so `numpy.asarray(...)` +# wraps them zero-copy when NumPy is installed — but it is never required. +numpy = [ + "numpy>=1.22", +] test = [ "pytest>=7", "numpy>=1.22", diff --git a/bindings/python/python/wickra/__init__.py b/bindings/python/python/wickra/__init__.py index 8e0e1aed..dde16acc 100644 --- a/bindings/python/python/wickra/__init__.py +++ b/bindings/python/python/wickra/__init__.py @@ -1,24 +1,35 @@ """Wickra: streaming-first technical indicators. Every indicator is available both in streaming mode (call ``update(value)`` per -new data point) and batch mode (call ``batch(numpy_array)`` over a full series). -Warmup positions in batch output are returned as ``NaN`` so the shape always +new data point) and batch mode (call ``batch(series)`` over a full series). +Warmup positions in batch output are returned as ``NaN`` so the length always matches the input. +Wickra has **zero third-party dependencies**. Batch inputs accept any sequence or +buffer of numbers (``array.array``, ``memoryview``, a NumPy array, or a plain +``list``); single-output batches return a stdlib ``array.array('d')`` and +multi-output indicators return a ``Matrix`` (with ``.shape`` and ``[i, j]`` +access). Both expose the buffer protocol, so ``numpy.asarray(result)`` wraps a +1-D result zero-copy when NumPy is installed — but NumPy is never required. + Example:: - import numpy as np + import array import wickra as ta - prices = np.linspace(100, 200, 1000) + prices = array.array("d", [100.0 + i * 0.1 for i in range(1000)]) rsi = ta.RSI(14) - values = rsi.batch(prices) # numpy array, NaN during warmup + values = rsi.batch(prices) # array.array('d'), NaN during warmup # Or streaming: rsi = ta.RSI(14) for p in prices: v = rsi.update(p) # None during warmup, then float + # Optional NumPy interop (zero-copy): + # import numpy as np + # np.asarray(values) + """ from __future__ import annotations diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 234e303b..ff83c4ec 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -1,7 +1,10 @@ //! Python bindings for Wickra. Built with `PyO3` and exposed under the `wickra` package. //! //! This module is the thin glue between `wickra-core` and Python. Every indicator -//! has both a streaming class and a batch helper that takes a `NumPy` array. +//! has both a streaming class and a batch helper. Inputs accept any sequence or +//! buffer of numbers (`array.array`, `memoryview`, a `NumPy` array, or a list); +//! results are stdlib `array.array('d')` objects (and a buffer-protocol [`Matrix`] +//! for multi-output indicators), so the package has zero third-party dependencies. #![allow(clippy::needless_pass_by_value)] // Python `__repr__` is an instance method by protocol, so the `&self` parameter is @@ -12,13 +15,162 @@ // (o/h/l/c/v) that match the domain and the NumPy call sites. #![allow(clippy::many_single_char_names)] -use numpy::{IntoPyArray, PyArray1, PyArray2, PyReadonlyArray1}; -use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError}; +use pyo3::exceptions::{PyIndexError, PyRuntimeError, PyTypeError, PyValueError}; use pyo3::prelude::*; -use pyo3::types::PyDict; +use pyo3::types::{PyBytes, PyDict}; +use pyo3::Borrowed; use wickra_core as wc; use wickra_core::{BarBuilder, BatchExt, BatchNanExt, Indicator}; +/// A one-dimensional `f64` input. +/// +/// Accepts `array.array('d')`, `memoryview`, a `NumPy` `ndarray`, or any plain Python +/// sequence of numbers — the same set the previous `NumPy` `PyReadonlyArray1` covered, +/// now without depending on `NumPy`. The values are copied into an owned `Vec` once. +struct Buf1 { + data: Vec, +} + +impl<'py> FromPyObject<'_, 'py> for Buf1 { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> PyResult { + Vec::::extract(obj).map(|data| Self { data }) + } +} + +impl Buf1 { + /// Borrow the values as a slice. The `PyResult` wrapper keeps the ~800 batch call + /// sites uniform with the historical `NumPy` code path; extraction already succeeded. + #[allow(clippy::unnecessary_wraps)] + fn as_slice(&self) -> PyResult<&[f64]> { + Ok(&self.data) + } +} + +/// A one-dimensional `i64` input (e.g. millisecond timestamps for seasonality). +/// +/// Mirrors [`Buf1`]: accepts any `i64` buffer-protocol object or a Python sequence +/// of integers, copied once into an owned `Vec`. +struct BufI64 { + data: Vec, +} + +impl<'py> FromPyObject<'_, 'py> for BufI64 { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> PyResult { + Vec::::extract(obj).map(|data| Self { data }) + } +} + +impl BufI64 { + #[allow(clippy::unnecessary_wraps)] + fn as_slice(&self) -> PyResult<&[i64]> { + Ok(&self.data) + } +} + +/// Build a stdlib `array.array('d')` from a slice of `f64`s. +/// +/// `array.array` is a buffer-protocol object, so `numpy.asarray(result)` wraps it +/// zero-copy for callers who opt into `NumPy` — but importing `NumPy` is never required. +fn f64_array<'py>(py: Python<'py>, data: &[f64]) -> PyResult> { + let bytes = PyBytes::new(py, bytemuck::cast_slice(data)); + py.import("array")?.getattr("array")?.call1(("d", bytes)) +} + +/// A row-major, two-dimensional `f64` result returned by multi-output batch helpers. +/// +/// Backed by a flat, buffer-protocol `array.array('d')`, it preserves the ergonomics +/// of the former `NumPy` return type — `result.shape`, integer row access and +/// `result[i, j]` element access — without depending on `NumPy`. `numpy.asarray(result)` +/// rebuilds an `(nrows, ncols)` array for callers who want one. +#[pyclass(name = "Matrix", module = "wickra._wickra")] +struct Matrix { + data: Vec, + nrows: usize, + ncols: usize, +} + +impl Matrix { + /// Resolve a possibly-negative index against `len`, mirroring Python semantics. + fn resolve(index: isize, len: usize) -> PyResult { + let idx = if index < 0 { + let back = index.unsigned_abs(); + if back > len { + return Err(PyIndexError::new_err("index out of range")); + } + len - back + } else { + index.unsigned_abs() + }; + if idx >= len { + return Err(PyIndexError::new_err("index out of range")); + } + Ok(idx) + } +} + +#[pymethods] +impl Matrix { + #[getter] + fn shape(&self) -> (usize, usize) { + (self.nrows, self.ncols) + } + + fn __len__(&self) -> usize { + self.nrows + } + + fn __getitem__<'py>( + &self, + py: Python<'py>, + key: &Bound<'py, PyAny>, + ) -> PyResult> { + if let Ok((row, col)) = key.extract::<(isize, isize)>() { + let r = Self::resolve(row, self.nrows)?; + let c = Self::resolve(col, self.ncols)?; + return Ok(self.data[r * self.ncols + c].into_pyobject(py)?.into_any()); + } + let row = Self::resolve(key.extract::()?, self.nrows)?; + let start = row * self.ncols; + f64_array(py, &self.data[start..start + self.ncols]) + } + + /// Return the matrix as a list of row lists. + fn tolist(&self) -> Vec> { + self.data.chunks(self.ncols).map(<[f64]>::to_vec).collect() + } + + fn __repr__(&self) -> String { + format!("Matrix(shape=({}, {}))", self.nrows, self.ncols) + } +} + +/// Build a [`Matrix`] from flat row-major data. +fn matrix( + py: Python<'_>, + data: Vec, + nrows: usize, + ncols: usize, +) -> PyResult> { + Ok(Bound::new(py, Matrix { data, nrows, ncols })?.into_any()) +} + +/// Convert an owned `f64` batch result into its Python representation +/// (a buffer-protocol `array.array('d')`), keeping the streaming batch call sites +/// uniform after the `NumPy` return type was dropped. +trait IntoPyData<'py> { + fn into_pydata(self, py: Python<'py>) -> PyResult>; +} + +impl<'py> IntoPyData<'py> for Vec { + fn into_pydata(self, py: Python<'py>) -> PyResult> { + f64_array(py, &self) + } +} + fn map_err(e: wc::Error) -> PyErr { match e { wc::Error::PeriodZero @@ -53,11 +205,11 @@ type ImbalanceBarRows = Vec<(f64, f64, f64, f64, f64, i64)>; type RunBarRows = Vec<(f64, f64, f64, f64, i64, i64)>; /// Extract four equal-length OHLC slices, erroring on non-contiguous or mismatched input. -fn ohlc_slices<'a, 'py>( - open: &'a PyReadonlyArray1<'py, f64>, - high: &'a PyReadonlyArray1<'py, f64>, - low: &'a PyReadonlyArray1<'py, f64>, - close: &'a PyReadonlyArray1<'py, f64>, +fn ohlc_slices<'a>( + open: &'a Buf1, + high: &'a Buf1, + low: &'a Buf1, + close: &'a Buf1, ) -> PyResult> { let o = open .as_slice() @@ -80,12 +232,12 @@ fn ohlc_slices<'a, 'py>( } /// Extract five equal-length OHLCV slices, erroring on non-contiguous or mismatched input. -fn ohlcv_slices<'a, 'py>( - open: &'a PyReadonlyArray1<'py, f64>, - high: &'a PyReadonlyArray1<'py, f64>, - low: &'a PyReadonlyArray1<'py, f64>, - close: &'a PyReadonlyArray1<'py, f64>, - volume: &'a PyReadonlyArray1<'py, f64>, +fn ohlcv_slices<'a>( + open: &'a Buf1, + high: &'a Buf1, + low: &'a Buf1, + close: &'a Buf1, + volume: &'a Buf1, ) -> PyResult> { let (o, h, l, c) = ohlc_slices(open, high, low, close)?; let v = volume @@ -135,15 +287,11 @@ impl PySma { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -190,15 +338,11 @@ impl PyEma { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -249,15 +393,11 @@ impl PyWma { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -305,15 +445,11 @@ impl PyRsi { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -366,19 +502,13 @@ impl PyMacd { } /// Batch over a numpy array of closes. Returns a 2D array of shape `(n, 3)` /// with columns `[macd, signal, histogram]`. Warmup rows are NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; let n = slice.len(); let out = self.inner.batch_macd(slice); - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } #[getter] fn periods(&self) -> (usize, usize, usize) { @@ -431,19 +561,13 @@ impl PyBb { .map(|o| (o.upper, o.middle, o.lower, o.stddev)) } /// Batch returns shape `(n, 4)` columns `[upper, middle, lower, stddev]`. - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; let n = slice.len(); let out = self.inner.batch_bands(slice); - Ok(numpy::ndarray::Array2::from_shape_vec((n, 4), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 4) } #[getter] fn period(&self) -> usize { @@ -532,10 +656,10 @@ impl PyAtr { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -563,7 +687,7 @@ impl PyAtr { )); } } - Ok(self.inner.batch_atr(h, l, c).into_pyarray(py)) + self.inner.batch_atr(h, l, c).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -612,10 +736,10 @@ impl PyPlusDm { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -635,7 +759,7 @@ impl PyPlusDm { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -684,10 +808,10 @@ impl PyMinusDm { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -707,7 +831,7 @@ impl PyMinusDm { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -756,10 +880,10 @@ impl PyPlusDi { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -779,7 +903,7 @@ impl PyPlusDi { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -828,10 +952,10 @@ impl PyMinusDi { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -851,7 +975,7 @@ impl PyMinusDi { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -900,10 +1024,10 @@ impl PyDx { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -923,7 +1047,7 @@ impl PyDx { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -972,10 +1096,10 @@ impl PyMidPrice { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -995,7 +1119,7 @@ impl PyMidPrice { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1039,15 +1163,11 @@ impl PyMidPoint { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1095,11 +1215,11 @@ impl PyAvgPrice { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -1122,7 +1242,7 @@ impl PyAvgPrice { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -1162,15 +1282,11 @@ impl PyRocp { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1214,15 +1330,11 @@ impl PyRocr { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1266,15 +1378,11 @@ impl PyRocr100 { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1322,15 +1430,11 @@ impl PyLinRegIntercept { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1374,15 +1478,11 @@ impl PyTsf { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1431,11 +1531,7 @@ impl PyMacdFix { } /// Batch over a numpy array of closes. Returns a 2D array of shape `(n, 3)` /// with columns `[macd, signal, histogram]`. Warmup rows are NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -1448,9 +1544,7 @@ impl PyMacdFix { out[i * 3 + 2] = o.histogram; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } #[getter] fn signal_period(&self) -> usize { @@ -1528,10 +1622,10 @@ impl PySarExt { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -1551,7 +1645,7 @@ impl PySarExt { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -1620,11 +1714,7 @@ impl PyMacdExt { } /// Batch over a numpy array of closes. Returns a 2D array of shape `(n, 3)` /// with columns `[macd, signal, histogram]`. Warmup rows are NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -1637,9 +1727,7 @@ impl PyMacdExt { out[i * 3 + 2] = o.histogram; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -1681,11 +1769,7 @@ impl PyHtPhasor { } /// Batch over a numpy array of closes. Returns a 2D array of shape `(n, 2)` /// with columns `[inphase, quadrature]`. Warmup rows are NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -1697,9 +1781,7 @@ impl PyHtPhasor { out[i * 2 + 1] = o.quadrature; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -1739,15 +1821,11 @@ impl PyLogReturn { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1795,15 +1873,11 @@ impl PyRealizedVolatility { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1847,15 +1921,11 @@ impl PyRollingIqr { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1903,15 +1973,11 @@ impl PyRollingPercentileRank { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -1959,15 +2025,11 @@ impl PyRollingQuantile { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2023,11 +2085,11 @@ impl PyCloseVsOpen { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -2050,7 +2112,7 @@ impl PyCloseVsOpen { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -2094,11 +2156,11 @@ impl PyBodySizePct { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -2121,7 +2183,7 @@ impl PyBodySizePct { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -2165,11 +2227,11 @@ impl PyWickRatio { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -2192,7 +2254,7 @@ impl PyWickRatio { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -2236,11 +2298,11 @@ impl PyHighLowRange { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -2263,7 +2325,7 @@ impl PyHighLowRange { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -2303,15 +2365,11 @@ impl PyTrendLabel { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2355,15 +2413,11 @@ impl PyJumpIndicator { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2412,15 +2466,11 @@ impl PyRegimeLabel { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn vol_period(&self) -> usize { @@ -2469,15 +2519,11 @@ impl PyWinRate { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2521,15 +2567,11 @@ impl PyExpectancy { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2573,15 +2615,11 @@ impl PySineWeightedMa { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2625,15 +2663,11 @@ impl PyGeometricMa { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2677,15 +2711,11 @@ impl PyEhma { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2729,15 +2759,11 @@ impl PyMedianMa { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2785,15 +2811,11 @@ impl PyAdaptiveLaguerreFilter { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2841,15 +2863,11 @@ impl PyDisparityIndex { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2893,15 +2911,11 @@ impl PyFisherRsi { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -2945,15 +2959,11 @@ impl PyRsx { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn length(&self) -> usize { @@ -3001,15 +3011,11 @@ impl PyDynamicMomentumIndex { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -3058,10 +3064,10 @@ impl PyStochasticCci { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -3081,7 +3087,7 @@ impl PyStochasticCci { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -3130,10 +3136,10 @@ impl PyTtmTrend { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -3153,7 +3159,7 @@ impl PyTtmTrend { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -3201,15 +3207,11 @@ impl PyTrendStrengthIndex { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -3258,9 +3260,9 @@ impl PyQstick { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -3278,7 +3280,7 @@ impl PyQstick { let candle = wc::Candle::new(o[i], hi, lo, c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -3323,15 +3325,11 @@ impl PyPolarizedFractalEfficiency { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -3376,15 +3374,11 @@ impl PyWavePm { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn length(&self) -> usize { @@ -3440,10 +3434,10 @@ impl PyGatorOscillator { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -3467,9 +3461,7 @@ impl PyGatorOscillator { out[i * 2 + 1] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -3516,10 +3508,10 @@ impl PyKasePermissionStochastic { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -3543,9 +3535,7 @@ impl PyKasePermissionStochastic { out[i * 2 + 1] = o.slow; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -3582,15 +3572,11 @@ impl PyTsfOscillator { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -3634,15 +3620,11 @@ impl PyMacdHistogram { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -3683,15 +3665,11 @@ impl PyPpoHistogram { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -3736,15 +3714,11 @@ impl PyBipowerVariation { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -3797,10 +3771,10 @@ impl PyVolatilityRatio { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -3820,7 +3794,7 @@ impl PyVolatilityRatio { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -3873,10 +3847,10 @@ impl PyProjectionOscillator { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -3896,7 +3870,7 @@ impl PyProjectionOscillator { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -3946,10 +3920,10 @@ impl PyTimeBasedStop { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -3969,7 +3943,7 @@ impl PyTimeBasedStop { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn max_bars(&self) -> usize { @@ -4013,15 +3987,11 @@ impl PyJarqueBera { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4065,15 +4035,11 @@ impl PyRollingMinMaxScaler { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4117,15 +4083,11 @@ impl PyHighpassFilter { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4169,15 +4131,11 @@ impl PyReflex { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4221,15 +4179,11 @@ impl PyTrendflex { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4273,15 +4227,11 @@ impl PyCorrelationTrendIndicator { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4325,15 +4275,11 @@ impl PyAdaptiveRsi { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4377,15 +4323,11 @@ impl PyUniversalOscillator { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4434,10 +4376,10 @@ impl PyAdaptiveCci { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -4457,7 +4399,7 @@ impl PyAdaptiveCci { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4501,15 +4443,11 @@ impl PySterlingRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4553,15 +4491,11 @@ impl PyBurkeRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4605,15 +4539,11 @@ impl PyMartinRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4657,15 +4587,11 @@ impl PyTailRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4709,15 +4635,11 @@ impl PyKRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4765,15 +4687,11 @@ impl PyCommonSenseRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4821,15 +4739,11 @@ impl PyGainToPainRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4877,11 +4791,11 @@ impl PyImi { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -4905,7 +4819,7 @@ impl PyImi { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -4949,11 +4863,7 @@ impl PyQqe { } /// Batch over a numpy array of closes. Returns shape `(n, 2)` with columns /// `[rsi_ma, trailing_line]`. Warmup rows are NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -4965,9 +4875,7 @@ impl PyQqe { out[i * 2 + 1] = o.trailing_line; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn factor(&self) -> f64 { @@ -5012,10 +4920,10 @@ impl PyElderRay { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -5039,9 +4947,7 @@ impl PyElderRay { out[i * 2 + 1] = o.bear_power; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn period(&self) -> usize { @@ -5085,10 +4991,10 @@ impl PyStoch { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -5112,9 +5018,7 @@ impl PyStoch { out[i * 2 + 1] = o.d; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn periods(&self) -> (usize, usize) { @@ -5163,9 +5067,9 @@ impl PyObv { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -5182,7 +5086,7 @@ impl PyObv { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn value(&self) -> Option { @@ -5225,15 +5129,11 @@ impl PyDema { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -5276,15 +5176,11 @@ impl PyTema { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -5327,15 +5223,11 @@ impl PyHma { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -5379,15 +5271,11 @@ impl PyKama { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -5431,11 +5319,11 @@ impl PyInertia { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -5458,7 +5346,7 @@ impl PyInertia { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -5499,15 +5387,11 @@ impl PyConnorsRsi { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -5548,15 +5432,11 @@ impl PyLaguerreRsi { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn gamma(&self) -> f64 { @@ -5604,10 +5484,10 @@ impl PySmi { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -5627,7 +5507,7 @@ impl PySmi { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -5686,11 +5566,7 @@ impl PyKst { fn update(&mut self, value: f64) -> Option<(f64, f64)> { self.inner.update(value).map(|o| (o.kst, o.signal)) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -5702,9 +5578,7 @@ impl PyKst { out[i * 2 + 1] = o.signal; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -5748,10 +5622,10 @@ impl PyPgo { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -5771,7 +5645,7 @@ impl PyPgo { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -5819,11 +5693,11 @@ impl PyRvi { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -5846,7 +5720,7 @@ impl PyRvi { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -5890,15 +5764,11 @@ impl PyFrama { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -5946,9 +5816,9 @@ impl PyEvwma { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -5965,7 +5835,7 @@ impl PyEvwma { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -6013,9 +5883,9 @@ impl PyAlligator { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -6035,9 +5905,7 @@ impl PyAlligator { out[i * 3 + 2] = o.lips; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -6078,15 +5946,11 @@ impl PyJma { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -6127,15 +5991,11 @@ impl PyVidya { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -6180,15 +6040,11 @@ impl PyMcGinleyDynamic { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -6232,15 +6088,11 @@ impl PyAlma { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -6305,9 +6157,9 @@ impl PyAoHist { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -6322,7 +6174,7 @@ impl PyAoHist { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -6363,15 +6215,11 @@ impl PyStc { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -6418,15 +6266,11 @@ impl PyElderImpulse { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -6469,11 +6313,7 @@ impl PyZeroLagMacd { .update(value) .map(|o| (o.macd, o.signal, o.histogram)) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -6486,9 +6326,7 @@ impl PyZeroLagMacd { out[i * 3 + 2] = o.histogram; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -6529,15 +6367,11 @@ impl PyCfo { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -6581,15 +6415,11 @@ impl PyApo { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -6634,10 +6464,10 @@ impl PyCci { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -6657,7 +6487,7 @@ impl PyCci { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -6701,15 +6531,11 @@ impl PyRoc { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -6757,10 +6583,10 @@ impl PyWilliamsR { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -6780,7 +6606,7 @@ impl PyWilliamsR { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -6823,10 +6649,10 @@ impl PyAdx { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -6851,9 +6677,7 @@ impl PyAdx { out[i * 3 + 2] = o.adx; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -6894,10 +6718,10 @@ impl PyAdxr { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -6920,7 +6744,7 @@ impl PyAdxr { out[i] = v; } } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -6972,11 +6796,11 @@ impl PyMfi { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -6999,7 +6823,7 @@ impl PyMfi { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -7036,15 +6860,11 @@ impl PyTrix { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -7085,10 +6905,10 @@ impl PyPsar { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -7108,7 +6928,7 @@ impl PyPsar { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -7151,10 +6971,10 @@ impl PyKeltner { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -7179,9 +6999,7 @@ impl PyKeltner { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -7222,9 +7040,9 @@ impl PyDonchian { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -7244,9 +7062,7 @@ impl PyDonchian { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -7286,11 +7102,11 @@ impl PyVwap { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -7313,7 +7129,7 @@ impl PyVwap { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -7353,11 +7169,11 @@ impl PyRollingVwap { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -7380,7 +7196,7 @@ impl PyRollingVwap { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -7432,9 +7248,9 @@ impl PyAo { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -7449,7 +7265,7 @@ impl PyAo { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -7490,9 +7306,9 @@ impl PyAroon { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -7511,9 +7327,7 @@ impl PyAroon { out[i * 2 + 1] = o.down; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -7554,11 +7368,11 @@ impl PyAdl { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -7581,7 +7395,7 @@ impl PyAdl { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn value(&self) -> Option { @@ -7633,9 +7447,9 @@ impl PyVolumePriceTrend { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -7652,7 +7466,7 @@ impl PyVolumePriceTrend { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn value(&self) -> Option { @@ -7700,15 +7514,11 @@ impl PyBollingerBandwidth { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -7764,15 +7574,11 @@ impl PyPercentB { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -7833,10 +7639,10 @@ impl PyNatr { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -7856,7 +7662,7 @@ impl PyNatr { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -7904,15 +7710,11 @@ impl PyStdDev { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -7960,15 +7762,11 @@ impl PyUlcerIndex { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -8020,15 +7818,11 @@ impl PyHistoricalVolatility { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -8086,9 +7880,9 @@ impl PyAroonOscillator { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -8103,7 +7897,7 @@ impl PyAroonOscillator { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -8157,10 +7951,10 @@ impl PyVortex { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -8184,9 +7978,7 @@ impl PyVortex { out[i * 2 + 1] = o.minus; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn period(&self) -> usize { @@ -8236,10 +8028,10 @@ impl PyRwi { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -8263,9 +8055,7 @@ impl PyRwi { out[i * 2 + 1] = o.low; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn period(&self) -> usize { @@ -8320,10 +8110,10 @@ impl PyWaveTrend { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -8347,9 +8137,7 @@ impl PyWaveTrend { out[i * 2 + 1] = o.wt2; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn periods(&self) -> (usize, usize, usize) { @@ -8399,9 +8187,9 @@ impl PyMassIndex { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -8416,7 +8204,7 @@ impl PyMassIndex { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -8465,15 +8253,11 @@ impl PyPpo { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -8522,15 +8306,11 @@ impl PyDpo { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -8582,15 +8362,11 @@ impl PyCoppock { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize, usize) { @@ -8639,15 +8415,11 @@ impl PyStochRsi { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -8705,10 +8477,10 @@ impl PyUltimateOscillator { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -8728,7 +8500,7 @@ impl PyUltimateOscillator { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize, usize) { @@ -8777,15 +8549,11 @@ impl PyMom { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -8833,15 +8601,11 @@ impl PyCmo { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -8889,15 +8653,11 @@ impl PyTsi { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -8946,15 +8706,11 @@ impl PyPmo { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -9003,15 +8759,11 @@ impl PyTii { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -9059,15 +8811,11 @@ impl PyZlema { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -9119,15 +8867,11 @@ impl PyT3 { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -9183,15 +8927,11 @@ impl PyGeneralizedDema { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -9243,15 +8983,11 @@ impl PyHoltWinters { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn alpha(&self) -> f64 { @@ -9315,15 +9051,11 @@ impl PyRmi { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -9385,15 +9117,11 @@ impl PyDerivativeOscillator { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -9434,9 +9162,9 @@ impl PyVwma { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -9453,7 +9181,7 @@ impl PyVwma { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -9500,15 +9228,11 @@ impl PySmma { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -9555,15 +9279,11 @@ impl PyTrima { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -9620,11 +9340,11 @@ impl PyChaikinMoneyFlow { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -9647,7 +9367,7 @@ impl PyChaikinMoneyFlow { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -9700,11 +9420,11 @@ impl PyChaikinOscillator { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -9727,7 +9447,7 @@ impl PyChaikinOscillator { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -9777,9 +9497,9 @@ impl PyForceIndex { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -9796,7 +9516,7 @@ impl PyForceIndex { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -9845,9 +9565,9 @@ impl PyNvi { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -9864,7 +9584,7 @@ impl PyNvi { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -9908,9 +9628,9 @@ impl PyPvi { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -9927,7 +9647,7 @@ impl PyPvi { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -9973,11 +9693,7 @@ impl PyVolumeOscillator { Ok(self.inner.update(c)) } /// Batch over a 1-D numpy volume array. - fn batch<'py>( - &mut self, - py: Python<'py>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, volume: Buf1) -> PyResult> { let v = volume .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -9986,7 +9702,7 @@ impl PyVolumeOscillator { let candle = wc::Candle::new(10.0, 10.0, 10.0, 10.0, vol, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -10036,11 +9752,11 @@ impl PyKvo { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10063,7 +9779,7 @@ impl PyKvo { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -10112,10 +9828,10 @@ impl PyAdOscillator { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10135,7 +9851,7 @@ impl PyAdOscillator { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -10179,15 +9895,11 @@ impl PyAnchoredRsi { self.inner.set_anchor(); } /// Batch over a close-price numpy column. - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn value(&self) -> Option { @@ -10239,11 +9951,11 @@ impl PyAnchoredVwap { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10266,7 +9978,7 @@ impl PyAnchoredVwap { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -10311,11 +10023,11 @@ impl PyDemandIndex { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10338,7 +10050,7 @@ impl PyDemandIndex { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -10387,9 +10099,9 @@ impl PyTsv { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10406,7 +10118,7 @@ impl PyTsv { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -10455,9 +10167,9 @@ impl PyVzo { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10474,7 +10186,7 @@ impl PyVzo { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -10526,10 +10238,10 @@ impl PyMarketFacilitationIndex { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10549,7 +10261,7 @@ impl PyMarketFacilitationIndex { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -10598,10 +10310,10 @@ impl PyEaseOfMovement { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10621,7 +10333,7 @@ impl PyEaseOfMovement { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -10679,10 +10391,10 @@ impl PySuperTrend { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10706,9 +10418,7 @@ impl PySuperTrend { out[i * 2 + 1] = o.direction; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn params(&self) -> (usize, f64) { @@ -10763,10 +10473,10 @@ impl PyChandelierExit { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10790,9 +10500,7 @@ impl PyChandelierExit { out[i * 2 + 1] = o.short_stop; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn params(&self) -> (usize, f64) { @@ -10848,10 +10556,10 @@ impl PyChandeKrollStop { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10875,9 +10583,7 @@ impl PyChandeKrollStop { out[i * 2 + 1] = o.stop_short; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn params(&self) -> (usize, f64, usize) { @@ -10933,10 +10639,10 @@ impl PyAtrTrailingStop { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -10956,7 +10662,7 @@ impl PyAtrTrailingStop { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn params(&self) -> (usize, f64) { @@ -11005,10 +10711,10 @@ impl PyHiLoActivator { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11028,7 +10734,7 @@ impl PyHiLoActivator { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -11076,10 +10782,10 @@ impl PyVoltyStop { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11099,7 +10805,7 @@ impl PyVoltyStop { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn params(&self) -> (usize, f64) { @@ -11148,10 +10854,10 @@ impl PyYoyoExit { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11171,7 +10877,7 @@ impl PyYoyoExit { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn params(&self) -> (usize, f64) { @@ -11224,9 +10930,9 @@ impl PyDonchianStop { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11245,9 +10951,7 @@ impl PyDonchianStop { out[i * 2 + 1] = o.stop_short; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn period(&self) -> usize { @@ -11295,15 +10999,11 @@ impl PyPercentageTrailingStop { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn percent(&self) -> f64 { @@ -11351,15 +11051,11 @@ impl PyStepTrailingStop { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn step_size(&self) -> f64 { @@ -11407,15 +11103,11 @@ impl PyRenkoTrailingStop { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn block_size(&self) -> f64 { @@ -11465,10 +11157,10 @@ impl PyKaseDevStop { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11492,9 +11184,7 @@ impl PyKaseDevStop { out[i * 2 + 1] = o.direction; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn params(&self) -> (usize, f64) { @@ -11545,10 +11235,10 @@ impl PyElderSafeZone { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11572,9 +11262,7 @@ impl PyElderSafeZone { out[i * 2 + 1] = o.direction; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn params(&self) -> (usize, f64) { @@ -11625,10 +11313,10 @@ impl PyAtrRatchet { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11652,9 +11340,7 @@ impl PyAtrRatchet { out[i * 2 + 1] = o.direction; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn params(&self) -> (usize, f64, f64) { @@ -11707,10 +11393,10 @@ impl PyNrtr { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11734,9 +11420,7 @@ impl PyNrtr { out[i * 2 + 1] = o.direction; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn pct(&self) -> f64 { @@ -11790,10 +11474,10 @@ impl PyModifiedMaStop { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11817,9 +11501,7 @@ impl PyModifiedMaStop { out[i * 2 + 1] = o.direction; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn period(&self) -> usize { @@ -11867,10 +11549,10 @@ impl PyTypicalPrice { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11890,7 +11572,7 @@ impl PyTypicalPrice { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -11934,9 +11616,9 @@ impl PyMedianPrice { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -11951,7 +11633,7 @@ impl PyMedianPrice { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -11995,10 +11677,10 @@ impl PyWeightedClose { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -12018,7 +11700,7 @@ impl PyWeightedClose { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -12062,15 +11744,11 @@ impl PyLinearRegression { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -12114,15 +11792,11 @@ impl PyLinRegSlope { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -12176,9 +11850,9 @@ impl PyAcceleratorOscillator { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -12193,7 +11867,7 @@ impl PyAcceleratorOscillator { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn params(&self) -> (usize, usize, usize) { @@ -12246,11 +11920,11 @@ impl PyBalanceOfPower { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -12273,7 +11947,7 @@ impl PyBalanceOfPower { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -12322,10 +11996,10 @@ impl PyChoppinessIndex { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -12345,7 +12019,7 @@ impl PyChoppinessIndex { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -12393,15 +12067,11 @@ impl PyVerticalHorizontalFilter { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -12449,10 +12119,10 @@ impl PyTrueRange { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -12472,7 +12142,7 @@ impl PyTrueRange { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -12521,9 +12191,9 @@ impl PyChaikinVolatility { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -12538,7 +12208,7 @@ impl PyChaikinVolatility { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -12583,15 +12253,11 @@ impl PyZScore { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -12635,15 +12301,11 @@ impl PyLinRegAngle { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -12694,11 +12356,11 @@ impl PyYangZhangVolatility { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -12721,7 +12383,7 @@ impl PyYangZhangVolatility { let candle = wc::Candle::new(o[i], h[i], l[i], cl[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -12779,11 +12441,11 @@ impl PyRogersSatchellVolatility { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -12806,7 +12468,7 @@ impl PyRogersSatchellVolatility { let candle = wc::Candle::new(o[i], h[i], l[i], cl[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -12860,11 +12522,11 @@ impl PyGarmanKlassVolatility { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -12887,7 +12549,7 @@ impl PyGarmanKlassVolatility { let candle = wc::Candle::new(o[i], h[i], l[i], cl[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -12941,9 +12603,9 @@ impl PyParkinsonVolatility { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -12958,7 +12620,7 @@ impl PyParkinsonVolatility { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -13007,15 +12669,11 @@ impl PyRviVolatility { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -13067,11 +12725,7 @@ impl PyMaEnvelope { .map(|o| (o.upper, o.middle, o.lower)) } /// Batch returns shape `(n, 3)` columns `[upper, middle, lower]`. - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13084,9 +12738,7 @@ impl PyMaEnvelope { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13131,10 +12783,10 @@ impl PyAccelerationBands { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13159,9 +12811,7 @@ impl PyAccelerationBands { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13202,10 +12852,10 @@ impl PyStarcBands { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13230,9 +12880,7 @@ impl PyStarcBands { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13273,10 +12921,10 @@ impl PyAtrBands { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13301,9 +12949,7 @@ impl PyAtrBands { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13344,10 +12990,10 @@ impl PyHurstChannel { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13372,9 +13018,7 @@ impl PyHurstChannel { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13413,11 +13057,7 @@ impl PyLinRegChannel { .update(value) .map(|o| (o.upper, o.middle, o.lower)) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13430,9 +13070,7 @@ impl PyLinRegChannel { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13475,11 +13113,7 @@ impl PyStandardErrorBands { .update(value) .map(|o| (o.upper, o.middle, o.lower)) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13492,9 +13126,7 @@ impl PyStandardErrorBands { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13533,11 +13165,7 @@ impl PyQuartileBands { .update(value) .map(|o| (o.upper, o.middle, o.lower)) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13550,9 +13178,7 @@ impl PyQuartileBands { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13591,11 +13217,7 @@ impl PyBomarBands { .update(value) .map(|o| (o.upper, o.middle, o.lower)) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13608,9 +13230,7 @@ impl PyBomarBands { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13649,11 +13269,7 @@ impl PyMedianChannel { .update(value) .map(|o| (o.upper, o.middle, o.lower)) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13666,9 +13282,7 @@ impl PyMedianChannel { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13713,9 +13327,9 @@ impl PyProjectionBands { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13735,9 +13349,7 @@ impl PyProjectionBands { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13782,10 +13394,10 @@ impl PyCentralPivotRange { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13810,9 +13422,7 @@ impl PyCentralPivotRange { out[i * 3 + 2] = o.bc; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -13866,9 +13476,9 @@ impl PyMurreyMathLines { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13894,9 +13504,7 @@ impl PyMurreyMathLines { out[i * 9 + 8] = o.mm0_8; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 9), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 9) } fn reset(&mut self) { self.inner.reset(); @@ -13942,9 +13550,9 @@ impl PyAndrewsPitchfork { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -13964,9 +13572,7 @@ impl PyAndrewsPitchfork { out[i * 3 + 2] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -14012,10 +13618,10 @@ impl PyVolumeWeightedSr { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14039,9 +13645,7 @@ impl PyVolumeWeightedSr { out[i * 2 + 1] = o.resistance; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -14083,10 +13687,10 @@ impl PyPivotReversal { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14106,7 +13710,7 @@ impl PyPivotReversal { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -14158,11 +13762,7 @@ impl PyDoubleBollinger { } /// Returns shape `(n, 5)` columns /// `[upper_outer, upper_inner, middle, lower_inner, lower_outer]`. - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14177,9 +13777,7 @@ impl PyDoubleBollinger { out[i * 5 + 4] = o.lower_outer; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 5), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 5) } fn reset(&mut self) { self.inner.reset(); @@ -14223,10 +13821,10 @@ impl PyTtmSqueeze { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14250,9 +13848,7 @@ impl PyTtmSqueeze { out[i * 2 + 1] = o.momentum; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -14299,9 +13895,9 @@ impl PyFractalChaosBands { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14320,9 +13916,7 @@ impl PyFractalChaosBands { out[i * 2 + 1] = o.lower; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -14373,11 +13967,11 @@ impl PyVwapStdDevBands { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14406,9 +14000,7 @@ impl PyVwapStdDevBands { out[i * 4 + 3] = o.stddev; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 4), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 4) } fn reset(&mut self) { self.inner.reset(); @@ -14454,10 +14046,10 @@ impl PyClassicPivots { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14486,9 +14078,7 @@ impl PyClassicPivots { out[i * 7 + 6] = o.s3; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 7), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 7) } fn reset(&mut self) { self.inner.reset(); @@ -14535,10 +14125,10 @@ impl PyFibonacciPivots { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14567,9 +14157,7 @@ impl PyFibonacciPivots { out[i * 7 + 6] = o.s3; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 7), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 7) } fn reset(&mut self) { self.inner.reset(); @@ -14619,10 +14207,10 @@ impl PyCamarilla { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14653,9 +14241,7 @@ impl PyCamarilla { out[i * 9 + 8] = o.s4; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 9), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 9) } fn reset(&mut self) { self.inner.reset(); @@ -14698,10 +14284,10 @@ impl PyWoodiePivots { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14728,9 +14314,7 @@ impl PyWoodiePivots { out[i * 5 + 4] = o.s2; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 5), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 5) } fn reset(&mut self) { self.inner.reset(); @@ -14773,11 +14357,11 @@ impl PyDemarkPivots { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14805,9 +14389,7 @@ impl PyDemarkPivots { out[i * 3 + 2] = v.s1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -14860,9 +14442,9 @@ impl PyWilliamsFractals { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14885,9 +14467,7 @@ impl PyWilliamsFractals { } } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -14932,9 +14512,9 @@ impl PyZigZag { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -14953,9 +14533,7 @@ impl PyZigZag { out[i * 2 + 1] = o.direction; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn threshold(&self) -> f64 { @@ -14999,10 +14577,10 @@ impl PyTdSetup { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15022,7 +14600,7 @@ impl PyTdSetup { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -15087,10 +14665,10 @@ impl PyTdSequential { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15115,9 +14693,7 @@ impl PyTdSequential { out[i * 3 + 2] = o.direction; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -15158,9 +14734,9 @@ impl PyTdDeMarker { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15175,7 +14751,7 @@ impl PyTdDeMarker { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -15227,9 +14803,9 @@ impl PyTdRei { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15244,7 +14820,7 @@ impl PyTdRei { let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -15297,12 +14873,12 @@ impl PyTdPressure { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15328,7 +14904,7 @@ impl PyTdPressure { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -15391,10 +14967,10 @@ impl PyTdCombo { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15414,7 +14990,7 @@ impl PyTdCombo { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -15455,10 +15031,10 @@ impl PyTdDWave { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15478,7 +15054,7 @@ impl PyTdDWave { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -15524,9 +15100,9 @@ impl PyTdMovingAverage { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15545,9 +15121,7 @@ impl PyTdMovingAverage { out[i * 2 + 1] = o.st2; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -15599,10 +15173,10 @@ impl PyTdCountdown { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15622,7 +15196,7 @@ impl PyTdCountdown { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -15666,10 +15240,10 @@ impl PyTdLines { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15693,9 +15267,7 @@ impl PyTdLines { out[i * 2 + 1] = o.support; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -15741,11 +15313,11 @@ impl PyTdRangeProjection { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15772,9 +15344,7 @@ impl PyTdRangeProjection { out[i * 2 + 1] = p.low; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -15818,10 +15388,10 @@ impl PyTdDifferential { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15841,7 +15411,7 @@ impl PyTdDifferential { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -15881,11 +15451,11 @@ impl PyTdOpen { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15908,7 +15478,7 @@ impl PyTdOpen { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -15952,10 +15522,10 @@ impl PyTdRiskLevel { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -15979,9 +15549,7 @@ impl PyTdRiskLevel { out[i * 2 + 1] = o.sell_risk; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -16019,15 +15587,11 @@ macro_rules! py_scalar_one_period { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -16093,15 +15657,11 @@ impl PyInverseFisherTransform { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn scale(&self) -> f64 { @@ -16152,15 +15712,11 @@ impl PyDecyclerOscillator { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -16205,15 +15761,11 @@ impl PyRoofingFilter { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { @@ -16262,15 +15814,11 @@ impl PyEmd { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -16323,15 +15871,11 @@ macro_rules! py_no_params_scalar { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn value(&self) -> Option { @@ -16385,15 +15929,11 @@ impl PySineWave { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn value(&self) -> Option { @@ -16443,11 +15983,7 @@ impl PyMama { self.inner.update(value).map(|o| (o.mama, o.fama)) } /// Batch returns shape `(n, 2)` columns `[mama, fama]`. Warmup rows NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -16459,9 +15995,7 @@ impl PyMama { out[i * 2 + 1] = o.fama; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn limits(&self) -> (f64, f64) { @@ -16506,15 +16040,11 @@ impl PyFama { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn limits(&self) -> (f64, f64) { @@ -16581,10 +16111,10 @@ impl PyIchimoku { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -16621,9 +16151,7 @@ impl PyIchimoku { } } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 5), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 5) } #[getter] fn periods(&self) -> (usize, usize, usize, usize) { @@ -16677,11 +16205,11 @@ impl PyHeikinAshi { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -16710,9 +16238,7 @@ impl PyHeikinAshi { out[i * 4 + 3] = v.close; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 4), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 4) } fn reset(&mut self) { self.inner.reset(); @@ -16750,15 +16276,11 @@ impl PyVariance { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -16810,11 +16332,11 @@ impl PyHeikinAshiOscillator { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -16837,7 +16359,7 @@ impl PyHeikinAshiOscillator { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -16882,10 +16404,10 @@ impl PyThreeLineBreak { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -16905,7 +16427,7 @@ impl PyThreeLineBreak { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -16954,11 +16476,11 @@ impl PySmoothedHeikinAshi { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -16987,9 +16509,7 @@ impl PySmoothedHeikinAshi { out[i * 4 + 3] = v.close; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 4), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 4) } fn reset(&mut self) { self.inner.reset(); @@ -17031,10 +16551,10 @@ impl PyEquivolume { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -17058,9 +16578,7 @@ impl PyEquivolume { out[i * 2 + 1] = o.width; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -17102,10 +16620,10 @@ impl PyCandleVolume { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -17131,9 +16649,7 @@ impl PyCandleVolume { out[i * 2 + 1] = v.width; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -17174,10 +16690,10 @@ impl PyFryPanBottom { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -17197,7 +16713,7 @@ impl PyFryPanBottom { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -17238,10 +16754,10 @@ impl PyDumplingTop { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -17261,7 +16777,7 @@ impl PyDumplingTop { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -17302,10 +16818,10 @@ impl PyNewPriceLines { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -17325,7 +16841,7 @@ impl PyNewPriceLines { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -17366,15 +16882,11 @@ impl PyCoefficientOfVariation { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17418,15 +16930,11 @@ impl PySkewness { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17470,15 +16978,11 @@ impl PyKurtosis { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17522,15 +17026,11 @@ impl PyStandardError { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17578,15 +17078,11 @@ impl PyDetrendedStdDev { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17630,15 +17126,11 @@ impl PyRSquared { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17686,15 +17178,11 @@ impl PyAutocorrelation { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17750,15 +17238,11 @@ impl PyMedianAbsoluteDeviation { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17802,15 +17286,11 @@ impl PyHurstExponent { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let s = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(s).into_pyarray(py)) + self.inner.batch_nan(s).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17867,12 +17347,7 @@ impl PyPearsonCorrelation { self.inner.update((x, y)) } /// Batch over two equally-sized numpy arrays. - fn batch<'py>( - &mut self, - py: Python<'py>, - x: PyReadonlyArray1<'py, f64>, - y: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, x: Buf1, y: Buf1) -> PyResult> { let xs = x .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -17886,7 +17361,7 @@ impl PyPearsonCorrelation { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17934,9 +17409,9 @@ impl PyBeta { fn batch<'py>( &mut self, py: Python<'py>, - asset: PyReadonlyArray1<'py, f64>, - benchmark: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + asset: Buf1, + benchmark: Buf1, + ) -> PyResult> { let a = asset .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -17952,7 +17427,7 @@ impl PyBeta { for i in 0..a.len() { out.push(self.inner.update((a[i], b[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -17997,12 +17472,7 @@ impl PyPairwiseBeta { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays of prices: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18016,7 +17486,7 @@ impl PyPairwiseBeta { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -18065,12 +17535,7 @@ impl PySpreadAr1Coefficient { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays of prices: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18084,7 +17549,7 @@ impl PySpreadAr1Coefficient { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -18133,12 +17598,7 @@ impl PyPairSpreadZScore { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays of prices: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18152,7 +17612,7 @@ impl PyPairSpreadZScore { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn beta_period(&self) -> usize { @@ -18212,12 +17672,7 @@ impl PyLeadLagCrossCorrelation { } /// Batch over two equally-sized numpy arrays. Returns a 2D array of shape /// `(n, 2)` with columns `[lag, correlation]`. Warmup rows are NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18235,9 +17690,7 @@ impl PyLeadLagCrossCorrelation { out[i * 2 + 1] = o.correlation; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn window(&self) -> usize { @@ -18295,12 +17748,7 @@ impl PyCointegration { /// Batch over two equally-sized numpy arrays. Returns a 2D array of shape /// `(n, 3)` with columns `[hedge_ratio, spread, adf_stat]`. Warmup rows are /// NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18319,9 +17767,7 @@ impl PyCointegration { out[i * 3 + 2] = o.adf_stat; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } #[getter] fn period(&self) -> usize { @@ -18383,12 +17829,7 @@ impl PyRelativeStrengthAB { /// Batch over two equally-sized numpy arrays. Returns a 2D array of shape /// `(n, 3)` with columns `[ratio, ratio_ma, ratio_rsi]`. Warmup rows are /// NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18407,9 +17848,7 @@ impl PyRelativeStrengthAB { out[i * 3 + 2] = o.ratio_rsi; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } #[getter] fn ma_period(&self) -> usize { @@ -18466,12 +17905,7 @@ impl PyRollingCorrelation { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18485,7 +17919,7 @@ impl PyRollingCorrelation { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -18534,12 +17968,7 @@ impl PyHasbrouckInformationShare { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18553,7 +17982,7 @@ impl PyHasbrouckInformationShare { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -18602,12 +18031,7 @@ impl PyRollingCovariance { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18621,7 +18045,7 @@ impl PyRollingCovariance { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -18666,12 +18090,7 @@ impl PyOuHalfLife { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18685,7 +18104,7 @@ impl PyOuHalfLife { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -18730,12 +18149,7 @@ impl PySpreadHurst { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18749,7 +18163,7 @@ impl PySpreadHurst { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -18794,12 +18208,7 @@ impl PyDistanceSsd { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18813,7 +18222,7 @@ impl PyDistanceSsd { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -18862,12 +18271,7 @@ impl PyBetaNeutralSpread { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18881,7 +18285,7 @@ impl PyBetaNeutralSpread { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -18926,12 +18330,7 @@ impl PyVarianceRatio { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -18945,7 +18344,7 @@ impl PyVarianceRatio { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -19002,12 +18401,7 @@ impl PyGrangerCausality { self.inner.update((a, b)) } /// Batch over two equally-sized numpy arrays: `a` and `b`. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19021,7 +18415,7 @@ impl PyGrangerCausality { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -19083,12 +18477,7 @@ impl PyKalmanHedgeRatio { /// Batch over two equally-sized numpy arrays. Returns a 2D array of shape /// `(n, 3)` with columns `[hedge_ratio, intercept, spread]`. Warmup rows are /// NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19107,9 +18496,7 @@ impl PyKalmanHedgeRatio { out[i * 3 + 2] = o.spread; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } #[getter] fn delta(&self) -> f64 { @@ -19171,12 +18558,7 @@ impl PySpreadBollingerBands { /// Batch over two equally-sized numpy arrays. Returns a 2D array of shape /// `(n, 4)` with columns `[middle, upper, lower, percent_b]`. Warmup rows are /// NaN. - fn batch<'py>( - &mut self, - py: Python<'py>, - a: PyReadonlyArray1<'py, f64>, - b: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, a: Buf1, b: Buf1) -> PyResult> { let xs = a .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19196,9 +18578,7 @@ impl PySpreadBollingerBands { out[i * 4 + 3] = o.percent_b; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 4), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 4) } #[getter] fn period(&self) -> usize { @@ -19255,12 +18635,7 @@ impl PySpearmanCorrelation { self.inner.update((x, y)) } /// Batch over two equally-sized numpy arrays. - fn batch<'py>( - &mut self, - py: Python<'py>, - x: PyReadonlyArray1<'py, f64>, - y: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, x: Buf1, y: Buf1) -> PyResult> { let xs = x .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19274,7 +18649,7 @@ impl PySpearmanCorrelation { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -19324,10 +18699,10 @@ impl PyValueArea { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19354,9 +18729,7 @@ impl PyValueArea { out[i * 3 + 2] = o.val; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } #[getter] fn params(&self) -> (usize, usize, f64) { @@ -19386,7 +18759,7 @@ impl PyValueArea { /// Streaming profile output: `(price_low, price_high, per_bin_values)`, or `None` /// during warmup. Shared by `VolumeProfile` (volume bins) and `TpoProfile` (TPO /// counts). -type ProfileHistogram<'py> = Option<(f64, f64, Bound<'py, PyArray1>)>; +type ProfileHistogram<'py> = Option<(f64, f64, Bound<'py, PyAny>)>; #[pyclass(name = "VolumeProfile", module = "wickra._wickra", skip_from_py_object)] #[derive(Clone)] @@ -19410,20 +18783,20 @@ impl PyVolumeProfile { candle: &Bound<'_, PyAny>, ) -> PyResult> { let c = extract_candle(candle)?; - Ok(self - .inner + self.inner .update(c) - .map(|o| (o.price_low, o.price_high, o.bins.into_pyarray(py)))) + .map(|o| PyResult::Ok((o.price_low, o.price_high, f64_array(py, &o.bins)?))) + .transpose() } /// Batch over numpy columns high, low, volume. Returns shape `(n, bin_count + 2)` /// with columns `[price_low, price_high, bin_0, ..., bin_{k-1}]`; warmup rows are `NaN`. fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19452,9 +18825,7 @@ impl PyVolumeProfile { } } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, k), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, k) } #[getter] fn params(&self) -> (usize, usize) { @@ -19503,19 +18874,19 @@ impl PyTpoProfile { candle: &Bound<'_, PyAny>, ) -> PyResult> { let c = extract_candle(candle)?; - Ok(self - .inner + self.inner .update(c) - .map(|o| (o.price_low, o.price_high, o.counts.into_pyarray(py)))) + .map(|o| PyResult::Ok((o.price_low, o.price_high, f64_array(py, &o.counts)?))) + .transpose() } /// Batch over numpy columns high, low. Returns shape `(n, bin_count + 2)` /// with columns `[price_low, price_high, count_0, ..., count_{k-1}]`; warmup rows are `NaN`. fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19539,9 +18910,7 @@ impl PyTpoProfile { } } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, k), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, k) } #[getter] fn params(&self) -> (usize, usize) { @@ -19596,9 +18965,9 @@ impl PyInitialBalance { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19618,9 +18987,7 @@ impl PyInitialBalance { out[i * 2 + 1] = o.low; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn period(&self) -> usize { @@ -19676,10 +19043,10 @@ impl PyOpeningRange { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19704,9 +19071,7 @@ impl PyOpeningRange { out[i * 3 + 2] = o.breakout_distance; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } #[getter] fn period(&self) -> usize { @@ -19757,11 +19122,11 @@ impl PyNakedPoc { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19784,7 +19149,7 @@ impl PyNakedPoc { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -19829,9 +19194,9 @@ impl PySinglePrints { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19847,7 +19212,7 @@ impl PySinglePrints { let candle = wc::Candle::new(mid, h[i], l[i], mid, 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -19892,10 +19257,10 @@ impl PyProfileShape { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19916,7 +19281,7 @@ impl PyProfileShape { let candle = wc::Candle::new(mid, h[i], l[i], mid, v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -19965,10 +19330,10 @@ impl PyHighLowVolumeNodes { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -19993,9 +19358,7 @@ impl PyHighLowVolumeNodes { out[i * 2 + 1] = o.lvn; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -20044,10 +19407,10 @@ impl PyCompositeProfile { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -20073,9 +19436,7 @@ impl PyCompositeProfile { out[i * 3 + 2] = o.val; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -20126,11 +19487,11 @@ macro_rules! candle_pattern_no_param { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -20154,7 +19515,7 @@ macro_rules! candle_pattern_no_param { wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -20203,11 +19564,11 @@ impl PyDoji { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let o = open .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -20230,7 +19591,7 @@ impl PyDoji { let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -20455,13 +19816,13 @@ macro_rules! py_ob_indicator { &mut self, py: Python<'py>, snapshots: Vec<(Vec, Vec, Vec, Vec)>, - ) -> PyResult>> { + ) -> PyResult> { let mut out = Vec::with_capacity(snapshots.len()); for (bid_px, bid_sz, ask_px, ask_sz) in &snapshots { let book = build_order_book(bid_px, bid_sz, ask_px, ask_sz)?; out.push(self.inner.update(book).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -20531,13 +19892,13 @@ impl PyOrderBookImbalanceTopN { &mut self, py: Python<'py>, snapshots: Vec<(Vec, Vec, Vec, Vec)>, - ) -> PyResult>> { + ) -> PyResult> { let mut out = Vec::with_capacity(snapshots.len()); for (bid_px, bid_sz, ask_px, ask_sz) in &snapshots { let book = build_order_book(bid_px, bid_sz, ask_px, ask_sz)?; out.push(self.inner.update(book).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -20597,7 +19958,7 @@ macro_rules! py_trade_indicator { price: Vec, size: Vec, is_buy: Vec, - ) -> PyResult>> { + ) -> PyResult> { if price.len() != size.len() || size.len() != is_buy.len() { return Err(PyValueError::new_err( "price, size, is_buy must be equal length", @@ -20608,7 +19969,7 @@ macro_rules! py_trade_indicator { let trade = build_trade(price[i], size[i], is_buy[i])?; out.push(self.inner.update(trade).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -20665,7 +20026,7 @@ impl PyTradeImbalance { price: Vec, size: Vec, is_buy: Vec, - ) -> PyResult>> { + ) -> PyResult> { if price.len() != size.len() || size.len() != is_buy.len() { return Err(PyValueError::new_err( "price, size, is_buy must be equal length", @@ -20676,7 +20037,7 @@ impl PyTradeImbalance { let trade = build_trade(price[i], size[i], is_buy[i])?; out.push(self.inner.update(trade).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -20724,7 +20085,7 @@ impl PyTradeSignAutocorrelation { price: Vec, size: Vec, is_buy: Vec, - ) -> PyResult>> { + ) -> PyResult> { if price.len() != size.len() || size.len() != is_buy.len() { return Err(PyValueError::new_err( "price, size, is_buy must be equal length", @@ -20735,7 +20096,7 @@ impl PyTradeSignAutocorrelation { let trade = build_trade(price[i], size[i], is_buy[i])?; out.push(self.inner.update(trade).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -20779,7 +20140,7 @@ impl PyPin { price: Vec, size: Vec, is_buy: Vec, - ) -> PyResult>> { + ) -> PyResult> { if price.len() != size.len() || size.len() != is_buy.len() { return Err(PyValueError::new_err( "price, size, is_buy must be equal length", @@ -20790,7 +20151,7 @@ impl PyPin { let trade = build_trade(price[i], size[i], is_buy[i])?; out.push(self.inner.update(trade).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -20845,13 +20206,13 @@ impl PyOrderFlowImbalance { &mut self, py: Python<'py>, snapshots: Vec<(Vec, Vec, Vec, Vec)>, - ) -> PyResult>> { + ) -> PyResult> { let mut out = Vec::with_capacity(snapshots.len()); for (bid_px, bid_sz, ask_px, ask_sz) in &snapshots { let book = build_order_book(bid_px, bid_sz, ask_px, ask_sz)?; out.push(self.inner.update(book).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -20895,7 +20256,7 @@ impl PyVpin { price: Vec, size: Vec, is_buy: Vec, - ) -> PyResult>> { + ) -> PyResult> { if price.len() != size.len() || size.len() != is_buy.len() { return Err(PyValueError::new_err( "price, size, is_buy must be equal length", @@ -20906,7 +20267,7 @@ impl PyVpin { let trade = build_trade(price[i], size[i], is_buy[i])?; out.push(self.inner.update(trade).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -20955,7 +20316,7 @@ impl PyAmihudIlliquidity { price: Vec, size: Vec, is_buy: Vec, - ) -> PyResult>> { + ) -> PyResult> { if price.len() != size.len() || size.len() != is_buy.len() { return Err(PyValueError::new_err( "price, size, is_buy must be equal length", @@ -20966,7 +20327,7 @@ impl PyAmihudIlliquidity { let trade = build_trade(price[i], size[i], is_buy[i])?; out.push(self.inner.update(trade).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21010,7 +20371,7 @@ impl PyRollMeasure { price: Vec, size: Vec, is_buy: Vec, - ) -> PyResult>> { + ) -> PyResult> { if price.len() != size.len() || size.len() != is_buy.len() { return Err(PyValueError::new_err( "price, size, is_buy must be equal length", @@ -21021,7 +20382,7 @@ impl PyRollMeasure { let trade = build_trade(price[i], size[i], is_buy[i])?; out.push(self.inner.update(trade).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21087,7 +20448,7 @@ macro_rules! py_trade_quote_indicator { size: Vec, is_buy: Vec, mid: Vec, - ) -> PyResult>> { + ) -> PyResult> { if price.len() != size.len() || size.len() != is_buy.len() || is_buy.len() != mid.len() @@ -21101,7 +20462,7 @@ macro_rules! py_trade_quote_indicator { let quote = build_trade_quote(price[i], size[i], is_buy[i], mid[i])?; out.push(self.inner.update(quote).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21156,7 +20517,7 @@ impl PyRealizedSpread { size: Vec, is_buy: Vec, mid: Vec, - ) -> PyResult>> { + ) -> PyResult> { if price.len() != size.len() || size.len() != is_buy.len() || is_buy.len() != mid.len() { return Err(PyValueError::new_err( "price, size, is_buy, mid must be equal length", @@ -21167,7 +20528,7 @@ impl PyRealizedSpread { let quote = build_trade_quote(price[i], size[i], is_buy[i], mid[i])?; out.push(self.inner.update(quote).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21214,7 +20575,7 @@ impl PyKylesLambda { size: Vec, is_buy: Vec, mid: Vec, - ) -> PyResult>> { + ) -> PyResult> { if price.len() != size.len() || size.len() != is_buy.len() || is_buy.len() != mid.len() { return Err(PyValueError::new_err( "price, size, is_buy, mid must be equal length", @@ -21225,7 +20586,7 @@ impl PyKylesLambda { let quote = build_trade_quote(price[i], size[i], is_buy[i], mid[i])?; out.push(self.inner.update(quote).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21256,7 +20617,7 @@ impl PyKylesLambda { fn footprint_to_array<'py>( py: Python<'py>, out: &wc::FootprintOutput, -) -> Bound<'py, PyArray2> { +) -> PyResult> { let rows = out.levels.len(); let mut data = Vec::with_capacity(rows * 3); for level in &out.levels { @@ -21264,9 +20625,7 @@ fn footprint_to_array<'py>( data.push(level.bid_vol); data.push(level.ask_vol); } - numpy::ndarray::Array2::from_shape_vec((rows, 3), data) - .expect("shape consistent") - .into_pyarray(py) + matrix(py, data, rows, 3) } #[pyclass(name = "Footprint", module = "wickra._wickra", skip_from_py_object)] @@ -21289,12 +20648,12 @@ impl PyFootprint { price: f64, size: f64, is_buy: bool, - ) -> PyResult>> { + ) -> PyResult> { let out = self .inner .update(build_trade(price, size, is_buy)?) .expect("footprint emits on every trade"); - Ok(footprint_to_array(py, &out)) + footprint_to_array(py, &out) } fn batch<'py>( &mut self, @@ -21302,7 +20661,7 @@ impl PyFootprint { price: Vec, size: Vec, is_buy: Vec, - ) -> PyResult>>> { + ) -> PyResult>> { if price.len() != size.len() || size.len() != is_buy.len() { return Err(PyValueError::new_err( "price, size, is_buy must be equal length", @@ -21314,7 +20673,7 @@ impl PyFootprint { .inner .update(build_trade(price[i], size[i], is_buy[i])?) .expect("footprint emits on every trade"); - out.push(footprint_to_array(py, &snapshot)); + out.push(footprint_to_array(py, &snapshot)?); } Ok(out) } @@ -21564,12 +20923,12 @@ impl PyFundingRate { &mut self, py: Python<'py>, funding_rate: Vec, - ) -> PyResult>> { + ) -> PyResult> { let mut out = Vec::with_capacity(funding_rate.len()); for rate in funding_rate { out.push(self.inner.update(deriv_funding(rate)?).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21615,12 +20974,12 @@ impl PyFundingRateMean { &mut self, py: Python<'py>, funding_rate: Vec, - ) -> PyResult>> { + ) -> PyResult> { let mut out = Vec::with_capacity(funding_rate.len()); for rate in funding_rate { out.push(self.inner.update(deriv_funding(rate)?).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21666,12 +21025,12 @@ impl PyFundingRateZScore { &mut self, py: Python<'py>, funding_rate: Vec, - ) -> PyResult>> { + ) -> PyResult> { let mut out = Vec::with_capacity(funding_rate.len()); for rate in funding_rate { out.push(self.inner.update(deriv_funding(rate)?).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21714,7 +21073,7 @@ impl PyFundingBasis { py: Python<'py>, mark_price: Vec, index_price: Vec, - ) -> PyResult>> { + ) -> PyResult> { if mark_price.len() != index_price.len() { return Err(PyValueError::new_err( "mark_price and index_price must be equal length", @@ -21728,7 +21087,7 @@ impl PyFundingBasis { .unwrap_or(f64::NAN), ); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21774,12 +21133,12 @@ impl PyOpenInterestDelta { &mut self, py: Python<'py>, open_interest: Vec, - ) -> PyResult>> { + ) -> PyResult> { let mut out = Vec::with_capacity(open_interest.len()); for oi in open_interest { out.push(self.inner.update(deriv_oi(oi)?).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21827,7 +21186,7 @@ impl PyOIPriceDivergence { py: Python<'py>, open_interest: Vec, mark_price: Vec, - ) -> PyResult>> { + ) -> PyResult> { if open_interest.len() != mark_price.len() { return Err(PyValueError::new_err( "open_interest and mark_price must be equal length", @@ -21841,7 +21200,7 @@ impl PyOIPriceDivergence { .unwrap_or(f64::NAN), ); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21884,7 +21243,7 @@ impl PyOIWeighted { py: Python<'py>, mark_price: Vec, open_interest: Vec, - ) -> PyResult>> { + ) -> PyResult> { if mark_price.len() != open_interest.len() { return Err(PyValueError::new_err( "mark_price and open_interest must be equal length", @@ -21898,7 +21257,7 @@ impl PyOIWeighted { .unwrap_or(f64::NAN), ); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -21945,7 +21304,7 @@ impl PyLongShortRatio { py: Python<'py>, long_size: Vec, short_size: Vec, - ) -> PyResult>> { + ) -> PyResult> { if long_size.len() != short_size.len() { return Err(PyValueError::new_err( "long_size and short_size must be equal length", @@ -21959,7 +21318,7 @@ impl PyLongShortRatio { .unwrap_or(f64::NAN), ); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22009,7 +21368,7 @@ impl PyTakerBuySellRatio { py: Python<'py>, taker_buy_volume: Vec, taker_sell_volume: Vec, - ) -> PyResult>> { + ) -> PyResult> { if taker_buy_volume.len() != taker_sell_volume.len() { return Err(PyValueError::new_err( "taker_buy_volume and taker_sell_volume must be equal length", @@ -22023,7 +21382,7 @@ impl PyTakerBuySellRatio { .unwrap_or(f64::NAN), ); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22081,7 +21440,7 @@ impl PyLiquidationFeatures { py: Python<'py>, long_liquidation: Vec, short_liquidation: Vec, - ) -> PyResult>> { + ) -> PyResult> { if long_liquidation.len() != short_liquidation.len() { return Err(PyValueError::new_err( "long_liquidation and short_liquidation must be equal length", @@ -22103,9 +21462,7 @@ impl PyLiquidationFeatures { data.push(out.total); data.push(out.imbalance); } - Ok(numpy::ndarray::Array2::from_shape_vec((rows, 5), data) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, data, rows, 5) } fn reset(&mut self) { self.inner.reset(); @@ -22155,7 +21512,7 @@ impl PyTermStructureBasis { py: Python<'py>, futures_price: Vec, index_price: Vec, - ) -> PyResult>> { + ) -> PyResult> { if futures_price.len() != index_price.len() { return Err(PyValueError::new_err( "futures_price and index_price must be equal length", @@ -22169,7 +21526,7 @@ impl PyTermStructureBasis { .unwrap_or(f64::NAN), ); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22218,7 +21575,7 @@ impl PyCalendarSpread { py: Python<'py>, futures_price: Vec, mark_price: Vec, - ) -> PyResult>> { + ) -> PyResult> { if futures_price.len() != mark_price.len() { return Err(PyValueError::new_err( "futures_price and mark_price must be equal length", @@ -22232,7 +21589,7 @@ impl PyCalendarSpread { .unwrap_or(f64::NAN), ); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22287,7 +21644,7 @@ impl PyEstimatedLeverageRatio { open_interest: Vec, long_size: Vec, short_size: Vec, - ) -> PyResult>> { + ) -> PyResult> { if open_interest.len() != long_size.len() || long_size.len() != short_size.len() { return Err(PyValueError::new_err( "open_interest, long_size, short_size must be equal length", @@ -22305,7 +21662,7 @@ impl PyEstimatedLeverageRatio { .unwrap_or(f64::NAN), ); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22362,7 +21719,7 @@ impl PyOiToVolumeRatio { open_interest: Vec, taker_buy_volume: Vec, taker_sell_volume: Vec, - ) -> PyResult>> { + ) -> PyResult> { if open_interest.len() != taker_buy_volume.len() || taker_buy_volume.len() != taker_sell_volume.len() { @@ -22382,7 +21739,7 @@ impl PyOiToVolumeRatio { .unwrap_or(f64::NAN), ); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22429,7 +21786,7 @@ impl PyPerpetualPremiumIndex { py: Python<'py>, mark_price: Vec, index_price: Vec, - ) -> PyResult>> { + ) -> PyResult> { if mark_price.len() != index_price.len() { return Err(PyValueError::new_err( "mark_price and index_price must be equal length", @@ -22443,7 +21800,7 @@ impl PyPerpetualPremiumIndex { .unwrap_or(f64::NAN), ); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22489,12 +21846,12 @@ impl PyFundingImpliedApr { &mut self, py: Python<'py>, funding_rate: Vec, - ) -> PyResult>> { + ) -> PyResult> { let mut out = Vec::with_capacity(funding_rate.len()); for r in funding_rate { out.push(self.inner.update(deriv_funding(r)?).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22543,12 +21900,12 @@ impl PyOpenInterestMomentum { &mut self, py: Python<'py>, open_interest: Vec, - ) -> PyResult>> { + ) -> PyResult> { let mut out = Vec::with_capacity(open_interest.len()); for oi in open_interest { out.push(self.inner.update(deriv_oi(oi)?).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22633,7 +21990,7 @@ impl PyAdvanceDecline { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -22647,7 +22004,7 @@ impl PyAdvanceDecline { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22765,7 +22122,7 @@ impl PyAdvanceDeclineRatio { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -22779,7 +22136,7 @@ impl PyAdvanceDeclineRatio { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22831,7 +22188,7 @@ impl PyAdVolumeLine { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -22845,7 +22202,7 @@ impl PyAdVolumeLine { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22901,7 +22258,7 @@ impl PyMcClellanOscillator { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -22915,7 +22272,7 @@ impl PyMcClellanOscillator { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -22971,7 +22328,7 @@ impl PyMcClellanSummationIndex { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -22985,7 +22342,7 @@ impl PyMcClellanSummationIndex { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23037,7 +22394,7 @@ impl PyTrin { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -23051,7 +22408,7 @@ impl PyTrin { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23103,7 +22460,7 @@ impl PyBreadthThrust { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -23117,7 +22474,7 @@ impl PyBreadthThrust { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23173,7 +22530,7 @@ impl PyNewHighsNewLows { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -23187,7 +22544,7 @@ impl PyNewHighsNewLows { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23239,7 +22596,7 @@ impl PyHighLowIndex { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -23253,7 +22610,7 @@ impl PyHighLowIndex { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23311,7 +22668,7 @@ impl PyPercentAboveMa { new_high: Vec>, new_low: Vec>, above_ma: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -23332,7 +22689,7 @@ impl PyPercentAboveMa { )?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23388,7 +22745,7 @@ impl PyUpDownVolumeRatio { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -23402,7 +22759,7 @@ impl PyUpDownVolumeRatio { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23464,7 +22821,7 @@ impl PyBullishPercentIndex { new_high: Vec>, new_low: Vec>, on_buy_signal: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -23485,7 +22842,7 @@ impl PyBullishPercentIndex { )?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23541,7 +22898,7 @@ impl PyCumulativeVolumeIndex { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -23555,7 +22912,7 @@ impl PyCumulativeVolumeIndex { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23611,7 +22968,7 @@ impl PyAbsoluteBreadthIndex { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -23625,7 +22982,7 @@ impl PyAbsoluteBreadthIndex { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23677,7 +23034,7 @@ impl PyTickIndex { volume: Vec>, new_high: Vec>, new_low: Vec>, - ) -> PyResult>> { + ) -> PyResult> { if change.len() != volume.len() || change.len() != new_high.len() || change.len() != new_low.len() @@ -23691,7 +23048,7 @@ impl PyTickIndex { let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?; out.push(self.inner.update(section).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -23735,15 +23092,11 @@ impl PyUpsidePotentialRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -23793,15 +23146,11 @@ impl PyM2Measure { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -23856,15 +23205,11 @@ impl PySharpeRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -23914,15 +23259,11 @@ impl PySortinoRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -23971,15 +23312,11 @@ impl PyCalmarRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24021,15 +23358,11 @@ impl PyOmegaRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24078,15 +23411,11 @@ impl PyMaxDrawdown { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24131,15 +23460,11 @@ impl PyAverageDrawdown { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24184,11 +23509,7 @@ impl PyDrawdownDuration { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -24198,7 +23519,7 @@ impl PyDrawdownDuration { .into_iter() .map(|v| v.map_or(f64::NAN, f64::from)) .collect(); - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -24235,15 +23556,11 @@ impl PyPainIndex { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24285,15 +23602,11 @@ impl PyValueAtRisk { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24347,15 +23660,11 @@ impl PyConditionalValueAtRisk { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24404,15 +23713,11 @@ impl PyProfitFactor { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24453,15 +23758,11 @@ impl PyGainLossRatio { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24506,15 +23807,11 @@ impl PyRecoveryFactor { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -24555,15 +23852,11 @@ impl PyKellyCriterion { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24610,9 +23903,9 @@ impl PyTreynorRatio { fn batch<'py>( &mut self, py: Python<'py>, - asset: PyReadonlyArray1<'py, f64>, - benchmark: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + asset: Buf1, + benchmark: Buf1, + ) -> PyResult> { let a = asset .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -24628,7 +23921,7 @@ impl PyTreynorRatio { for i in 0..a.len() { out.push(self.inner.update((a[i], b[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24684,9 +23977,9 @@ impl PyInformationRatio { fn batch<'py>( &mut self, py: Python<'py>, - asset: PyReadonlyArray1<'py, f64>, - benchmark: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + asset: Buf1, + benchmark: Buf1, + ) -> PyResult> { let a = asset .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -24702,7 +23995,7 @@ impl PyInformationRatio { for i in 0..a.len() { out.push(self.inner.update((a[i], b[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24747,9 +24040,9 @@ impl PyAlpha { fn batch<'py>( &mut self, py: Python<'py>, - asset: PyReadonlyArray1<'py, f64>, - benchmark: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + asset: Buf1, + benchmark: Buf1, + ) -> PyResult> { let a = asset .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -24765,7 +24058,7 @@ impl PyAlpha { for i in 0..a.len() { out.push(self.inner.update((a[i], b[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -24828,11 +24121,7 @@ impl PyRenkoBars { .collect()) } /// Batch over a close column. Returns shape `(k, 3)` of `[open, close, direction]`. - fn batch<'py>( - &mut self, - py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, close: Buf1) -> PyResult> { let prices = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -24847,9 +24136,7 @@ impl PyRenkoBars { k += 1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((k, 3), rows) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, rows, k, 3) } #[getter] fn box_size(&self) -> f64 { @@ -24892,11 +24179,7 @@ impl PyKagiBars { .collect()) } /// Batch over a close column. Returns shape `(k, 3)` of `[start, end, direction]`. - fn batch<'py>( - &mut self, - py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, close: Buf1) -> PyResult> { let prices = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -24911,9 +24194,7 @@ impl PyKagiBars { k += 1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((k, 3), rows) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, rows, k, 3) } #[getter] fn reversal(&self) -> f64 { @@ -24961,11 +24242,7 @@ impl PyPointAndFigureBars { .collect()) } /// Batch over a close column. Returns shape `(k, 3)` of `[direction, high, low]`. - fn batch<'py>( - &mut self, - py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, close: Buf1) -> PyResult> { let prices = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -24980,9 +24257,7 @@ impl PyPointAndFigureBars { k += 1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((k, 3), rows) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, rows, k, 3) } #[getter] fn box_size(&self) -> f64 { @@ -25033,11 +24308,7 @@ impl PyRangeBars { .collect()) } /// Batch over a close column. Returns shape `(k, 3)` of `[open, close, direction]`. - fn batch<'py>( - &mut self, - py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, close: Buf1) -> PyResult> { let prices = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -25052,9 +24323,7 @@ impl PyRangeBars { k += 1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((k, 3), rows) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, rows, k, 3) } #[getter] fn range(&self) -> f64 { @@ -25108,12 +24377,12 @@ impl PyTickBars { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let (o, h, l, c, v) = ohlcv_slices(&open, &high, &low, &close, &volume)?; let mut rows: Vec = Vec::new(); let mut k = 0usize; @@ -25124,9 +24393,7 @@ impl PyTickBars { k += 1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((k, 5), rows) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, rows, k, 5) } #[getter] fn ticks(&self) -> usize { @@ -25180,12 +24447,12 @@ impl PyVolumeBars { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let (o, h, l, c, v) = ohlcv_slices(&open, &high, &low, &close, &volume)?; let mut rows: Vec = Vec::new(); let mut k = 0usize; @@ -25196,9 +24463,7 @@ impl PyVolumeBars { k += 1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((k, 5), rows) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, rows, k, 5) } #[getter] fn volume_per_bar(&self) -> f64 { @@ -25252,12 +24517,12 @@ impl PyDollarBars { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let (o, h, l, c, v) = ohlcv_slices(&open, &high, &low, &close, &volume)?; let mut rows: Vec = Vec::new(); let mut k = 0usize; @@ -25268,9 +24533,7 @@ impl PyDollarBars { k += 1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((k, 6), rows) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, rows, k, 6) } #[getter] fn dollar_per_bar(&self) -> f64 { @@ -25325,11 +24588,11 @@ impl PyImbalanceBars { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let (o, h, l, c) = ohlc_slices(&open, &high, &low, &close)?; let mut rows: Vec = Vec::new(); let mut k = 0usize; @@ -25347,9 +24610,7 @@ impl PyImbalanceBars { k += 1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((k, 6), rows) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, rows, k, 6) } #[getter] fn threshold(&self) -> f64 { @@ -25404,11 +24665,11 @@ impl PyRunBars { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let (o, h, l, c) = ohlc_slices(&open, &high, &low, &close)?; let mut rows: Vec = Vec::new(); let mut k = 0usize; @@ -25427,9 +24688,7 @@ impl PyRunBars { k += 1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((k, 6), rows) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, rows, k, 6) } #[getter] fn run_length(&self) -> usize { @@ -25477,11 +24736,7 @@ impl PyThreeLineBreakBars { .collect()) } /// Batch over a close column. Returns shape `(k, 3)` of `[open, close, direction]`. - fn batch<'py>( - &mut self, - py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, close: Buf1) -> PyResult> { let prices = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -25496,9 +24751,7 @@ impl PyThreeLineBreakBars { k += 1; } } - Ok(numpy::ndarray::Array2::from_shape_vec((k, 3), rows) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, rows, k, 3) } #[getter] fn lines(&self) -> usize { @@ -25524,13 +24777,13 @@ impl PyThreeLineBreakBars { // bindings consume the FULL candle (open, high, low, close, volume, timestamp) // — unlike the high/low/close candle indicators above. -fn build_seasonality_candles<'py>( - open: &PyReadonlyArray1<'py, f64>, - high: &PyReadonlyArray1<'py, f64>, - low: &PyReadonlyArray1<'py, f64>, - close: &PyReadonlyArray1<'py, f64>, - volume: &PyReadonlyArray1<'py, f64>, - timestamp: &PyReadonlyArray1<'py, i64>, +fn build_seasonality_candles( + open: &Buf1, + high: &Buf1, + low: &Buf1, + close: &Buf1, + volume: &Buf1, + timestamp: &BufI64, ) -> PyResult> { let o = open .as_slice() @@ -25589,20 +24842,20 @@ macro_rules! py_seasonality_offset_scalar { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - timestamp: PyReadonlyArray1<'py, i64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + timestamp: BufI64, + ) -> PyResult> { let candles = build_seasonality_candles(&open, &high, &low, &close, &volume, ×tamp)?; let out: Vec = candles .into_iter() .map(|c| self.inner.update(c).unwrap_or(f64::NAN)) .collect(); - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn utc_offset_minutes(&self) -> i32 { @@ -25652,21 +24905,24 @@ macro_rules! py_seasonality_bucket_profile { &mut self, py: Python<'py>, candle: &Bound<'_, PyAny>, - ) -> PyResult>>> { + ) -> PyResult>> { let c = extract_candle(candle)?; - Ok(self.inner.update(c).map(|o| o.bins.into_pyarray(py))) + self.inner + .update(c) + .map(|o| f64_array(py, &o.bins)) + .transpose() } #[allow(clippy::too_many_arguments)] fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - timestamp: PyReadonlyArray1<'py, i64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + timestamp: BufI64, + ) -> PyResult> { let candles = build_seasonality_candles(&open, &high, &low, &close, &volume, ×tamp)?; let k = self.inner.params().0; @@ -25679,9 +24935,7 @@ macro_rules! py_seasonality_bucket_profile { } } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, k), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, k) } #[getter] fn params(&self) -> (usize, i32) { @@ -25728,21 +24982,24 @@ macro_rules! py_seasonality_offset_profile { &mut self, py: Python<'py>, candle: &Bound<'_, PyAny>, - ) -> PyResult>>> { + ) -> PyResult>> { let c = extract_candle(candle)?; - Ok(self.inner.update(c).map(|o| o.bins.into_pyarray(py))) + self.inner + .update(c) + .map(|o| f64_array(py, &o.bins)) + .transpose() } #[allow(clippy::too_many_arguments)] fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - timestamp: PyReadonlyArray1<'py, i64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + timestamp: BufI64, + ) -> PyResult> { let candles = build_seasonality_candles(&open, &high, &low, &close, &volume, ×tamp)?; let k = $k; @@ -25755,9 +25012,7 @@ macro_rules! py_seasonality_offset_profile { } } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, k), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, k) } #[getter] fn utc_offset_minutes(&self) -> i32 { @@ -25832,19 +25087,19 @@ impl PyAverageDailyRange { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - timestamp: PyReadonlyArray1<'py, i64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + timestamp: BufI64, + ) -> PyResult> { let candles = build_seasonality_candles(&open, &high, &low, &close, &volume, ×tamp)?; let out: Vec = candles .into_iter() .map(|c| self.inner.update(c).unwrap_or(f64::NAN)) .collect(); - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn params(&self) -> (usize, i32) { @@ -25890,19 +25145,19 @@ impl PyTurnOfMonth { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - timestamp: PyReadonlyArray1<'py, i64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + timestamp: BufI64, + ) -> PyResult> { let candles = build_seasonality_candles(&open, &high, &low, &close, &volume, ×tamp)?; let out: Vec = candles .into_iter() .map(|c| self.inner.update(c).unwrap_or(f64::NAN)) .collect(); - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn params(&self) -> (u32, u32, i32) { @@ -25953,13 +25208,13 @@ impl PySessionHighLow { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - timestamp: PyReadonlyArray1<'py, i64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + timestamp: BufI64, + ) -> PyResult> { let candles = build_seasonality_candles(&open, &high, &low, &close, &volume, ×tamp)?; let n = candles.len(); let mut out = vec![f64::NAN; n * 2]; @@ -25969,9 +25224,7 @@ impl PySessionHighLow { out[i * 2 + 1] = o.low; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn utc_offset_minutes(&self) -> i32 { @@ -26020,13 +25273,13 @@ impl PySessionRange { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - timestamp: PyReadonlyArray1<'py, i64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + timestamp: BufI64, + ) -> PyResult> { let candles = build_seasonality_candles(&open, &high, &low, &close, &volume, ×tamp)?; let n = candles.len(); let mut out = vec![f64::NAN; n * 3]; @@ -26037,9 +25290,7 @@ impl PySessionRange { out[i * 3 + 2] = o.us; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } #[getter] fn utc_offset_minutes(&self) -> i32 { @@ -26092,13 +25343,13 @@ impl PyOvernightIntradayReturn { fn batch<'py>( &mut self, py: Python<'py>, - open: PyReadonlyArray1<'py, f64>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - timestamp: PyReadonlyArray1<'py, i64>, - ) -> PyResult>> { + open: Buf1, + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + timestamp: BufI64, + ) -> PyResult> { let candles = build_seasonality_candles(&open, &high, &low, &close, &volume, ×tamp)?; let n = candles.len(); let mut out = vec![f64::NAN; n * 2]; @@ -26108,9 +25359,7 @@ impl PyOvernightIntradayReturn { out[i * 2 + 1] = o.intraday; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } #[getter] fn utc_offset_minutes(&self) -> i32 { @@ -26184,9 +25433,9 @@ impl PyFibRetracement { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -26212,9 +25461,7 @@ impl PyFibRetracement { out[i * 7 + 6] = o.level_1000; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 7), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 7) } fn reset(&mut self) { self.inner.reset(); @@ -26265,9 +25512,9 @@ impl PyFibExtension { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -26291,9 +25538,7 @@ impl PyFibExtension { out[i * 5 + 4] = o.level_2618; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 5), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 5) } fn reset(&mut self) { self.inner.reset(); @@ -26339,9 +25584,9 @@ impl PyFibProjection { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -26364,9 +25609,7 @@ impl PyFibProjection { out[i * 4 + 3] = o.level_2618; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 4), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 4) } fn reset(&mut self) { self.inner.reset(); @@ -26419,9 +25662,9 @@ impl PyAutoFib { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -26447,9 +25690,7 @@ impl PyAutoFib { out[i * 7 + 6] = o.level_1000; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 7), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 7) } fn reset(&mut self) { self.inner.reset(); @@ -26492,9 +25733,9 @@ impl PyGoldenPocket { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -26516,9 +25757,7 @@ impl PyGoldenPocket { out[i * 3 + 2] = o.high; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -26561,9 +25800,9 @@ impl PyFibConfluence { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -26584,9 +25823,7 @@ impl PyFibConfluence { out[i * 2 + 1] = o.strength; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -26632,9 +25869,9 @@ impl PyFibFan { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -26656,9 +25893,7 @@ impl PyFibFan { out[i * 3 + 2] = o.fan_618; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -26704,9 +25939,9 @@ impl PyFibArcs { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -26728,9 +25963,7 @@ impl PyFibArcs { out[i * 3 + 2] = o.arc_618; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } fn reset(&mut self) { self.inner.reset(); @@ -26776,9 +26009,9 @@ impl PyFibChannel { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -26801,9 +26034,7 @@ impl PyFibChannel { out[i * 4 + 3] = o.level_1618; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 4), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 4) } fn reset(&mut self) { self.inner.reset(); @@ -26846,9 +26077,9 @@ impl PyFibTimeZones { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -26869,9 +26100,7 @@ impl PyFibTimeZones { out[i * 2 + 1] = o.bars_to_next; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 2) } fn reset(&mut self) { self.inner.reset(); @@ -26915,15 +26144,11 @@ impl PyEwmaVolatility { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn lambda_(&self) -> f64 { @@ -26968,15 +26193,11 @@ impl PyGarch11 { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn params(&self) -> (f64, f64, f64) { @@ -27029,15 +26250,11 @@ impl PyVolatilityOfVolatility { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn windows(&self) -> (usize, usize) { @@ -27093,10 +26310,10 @@ impl PyVolatilityCone { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -27123,9 +26340,7 @@ impl PyVolatilityCone { out[i * 5 + 4] = o.percentile; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 5), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 5) } #[getter] fn windows(&self) -> (usize, usize) { @@ -27171,9 +26386,9 @@ impl PyVolumeRsi { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -27190,7 +26405,7 @@ impl PyVolumeRsi { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -27238,10 +26453,10 @@ impl PyWad { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -27261,7 +26476,7 @@ impl PyWad { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -27310,11 +26525,11 @@ impl PyTwiggsMoneyFlow { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -27337,7 +26552,7 @@ impl PyTwiggsMoneyFlow { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], vol[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -27390,9 +26605,9 @@ impl PyTradeVolumeIndex { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -27409,7 +26624,7 @@ impl PyTradeVolumeIndex { let candle = wc::Candle::new(c[i], c[i], c[i], c[i], v[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn min_tick(&self) -> f64 { @@ -27461,11 +26676,11 @@ impl PyIntradayIntensity { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -27488,7 +26703,7 @@ impl PyIntradayIntensity { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], vol[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } fn reset(&mut self) { self.inner.reset(); @@ -27533,11 +26748,11 @@ impl PyBetterVolume { fn batch<'py>( &mut self, py: Python<'py>, - high: PyReadonlyArray1<'py, f64>, - low: PyReadonlyArray1<'py, f64>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + high: Buf1, + low: Buf1, + close: Buf1, + volume: Buf1, + ) -> PyResult> { let h = high .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -27560,7 +26775,7 @@ impl PyBetterVolume { let candle = wc::Candle::new(c[i], h[i], l[i], c[i], vol[i], 0).map_err(map_err)?; out.push(self.inner.update(candle).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -27617,9 +26832,9 @@ impl PyVolumeWeightedMacd { fn batch<'py>( &mut self, py: Python<'py>, - close: PyReadonlyArray1<'py, f64>, - volume: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + close: Buf1, + volume: Buf1, + ) -> PyResult> { let c = close .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -27641,9 +26856,7 @@ impl PyVolumeWeightedMacd { out[i * 3 + 2] = o.histogram; } } - Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out) - .expect("shape consistent") - .into_pyarray(py)) + matrix(py, out, n, 3) } #[getter] fn periods(&self) -> (usize, usize, usize) { @@ -27688,15 +26901,11 @@ impl PyShannonEntropy { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn params(&self) -> (usize, usize) { @@ -27745,15 +26954,11 @@ impl PySampleEntropy { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn params(&self) -> (usize, usize, f64) { @@ -27803,12 +27008,7 @@ impl PyKendallTau { self.inner.update((x, y)) } /// Batch over two equally-sized numpy arrays. - fn batch<'py>( - &mut self, - py: Python<'py>, - x: PyReadonlyArray1<'py, f64>, - y: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, x: Buf1, y: Buf1) -> PyResult> { let xs = x .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; @@ -27822,7 +27022,7 @@ impl PyKendallTau { for i in 0..xs.len() { out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN)); } - Ok(out.into_pyarray(py)) + out.into_pydata(py) } #[getter] fn period(&self) -> usize { @@ -27870,15 +27070,11 @@ impl PyBandpassFilter { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn params(&self) -> (usize, f64) { @@ -27931,15 +27127,11 @@ impl PyEvenBetterSinewave { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn params(&self) -> (usize, usize) { @@ -27988,15 +27180,11 @@ impl PyAutocorrelationPeriodogram { fn update(&mut self, value: f64) -> Option { self.inner.update(value) } - fn batch<'py>( - &mut self, - py: Python<'py>, - prices: PyReadonlyArray1<'py, f64>, - ) -> PyResult>> { + fn batch<'py>(&mut self, py: Python<'py>, prices: Buf1) -> PyResult> { let slice = prices .as_slice() .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; - Ok(self.inner.batch_nan(slice).into_pyarray(py)) + self.inner.batch_nan(slice).into_pydata(py) } #[getter] fn periods(&self) -> (usize, usize) { diff --git a/bindings/python/tests/test_input_validation.py b/bindings/python/tests/test_input_validation.py index fc1bb4db..25907b52 100644 --- a/bindings/python/tests/test_input_validation.py +++ b/bindings/python/tests/test_input_validation.py @@ -8,20 +8,22 @@ import pytest import wickra as ta -def test_non_contiguous_array_raises_value_error(): - # A strided view is not C-contiguous; batch() must reject it cleanly. +def test_non_contiguous_array_is_accepted(): + # Inputs are read through the buffer/sequence protocol, so a strided (non + # C-contiguous) view is accepted and yields the same result as a dense copy. base = np.linspace(1.0, 100.0, 60) non_contiguous = base[::2] assert not non_contiguous.flags["C_CONTIGUOUS"] - with pytest.raises(ValueError): - ta.SMA(5).batch(non_contiguous) + strided = np.asarray(ta.SMA(5).batch(non_contiguous)) + dense = np.asarray(ta.SMA(5).batch(np.ascontiguousarray(non_contiguous))) + np.testing.assert_array_equal(strided, dense) -def test_ascontiguousarray_recovers(): +def test_dense_array_batches(): base = np.linspace(1.0, 100.0, 60) fixed = np.ascontiguousarray(base[::2]) out = ta.SMA(5).batch(fixed) - assert out.shape == fixed.shape + assert len(out) == len(fixed) def test_unequal_length_candle_batch_raises(ohlc_series): diff --git a/bindings/python/tests/test_known_values.py b/bindings/python/tests/test_known_values.py index d768bb3e..063d242d 100644 --- a/bindings/python/tests/test_known_values.py +++ b/bindings/python/tests/test_known_values.py @@ -10,8 +10,17 @@ import pytest import wickra as ta +def _to_np(x) -> np.ndarray: + """Normalize a Wickra batch result (``array.array`` or ``Matrix``) to an ndarray.""" + if isinstance(x, np.ndarray): + return x.astype(np.float64) + if hasattr(x, "tolist") and hasattr(x, "shape"): + return np.asarray(x.tolist(), dtype=np.float64) + return np.asarray(x, dtype=np.float64) + + def test_sma_constant_series(): - out = ta.SMA(5).batch(np.full(20, 42.0, dtype=np.float64)) + out = _to_np(ta.SMA(5).batch(np.full(20, 42.0, dtype=np.float64))) # First 4 are warmup -> NaN; rest equal 42. assert np.all(np.isnan(out[:4])) assert np.allclose(out[4:], 42.0) @@ -19,37 +28,37 @@ def test_sma_constant_series(): def test_sma_known_window(): # SMA(3) of [2, 4, 6, 8, 10] -> [_, _, 4, 6, 8] - out = ta.SMA(3).batch(np.array([2.0, 4.0, 6.0, 8.0, 10.0])) + out = _to_np(ta.SMA(3).batch(np.array([2.0, 4.0, 6.0, 8.0, 10.0]))) assert math.isnan(out[0]) and math.isnan(out[1]) np.testing.assert_allclose(out[2:], [4.0, 6.0, 8.0]) def test_ema_seed_equals_simple_mean_of_first_window(): # EMA(5) seed = mean([10, 20, 30, 40, 50]) = 30 - out = ta.EMA(5).batch(np.array([10.0, 20.0, 30.0, 40.0, 50.0])) + out = _to_np(ta.EMA(5).batch(np.array([10.0, 20.0, 30.0, 40.0, 50.0]))) assert math.isnan(out[0]) assert math.isclose(out[4], 30.0, abs_tol=1e-12) def test_wma_known_window(): # WMA(4) of [1, 2, 3, 4] = (1*1 + 2*2 + 3*3 + 4*4)/10 = 3 - out = ta.WMA(4).batch(np.array([1.0, 2.0, 3.0, 4.0])) + out = _to_np(ta.WMA(4).batch(np.array([1.0, 2.0, 3.0, 4.0]))) assert math.isnan(out[0]) and math.isnan(out[1]) and math.isnan(out[2]) assert math.isclose(out[3], 3.0, abs_tol=1e-12) def test_rsi_pure_uptrend_is_100(): - out = ta.RSI(14).batch(np.arange(1.0, 21.0, dtype=np.float64)) + out = _to_np(ta.RSI(14).batch(np.arange(1.0, 21.0, dtype=np.float64))) np.testing.assert_allclose(out[14:], 100.0) def test_rsi_pure_downtrend_is_0(): - out = ta.RSI(14).batch(np.arange(20.0, 0.0, -1.0)) + out = _to_np(ta.RSI(14).batch(np.arange(20.0, 0.0, -1.0))) np.testing.assert_allclose(out[14:], 0.0) def test_rsi_flat_series_is_50(): - out = ta.RSI(14).batch(np.full(30, 100.0)) + out = _to_np(ta.RSI(14).batch(np.full(30, 100.0))) np.testing.assert_allclose(out[14:], 50.0) @@ -62,13 +71,13 @@ def test_rsi_wilder_textbook_first_value(): ], dtype=np.float64, ) - out = ta.RSI(14).batch(prices) + out = _to_np(ta.RSI(14).batch(prices)) assert math.isclose(out[14], 70.464, abs_tol=0.05) def test_anchored_rsi_cumulative_reference(): """Cumulative anchored RSI: 10 -> 11 (+1) -> 9 (-2) -> 12 (+3).""" - out = ta.AnchoredRSI().batch(np.array([10.0, 11.0, 9.0, 12.0])) + out = _to_np(ta.AnchoredRSI().batch(np.array([10.0, 11.0, 9.0, 12.0]))) assert math.isclose(out[1], 100.0, abs_tol=1e-9) assert math.isclose(out[2], 100.0 - 100.0 / 1.5, abs_tol=1e-6) assert math.isclose(out[3], 100.0 - 100.0 / 3.0, abs_tol=1e-6) @@ -79,9 +88,9 @@ def test_inertia_constant_rvi_passes_through_linreg(): # RVI = (c-o) / (h-l) = 0.5 / 2 = 0.25 every bar. LinReg of a constant # series equals that constant after warmup. n = 60 - out = ta.Inertia(3, 4).batch( + out = _to_np(ta.Inertia(3, 4).batch( np.full(n, 10.0), np.full(n, 11.0), np.full(n, 9.0), np.full(n, 10.5) - ) + )) # warmup_period = 3 + 4 - 1 = 6. np.testing.assert_allclose(out[5:], 0.25, atol=1e-12) @@ -90,7 +99,7 @@ def test_connors_rsi_output_is_bounded(): # CRSI is the average of three [0, 100] components, so the aggregate must # also sit in [0, 100] after warmup. prices = 100.0 + 20.0 * np.sin(np.linspace(0, 30, 250)) - out = ta.ConnorsRSI(3, 2, 100).batch(prices.astype(np.float64)) + out = _to_np(ta.ConnorsRSI(3, 2, 100).batch(prices.astype(np.float64))) ready = out[~np.isnan(out)] assert ready.size > 0 assert ready.min() >= 0.0 @@ -101,7 +110,7 @@ def test_laguerre_rsi_constant_series_stays_at_mid_band(): # All four Laguerre stages seed to the first input, so subsequent flat # inputs keep them equal and the up/down accumulator is 0 — Wickra maps # that to the neutral 50. - out = ta.LaguerreRSI(0.5).batch(np.full(40, 42.0, dtype=np.float64)) + out = _to_np(ta.LaguerreRSI(0.5).batch(np.full(40, 42.0, dtype=np.float64))) np.testing.assert_allclose(out, 50.0, atol=1e-12) @@ -109,7 +118,7 @@ def test_smi_close_at_centre_yields_zero(): # Close at the midpoint of a flat high/low range -> displacement is # always zero -> SMI converges to 0. n = 60 - out = ta.SMI(5, 3, 3).batch(np.full(n, 11.0), np.full(n, 9.0), np.full(n, 10.0)) + out = _to_np(ta.SMI(5, 3, 3).batch(np.full(n, 11.0), np.full(n, 9.0), np.full(n, 10.0))) # warmup_period = 5 + 3 + 3 - 2 = 9. np.testing.assert_allclose(out[8:], 0.0, atol=1e-12) @@ -118,7 +127,7 @@ def test_kst_constant_series_yields_zero(): # ROC is zero on a flat input, so every RCMA is zero, so KST and its # signal SMA are both zero after warmup. kst = ta.KST(10, 15, 20, 30, 10, 10, 10, 15, 9) - out = kst.batch(np.full(80, 42.0, dtype=np.float64)) + out = _to_np(kst.batch(np.full(80, 42.0, dtype=np.float64))) warmup = kst.warmup_period() # Use NaN-safe comparison on the post-warmup tail. tail = out[warmup - 1 :] @@ -133,7 +142,7 @@ def test_pgo_flat_close_yields_zero(): high = np.full(n, 11.0) low = np.full(n, 9.0) close = np.full(n, 10.0) - out = ta.PGO(5).batch(high, low, close) + out = _to_np(ta.PGO(5).batch(high, low, close)) assert np.all(np.isnan(out[:4])) np.testing.assert_allclose(out[4:], 0.0, atol=1e-12) @@ -141,12 +150,12 @@ def test_pgo_flat_close_yields_zero(): def test_rvi_reference_value_period_2(): # Two bars: (open, high, low, close) = (10, 11, 9, 10.5), (10.5, 11.5, 10, 11). # num = (0.5 + 0.5) = 1.0; den = (2.0 + 1.5) = 3.5; RVI = 1 / 3.5. - out = ta.RVI(2).batch( + out = _to_np(ta.RVI(2).batch( np.array([10.0, 10.5]), np.array([11.0, 11.5]), np.array([9.0, 10.0]), np.array([10.5, 11.0]), - ) + )) assert math.isnan(out[0]) assert math.isclose(out[1], 1.0 / 3.5, abs_tol=1e-12) @@ -154,7 +163,7 @@ def test_rvi_reference_value_period_2(): def test_alma_constant_series_yields_the_constant(): # ALMA's Gaussian weights are normalised, so any constant series is # reproduced exactly after warmup. - out = ta.ALMA(9, 0.85, 6.0).batch(np.full(30, 42.0, dtype=np.float64)) + out = _to_np(ta.ALMA(9, 0.85, 6.0).batch(np.full(30, 42.0, dtype=np.float64))) assert np.all(np.isnan(out[:8])) np.testing.assert_allclose(out[8:], 42.0, atol=1e-12) @@ -162,7 +171,7 @@ def test_alma_constant_series_yields_the_constant(): def test_alma_reference_value_period_3(): # ALMA(period=3, offset=0.85, sigma=6) on [10, 20, 30]. # m = 0.85 * 2 = 1.7; s = 3 / 6 = 0.5; 2*s^2 = 0.5. - out = ta.ALMA(3, 0.85, 6.0).batch(np.array([10.0, 20.0, 30.0])) + out = _to_np(ta.ALMA(3, 0.85, 6.0).batch(np.array([10.0, 20.0, 30.0]))) assert math.isnan(out[0]) and math.isnan(out[1]) # Independently compute the expected Gaussian-weighted sum. w = np.exp(-((np.arange(3, dtype=np.float64) - 1.7) ** 2) / 0.5) @@ -175,7 +184,7 @@ def test_alma_reference_value_period_3(): def test_mcginley_dynamic_constant_series_yields_the_constant(): # ratio = 1, so the recurrence collapses to MD + 0 / divisor = MD. - out = ta.McGinleyDynamic(5).batch(np.full(30, 42.0, dtype=np.float64)) + out = _to_np(ta.McGinleyDynamic(5).batch(np.full(30, 42.0, dtype=np.float64))) assert np.all(np.isnan(out[:4])) np.testing.assert_allclose(out[4:], 42.0, atol=1e-12) @@ -183,7 +192,7 @@ def test_mcginley_dynamic_constant_series_yields_the_constant(): def test_mcginley_dynamic_reference_value(): # Period 3, seed = SMA([10, 20, 30]) = 20.0. Next price 40.0: # ratio = 2; divisor = 0.6 * 3 * 16 = 28.8; next = 20 + 20/28.8. - out = ta.McGinleyDynamic(3).batch(np.array([10.0, 20.0, 30.0, 40.0])) + out = _to_np(ta.McGinleyDynamic(3).batch(np.array([10.0, 20.0, 30.0, 40.0]))) assert math.isnan(out[0]) and math.isnan(out[1]) assert math.isclose(out[2], 20.0, abs_tol=1e-12) expected = 20.0 + 20.0 / (0.6 * 3.0 * 16.0) @@ -193,21 +202,21 @@ def test_mcginley_dynamic_reference_value(): def test_frama_constant_series_yields_the_constant(): # Flat input -> degenerate ranges -> alpha clamps to 0.01 and the EMA # recurrence holds the seed value. - out = ta.FRAMA(4).batch(np.full(20, 42.0, dtype=np.float64)) + out = _to_np(ta.FRAMA(4).batch(np.full(20, 42.0, dtype=np.float64))) assert np.all(np.isnan(out[:3])) np.testing.assert_allclose(out[3:], 42.0, atol=1e-12) def test_frama_pure_uptrend_hugs_latest(): # Monotonic uptrend -> alpha pushed toward 1.0, FRAMA tracks close. - out = ta.FRAMA(4).batch(np.arange(1.0, 9.0, dtype=np.float64)) + out = _to_np(ta.FRAMA(4).batch(np.arange(1.0, 9.0, dtype=np.float64))) assert math.isclose(out[-1], 8.0, abs_tol=0.05) def test_jma_constant_series_yields_the_constant(): # JMA seeds e0 and the output to the first input, so a constant series # is reproduced exactly from the first sample. - out = ta.JMA(14, 0.0, 2).batch(np.full(30, 42.0, dtype=np.float64)) + out = _to_np(ta.JMA(14, 0.0, 2).batch(np.full(30, 42.0, dtype=np.float64))) np.testing.assert_allclose(out, 42.0, atol=1e-12) @@ -215,7 +224,7 @@ def test_evwma_reference_value_period_2(): # EVWMA(2). Bars: (close, volume) = (10, 1), (20, 3), (30, 1). # Bar 2: sum_v = 4, seeded prev = 20, EVWMA = (1*20 + 3*20)/4 = 20. # Bar 3: sum_v = 4 (drops 1, gains 1), EVWMA = (3*20 + 1*30)/4 = 22.5. - out = ta.EVWMA(2).batch(np.array([10.0, 20.0, 30.0]), np.array([1.0, 3.0, 1.0])) + out = _to_np(ta.EVWMA(2).batch(np.array([10.0, 20.0, 30.0]), np.array([1.0, 3.0, 1.0]))) assert math.isnan(out[0]) assert math.isclose(out[1], 20.0, abs_tol=1e-12) assert math.isclose(out[2], 22.5, abs_tol=1e-12) @@ -227,7 +236,7 @@ def test_alligator_constant_series_holds_at_median_price(): n = 30 high = np.full(n, 11.0) low = np.full(n, 9.0) - out = ta.Alligator(13, 8, 5).batch(high, low) + out = _to_np(ta.Alligator(13, 8, 5).batch(high, low)) assert out.shape == (n, 3) for row in out[12:]: assert math.isclose(row[0], 10.0, abs_tol=1e-12) @@ -237,7 +246,7 @@ def test_alligator_constant_series_holds_at_median_price(): def test_vidya_constant_series_holds_seed(): # CMO = 0 on a flat series -> alpha = 0 -> VIDYA holds its seed value. - out = ta.VIDYA(14, 4).batch(np.full(20, 42.0, dtype=np.float64)) + out = _to_np(ta.VIDYA(14, 4).batch(np.full(20, 42.0, dtype=np.float64))) assert np.all(np.isnan(out[:4])) np.testing.assert_allclose(out[4:], 42.0, atol=1e-12) @@ -245,7 +254,7 @@ def test_vidya_constant_series_holds_seed(): def test_zero_lag_macd_constant_series_converges_to_zero(): # Each inner ZLEMA reproduces a constant, so macd, signal and histogram # are all 0 once the slowest branch warms up. - out = ta.ZeroLagMACD(3, 5, 3).batch(np.full(60, 42.0, dtype=np.float64)) + out = _to_np(ta.ZeroLagMACD(3, 5, 3).batch(np.full(60, 42.0, dtype=np.float64))) # Take the last row and verify all three columns are 0. last = out[-1] assert math.isclose(last[0], 0.0, abs_tol=1e-12) @@ -265,7 +274,7 @@ def test_awesome_oscillator_histogram_flat_series_converges_to_zero(): def test_stc_constant_series_yields_zero(): # Flat input collapses both stochastic stages to zero -> STC stays at 0. - out = ta.STC(3, 5, 4, 0.5).batch(np.full(60, 42.0, dtype=np.float64)) + out = _to_np(ta.STC(3, 5, 4, 0.5).batch(np.full(60, 42.0, dtype=np.float64))) ready = out[~np.isnan(out)] assert ready.size > 0 np.testing.assert_array_equal(ready[-5:], np.zeros(5)) @@ -273,7 +282,7 @@ def test_stc_constant_series_yields_zero(): def test_elder_impulse_constant_series_is_neutral(): # Flat input -> neither EMA nor MACD histogram moves -> Impulse stays at 0. - out = ta.ElderImpulse(13, 12, 26, 9).batch(np.full(120, 42.0, dtype=np.float64)) + out = _to_np(ta.ElderImpulse(13, 12, 26, 9).batch(np.full(120, 42.0, dtype=np.float64))) ready = out[~np.isnan(out)] assert ready.size > 0 np.testing.assert_array_equal(ready, np.zeros_like(ready)) @@ -281,19 +290,19 @@ def test_elder_impulse_constant_series_is_neutral(): def test_cfo_perfect_linear_series_yields_zero(): # LinReg of a perfectly linear series fits exactly, so CFO = 0 after warmup. - out = ta.CFO(5).batch(np.arange(1.0, 21.0, dtype=np.float64) * 2.0) + out = _to_np(ta.CFO(5).batch(np.arange(1.0, 21.0, dtype=np.float64) * 2.0)) np.testing.assert_allclose(out[4:], 0.0, atol=1e-9) def test_apo_constant_series_converges_to_zero(): # Both EMAs reproduce a constant exactly, so APO = 0 after warmup. - out = ta.APO(3, 5).batch(np.full(30, 42.0, dtype=np.float64)) + out = _to_np(ta.APO(3, 5).batch(np.full(30, 42.0, dtype=np.float64))) assert np.all(np.isnan(out[:4])) np.testing.assert_allclose(out[4:], 0.0, atol=1e-12) def test_macd_constant_series_converges_to_zero(): - out = ta.MACD().batch(np.full(200, 100.0)) + out = _to_np(ta.MACD().batch(np.full(200, 100.0))) # Last row's MACD and signal must be ~0. last = out[-1] assert math.isclose(last[0], 0.0, abs_tol=1e-9) @@ -302,13 +311,13 @@ def test_macd_constant_series_converges_to_zero(): def test_bollinger_constant_series_zero_width(): - out = ta.BollingerBands(20, 2.0).batch(np.full(50, 100.0)) + out = _to_np(ta.BollingerBands(20, 2.0).batch(np.full(50, 100.0))) row = out[-1] np.testing.assert_allclose(row, [100.0, 100.0, 100.0, 0.0], atol=1e-12) def test_bollinger_upper_middle_lower_ordering(): - out = ta.BollingerBands(20, 2.0).batch(np.linspace(50.0, 150.0, 100)) + out = _to_np(ta.BollingerBands(20, 2.0).batch(np.linspace(50.0, 150.0, 100))) ready = out[~np.isnan(out[:, 0])] assert np.all(ready[:, 0] >= ready[:, 1]) assert np.all(ready[:, 1] >= ready[:, 2]) @@ -319,7 +328,7 @@ def test_atr_constant_range_constant_output(): high = np.full(30, 11.0) low = np.full(30, 9.0) close = np.full(30, 10.0) - out = ta.ATR(14).batch(high, low, close) + out = _to_np(ta.ATR(14).batch(high, low, close)) # Once seeded, ATR equals the constant TR of 2. np.testing.assert_allclose(out[13:], 2.0, atol=1e-12) @@ -329,14 +338,14 @@ def test_stochastic_extremes(): high = np.array([10.0, 11.0, 12.0]) low = np.array([8.0, 9.0, 10.0]) close = np.array([9.0, 10.0, 12.0]) - out = ta.Stochastic(3, 1).batch(high, low, close) + out = _to_np(ta.Stochastic(3, 1).batch(high, low, close)) assert math.isclose(out[2, 0], 100.0, abs_tol=1e-12) def test_obv_cumulative_known_sequence(): close = np.array([10.0, 11.0, 10.5, 10.5, 12.0]) volume = np.array([100.0, 20.0, 30.0, 40.0, 10.0]) - out = ta.OBV().batch(close, volume) + out = _to_np(ta.OBV().batch(close, volume)) np.testing.assert_allclose(out, [0.0, 20.0, -10.0, -10.0, 0.0]) @@ -346,7 +355,7 @@ def test_obv_cumulative_known_sequence(): def test_sharpe_ratio_known_window(): # returns [0.01, 0.02, 0.03, 0.04], rf = 0; mean = 0.025; # sample-var = 0.000166...; Sharpe = 0.025 / sqrt(var). - out = ta.SharpeRatio(4, 0.0).batch(np.array([0.01, 0.02, 0.03, 0.04])) + out = _to_np(ta.SharpeRatio(4, 0.0).batch(np.array([0.01, 0.02, 0.03, 0.04]))) expected = 0.025 / math.sqrt(0.000_166_666_666_666_666_67) assert math.isclose(out[3], expected, rel_tol=1e-9) @@ -354,49 +363,49 @@ def test_sharpe_ratio_known_window(): def test_sortino_ratio_known_window(): # returns [-0.02, 0.01, -0.01, 0.03], mar = 0; mean = 0.0025; # downside_sq = 0.0005; dd = sqrt(0.0005/4); Sortino = 0.0025/dd. - out = ta.SortinoRatio(4, 0.0).batch(np.array([-0.02, 0.01, -0.01, 0.03])) + out = _to_np(ta.SortinoRatio(4, 0.0).batch(np.array([-0.02, 0.01, -0.01, 0.03]))) expected = 0.0025 / math.sqrt(0.000_125) assert math.isclose(out[3], expected, rel_tol=1e-9) def test_max_drawdown_known_window(): # window [100, 120, 90] -> peak 120, trough 90 -> 25% drawdown. - out = ta.MaxDrawdown(3).batch(np.array([100.0, 120.0, 90.0])) + out = _to_np(ta.MaxDrawdown(3).batch(np.array([100.0, 120.0, 90.0]))) assert math.isclose(out[2], 0.25, abs_tol=1e-12) def test_pain_index_known_window(): # dd[0..2] = 0, 0, 0.25; mean = 0.25/3. - out = ta.PainIndex(3).batch(np.array([100.0, 120.0, 90.0])) + out = _to_np(ta.PainIndex(3).batch(np.array([100.0, 120.0, 90.0]))) assert math.isclose(out[2], 0.25 / 3.0, abs_tol=1e-12) def test_profit_factor_known_window(): # gains 0.05, losses 0.03 -> PF = 5/3. - out = ta.ProfitFactor(4).batch(np.array([0.02, -0.01, 0.03, -0.02])) + out = _to_np(ta.ProfitFactor(4).batch(np.array([0.02, -0.01, 0.03, -0.02]))) assert math.isclose(out[3], 5.0 / 3.0, rel_tol=1e-9) def test_gain_loss_ratio_known_window(): # avg_win 0.03, avg_loss 0.02 -> GLR = 1.5. - out = ta.GainLossRatio(4).batch(np.array([0.02, -0.01, 0.04, -0.03])) + out = _to_np(ta.GainLossRatio(4).batch(np.array([0.02, -0.01, 0.04, -0.03]))) assert math.isclose(out[3], 1.5, rel_tol=1e-9) def test_omega_ratio_known_window(): # gains 0.04, losses 0.03 -> Omega = 4/3. - out = ta.OmegaRatio(4, 0.0).batch(np.array([-0.02, 0.01, -0.01, 0.03])) + out = _to_np(ta.OmegaRatio(4, 0.0).batch(np.array([-0.02, 0.01, -0.01, 0.03]))) assert math.isclose(out[3], 4.0 / 3.0, rel_tol=1e-9) def test_kelly_criterion_known_window(): # n_win=n_loss=2, payoff=2 -> Kelly = 0.5 - 0.5/2 = 0.25. - out = ta.KellyCriterion(4).batch(np.array([0.02, 0.04, -0.01, -0.02])) + out = _to_np(ta.KellyCriterion(4).batch(np.array([0.02, 0.04, -0.01, -0.02]))) assert math.isclose(out[3], 0.25, rel_tol=1e-9) def test_drawdown_duration_under_water_counter(): - out = ta.DrawdownDuration().batch(np.array([100.0, 95.0, 90.0, 85.0])) + out = _to_np(ta.DrawdownDuration().batch(np.array([100.0, 95.0, 90.0, 85.0]))) np.testing.assert_allclose(out, [0.0, 1.0, 2.0, 3.0]) @@ -404,35 +413,35 @@ def test_recovery_factor_known_path(): # Start 100, peak 110, trough 88 -> max_dd = 0.20; end 130 -> # net_return = 0.30 -> Recovery = 1.5. prices = np.array([100.0, 110.0, 105.0, 95.0, 88.0, 100.0, 120.0, 130.0]) - out = ta.RecoveryFactor().batch(prices) + out = _to_np(ta.RecoveryFactor().batch(prices)) assert math.isclose(out[-1], 1.5, rel_tol=1e-9) def test_alpha_perfect_capm_fit_yields_zero(): bench = np.array([0.01 * i for i in range(1, 21)]) asset = 2.0 * bench - out = ta.Alpha(20, 0.0).batch(asset, bench) + out = _to_np(ta.Alpha(20, 0.0).batch(asset, bench)) assert math.isclose(out[-1], 0.0, abs_tol=1e-12) def test_alpha_additive_offset_recovered(): bench = np.array([0.01 * i for i in range(1, 21)]) asset = bench + 0.005 - out = ta.Alpha(20, 0.0).batch(asset, bench) + out = _to_np(ta.Alpha(20, 0.0).batch(asset, bench)) assert math.isclose(out[-1], 0.005, rel_tol=1e-9) def test_treynor_ratio_known_window(): bench = np.array([0.01 * i for i in range(1, 21)]) asset = 2.0 * bench - out = ta.TreynorRatio(20, 0.0).batch(asset, bench) + out = _to_np(ta.TreynorRatio(20, 0.0).batch(asset, bench)) assert math.isclose(out[-1], bench.mean(), rel_tol=1e-9) def test_information_ratio_known_window(): asset = np.array([0.02, 0.04, 0.06, 0.08]) bench = np.array([0.01, 0.02, 0.03, 0.04]) - out = ta.InformationRatio(4).batch(asset, bench) + out = _to_np(ta.InformationRatio(4).batch(asset, bench)) expected = 0.025 / math.sqrt(0.000_166_666_666_666_666_67) assert math.isclose(out[-1], expected, rel_tol=1e-9) @@ -443,7 +452,7 @@ def test_pairwise_beta_squared_price_is_two(): # and an undefined slope, which the indicator reports as 0). b = np.array([100.0 + 10.0 * math.sin(i * 0.5) for i in range(20)]) a = b**2 - out = ta.PairwiseBeta(5).batch(a, b) + out = _to_np(ta.PairwiseBeta(5).batch(a, b)) assert math.isclose(out[-1], 2.0, rel_tol=1e-9) @@ -451,7 +460,7 @@ def test_pairwise_beta_inverse_price_is_minus_one(): # a = 1/b ⇒ a's log-returns are −1× b's ⇒ pairwise beta = −1. b = np.array([100.0 + 10.0 * math.sin(i * 0.5) for i in range(20)]) a = 1.0 / b - out = ta.PairwiseBeta(5).batch(a, b) + out = _to_np(ta.PairwiseBeta(5).batch(a, b)) assert math.isclose(out[-1], -1.0, rel_tol=1e-9) @@ -460,7 +469,7 @@ def test_pair_spread_zscore_flat_benchmark_sign(): # collapses to the sign of the last move: rising a ⇒ +1, falling a ⇒ −1. a = np.array([100.0, 100.0, 110.0, 105.0, 130.0]) b = np.full_like(a, 100.0) - out = ta.PairSpreadZScore(2, 2).batch(a, b) + out = _to_np(ta.PairSpreadZScore(2, 2).batch(a, b)) assert math.isclose(out[-1], 1.0, abs_tol=1e-9) assert math.isclose(out[-2], -1.0, abs_tol=1e-9) @@ -473,7 +482,7 @@ def test_lead_lag_cross_correlation_negative_lead(): n = 60 a = np.array([sig(t - 2) for t in range(n)]) b = np.array([sig(t) for t in range(n)]) - out = ta.LeadLagCrossCorrelation(12, 5).batch(a, b) + out = _to_np(ta.LeadLagCrossCorrelation(12, 5).batch(a, b)) assert int(out[-1, 0]) == -2 assert out[-1, 1] > 0.99 @@ -482,7 +491,7 @@ def test_cointegration_perfect_pair(): # a = 2*b + 5 exactly ⇒ hedge ratio 2, zero spread, degenerate ADF ⇒ 0. b = np.array([100.0 + t for t in range(40)]) a = 2.0 * b + 5.0 - out = ta.Cointegration(20, 1).batch(a, b) + out = _to_np(ta.Cointegration(20, 1).batch(a, b)) assert math.isclose(out[-1, 0], 2.0, rel_tol=1e-9) assert math.isclose(out[-1, 1], 0.0, abs_tol=1e-6) assert math.isclose(out[-1, 2], 0.0, abs_tol=1e-12) @@ -493,7 +502,7 @@ def test_relative_strength_rising_ratio_is_overbought(): n = 20 a = np.array([100.0 + 2.0 * t for t in range(n)]) b = np.full(n, 100.0) - out = ta.RelativeStrengthAB(5, 5).batch(a, b) + out = _to_np(ta.RelativeStrengthAB(5, 5).batch(a, b)) assert out[-1, 0] > 1.0 assert math.isclose(out[-1, 2], 100.0, abs_tol=1e-9) @@ -501,21 +510,21 @@ def test_relative_strength_rising_ratio_is_overbought(): def test_value_at_risk_known_window(): # returns -5..4 *0.01; q=0.05*9=0.45 -> -0.0455; VaR = 0.0455. returns = np.array([i * 0.01 for i in range(-5, 5)]) - out = ta.ValueAtRisk(10, 0.95).batch(returns) + out = _to_np(ta.ValueAtRisk(10, 0.95).batch(returns)) assert math.isclose(out[-1], 0.0455, rel_tol=1e-9) def test_conditional_value_at_risk_known_window(): # tail = {-0.10}; CVaR = 0.10. returns = np.array([i * 0.01 for i in range(-10, 10)]) - out = ta.ConditionalValueAtRisk(20, 0.95).batch(returns) + out = _to_np(ta.ConditionalValueAtRisk(20, 0.95).batch(returns)) assert math.isclose(out[-1], 0.10, rel_tol=1e-9) def test_calmar_ratio_known_path(): # returns [0.10, -0.20, 0.05]; equity 1.0->1.10->0.88->0.924; # mdd = 0.20; mean = -0.01666...; Calmar = mean / 0.20. - out = ta.CalmarRatio(3).batch(np.array([0.10, -0.20, 0.05])) + out = _to_np(ta.CalmarRatio(3).batch(np.array([0.10, -0.20, 0.05]))) expected = ((0.10 - 0.20 + 0.05) / 3.0) / 0.20 assert math.isclose(out[-1], expected, rel_tol=1e-9) @@ -523,7 +532,7 @@ def test_calmar_ratio_known_path(): def test_average_drawdown_known_window(): # window [100, 120, 90, 110]: one drawdown episode (peak 120, trough 90), # never recovering -> depth (120-90)/120 = 0.25; one episode -> AvgDD = 0.25. - out = ta.AverageDrawdown(4).batch(np.array([100.0, 120.0, 90.0, 110.0])) + out = _to_np(ta.AverageDrawdown(4).batch(np.array([100.0, 120.0, 90.0, 110.0]))) expected = 0.25 assert math.isclose(out[-1], expected, rel_tol=1e-12) @@ -536,7 +545,7 @@ def test_value_area_concentrated_volume_locates_poc(): high = np.array([100.5, 100.5, 100.5, 100.5, 110.5]) low = np.array([99.5, 99.5, 99.5, 99.5, 109.5]) volume = np.array([1.0, 1.0, 1.0, 1.0, 1000.0]) - out = ta.ValueArea(5, 50, 0.70).batch(high, low, volume) + out = _to_np(ta.ValueArea(5, 50, 0.70).batch(high, low, volume)) poc = out[-1, 0] assert 109.5 <= poc <= 110.5 # VAH >= POC >= VAL. @@ -547,7 +556,7 @@ def test_initial_balance_locks_after_period(): # First two bars set IB = [99, 103]. Third bar (extreme) must be ignored. high = np.array([102.0, 103.0, 200.0]) low = np.array([100.0, 99.0, 50.0]) - out = ta.InitialBalance(2).batch(high, low) + out = _to_np(ta.InitialBalance(2).batch(high, low)) # Bar 0: IB = [100, 102]; Bar 1: IB locked at [99, 103]; Bar 2: unchanged. np.testing.assert_allclose(out[0], [102.0, 100.0]) np.testing.assert_allclose(out[1], [103.0, 99.0]) @@ -560,7 +569,7 @@ def test_opening_range_breakout_distance_signed(): high = np.array([102.0, 103.0, 110.0, 110.0]) low = np.array([100.0, 101.0, 102.0, 90.0]) close = np.array([101.0, 102.0, 105.0, 95.0]) - out = ta.OpeningRange(2).batch(high, low, close) + out = _to_np(ta.OpeningRange(2).batch(high, low, close)) assert math.isclose(out[2, 0], 103.0) assert math.isclose(out[2, 1], 100.0) assert math.isclose(out[2, 2], 105.0 - 101.5) @@ -578,19 +587,19 @@ def test_inverse_fisher_saturates_for_large_input(): def test_super_smoother_constant_input_is_constant(): - out = ta.SuperSmoother(20).batch(np.full(200, 50.0)) + out = _to_np(ta.SuperSmoother(20).batch(np.full(200, 50.0))) # Steady-state gain is 1, so a flat input stays flat. np.testing.assert_allclose(out[-50:], 50.0, atol=1e-9) def test_decycler_oscillator_flat_series_is_zero(): - out = ta.DecyclerOscillator(10, 30).batch(np.full(80, 42.0)) + out = _to_np(ta.DecyclerOscillator(10, 30).batch(np.full(80, 42.0))) ready = out[~np.isnan(out)] np.testing.assert_allclose(ready, 0.0, atol=1e-9) def test_mama_constant_series_both_lines_converge_to_price(): - out = ta.MAMA().batch(np.full(200, 100.0)) + out = _to_np(ta.MAMA().batch(np.full(200, 100.0))) last = out[-1] # MAMA and FAMA both track price closely on a flat series. assert abs(last[0] - 100.0) < 1.0 @@ -606,7 +615,7 @@ def test_td_setup_buy_setup_completes_at_minus_9_uptrend(): h = np.arange(2.0, 22.0) l = h - 1.0 c = h - 0.5 - out = ta.TDSetup(4, 9).batch(h, l, c) + out = _to_np(ta.TDSetup(4, 9).batch(h, l, c)) assert out[12] == pytest.approx(-9.0) assert out[-1] == pytest.approx(-9.0) @@ -615,7 +624,7 @@ def test_td_demarker_downtrend_pegs_at_zero(): n = 20 h = np.arange(30.0, 30.0 - n, -1.0) l = h - 2.0 - out = ta.TDDeMarker(5).batch(h, l) + out = _to_np(ta.TDDeMarker(5).batch(h, l)) assert out[-1] == pytest.approx(0.0) @@ -626,7 +635,7 @@ def test_td_pressure_pure_bearish_yields_minus_100(): low = np.full(n, 9.0) close = np.full(n, 9.0) volume = np.full(n, 100.0) - out = ta.TDPressure(5).batch(open_, high, low, close, volume) + out = _to_np(ta.TDPressure(5).batch(open_, high, low, close, volume)) assert out[-1] == pytest.approx(-100.0) @@ -638,7 +647,7 @@ def test_td_combo_uptrend_completes_to_minus_13(): high = np.arange(1.0, 1.0 + n) + 0.5 low = high - 1.0 close = high - 0.5 - out = ta.TDCombo().batch(high, low, close) + out = _to_np(ta.TDCombo().batch(high, low, close)) assert out[-1] == pytest.approx(-13.0) @@ -647,7 +656,7 @@ def test_td_countdown_uptrend_completes_to_minus_13(): high = np.arange(1.0, 1.0 + n) + 0.5 low = high - 1.0 close = high - 0.5 - out = ta.TDCountdown().batch(high, low, close) + out = _to_np(ta.TDCountdown().batch(high, low, close)) assert out[-1] == pytest.approx(-13.0) @@ -655,9 +664,9 @@ def test_td_range_projection_doji_reference(): # open=close=10, high=12, low=9 -> doji branch. # pivot_sum = 12 + 9 + 2*10 = 41; half = 20.5. # projHigh = 20.5 - 9 = 11.5; projLow = 20.5 - 12 = 8.5. - out = ta.TDRangeProjection().batch( + out = _to_np(ta.TDRangeProjection().batch( np.array([10.0]), np.array([12.0]), np.array([9.0]), np.array([10.0]) - ) + )) assert out[0, 0] == pytest.approx(11.5) assert out[0, 1] == pytest.approx(8.5) @@ -685,7 +694,7 @@ def test_td_lines_uptrend_support_reference(): high = np.arange(1.0, 1.0 + n) + 0.5 low = high - 1.0 close = high - 0.5 - out = ta.TDLines().batch(high, low, close) + out = _to_np(ta.TDLines().batch(high, low, close)) assert math.isnan(out[-1, 0]) assert out[-1, 1] == pytest.approx(4.5) @@ -699,7 +708,7 @@ def test_td_risk_level_uptrend_sell_risk_reference(): high = np.arange(1.0, 1.0 + n) + 0.5 low = high - 1.0 close = high - 0.5 - out = ta.TDRiskLevel().batch(high, low, close) + out = _to_np(ta.TDRiskLevel().batch(high, low, close)) assert math.isnan(out[12, 0]) assert out[12, 1] == pytest.approx(15.0) @@ -729,7 +738,7 @@ def test_donchian_stop_window_extremes(): # 5-bar window of highs 1..5 and lows 0..4. high = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) low = np.array([0.0, 1.0, 2.0, 3.0, 4.0]) - out = ta.DonchianStop(5).batch(high, low) + out = _to_np(ta.DonchianStop(5).batch(high, low)) # First 4 rows NaN, fifth row: stop_long = 0, stop_short = 5. for i in range(4): assert math.isnan(out[i, 0]) @@ -744,7 +753,7 @@ def test_hilo_activator_flat_market_holds_low_sma(): h = np.full(15, 11.0) l = np.full(15, 9.0) c = np.full(15, 10.0) - out = ta.HiLoActivator(3).batch(h, l, c) + out = _to_np(ta.HiLoActivator(3).batch(h, l, c)) # warmup_period == period + 1 == 4, so indices 0..2 are NaN; index 3 onwards is 9. for i in range(3): assert math.isnan(out[i]) @@ -757,7 +766,7 @@ def test_volty_stop_flat_market_constant_level(): h = np.full(20, 11.0) l = np.full(20, 9.0) c = np.full(20, 10.0) - out = ta.VoltyStop(5, 2.0).batch(h, l, c) + out = _to_np(ta.VoltyStop(5, 2.0).batch(h, l, c)) for i in range(4): assert math.isnan(out[i]) for i in range(4, 20): @@ -769,7 +778,7 @@ def test_yoyo_exit_flat_market_constant_level(): h = np.full(20, 11.0) l = np.full(20, 9.0) c = np.full(20, 10.0) - out = ta.YoyoExit(5, 2.0).batch(h, l, c) + out = _to_np(ta.YoyoExit(5, 2.0).batch(h, l, c)) for i in range(4): assert math.isnan(out[i]) for i in range(4, 20): @@ -781,7 +790,7 @@ def test_rvi_volatility_pure_uptrend_saturates_at_one_hundred(): # RVIVolatility saturates at 100. Renamed from the original ta.RVI in # PR 42 to disambiguate from Family 02's Relative Vigor Index, which # now owns the short ta.RVI name (candle input). - out = ta.RVIVolatility(5).batch(np.arange(1.0, 41.0, dtype=np.float64)) + out = _to_np(ta.RVIVolatility(5).batch(np.arange(1.0, 41.0, dtype=np.float64))) ready = out[~np.isnan(out)] assert ready.size > 0 np.testing.assert_allclose(ready[-10:], 100.0, atol=1e-9) @@ -791,7 +800,7 @@ def test_parkinson_volatility_zero_range_yields_zero(): # H == L every bar -> ln(H/L) = 0 -> Parkinson sigma is zero. h = np.full(30, 10.0) l = np.full(30, 10.0) - out = ta.ParkinsonVolatility(14, 252).batch(h, l) + out = _to_np(ta.ParkinsonVolatility(14, 252).batch(h, l)) ready = out[~np.isnan(out)] assert ready.size > 0 np.testing.assert_allclose(ready, 0.0, atol=1e-12) @@ -803,7 +812,7 @@ def test_garman_klass_zero_movement_yields_zero(): h = np.full(30, 10.0) l = np.full(30, 10.0) c = np.full(30, 10.0) - out = ta.GarmanKlassVolatility(14, 252).batch(o, h, l, c) + out = _to_np(ta.GarmanKlassVolatility(14, 252).batch(o, h, l, c)) ready = out[~np.isnan(out)] assert ready.size > 0 np.testing.assert_allclose(ready, 0.0, atol=1e-12) @@ -814,7 +823,7 @@ def test_rogers_satchell_zero_movement_yields_zero(): h = np.full(30, 10.0) l = np.full(30, 10.0) c = np.full(30, 10.0) - out = ta.RogersSatchellVolatility(14, 252).batch(o, h, l, c) + out = _to_np(ta.RogersSatchellVolatility(14, 252).batch(o, h, l, c)) ready = out[~np.isnan(out)] assert ready.size > 0 np.testing.assert_allclose(ready, 0.0, atol=1e-12) @@ -827,7 +836,7 @@ def test_yang_zhang_zero_movement_yields_zero(): h = np.full(30, 10.0) l = np.full(30, 10.0) c = np.full(30, 10.0) - out = ta.YangZhangVolatility(14, 252).batch(o, h, l, c) + out = _to_np(ta.YangZhangVolatility(14, 252).batch(o, h, l, c)) ready = out[~np.isnan(out)] assert ready.size > 0 np.testing.assert_allclose(ready, 0.0, atol=1e-12) @@ -952,7 +961,7 @@ def test_kyles_lambda_recovers_constant_impact(): size.append(sz) is_buy.append(buy) mids.append(mid) - out = ta.KylesLambda(6).batch(price, size, is_buy, mids) + out = _to_np(ta.KylesLambda(6).batch(price, size, is_buy, mids)) assert out[-1] == pytest.approx(0.5, abs=1e-9) diff --git a/bindings/python/tests/test_new_indicators.py b/bindings/python/tests/test_new_indicators.py index 227e2aaf..ea7f64df 100644 --- a/bindings/python/tests/test_new_indicators.py +++ b/bindings/python/tests/test_new_indicators.py @@ -16,10 +16,23 @@ import pytest import wickra as ta -def _eq_nan(a: np.ndarray, b: np.ndarray, tol: float = 1e-9) -> bool: +def _to_np(x) -> np.ndarray: + """Normalize a Wickra batch result to a float64 ndarray. + + Scalar batches return a stdlib ``array.array`` (1-D, wrapped zero-copy); + multi-output batches return a ``Matrix`` exposing ``.shape``/``.tolist()``. + """ + if isinstance(x, np.ndarray): + return x.astype(np.float64) + if hasattr(x, "tolist") and hasattr(x, "shape"): + return np.asarray(x.tolist(), dtype=np.float64) + return np.asarray(x, dtype=np.float64) + + +def _eq_nan(a, b, tol: float = 1e-9) -> bool: """Compare two float arrays treating NaN and matching-sign inf positions as equal.""" - a = np.asarray(a, dtype=np.float64) - b = np.asarray(b, dtype=np.float64) + a = _to_np(a) + b = _to_np(b) if a.shape != b.shape: return False both_nan = np.isnan(a) & np.isnan(b) @@ -212,8 +225,8 @@ SCALAR_MULTI = { @pytest.mark.parametrize("cls, args", SCALAR, ids=[c.__name__ for c, _ in SCALAR]) def test_scalar_streaming_matches_batch(cls, args, sine_prices): batch = cls(*args).batch(sine_prices) - assert batch.shape == sine_prices.shape - assert batch.dtype == np.float64 + assert len(batch) == len(sine_prices) + assert batch.typecode == "d" streamer = cls(*args) streamed = [] @@ -250,8 +263,8 @@ def test_pair_streaming_matches_batch(cls, args, sine_prices): asset = np.ascontiguousarray(sine_prices.astype(np.float64)) bench = np.ascontiguousarray((sine_prices * 0.7 + 0.001).astype(np.float64)) batch = cls(*args).batch(asset, bench) - assert batch.shape == asset.shape - assert batch.dtype == np.float64 + assert len(batch) == len(asset) + assert batch.typecode == "d" streamer = cls(*args) streamed = [] @@ -270,7 +283,7 @@ def test_lead_lag_detects_lead(): a = np.array([_ll_signal(t) for t in range(n)]) # b is a delayed by 3 ⇒ a leads b ⇒ lag = +3, correlation ≈ 1. b = np.array([_ll_signal(t - 3) for t in range(n)]) - out = ta.LeadLagCrossCorrelation(12, 5).batch(a, b) + out = _to_np(ta.LeadLagCrossCorrelation(12, 5).batch(a, b)) assert out.shape == (n, 2) assert int(out[-1, 0]) == 3 assert out[-1, 1] > 0.99 @@ -298,7 +311,7 @@ def test_cointegration_detects_mean_reverting_pair(): b = np.array([50.0 + 0.5 * t for t in range(n)]) # a tracks 2*b with a small mean-reverting wobble ⇒ cointegrated. a = 2.0 * b + 1.0 + 0.5 * np.sin(np.arange(n) * 0.6) - out = ta.Cointegration(40, 1).batch(a, b) + out = _to_np(ta.Cointegration(40, 1).batch(a, b)) assert out.shape == (n, 3) assert abs(out[-1, 0] - 2.0) < 0.1 # hedge ratio assert out[-1, 2] < -2.0 # ADF statistic: strongly mean-reverting @@ -361,7 +374,7 @@ def test_relative_strength_constant_ratio(): n = 30 a = np.full(n, 200.0) b = np.full(n, 100.0) # ratio is a constant 2 - out = ta.RelativeStrengthAB(5, 5).batch(a, b) + out = _to_np(ta.RelativeStrengthAB(5, 5).batch(a, b)) assert out.shape == (n, 3) assert math.isclose(out[-1, 0], 2.0, abs_tol=1e-12) # ratio assert math.isclose(out[-1, 1], 2.0, abs_tol=1e-12) # ratio MA @@ -1009,7 +1022,7 @@ def test_candle_scalar_streaming_matches_batch(name, ohlcv): make, batch_call = CANDLE_SCALAR[name] batch = batch_call(make(), high, low, close, volume) - assert batch.shape == close.shape + assert len(batch) == len(close) streamer = make() streamed = [] @@ -1527,7 +1540,7 @@ def test_td_pressure_streaming_matches_batch(ohlcv): high, low, close, volume = ohlcv open_ = close.copy() # TD Pressure needs open; reuse close as the open column. batch = ta.TDPressure(5).batch(open_, high, low, close, volume) - assert batch.shape == close.shape + assert len(batch) == len(close) streamer = ta.TDPressure(5) streamed = [] @@ -1629,7 +1642,7 @@ def test_plus_dm_reference(): high = np.array([11.0, 12.0, 13.0, 14.0, 15.0]) low = np.array([9.0, 9.5, 10.0, 10.5, 11.0]) close = np.array([10.0, 11.0, 12.0, 13.0, 14.0]) - out = ta.PLUS_DM(3).batch(high, low, close) + out = _to_np(ta.PLUS_DM(3).batch(high, low, close)) assert math.isnan(out[0]) and math.isnan(out[2]) assert out[3] == pytest.approx(3.0) assert out[4] == pytest.approx(3.0) @@ -1641,7 +1654,7 @@ def test_minus_dm_reference(): high = np.array([20.0, 19.5, 19.0, 18.5, 18.0]) low = np.array([18.0, 17.0, 16.0, 15.0, 14.0]) close = np.array([19.0, 18.0, 17.0, 16.0, 15.0]) - out = ta.MINUS_DM(3).batch(high, low, close) + out = _to_np(ta.MINUS_DM(3).batch(high, low, close)) assert math.isnan(out[0]) and math.isnan(out[2]) assert out[3] == pytest.approx(3.0) assert out[4] == pytest.approx(3.0) @@ -1652,7 +1665,7 @@ def test_plus_di_reference(): high = np.array([101.0, 103.0, 105.0, 107.0, 109.0, 111.0]) low = np.array([99.5, 101.5, 103.5, 105.5, 107.5, 109.5]) close = np.array([100.5, 102.5, 104.5, 106.5, 108.5, 110.5]) - out = ta.PLUS_DI(3).batch(high, low, close) + out = _to_np(ta.PLUS_DI(3).batch(high, low, close)) assert 0.0 < out[-1] <= 100.0 @@ -1661,7 +1674,7 @@ def test_minus_di_reference(): high = np.array([111.0, 109.0, 107.0, 105.0, 103.0, 101.0]) low = np.array([109.5, 107.5, 105.5, 103.5, 101.5, 99.5]) close = np.array([110.5, 108.5, 106.5, 104.5, 102.5, 100.5]) - out = ta.MINUS_DI(3).batch(high, low, close) + out = _to_np(ta.MINUS_DI(3).batch(high, low, close)) assert 0.0 < out[-1] <= 100.0 @@ -1670,7 +1683,7 @@ def test_dx_reference(): high = np.array([101.0, 103.0, 105.0, 107.0, 109.0, 111.0]) low = np.array([99.5, 101.5, 103.5, 105.5, 107.5, 109.5]) close = np.array([100.5, 102.5, 104.5, 106.5, 108.5, 110.5]) - out = ta.DX(3).batch(high, low, close) + out = _to_np(ta.DX(3).batch(high, low, close)) assert 50.0 < out[-1] <= 100.0 @@ -1679,13 +1692,13 @@ def test_mid_price_reference(): high = np.array([12.0, 14.0, 16.0]) low = np.array([8.0, 9.0, 10.0]) close = np.array([10.0, 11.0, 12.0]) - out = ta.MIDPRICE(3).batch(high, low, close) + out = _to_np(ta.MIDPRICE(3).batch(high, low, close)) assert out[-1] == pytest.approx(12.0) def test_mid_point_reference(): # Window {8, 12, 10}: (12 + 8) / 2 = 10. - out = ta.MIDPOINT(3).batch(np.array([8.0, 12.0, 10.0])) + out = _to_np(ta.MIDPOINT(3).batch(np.array([8.0, 12.0, 10.0]))) assert out[-1] == pytest.approx(10.0) @@ -1711,8 +1724,8 @@ def test_linreg_intercept_and_tsf_reference(): def test_macdfix_matches_macd(): # MACDFIX(signal) is exactly MACD(12, 26, signal). prices = 100.0 + np.sin(np.arange(80) * 0.3) * 5.0 - fix = ta.MACDFIX(9).batch(prices) - classic = ta.MACD(12, 26, 9).batch(prices) + fix = _to_np(ta.MACDFIX(9).batch(prices)) + classic = _to_np(ta.MACD(12, 26, 9).batch(prices)) np.testing.assert_allclose(fix, classic, equal_nan=True) @@ -1720,7 +1733,7 @@ def test_nvi_reference(): # closes [10, 11], volumes [200, 100]: volume contracts -> NVI absorbs +10%. # 1000 * (1 + 0.1) = 1100. nvi = ta.NVI() - out = nvi.batch(np.array([10.0, 11.0]), np.array([200.0, 100.0])) + out = _to_np(nvi.batch(np.array([10.0, 11.0]), np.array([200.0, 100.0]))) assert out[0] == pytest.approx(1000.0) assert out[1] == pytest.approx(1100.0) @@ -1728,7 +1741,7 @@ def test_nvi_reference(): def test_pvi_reference(): # closes [10, 11], volumes [100, 200]: volume expands -> PVI absorbs +10%. pvi = ta.PVI() - out = pvi.batch(np.array([10.0, 11.0]), np.array([100.0, 200.0])) + out = _to_np(pvi.batch(np.array([10.0, 11.0]), np.array([100.0, 200.0]))) assert out[0] == pytest.approx(1000.0) assert out[1] == pytest.approx(1100.0) @@ -1737,7 +1750,7 @@ def test_volume_oscillator_reference(): # fast=2, slow=4 over volumes [10, 20, 30, 40, 50]: # bar 4 -> fast=(30+40)/2=35, slow=(10+20+30+40)/4=25 -> VO = 100*(35-25)/25 = 40. vo = ta.VolumeOscillator(2, 4) - out = vo.batch(np.array([10.0, 20.0, 30.0, 40.0, 50.0])) + out = _to_np(vo.batch(np.array([10.0, 20.0, 30.0, 40.0, 50.0]))) assert math.isnan(out[2]) assert out[3] == pytest.approx(40.0) assert out[4] == pytest.approx(1000.0 / 35.0) @@ -1751,7 +1764,7 @@ def test_kvo_constant_series_is_zero(): low = np.full(60, 10.0) close = np.full(60, 10.0) volume = np.full(60, 100.0) - out = kvo.batch(high, low, close, volume) + out = _to_np(kvo.batch(high, low, close, volume)) for v in out[~np.isnan(out)]: assert v == pytest.approx(0.0, abs=1e-12) @@ -1766,7 +1779,7 @@ def test_wad_reference(): high = np.array([11.0, 13.0, 11.0]) low = np.array([9.0, 8.0, 7.0]) close = np.array([10.0, 12.0, 7.0]) - out = ad.batch(high, low, close) + out = _to_np(ad.batch(high, low, close)) assert math.isnan(out[0]) assert out[1] == pytest.approx(4.0) assert out[2] == pytest.approx(-1.0) @@ -1780,7 +1793,7 @@ def test_anchored_vwap_reference(): low = np.array([10.0, 20.0, 30.0]) close = np.array([10.0, 20.0, 30.0]) volume = np.array([1.0, 1.0, 1.0]) - out = avwap.batch(high, low, close, volume) + out = _to_np(avwap.batch(high, low, close, volume)) assert out[2] == pytest.approx(20.0) @@ -1801,7 +1814,7 @@ def test_anchored_rsi_reference(): # bar2: sum_gain=1, sum_loss=2 -> rs=0.5 -> 100 - 100/1.5 = 33.3333 # bar3: sum_gain=4, sum_loss=2 -> rs=2.0 -> 100 - 100/3 = 66.6667 rsi = ta.AnchoredRSI() - out = rsi.batch(np.array([10.0, 11.0, 9.0, 12.0])) + out = _to_np(rsi.batch(np.array([10.0, 11.0, 9.0, 12.0]))) assert np.isnan(out[0]) assert out[1] == pytest.approx(100.0) assert out[2] == pytest.approx(33.333333, abs=1e-4) @@ -1843,7 +1856,7 @@ def test_tsv_reference(): tsv = ta.TSV(3) close = np.array([10.0, 11.0, 13.0, 12.0, 14.0, 15.0]) volume = np.array([50.0, 100.0, 200.0, 150.0, 50.0, 200.0]) - out = tsv.batch(close, volume) + out = _to_np(tsv.batch(close, volume)) assert math.isnan(out[0]) and math.isnan(out[1]) and math.isnan(out[2]) assert out[3] == pytest.approx(350.0) assert out[4] == pytest.approx(350.0) @@ -1857,7 +1870,7 @@ def test_vzo_strictly_rising_saturates_to_plus_100(): vzo = ta.VZO(5) close = np.array([10.0 + i for i in range(60)]) volume = np.full(60, 100.0) - out = vzo.batch(close, volume) + out = _to_np(vzo.batch(close, volume)) last = out[~np.isnan(out)][-1] assert last == pytest.approx(100.0) @@ -1868,7 +1881,7 @@ def test_market_facilitation_index_reference(): high = np.array([12.0]) low = np.array([8.0]) volume = np.array([200.0]) - out = mfi_bw.batch(high, low, volume) + out = _to_np(mfi_bw.batch(high, low, volume)) assert out[0] == pytest.approx(0.02) @@ -1879,7 +1892,7 @@ def test_demand_index_constant_series_is_zero(): low = np.full(60, 10.0) close = np.full(60, 10.0) volume = np.full(60, 100.0) - out = di.batch(high, low, close, volume) + out = _to_np(di.batch(high, low, close, volume)) for v in out[~np.isnan(out)]: assert v == pytest.approx(0.0, abs=1e-12) @@ -1891,13 +1904,13 @@ def test_chaikin_money_flow_reference(): def test_linear_regression_reference(): - out = ta.LinearRegression(3).batch(np.array([1.0, 2.0, 9.0])) + out = _to_np(ta.LinearRegression(3).batch(np.array([1.0, 2.0, 9.0]))) assert math.isnan(out[0]) and math.isnan(out[1]) assert out[2] == pytest.approx(8.0) def test_linreg_slope_reference(): - out = ta.LinRegSlope(3).batch(np.array([1.0, 2.0, 9.0])) + out = _to_np(ta.LinRegSlope(3).batch(np.array([1.0, 2.0, 9.0]))) assert math.isnan(out[0]) and math.isnan(out[1]) assert out[2] == pytest.approx(4.0) @@ -1916,7 +1929,7 @@ def test_true_range_reference(): def test_linreg_angle_reference(): # A series rising by 1 per step has slope 1, and atan(1) = 45 degrees. - out = ta.LinRegAngle(5).batch(np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])) + out = _to_np(ta.LinRegAngle(5).batch(np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]))) assert out[4] == pytest.approx(45.0) @@ -1924,16 +1937,16 @@ def test_wave_trend_flat_market_yields_zero(): # On a perfectly flat market the flat-tolerance guard keeps both lines # at exactly zero (otherwise the ratio ci = (ap - esa) / (0.015 * d) # would explode on the first esa ULP). - out = ta.WaveTrend.classic().batch( + out = _to_np(ta.WaveTrend.classic().batch( np.full(80, 10.0), np.full(80, 10.0), np.full(80, 10.0) - ) + )) last = out[~np.isnan(out[:, 0])][-1] assert last[0] == 0.0 assert last[1] == 0.0 def test_kst_classic_constants_yield_zero(): - out = ta.KST.classic().batch(np.full(120, 100.0)) + out = _to_np(ta.KST.classic().batch(np.full(120, 100.0))) last_row = out[~np.isnan(out[:, 0])][-1] assert last_row[0] == pytest.approx(0.0) assert last_row[1] == pytest.approx(0.0) @@ -1943,13 +1956,13 @@ def test_tii_pure_uptrend_saturates_at_100(): # On a strictly increasing series every close sits above the lagging # SMA, so every deviation is positive and TII reaches 100. prices = np.arange(80, dtype=np.float64) + 100.0 - out = ta.TII(10, 5).batch(prices) + out = _to_np(ta.TII(10, 5).batch(prices)) last = out[~np.isnan(out)][-1] assert last == pytest.approx(100.0) def test_tii_flat_market_yields_50(): - out = ta.TII(5, 4).batch(np.full(30, 10.0)) + out = _to_np(ta.TII(5, 4).batch(np.full(30, 10.0))) last = out[~np.isnan(out)][-1] assert last == 50.0 @@ -1961,7 +1974,7 @@ def test_rwi_reference_uptrend_dominates_low_line(): high = base + 1.0 low = base - 0.5 close = base + 0.5 - out = ta.RWI(14).batch(high, low, close) + out = _to_np(ta.RWI(14).batch(high, low, close)) last_row = out[~np.isnan(out[:, 0])][-1] assert last_row[0] > last_row[1], f"RWI_High {last_row[0]} must dominate RWI_Low {last_row[1]}" assert last_row[0] > 1.0 @@ -1975,14 +1988,14 @@ def test_adxr_reference_on_pure_uptrend(): high = base + 1.0 low = base - 0.5 close = base + 0.5 - out = ta.ADXR(5).batch(high, low, close) + out = _to_np(ta.ADXR(5).batch(high, low, close)) last = out[~np.isnan(out)][-1] assert last == pytest.approx(100.0) def test_z_score_reference(): # Window [1, 3]: mean 2, population stddev 1; latest 3 -> z = 1. - out = ta.ZScore(2).batch(np.array([1.0, 3.0])) + out = _to_np(ta.ZScore(2).batch(np.array([1.0, 3.0]))) assert math.isnan(out[0]) assert out[1] == pytest.approx(1.0) @@ -1992,44 +2005,44 @@ def test_z_score_reference(): def test_variance_reference(): # Variance(3) of [2, 4, 6]: mean 4, variance (4 + 0 + 4) / 3 = 8/3. - out = ta.Variance(3).batch(np.array([2.0, 4.0, 6.0])) + out = _to_np(ta.Variance(3).batch(np.array([2.0, 4.0, 6.0]))) assert math.isnan(out[1]) assert out[2] == pytest.approx(8.0 / 3.0) def test_coefficient_of_variation_reference(): # CV(3) of [2, 4, 6]: sd / mean = sqrt(8/3) / 4. - out = ta.CoefficientOfVariation(3).batch(np.array([2.0, 4.0, 6.0])) + out = _to_np(ta.CoefficientOfVariation(3).batch(np.array([2.0, 4.0, 6.0]))) assert out[2] == pytest.approx(math.sqrt(8.0 / 3.0) / 4.0) def test_skewness_symmetric_window_is_zero(): # Symmetric window has zero Pearson skewness. - out = ta.Skewness(5).batch(np.array([-2.0, -1.0, 0.0, 1.0, 2.0])) + out = _to_np(ta.Skewness(5).batch(np.array([-2.0, -1.0, 0.0, 1.0, 2.0]))) assert out[4] == pytest.approx(0.0, abs=1e-9) def test_kurtosis_two_point_distribution_minimum(): # Alternating {-1, 1} has m4/m2² = 1, so excess kurtosis = -2. - out = ta.Kurtosis(4).batch(np.array([-1.0, 1.0, -1.0, 1.0])) + out = _to_np(ta.Kurtosis(4).batch(np.array([-1.0, 1.0, -1.0, 1.0]))) assert out[3] == pytest.approx(-2.0, abs=1e-9) def test_standard_error_perfect_line_is_zero(): # Residuals are zero on a perfectly linear series. - out = ta.StandardError(5).batch(np.linspace(1.0, 20.0, num=20, dtype=np.float64)) + out = _to_np(ta.StandardError(5).batch(np.linspace(1.0, 20.0, num=20, dtype=np.float64))) finite = out[~np.isnan(out)] assert np.allclose(finite, 0.0, atol=1e-9) def test_detrended_std_dev_perfect_line_is_zero(): - out = ta.DetrendedStdDev(5).batch(np.linspace(1.0, 20.0, num=20, dtype=np.float64)) + out = _to_np(ta.DetrendedStdDev(5).batch(np.linspace(1.0, 20.0, num=20, dtype=np.float64))) finite = out[~np.isnan(out)] assert np.allclose(finite, 0.0, atol=1e-9) def test_r_squared_perfect_line_is_one(): - out = ta.RSquared(5).batch(np.linspace(1.0, 20.0, num=20, dtype=np.float64)) + out = _to_np(ta.RSquared(5).batch(np.linspace(1.0, 20.0, num=20, dtype=np.float64))) finite = out[~np.isnan(out)] assert np.allclose(finite, 1.0, atol=1e-9) @@ -2037,35 +2050,35 @@ def test_r_squared_perfect_line_is_one(): def test_median_absolute_deviation_ignores_single_outlier(): # 9 equal values + 1 huge outlier: MAD is still 0 (more than half agree). prices = np.array([5.0] * 9 + [1000.0], dtype=np.float64) - out = ta.MedianAbsoluteDeviation(10).batch(prices) + out = _to_np(ta.MedianAbsoluteDeviation(10).batch(prices)) assert out[9] == pytest.approx(0.0, abs=1e-12) def test_autocorrelation_alternating_series_negative(): # ±1 alternating: lag-1 ACF must be strongly negative. prices = np.array([-1.0 if i % 2 == 0 else 1.0 for i in range(20)], dtype=np.float64) - out = ta.Autocorrelation(10, 1).batch(prices) + out = _to_np(ta.Autocorrelation(10, 1).batch(prices)) assert out[-1] < -0.5 def test_hurst_exponent_trending_above_half(): # A clean monotone ramp is the textbook persistent series. prices = np.arange(200, dtype=np.float64) - out = ta.HurstExponent(100, 4).batch(prices) + out = _to_np(ta.HurstExponent(100, 4).batch(prices)) assert out[-1] > 0.5 def test_pearson_correlation_perfect_positive_is_one(): x = np.arange(10, dtype=np.float64) y = 2.0 * x + 3.0 - out = ta.PearsonCorrelation(5).batch(x, y) + out = _to_np(ta.PearsonCorrelation(5).batch(x, y)) assert out[-1] == pytest.approx(1.0, abs=1e-9) def test_beta_perfect_two_to_one(): benchmark = np.arange(10, dtype=np.float64) asset = 2.0 * benchmark - out = ta.Beta(5).batch(asset, benchmark) + out = _to_np(ta.Beta(5).batch(asset, benchmark)) assert out[-1] == pytest.approx(2.0, abs=1e-9) @@ -2073,7 +2086,7 @@ def test_spearman_correlation_monotone_nonlinear_is_one(): # y = x^3 is monotone non-linear; Spearman = 1 (Pearson would not be). x = np.arange(1.0, 11.0, dtype=np.float64) y = x**3 - out = ta.SpearmanCorrelation(5).batch(x, y) + out = _to_np(ta.SpearmanCorrelation(5).batch(x, y)) assert out[-1] == pytest.approx(1.0, abs=1e-9) @@ -2109,32 +2122,32 @@ def test_mama_batch_shape_and_streaming_equivalence(sine_prices): def test_inverse_fisher_transform_zero_input_yields_zero(): - out = ta.InverseFisherTransform(1.0).batch(np.array([0.0, 0.0, 0.0])) + out = _to_np(ta.InverseFisherTransform(1.0).batch(np.array([0.0, 0.0, 0.0]))) np.testing.assert_allclose(out, [0.0, 0.0, 0.0], atol=1e-12) def test_fisher_transform_flat_series_is_zero(): # Zero range -> the normaliser yields 0, and tanh(0) chain stays at 0. - out = ta.FisherTransform(5).batch(np.full(20, 42.0)) + out = _to_np(ta.FisherTransform(5).batch(np.full(20, 42.0))) ready = out[~np.isnan(out)] assert np.all(np.abs(ready) < 1e-6) def test_decycler_flat_series_passes_through(): # High-pass of a flat input is zero, so the decycler equals the input. - out = ta.Decycler(20).batch(np.full(30, 100.0)) + out = _to_np(ta.Decycler(20).batch(np.full(30, 100.0))) ready = out[~np.isnan(out)] np.testing.assert_allclose(ready, 100.0, atol=1e-9) def test_center_of_gravity_flat_series_is_zero(): - out = ta.CenterOfGravity(5).batch(np.full(20, 7.0)) + out = _to_np(ta.CenterOfGravity(5).batch(np.full(20, 7.0))) ready = out[~np.isnan(out)] np.testing.assert_allclose(ready, 0.0, atol=1e-12) def test_super_smoother_first_two_outputs_equal_inputs(): - out = ta.SuperSmoother(10).batch(np.array([100.0, 101.0, 102.0])) + out = _to_np(ta.SuperSmoother(10).batch(np.array([100.0, 101.0, 102.0]))) # The 2-pole filter is seeded with raw values for the first two bars. assert out[0] == pytest.approx(100.0) assert out[1] == pytest.approx(101.0) @@ -2145,7 +2158,7 @@ def test_td_setup_pure_uptrend_reaches_minus_9(): h = np.arange(2.0, 22.0) l = h - 1.0 c = h - 0.5 - out = ta.TDSetup(4, 9).batch(h, l, c) + out = _to_np(ta.TDSetup(4, 9).batch(h, l, c)) # Setup completes at index 12 (warmup is 5 -> first emit at index 4 with # value -1, increments to -9 at index 12). assert out[12] == pytest.approx(-9.0) @@ -2156,7 +2169,7 @@ def test_td_demarker_uptrend_pegs_at_one(): # -> indicator == 1 after warmup. h = np.arange(11.0, 31.0) l = h - 2.0 - out = ta.TDDeMarker(5).batch(h, l) + out = _to_np(ta.TDDeMarker(5).batch(h, l)) assert out[-1] == pytest.approx(1.0) @@ -2164,7 +2177,7 @@ def test_td_demarker_flat_market_emits_05(): # All highs and lows equal -> denominator is zero -> neutral fallback 0.5. h = np.full(20, 11.0) l = np.full(20, 9.0) - out = ta.TDDeMarker(5).batch(h, l) + out = _to_np(ta.TDDeMarker(5).batch(h, l)) assert out[-1] == pytest.approx(0.5) @@ -2177,7 +2190,7 @@ def test_td_pressure_pure_bullish_yields_100(): low = np.full(n, 9.0) close = np.full(n, 11.0) volume = np.full(n, 100.0) - out = ta.TDPressure(5).batch(open_, high, low, close, volume) + out = _to_np(ta.TDPressure(5).batch(open_, high, low, close, volume)) assert out[-1] == pytest.approx(100.0) @@ -2189,7 +2202,7 @@ def test_td_combo_uptrend_saturates_at_minus_13(): high = np.arange(1.0, 1.0 + n) + 0.5 low = high - 1.0 close = high - 0.5 - out = ta.TDCombo().batch(high, low, close) + out = _to_np(ta.TDCombo().batch(high, low, close)) assert out[-1] == pytest.approx(-13.0) @@ -2198,7 +2211,7 @@ def test_td_countdown_uptrend_saturates_at_minus_13(): high = np.arange(1.0, 1.0 + n) + 0.5 low = high - 1.0 close = high - 0.5 - out = ta.TDCountdown().batch(high, low, close) + out = _to_np(ta.TDCountdown().batch(high, low, close)) assert out[-1] == pytest.approx(-13.0) @@ -2209,7 +2222,7 @@ def test_td_lines_uptrend_sets_support_at_first_run_low(): high = np.arange(1.0, 1.0 + n) + 0.5 low = high - 1.0 close = high - 0.5 - out = ta.TDLines().batch(high, low, close) + out = _to_np(ta.TDLines().batch(high, low, close)) # support is column 1; resistance is NaN at -1. assert math.isnan(out[-1, 0]) # low at idx 4 = 5 + 0.5 - 1.0 = 4.5. @@ -2220,9 +2233,9 @@ def test_td_range_projection_bullish_bar_reference(): # open=10, high=12, low=9, close=11 (close > open) -> # pivot_sum = 2*12 + 9 + 11 = 44; half = 22. # projHigh = 22 - 9 = 13; projLow = 22 - 12 = 10. - out = ta.TDRangeProjection().batch( + out = _to_np(ta.TDRangeProjection().batch( np.array([10.0]), np.array([12.0]), np.array([9.0]), np.array([11.0]) - ) + )) assert out[0, 0] == pytest.approx(13.0) assert out[0, 1] == pytest.approx(10.0) @@ -2252,7 +2265,7 @@ def test_td_risk_level_uptrend_sets_sell_risk(): high = np.arange(1.0, 1.0 + n) + 0.5 low = high - 1.0 close = high - 0.5 - out = ta.TDRiskLevel().batch(high, low, close) + out = _to_np(ta.TDRiskLevel().batch(high, low, close)) # buy_risk is column 0; sell_risk is column 1. assert math.isnan(out[12, 0]) assert out[12, 1] == pytest.approx(15.0) @@ -2346,7 +2359,7 @@ def test_zigzag_confirms_after_threshold_reversal(): def test_ma_envelope_reference(): # SMA([10, 20, 30]) = 20; with percent = 0.10: upper = 22, lower = 18. - out = ta.MaEnvelope(3, 0.10).batch(np.array([10.0, 20.0, 30.0])) + out = _to_np(ta.MaEnvelope(3, 0.10).batch(np.array([10.0, 20.0, 30.0]))) assert math.isnan(out[0, 0]) and math.isnan(out[1, 0]) assert out[2, 0] == pytest.approx(22.0) # upper assert out[2, 1] == pytest.approx(20.0) # middle @@ -2363,9 +2376,9 @@ def test_acceleration_bands_reference(): def test_atr_bands_reference(): # Five identical bars (h=11, l=9, c=10) → ATR=2, close=10, mult=3: # upper=16, middle=10, lower=4. - out = ta.AtrBands(5, 3.0).batch( + out = _to_np(ta.AtrBands(5, 3.0).batch( np.array([11.0] * 5), np.array([9.0] * 5), np.array([10.0] * 5) - ) + )) assert math.isnan(out[3, 0]) assert out[4, 0] == pytest.approx(16.0) assert out[4, 1] == pytest.approx(10.0) @@ -2374,9 +2387,9 @@ def test_atr_bands_reference(): def test_hurst_channel_reference(): # Five identical (h=12, l=8, c=10): SMA(close)=10, range=4, mult=0.5. - out = ta.HurstChannel(5, 0.5).batch( + out = _to_np(ta.HurstChannel(5, 0.5).batch( np.array([12.0] * 5), np.array([8.0] * 5), np.array([10.0] * 5) - ) + )) assert out[4, 0] == pytest.approx(12.0) assert out[4, 1] == pytest.approx(10.0) assert out[4, 2] == pytest.approx(8.0) @@ -2385,7 +2398,7 @@ def test_hurst_channel_reference(): def test_linreg_channel_reference(): # period 3 over [1, 2, 9]: line y=4x, endpoint=8, residuals=[1, -2, 1], # population sigma=sqrt(2); mult=2 → upper=8+2√2, lower=8-2√2. - out = ta.LinRegChannel(3, 2.0).batch(np.array([1.0, 2.0, 9.0])) + out = _to_np(ta.LinRegChannel(3, 2.0).batch(np.array([1.0, 2.0, 9.0]))) s = math.sqrt(2.0) assert out[2, 0] == pytest.approx(8.0 + 2.0 * s) assert out[2, 1] == pytest.approx(8.0) @@ -2395,7 +2408,7 @@ def test_linreg_channel_reference(): def test_standard_error_bands_reference(): # Same [1, 2, 9] with n=3: SSE=6, n-2=1, stderr=sqrt(6); mult=2 → # upper=8+2√6, lower=8-2√6. - out = ta.StandardErrorBands(3, 2.0).batch(np.array([1.0, 2.0, 9.0])) + out = _to_np(ta.StandardErrorBands(3, 2.0).batch(np.array([1.0, 2.0, 9.0]))) s = math.sqrt(6.0) assert out[2, 0] == pytest.approx(8.0 + 2.0 * s) assert out[2, 1] == pytest.approx(8.0) @@ -2404,9 +2417,9 @@ def test_standard_error_bands_reference(): def test_double_bollinger_orders_bands(): # On a non-trivial dispersion, outer >= inner >= middle >= -inner >= -outer. - out = ta.DoubleBollinger(5, 1.0, 2.0).batch( + out = _to_np(ta.DoubleBollinger(5, 1.0, 2.0).batch( np.array([1.0, 5.0, 2.0, 4.0, 3.0, 6.0]) - ) + )) v = out[5] assert v[0] >= v[1] >= v[2] >= v[3] >= v[4] @@ -2427,7 +2440,7 @@ def test_ttm_squeeze_flat_market(): # Zero volatility: BB and KC both collapse to a point → squeeze=1.0, # momentum=0.0. candles_h = np.array([10.0] * 25) - out = ta.TtmSqueeze(20, 2.0, 1.5).batch(candles_h, candles_h, candles_h) + out = _to_np(ta.TtmSqueeze(20, 2.0, 1.5).batch(candles_h, candles_h, candles_h)) assert out[24, 0] == pytest.approx(1.0) assert out[24, 1] == pytest.approx(0.0) @@ -2436,7 +2449,7 @@ def test_fractal_chaos_bands_detects_peak_and_trough(): # Sequence that creates one fractal high (i=2) and one low (i=3). h = np.array([1.0, 2.0, 5.0, 3.0, 2.0, 1.0, 2.0]) l = np.array([1.0, 2.0, 3.0, 0.5, 2.0, 1.0, 2.0]) - out = ta.FractalChaosBands(2).batch(h, l) + out = _to_np(ta.FractalChaosBands(2).batch(h, l)) # First bar with both bands set is index 5. assert math.isnan(out[4, 0]) assert out[5, 0] == pytest.approx(5.0) @@ -2989,7 +3002,7 @@ def test_spread_ar1_coefficient_reference(): # Spread a - b grows by exactly 1 each bar (unit root) => rho == 1. a = np.array([2.0 * i for i in range(40)]) b = np.array([float(i) for i in range(40)]) - out = ta.SpreadAr1Coefficient(20).batch(a, b) + out = _to_np(ta.SpreadAr1Coefficient(20).batch(a, b)) assert math.isclose(out[-1], 1.0, abs_tol=1e-9) @@ -2998,7 +3011,7 @@ def test_elder_ray_reference(): high = np.array([11.0, 13.0, 16.0]) low = np.array([9.0, 11.0, 13.0]) close = np.array([10.0, 12.0, 14.0]) - out = er.batch(high, low, close) + out = _to_np(er.batch(high, low, close)) # EMA(3) seeds at the third bar with mean close 12; bar high 16 -> bull 4, # low 13 -> bear 1. assert out[2][0] == pytest.approx(4.0) @@ -3011,7 +3024,7 @@ def test_imi_reference(): high = np.array([12.0, 12.0, 13.0]) low = np.array([9.0, 9.0, 9.0]) close = np.array([11.0, 10.0, 12.0]) - out = imi.batch(open_, high, low, close) + out = _to_np(imi.batch(open_, high, low, close)) # bodies +1, -1, +2 -> gain 3, loss 1 -> 100 * 3 / 4 = 75. assert math.isnan(out[0]) assert math.isnan(out[1]) @@ -3022,7 +3035,7 @@ def test_qstick_reference(): q = ta.Qstick(3) open_ = np.array([10.0, 10.0, 10.0]) close = np.array([11.0, 11.0, 11.0]) - out = q.batch(open_, close) + out = _to_np(q.batch(open_, close)) # Each body is close - open = 1; SMA(3) of [1, 1, 1] = 1. assert math.isnan(out[0]) assert math.isnan(out[1]) @@ -3034,7 +3047,7 @@ def test_ttm_trend_reference(): high = np.array([13.0, 13.0, 13.0]) low = np.array([9.0, 9.0, 9.0]) close = np.array([12.0, 12.0, 12.0]) - out = t.batch(high, low, close) + out = _to_np(t.batch(high, low, close)) # Median (13 + 9) / 2 = 11; close 12 is above the SMA(3) reference -> +1. assert math.isnan(out[0]) assert out[2] == pytest.approx(1.0) @@ -3043,7 +3056,7 @@ def test_ttm_trend_reference(): def test_trend_strength_index_reference(): tsi = ta.TREND_STRENGTH_INDEX(10) closes = np.arange(10, dtype=float) - out = tsi.batch(closes) + out = _to_np(tsi.batch(closes)) # A clean ramp is a perfect uptrend -> signed r^2 = +1. assert math.isclose(out[-1], 1.0, abs_tol=1e-9) @@ -3051,7 +3064,7 @@ def test_trend_strength_index_reference(): def test_polarized_fractal_efficiency_reference(): pfe = ta.POLARIZED_FRACTAL_EFFICIENCY(5, 3) closes = np.arange(20, dtype=float) - out = pfe.batch(closes) + out = _to_np(pfe.batch(closes)) # On a straight ramp the path equals the diagonal -> efficiency 1 -> +100. assert math.isclose(out[-1], 100.0, abs_tol=1e-9) @@ -3059,7 +3072,7 @@ def test_polarized_fractal_efficiency_reference(): def test_wave_pm_reference(): wpm = ta.WAVE_PM(10, 3) closes = np.arange(60, dtype=float) * 5.0 - out = wpm.batch(closes) + out = _to_np(wpm.batch(closes)) # Constant-slope ramp: momentum equals its energy -> 100 * (1 - e^-0.5). baseline = 100.0 * (1.0 - math.exp(-0.5)) assert math.isclose(out[-1], baseline, abs_tol=1e-9) @@ -3071,7 +3084,7 @@ def test_gator_oscillator_reference(): high = np.full(n, 11.0) low = np.full(n, 9.0) close = np.full(n, 10.0) - out = g.batch(high, low, close) + out = _to_np(g.batch(high, low, close)) # Constant median collapses all three Alligator lines -> both bars zero. assert out[-1][0] == pytest.approx(0.0) assert out[-1][1] == pytest.approx(0.0) @@ -3081,7 +3094,7 @@ def test_kase_permission_stochastic_reference(): k = ta.KasePermissionStochastic(4, 2) n = 20 flat = np.full(n, 10.0) - out = k.batch(flat, flat, flat) + out = _to_np(k.batch(flat, flat, flat)) # HH == LL -> raw %K defaults to the neutral 50 -> both lines at 50. assert out[-1][0] == pytest.approx(50.0) assert out[-1][1] == pytest.approx(50.0) @@ -3416,7 +3429,7 @@ def test_new_indicators_expose_lifecycle(): def test_ichimoku_batch_shape_and_warmup(ohlcv): high, low, close, _ = ohlcv ichi = ta.Ichimoku() - out = ichi.batch(high, low, close) + out = _to_np(ichi.batch(high, low, close)) assert out.shape == (close.size, 5) # Warmup is 77 for the classic (9, 26, 52, 26) configuration. assert ichi.warmup_period() == 77 @@ -3454,7 +3467,7 @@ def test_ichimoku_chikou_is_close_displacement_back(): close = np.full(n, 100.0) high = close + 1.0 low = close - 1.0 - out = ta.Ichimoku().batch(high, low, close) + out = _to_np(ta.Ichimoku().batch(high, low, close)) # Displacement = 26, so chikou is defined from bar 25 onwards. for i in range(25, n): assert out[i, 4] == pytest.approx(100.0) @@ -3706,7 +3719,7 @@ def test_orderbook_indicators_streaming_equals_batch(): streamed = np.array( [streamer.update(*snap) for snap in snaps], dtype=np.float64 ) - assert batch.shape == (len(snaps),) + assert len(batch) == len(snaps) assert _eq_nan(batch, streamed) @@ -3731,7 +3744,7 @@ def test_tradeflow_indicators_streaming_equals_batch(): [streamer.update(price[i], size[i], is_buy[i]) for i in range(n)], dtype=np.float64, ) - assert batch.shape == (n,) + assert len(batch) == n assert _eq_nan(batch, streamed) @@ -3779,7 +3792,7 @@ def test_price_impact_indicators_streaming_equals_batch(): [streamer.update(price[i], size[i], is_buy[i], mid[i]) for i in range(n)], dtype=np.float64, ) - assert batch.shape == (n,) + assert len(batch) == n assert _eq_nan(batch, streamed) @@ -3793,7 +3806,7 @@ def test_footprint_streaming_equals_batch(): assert len(batch) == n for i in range(n): streamed = streamer.update(price[i], size[i], is_buy[i]) - assert np.array_equal(streamed, batch[i]) + assert np.array_equal(_to_np(streamed), _to_np(batch[i])) def test_funding_indicators_streaming_equals_batch(): @@ -3809,7 +3822,7 @@ def test_funding_indicators_streaming_equals_batch(): streamed = np.array( [streamer.update(rate[i]) for i in range(n)], dtype=np.float64 ) - assert batch.shape == (n,) + assert len(batch) == n assert _eq_nan(batch, streamed) @@ -3833,7 +3846,7 @@ def test_advance_decline_streaming_equals_batch(): ], dtype=np.float64, ) - assert batch.shape == (3,) + assert len(batch) == 3 assert _eq_nan(batch, streamed) # Cumulative line: +2 -> 0 -> 0. assert list(batch) == [2.0, 0.0, 0.0] @@ -3856,7 +3869,7 @@ def _breadth_streaming_equals_batch(indicator, change, volume, new_high, new_low ], dtype=np.float64, ) - assert batch.shape == (len(change),) + assert len(batch) == len(change) assert _eq_nan(batch, streamed) return batch @@ -4040,7 +4053,7 @@ def test_funding_basis_streaming_equals_batch(): streamed = np.array( [streamer.update(mark[i], index[i]) for i in range(n)], dtype=np.float64 ) - assert batch.shape == (n,) + assert len(batch) == n assert _eq_nan(batch, streamed) @@ -4050,7 +4063,7 @@ def test_open_interest_delta_streaming_equals_batch(): batch = ta.OpenInterestDelta().batch(oi) streamer = ta.OpenInterestDelta() streamed = np.array([streamer.update(oi[i]) for i in range(n)], dtype=np.float64) - assert batch.shape == (n,) + assert len(batch) == n assert _eq_nan(batch, streamed) @@ -4067,7 +4080,7 @@ def test_oi_flow_indicators_streaming_equals_batch(): streamed = np.array( [streamer.update(oi[i], mark[i]) for i in range(n)], dtype=np.float64 ) - assert batch.shape == (n,) + assert len(batch) == n assert _eq_nan(batch, streamed) # OIWeighted; update(mark_price, open_interest). @@ -4121,7 +4134,7 @@ def test_basis_indicators_streaming_equals_batch(): streamed = np.array( [streamer.update(futures[i], index[i]) for i in range(n)], dtype=np.float64 ) - assert batch.shape == (n,) + assert len(batch) == n assert _eq_nan(batch, streamed) # CalendarSpread; update(futures_price, mark_price). @@ -4166,7 +4179,7 @@ def test_b16_derivatives_streaming_equals_batch(): streamed = np.array( [streamer.update(oi[i], long_sz[i], short_sz[i]) for i in range(n)], dtype=np.float64 ) - assert batch.shape == (n,) + assert len(batch) == n assert _eq_nan(batch, streamed) # OiToVolumeRatio; update(open_interest, taker_buy_volume, taker_sell_volume). @@ -4210,7 +4223,7 @@ def test_renko_bars_reference(): def test_renko_bars_batch_shape(): r = ta.RenkoBars(1.0) - out = r.batch(np.array([10.0, 11.0, 12.0, 13.0])) + out = _to_np(r.batch(np.array([10.0, 11.0, 12.0, 13.0]))) assert out.shape == (3, 3) np.testing.assert_allclose(out[:, 2], [1.0, 1.0, 1.0]) @@ -4247,7 +4260,7 @@ def test_range_bars_reference(): def test_range_bars_batch_shape(): rb = ta.RangeBars(1.0) - out = rb.batch(np.array([10.0, 11.0, 12.0, 13.0])) + out = _to_np(rb.batch(np.array([10.0, 11.0, 12.0, 13.0]))) assert out.shape == (3, 3) np.testing.assert_allclose(out[:, 2], [1.0, 1.0, 1.0]) @@ -4264,7 +4277,7 @@ def test_tick_bars_batch_shape(): tb = ta.TickBars(2) col = np.array([10.0, 10.0, 10.0, 10.0]) vol = np.array([1.0, 1.0, 1.0, 1.0]) - out = tb.batch(col, col, col, col, vol) + out = _to_np(tb.batch(col, col, col, col, vol)) assert out.shape == (2, 5) @@ -4315,5 +4328,5 @@ def test_three_line_break_bars_reference(): def test_three_line_break_bars_batch_shape(): tlb = ta.ThreeLineBreakBars(3) - out = tlb.batch(np.array([10.0, 11.0, 12.0, 13.0])) + out = _to_np(tlb.batch(np.array([10.0, 11.0, 12.0, 13.0]))) assert out.shape[1] == 3 diff --git a/bindings/python/tests/test_seasonality.py b/bindings/python/tests/test_seasonality.py index 2e0aba1f..dd5a6b16 100644 --- a/bindings/python/tests/test_seasonality.py +++ b/bindings/python/tests/test_seasonality.py @@ -14,6 +14,15 @@ import wickra as ta HOUR_MS = 3_600_000 +def _to_np(x) -> np.ndarray: + """Normalize a Wickra batch result (``array.array`` or ``Matrix``) to an ndarray.""" + if isinstance(x, np.ndarray): + return x.astype(np.float64) + if hasattr(x, "tolist") and hasattr(x, "shape"): + return np.asarray(x.tolist(), dtype=np.float64) + return np.asarray(x, dtype=np.float64) + + @pytest.fixture(scope="module") def candle_columns(): """240 hourly candles (10 days) with valid OHLCV and epoch-ms timestamps.""" @@ -43,7 +52,7 @@ def _check_scalar(make, cols): [np.nan if (v := a.update(c)) is None else v for c in candles], dtype=np.float64, ) - batch = np.asarray(b.batch(*cols)) + batch = _to_np(b.batch(*cols)) np.testing.assert_allclose(stream, batch, equal_nan=True, rtol=1e-9, atol=1e-9) @@ -55,7 +64,7 @@ def _check_matrix(make, k, cols): out = a.update(c) rows.append(np.full(k, np.nan) if out is None else np.asarray(out, dtype=float)) stream = np.vstack(rows) - batch = np.asarray(b.batch(*cols)) + batch = _to_np(b.batch(*cols)) assert batch.shape == (len(candles), k) np.testing.assert_allclose(stream, batch, equal_nan=True, rtol=1e-9, atol=1e-9) diff --git a/bindings/python/tests/test_smoke.py b/bindings/python/tests/test_smoke.py index 9845ba76..1378e9f3 100644 --- a/bindings/python/tests/test_smoke.py +++ b/bindings/python/tests/test_smoke.py @@ -24,8 +24,8 @@ def test_version_is_a_nonempty_string(): ) def test_scalar_batch_returns_same_length(cls, args, sine_prices): out = cls(*args).batch(sine_prices) - assert out.shape == sine_prices.shape - assert out.dtype == np.float64 + assert len(out) == len(sine_prices) + assert out.typecode == "d" def test_macd_batch_returns_n_by_3(sine_prices): @@ -41,7 +41,7 @@ def test_bollinger_batch_returns_n_by_4(sine_prices): def test_atr_batch_shape(ohlc_series): high, low, close = ohlc_series out = ta.ATR(14).batch(high, low, close) - assert out.shape == close.shape + assert len(out) == len(close) def test_stochastic_batch_shape(ohlc_series): @@ -54,7 +54,7 @@ def test_obv_batch_shape(ohlc_series): _, _, close = ohlc_series volume = np.ones_like(close) out = ta.OBV().batch(close, volume) - assert out.shape == close.shape + assert len(out) == len(close) def test_value_area_batch_shape(ohlc_series): @@ -91,7 +91,7 @@ def test_heikin_ashi_batch_returns_n_by_4(ohlc_series): def test_ehlers_super_smoother_batch_shape(sine_prices): out = ta.SuperSmoother(10).batch(sine_prices) - assert out.shape == sine_prices.shape + assert len(out) == len(sine_prices) def test_mama_batch_shape(sine_prices): @@ -118,8 +118,8 @@ def test_orderbook_indicators_construct_and_emit(): def test_orderbook_batch_returns_one_value_per_snapshot(): snapshots = [([100.0], [3.0], [101.0], [1.0])] * 5 out = ta.OrderBookImbalanceTop1().batch(snapshots) - assert out.shape == (5,) - assert out.dtype == np.float64 + assert len(out) == 5 + assert out.typecode == "d" def test_tradeflow_indicators_construct_and_emit(): @@ -134,8 +134,8 @@ def test_tradeflow_batch_returns_one_value_per_trade(): size = np.array([1.0, 2.0, 3.0, 1.0, 2.0, 3.0]) is_buy = [True, False, True, False, True, False] out = ta.CumulativeVolumeDelta().batch(price, size, is_buy) - assert out.shape == (6,) - assert out.dtype == np.float64 + assert len(out) == 6 + assert out.typecode == "d" def test_price_impact_indicators_construct_and_emit(): @@ -152,14 +152,14 @@ def test_price_impact_batch_returns_one_value_per_trade(): mid = np.full(4, 100.0) for ind in (ta.EffectiveSpread(), ta.RealizedSpread(2), ta.KylesLambda(2)): out = ind.batch(price, size, is_buy, mid) - assert out.shape == (4,) - assert out.dtype == np.float64 + assert len(out) == 4 + assert out.typecode == "d" def test_footprint_constructs_and_emits(): out = ta.Footprint(1.0).update(100.2, 2.0, True) assert out.shape == (1, 3) - assert out.dtype == np.float64 + assert isinstance(out[0, 0], float) def test_footprint_batch_returns_list_of_arrays(): diff --git a/bindings/python/tests/test_streaming_vs_batch.py b/bindings/python/tests/test_streaming_vs_batch.py index cb1944fa..20459dd3 100644 --- a/bindings/python/tests/test_streaming_vs_batch.py +++ b/bindings/python/tests/test_streaming_vs_batch.py @@ -15,8 +15,23 @@ import pytest import wickra as ta -def _equal_with_nan(a: np.ndarray, b: np.ndarray, tol: float = 1e-9) -> bool: +def _to_np(x) -> np.ndarray: + """Normalize a Wickra batch result to a float64 ndarray. + + Scalar batches return a stdlib ``array.array``; multi-output batches return a + ``Matrix`` exposing ``.shape``/``.tolist()``. + """ + if isinstance(x, np.ndarray): + return x.astype(np.float64) + if hasattr(x, "tolist") and hasattr(x, "shape"): + return np.asarray(x.tolist(), dtype=np.float64) + return np.asarray(x, dtype=np.float64) + + +def _equal_with_nan(a, b, tol: float = 1e-9) -> bool: """NumPy ``==`` treats NaN as not-equal; emulate ``equal_nan`` for floats.""" + a = _to_np(a) + b = _to_np(b) if a.shape != b.shape: return False both_nan = np.isnan(a) & np.isnan(b)