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

74 lines
2.2 KiB
Rust
Raw Normal View History

2026-03-30 12:45:52 +05:30
//! Alerts — condition evaluation helpers (thin PyO3 wrapper over ferro_ta_core::alerts).
2026-03-23 23:34:28 +05:30
use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
/// Fire an alert when *series* crosses a threshold level.
///
/// Parameters
/// ----------
2026-03-30 12:45:52 +05:30
/// series : 1-D float64 array
2026-03-23 23:34:28 +05:30
/// level : float — threshold value
2026-03-30 12:45:52 +05:30
/// direction : int — ``1`` (cross above) or ``-1`` (cross below)
2026-03-23 23:34:28 +05:30
///
/// Returns
/// -------
2026-03-30 12:45:52 +05:30
/// 1-D int8 array — 1 at crossing bars, 0 elsewhere.
2026-03-23 23:34:28 +05:30
#[pyfunction]
pub fn check_threshold<'py>(
py: Python<'py>,
series: PyReadonlyArray1<'py, f64>,
level: f64,
direction: i32,
) -> PyResult<Bound<'py, PyArray1<i8>>> {
if direction != 1 && direction != -1 {
return Err(PyValueError::new_err(
"direction must be 1 (cross above) or -1 (cross below)",
));
}
let s = series.as_slice()?;
2026-03-30 12:45:52 +05:30
let result = ferro_ta_core::alerts::check_threshold(s, level, direction);
Ok(result.into_pyarray(py))
2026-03-23 23:34:28 +05:30
}
/// Detect cross-over / cross-under events between two series.
///
/// Returns
/// -------
2026-03-30 12:45:52 +05:30
/// 1-D int8 array: ``1`` = bullish, ``-1`` = bearish, ``0`` = none.
2026-03-23 23:34:28 +05:30
#[pyfunction]
pub fn check_cross<'py>(
py: Python<'py>,
fast: PyReadonlyArray1<'py, f64>,
slow: PyReadonlyArray1<'py, f64>,
) -> PyResult<Bound<'py, PyArray1<i8>>> {
let f = fast.as_slice()?;
let s = slow.as_slice()?;
2026-03-30 12:45:52 +05:30
if f.len() != s.len() {
2026-03-23 23:34:28 +05:30
return Err(PyValueError::new_err(
"fast and slow must have the same length",
));
}
2026-03-30 12:45:52 +05:30
let result = ferro_ta_core::alerts::check_cross(f, s);
Ok(result.into_pyarray(py))
2026-03-23 23:34:28 +05:30
}
2026-03-30 12:45:52 +05:30
/// Collect bar indices where *mask* is non-zero.
2026-03-23 23:34:28 +05:30
#[pyfunction]
pub fn collect_alert_bars<'py>(
py: Python<'py>,
mask: PyReadonlyArray1<'py, i8>,
) -> PyResult<Bound<'py, PyArray1<i64>>> {
let m = mask.as_slice()?;
2026-03-30 12:45:52 +05:30
let result = ferro_ta_core::alerts::collect_alert_bars(m);
Ok(result.into_pyarray(py))
2026-03-23 23:34:28 +05:30
}
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(check_threshold, m)?)?;
m.add_function(wrap_pyfunction!(check_cross, m)?)?;
m.add_function(wrap_pyfunction!(collect_alert_bars, m)?)?;
Ok(())
}