feat: init the repo
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
use crate::validation;
|
||||
use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
|
||||
use pyo3::prelude::*;
|
||||
|
||||
/// Average True Range using TA-Lib–compatible 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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
Reference in New Issue
Block a user