feat: init the repo

This commit is contained in:
Pratik Bhadane
2026-03-23 23:34:28 +05:30
commit 7a5a220dfe
344 changed files with 75728 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
use crate::validation;
use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
use pyo3::prelude::*;
/// Average True Range using TA-Libcompatible Wilder smoothing.
///
/// Seeding: ATR[period] = SMA of TR[1..=period] (ignoring bar-0 TR which TA-Lib also skips).
/// Subsequent values: ATR[i] = (ATR[i-1] * (period-1) + TR[i]) / period.
/// Returns NaN for indices 0 through `timeperiod - 1`.
#[pyfunction]
#[pyo3(signature = (high, low, close, timeperiod = 14))]
pub fn atr<'py>(
py: Python<'py>,
high: PyReadonlyArray1<'py, f64>,
low: PyReadonlyArray1<'py, f64>,
close: PyReadonlyArray1<'py, f64>,
timeperiod: usize,
) -> PyResult<Bound<'py, PyArray1<f64>>> {
validation::validate_timeperiod(timeperiod, "timeperiod", 1)?;
let highs = high.as_slice()?;
let lows = low.as_slice()?;
let closes = close.as_slice()?;
let n = highs.len();
validation::validate_equal_length(&[
(n, "high"),
(lows.len(), "low"),
(closes.len(), "close"),
])?;
let result = ferro_ta_core::volatility::atr(highs, lows, closes, timeperiod);
Ok(result.into_pyarray(py))
}
+17
View File
@@ -0,0 +1,17 @@
/// Compute True Range for all bars.
/// Bar 0 uses H-L; subsequent bars use TA-Lib formula.
pub(super) fn compute_tr(highs: &[f64], lows: &[f64], closes: &[f64]) -> Vec<f64> {
let n = highs.len();
let mut tr = vec![0.0_f64; n];
if n == 0 {
return tr;
}
tr[0] = highs[0] - lows[0];
for i in 1..n {
let hl = highs[i] - lows[i];
let hpc = (highs[i] - closes[i - 1]).abs();
let lpc = (lows[i] - closes[i - 1]).abs();
tr[i] = hl.max(hpc).max(lpc);
}
tr
}
+15
View File
@@ -0,0 +1,15 @@
//! Volatility indicators — measure the magnitude of price fluctuations.
//! Each indicator lives in its own file for maintainability.
mod atr;
mod natr;
mod trange;
use pyo3::prelude::*;
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(pyo3::wrap_pyfunction!(self::trange::trange, m)?)?;
m.add_function(pyo3::wrap_pyfunction!(self::atr::atr, m)?)?;
m.add_function(pyo3::wrap_pyfunction!(self::natr::natr, m)?)?;
Ok(())
}
+34
View File
@@ -0,0 +1,34 @@
use crate::validation;
use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
use pyo3::prelude::*;
/// Normalized ATR: (ATR / close) * 100. Same warmup as ATR.
#[pyfunction]
#[pyo3(signature = (high, low, close, timeperiod = 14))]
pub fn natr<'py>(
py: Python<'py>,
high: PyReadonlyArray1<'py, f64>,
low: PyReadonlyArray1<'py, f64>,
close: PyReadonlyArray1<'py, f64>,
timeperiod: usize,
) -> PyResult<Bound<'py, PyArray1<f64>>> {
validation::validate_timeperiod(timeperiod, "timeperiod", 1)?;
let highs = high.as_slice()?;
let lows = low.as_slice()?;
let closes = close.as_slice()?;
let n = highs.len();
validation::validate_equal_length(&[
(n, "high"),
(lows.len(), "low"),
(closes.len(), "close"),
])?;
// Reuse the ATR core; divide by close to get NATR (saves duplicate TR computation).
let atr_vals = ferro_ta_core::volatility::atr(highs, lows, closes, timeperiod);
let mut result = vec![f64::NAN; n];
for i in timeperiod..n {
if !atr_vals[i].is_nan() && closes[i] != 0.0 {
result[i] = (atr_vals[i] / closes[i]) * 100.0;
}
}
Ok(result.into_pyarray(py))
}
+34
View File
@@ -0,0 +1,34 @@
use crate::validation;
use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
use pyo3::prelude::*;
/// True Range: max(high - low, |high - prev_close|, |low - prev_close|). Bar 0 uses high - low.
#[pyfunction]
pub fn trange<'py>(
py: Python<'py>,
high: PyReadonlyArray1<'py, f64>,
low: PyReadonlyArray1<'py, f64>,
close: PyReadonlyArray1<'py, f64>,
) -> PyResult<Bound<'py, PyArray1<f64>>> {
let highs = high.as_slice()?;
let lows = low.as_slice()?;
let closes = close.as_slice()?;
let n = highs.len();
validation::validate_equal_length(&[
(n, "high"),
(lows.len(), "low"),
(closes.len(), "close"),
])?;
let mut result = vec![f64::NAN; n];
if n == 0 {
return Ok(result.into_pyarray(py));
}
result[0] = highs[0] - lows[0];
for i in 1..n {
let hl = highs[i] - lows[i];
let hpc = (highs[i] - closes[i - 1]).abs();
let lpc = (lows[i] - closes[i - 1]).abs();
result[i] = hl.max(hpc).max(lpc);
}
Ok(result.into_pyarray(py))
}