Files
ferro-ta/src/streaming/mod.rs
T

389 lines
10 KiB
Rust
Raw Normal View History

2026-03-23 23:34:28 +05:30
//! Streaming / Incremental Indicators — bar-by-bar stateful classes.
//!
2026-03-30 12:45:52 +05:30
//! Thin PyO3 wrappers that delegate to `ferro_ta_core::streaming`.
2026-03-23 23:34:28 +05:30
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
2026-03-30 12:45:52 +05:30
use ferro_ta_core::streaming as core;
2026-03-23 23:34:28 +05:30
// ---------------------------------------------------------------------------
2026-03-30 12:45:52 +05:30
// Helper: convert core StreamingError to PyValueError
2026-03-23 23:34:28 +05:30
// ---------------------------------------------------------------------------
2026-03-30 12:45:52 +05:30
fn to_py_err(e: core::StreamingError) -> PyErr {
PyValueError::new_err(e.0)
2026-03-23 23:34:28 +05:30
}
// ---------------------------------------------------------------------------
// StreamingSMA
// ---------------------------------------------------------------------------
/// Simple Moving Average — O(1) per update via running sum.
///
/// Returns NaN during the first `period - 1` bars.
#[pyclass(module = "ferro_ta._ferro_ta")]
pub struct StreamingSMA {
2026-03-30 12:45:52 +05:30
inner: core::StreamingSMA,
2026-03-23 23:34:28 +05:30
}
#[pymethods]
impl StreamingSMA {
#[new]
#[pyo3(signature = (period))]
pub fn new(period: usize) -> PyResult<Self> {
Ok(Self {
2026-03-30 12:45:52 +05:30
inner: core::StreamingSMA::new(period).map_err(to_py_err)?,
2026-03-23 23:34:28 +05:30
})
}
/// Add a new bar and return the current SMA (NaN during warmup).
pub fn update(&mut self, value: f64) -> f64 {
2026-03-30 12:45:52 +05:30
self.inner.update(value)
2026-03-23 23:34:28 +05:30
}
/// Reset state to initial condition.
pub fn reset(&mut self) {
2026-03-30 12:45:52 +05:30
self.inner.reset();
2026-03-23 23:34:28 +05:30
}
#[getter]
pub fn period(&self) -> usize {
2026-03-30 12:45:52 +05:30
self.inner.period()
2026-03-23 23:34:28 +05:30
}
fn __repr__(&self) -> String {
2026-03-30 12:45:52 +05:30
format!("StreamingSMA(period={})", self.inner.period())
2026-03-23 23:34:28 +05:30
}
}
// ---------------------------------------------------------------------------
// StreamingEMA
// ---------------------------------------------------------------------------
/// Exponential Moving Average with SMA seeding.
#[pyclass(module = "ferro_ta._ferro_ta")]
pub struct StreamingEMA {
2026-03-30 12:45:52 +05:30
inner: core::StreamingEMA,
2026-03-23 23:34:28 +05:30
}
#[pymethods]
impl StreamingEMA {
#[new]
#[pyo3(signature = (period))]
pub fn new(period: usize) -> PyResult<Self> {
Ok(Self {
2026-03-30 12:45:52 +05:30
inner: core::StreamingEMA::new(period).map_err(to_py_err)?,
2026-03-23 23:34:28 +05:30
})
}
/// Add a new bar and return the current EMA (NaN during warmup).
pub fn update(&mut self, value: f64) -> f64 {
self.inner.update(value)
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[getter]
pub fn period(&self) -> usize {
2026-03-30 12:45:52 +05:30
self.inner.period()
2026-03-23 23:34:28 +05:30
}
fn __repr__(&self) -> String {
2026-03-30 12:45:52 +05:30
format!("StreamingEMA(period={})", self.inner.period())
2026-03-23 23:34:28 +05:30
}
}
// ---------------------------------------------------------------------------
// StreamingRSI
// ---------------------------------------------------------------------------
/// Relative Strength Index with TA-Libcompatible Wilder seeding.
#[pyclass(module = "ferro_ta._ferro_ta")]
pub struct StreamingRSI {
2026-03-30 12:45:52 +05:30
inner: core::StreamingRSI,
2026-03-23 23:34:28 +05:30
}
#[pymethods]
impl StreamingRSI {
#[new]
#[pyo3(signature = (period = 14))]
pub fn new(period: usize) -> PyResult<Self> {
Ok(Self {
2026-03-30 12:45:52 +05:30
inner: core::StreamingRSI::new(period).map_err(to_py_err)?,
2026-03-23 23:34:28 +05:30
})
}
/// Add a new close and return RSI in [0, 100] (NaN during warmup).
pub fn update(&mut self, value: f64) -> f64 {
2026-03-30 12:45:52 +05:30
self.inner.update(value)
2026-03-23 23:34:28 +05:30
}
pub fn reset(&mut self) {
2026-03-30 12:45:52 +05:30
self.inner.reset();
2026-03-23 23:34:28 +05:30
}
#[getter]
pub fn period(&self) -> usize {
2026-03-30 12:45:52 +05:30
self.inner.period()
2026-03-23 23:34:28 +05:30
}
fn __repr__(&self) -> String {
2026-03-30 12:45:52 +05:30
format!("StreamingRSI(period={})", self.inner.period())
2026-03-23 23:34:28 +05:30
}
}
// ---------------------------------------------------------------------------
// StreamingATR
// ---------------------------------------------------------------------------
/// Average True Range with TA-Libcompatible Wilder seeding.
#[pyclass(module = "ferro_ta._ferro_ta")]
pub struct StreamingATR {
2026-03-30 12:45:52 +05:30
inner: core::StreamingATR,
2026-03-23 23:34:28 +05:30
}
#[pymethods]
impl StreamingATR {
#[new]
#[pyo3(signature = (period = 14))]
pub fn new(period: usize) -> PyResult<Self> {
Ok(Self {
2026-03-30 12:45:52 +05:30
inner: core::StreamingATR::new(period).map_err(to_py_err)?,
2026-03-23 23:34:28 +05:30
})
}
/// Add a new bar (high, low, close) and return ATR (NaN during warmup).
pub fn update(&mut self, high: f64, low: f64, close: f64) -> f64 {
self.inner.update(high, low, close)
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[getter]
pub fn period(&self) -> usize {
2026-03-30 12:45:52 +05:30
self.inner.period()
2026-03-23 23:34:28 +05:30
}
fn __repr__(&self) -> String {
2026-03-30 12:45:52 +05:30
format!("StreamingATR(period={})", self.inner.period())
2026-03-23 23:34:28 +05:30
}
}
// ---------------------------------------------------------------------------
// StreamingBBands
// ---------------------------------------------------------------------------
2026-03-30 12:45:52 +05:30
/// Bollinger Bands — streaming variant using Welford's online algorithm.
2026-03-23 23:34:28 +05:30
#[pyclass(module = "ferro_ta._ferro_ta")]
pub struct StreamingBBands {
2026-03-30 12:45:52 +05:30
inner: core::StreamingBBands,
2026-03-23 23:34:28 +05:30
}
#[pymethods]
impl StreamingBBands {
#[new]
#[pyo3(signature = (period = 20, nbdevup = 2.0, nbdevdn = 2.0))]
pub fn new(period: usize, nbdevup: f64, nbdevdn: f64) -> PyResult<Self> {
Ok(Self {
2026-03-30 12:45:52 +05:30
inner: core::StreamingBBands::new(period, nbdevup, nbdevdn).map_err(to_py_err)?,
2026-03-23 23:34:28 +05:30
})
}
/// Add a new bar; return (upper, middle, lower). NaN tuple during warmup.
pub fn update(&mut self, value: f64) -> (f64, f64, f64) {
2026-03-30 12:45:52 +05:30
self.inner.update(value)
2026-03-23 23:34:28 +05:30
}
pub fn reset(&mut self) {
2026-03-30 12:45:52 +05:30
self.inner.reset();
2026-03-23 23:34:28 +05:30
}
#[getter]
pub fn period(&self) -> usize {
2026-03-30 12:45:52 +05:30
self.inner.period()
2026-03-23 23:34:28 +05:30
}
fn __repr__(&self) -> String {
format!(
2026-03-30 12:45:52 +05:30
"StreamingBBands(period={})",
self.inner.period()
2026-03-23 23:34:28 +05:30
)
}
}
// ---------------------------------------------------------------------------
// StreamingMACD
// ---------------------------------------------------------------------------
/// MACD — fast EMA, slow EMA, signal EMA.
#[pyclass(module = "ferro_ta._ferro_ta")]
pub struct StreamingMACD {
2026-03-30 12:45:52 +05:30
inner: core::StreamingMACD,
2026-03-23 23:34:28 +05:30
}
#[pymethods]
impl StreamingMACD {
#[new]
#[pyo3(signature = (fastperiod = 12, slowperiod = 26, signalperiod = 9))]
pub fn new(fastperiod: usize, slowperiod: usize, signalperiod: usize) -> PyResult<Self> {
Ok(Self {
2026-03-30 12:45:52 +05:30
inner: core::StreamingMACD::new(fastperiod, slowperiod, signalperiod)
.map_err(to_py_err)?,
2026-03-23 23:34:28 +05:30
})
}
/// Add a new close; return (macd_line, signal_line, histogram).
pub fn update(&mut self, value: f64) -> (f64, f64, f64) {
2026-03-30 12:45:52 +05:30
self.inner.update(value)
2026-03-23 23:34:28 +05:30
}
pub fn reset(&mut self) {
2026-03-30 12:45:52 +05:30
self.inner.reset();
2026-03-23 23:34:28 +05:30
}
fn __repr__(&self) -> String {
format!(
"StreamingMACD(fastperiod={}, slowperiod={}, signalperiod={})",
2026-03-30 12:45:52 +05:30
self.inner.fast_period(),
self.inner.slow_period(),
self.inner.signal_period()
2026-03-23 23:34:28 +05:30
)
}
}
// ---------------------------------------------------------------------------
// StreamingStoch
// ---------------------------------------------------------------------------
/// Slow Stochastic (SMA-smoothed).
#[pyclass(module = "ferro_ta._ferro_ta")]
pub struct StreamingStoch {
2026-03-30 12:45:52 +05:30
inner: core::StreamingStoch,
2026-03-23 23:34:28 +05:30
}
#[pymethods]
impl StreamingStoch {
#[new]
#[pyo3(signature = (fastk_period = 5, slowk_period = 3, slowd_period = 3))]
pub fn new(fastk_period: usize, slowk_period: usize, slowd_period: usize) -> PyResult<Self> {
Ok(Self {
2026-03-30 12:45:52 +05:30
inner: core::StreamingStoch::new(fastk_period, slowk_period, slowd_period)
.map_err(to_py_err)?,
2026-03-23 23:34:28 +05:30
})
}
/// Add a new bar (high, low, close); return (slowk, slowd).
pub fn update(&mut self, high: f64, low: f64, close: f64) -> (f64, f64) {
2026-03-30 12:45:52 +05:30
self.inner.update(high, low, close)
2026-03-23 23:34:28 +05:30
}
pub fn reset(&mut self) {
2026-03-30 12:45:52 +05:30
self.inner.reset();
2026-03-23 23:34:28 +05:30
}
fn __repr__(&self) -> String {
format!(
2026-03-30 12:45:52 +05:30
"StreamingStoch(fastk_period={})",
self.inner.period()
2026-03-23 23:34:28 +05:30
)
}
}
// ---------------------------------------------------------------------------
// StreamingVWAP
// ---------------------------------------------------------------------------
/// Cumulative Volume Weighted Average Price.
#[pyclass(module = "ferro_ta._ferro_ta")]
pub struct StreamingVWAP {
2026-03-30 12:45:52 +05:30
inner: core::StreamingVWAP,
2026-03-23 23:34:28 +05:30
}
#[pymethods]
impl StreamingVWAP {
#[new]
pub fn new() -> Self {
Self {
2026-03-30 12:45:52 +05:30
inner: core::StreamingVWAP::new(),
2026-03-23 23:34:28 +05:30
}
}
/// Add a new bar (high, low, close, volume) and return cumulative VWAP.
pub fn update(&mut self, high: f64, low: f64, close: f64, volume: f64) -> f64 {
2026-03-30 12:45:52 +05:30
self.inner.update(high, low, close, volume)
2026-03-23 23:34:28 +05:30
}
/// Reset for a new session.
pub fn reset(&mut self) {
2026-03-30 12:45:52 +05:30
self.inner.reset();
2026-03-23 23:34:28 +05:30
}
fn __repr__(&self) -> String {
"StreamingVWAP()".to_string()
}
}
// ---------------------------------------------------------------------------
// StreamingSupertrend
// ---------------------------------------------------------------------------
/// ATR-based Supertrend — streaming variant.
#[pyclass(module = "ferro_ta._ferro_ta")]
pub struct StreamingSupertrend {
2026-03-30 12:45:52 +05:30
inner: core::StreamingSupertrend,
2026-03-23 23:34:28 +05:30
}
#[pymethods]
impl StreamingSupertrend {
#[new]
#[pyo3(signature = (period = 7, multiplier = 3.0))]
pub fn new(period: usize, multiplier: f64) -> PyResult<Self> {
Ok(Self {
2026-03-30 12:45:52 +05:30
inner: core::StreamingSupertrend::new(period, multiplier).map_err(to_py_err)?,
2026-03-23 23:34:28 +05:30
})
}
/// Add a new bar (high, low, close); return (supertrend_line, direction).
pub fn update(&mut self, high: f64, low: f64, close: f64) -> (f64, i8) {
2026-03-30 12:45:52 +05:30
self.inner.update(high, low, close)
2026-03-23 23:34:28 +05:30
}
pub fn reset(&mut self) {
2026-03-30 12:45:52 +05:30
self.inner.reset();
2026-03-23 23:34:28 +05:30
}
#[getter]
pub fn period(&self) -> usize {
2026-03-30 12:45:52 +05:30
self.inner.period()
2026-03-23 23:34:28 +05:30
}
fn __repr__(&self) -> String {
format!(
2026-03-30 12:45:52 +05:30
"StreamingSupertrend(period={})",
self.inner.period()
2026-03-23 23:34:28 +05:30
)
}
}
// ---------------------------------------------------------------------------
// register
// ---------------------------------------------------------------------------
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<StreamingSMA>()?;
m.add_class::<StreamingEMA>()?;
m.add_class::<StreamingRSI>()?;
m.add_class::<StreamingATR>()?;
m.add_class::<StreamingBBands>()?;
m.add_class::<StreamingMACD>()?;
m.add_class::<StreamingStoch>()?;
m.add_class::<StreamingVWAP>()?;
m.add_class::<StreamingSupertrend>()?;
Ok(())
}