Files
ferro-ta/python/ferro_ta/indicators/extended.py
T
Pratik Bhadane 436954138f chore: prepare v1.1.0 release
Update version numbers across Rust, Python, and documentation files to 1.1.0. Enhance the .gitignore to include macOS dSYM files and plans directory. Introduce new dependencies in the Rust core library and update the README to reflect recent performance benchmarks and backtesting engine capabilities. Add new artifacts to the benchmarks manifest and improve documentation for the backtesting engine API.
2026-03-30 12:45:52 +05:30

499 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Extended Indicators — Popular indicators not in the TA-Lib standard set.
All indicator logic is implemented in Rust (PyO3) for maximum performance.
This module provides the public Python API with:
- Input validation
- ``_to_f64`` conversion
- pandas/polars-compatible return values (numpy arrays)
Functions
---------
VWAP — Volume Weighted Average Price (cumulative or rolling)
SUPERTREND — ATR-based trend-following signal
ICHIMOKU — Ichimoku Cloud
DONCHIAN — Donchian Channels
PIVOT_POINTS — Classic / Fibonacci / Camarilla pivot levels
KELTNER_CHANNELS — EMA ± ATR bands
HULL_MA — Hull Moving Average (WMA-based)
CHANDELIER_EXIT — ATR-based stop-loss / exit levels
VWMA — Volume Weighted Moving Average
CHOPPINESS_INDEX — Market choppiness / trending strength index
Rust backend
------------
All computations delegate to Rust functions in the ``_ferro_ta`` extension::
from ferro_ta._ferro_ta import supertrend, donchian, vwap, ...
"""
from __future__ import annotations
import numpy as np
from numpy.typing import ArrayLike
# ---------------------------------------------------------------------------
# Import Rust implementations
# ---------------------------------------------------------------------------
from ferro_ta._ferro_ta import (
chandelier_exit as _rust_chandelier_exit,
)
from ferro_ta._ferro_ta import (
choppiness_index as _rust_choppiness_index,
)
from ferro_ta._ferro_ta import (
donchian as _rust_donchian,
)
from ferro_ta._ferro_ta import (
hull_ma as _rust_hull_ma,
)
from ferro_ta._ferro_ta import (
ichimoku as _rust_ichimoku,
)
from ferro_ta._ferro_ta import (
keltner_channels as _rust_keltner_channels,
)
from ferro_ta._ferro_ta import (
pivot_points as _rust_pivot_points,
)
from ferro_ta._ferro_ta import (
supertrend as _rust_supertrend,
)
from ferro_ta._ferro_ta import (
vwap as _rust_vwap,
)
from ferro_ta._ferro_ta import (
vwma as _rust_vwma,
)
from ferro_ta._utils import _to_f64
from ferro_ta.core.exceptions import FerroTAValueError, _normalize_rust_error
def VWAP(
high: ArrayLike,
low: ArrayLike,
close: ArrayLike,
volume: ArrayLike,
timeperiod: int = 0,
) -> np.ndarray:
"""Volume Weighted Average Price.
Parameters
----------
high : array-like
Sequence of high prices.
low : array-like
Sequence of low prices.
close : array-like
Sequence of closing prices.
volume : array-like
Sequence of volumes.
timeperiod : int, optional
Rolling window length. ``0`` (default) computes a cumulative VWAP
from bar 0 (session VWAP). Any value ``>= 1`` uses a rolling window
of that length; the first ``timeperiod - 1`` values are ``NaN``.
Returns
-------
numpy.ndarray
Array of VWAP values.
Notes
-----
Typical price is used: ``(high + low + close) / 3``.
Implemented in Rust for maximum performance.
"""
if timeperiod < 0:
raise FerroTAValueError("timeperiod must be >= 0 for VWAP")
h = _to_f64(high)
lo = _to_f64(low)
c = _to_f64(close)
v = _to_f64(volume)
try:
return np.asarray(_rust_vwap(h, lo, c, v, timeperiod))
except ValueError as e:
_normalize_rust_error(e)
def SUPERTREND(
high: ArrayLike,
low: ArrayLike,
close: ArrayLike,
timeperiod: int = 7,
multiplier: float = 3.0,
) -> tuple[np.ndarray, np.ndarray]:
"""Supertrend indicator.
An ATR-based trend-following indicator. Returns the Supertrend line and a
direction array.
Parameters
----------
high : array-like
Sequence of high prices.
low : array-like
Sequence of low prices.
close : array-like
Sequence of closing prices.
timeperiod : int, optional
ATR period (default 7).
multiplier : float, optional
ATR multiplier for band width (default 3.0).
Returns
-------
supertrend : numpy.ndarray
The Supertrend line values. ``NaN`` during the warmup period.
direction : numpy.ndarray
``1`` = uptrend (price above Supertrend), ``-1`` = downtrend.
``0`` during warmup.
Notes
-----
Implemented in Rust — the sequential band-adjustment loop that was
previously a Python bottleneck now runs at native speed.
Examples
--------
>>> import numpy as np
>>> from ferro_ta import SUPERTREND
>>> h = np.array([10.0, 11.0, 12.0, 11.0, 10.0, 9.0, 8.0, 9.0, 10.0, 11.0,
... 12.0, 13.0, 14.0, 13.0, 12.0])
>>> l = h - 1.0
>>> c = (h + l) / 2.0
>>> st, dir_ = SUPERTREND(h, l, c)
"""
h = _to_f64(high)
lo = _to_f64(low)
c = _to_f64(close)
try:
st, d = _rust_supertrend(h, lo, c, timeperiod, multiplier)
except ValueError as e:
_normalize_rust_error(e)
return np.asarray(st), np.asarray(d)
def ICHIMOKU(
high: ArrayLike,
low: ArrayLike,
close: ArrayLike,
tenkan_period: int = 9,
kijun_period: int = 26,
senkou_b_period: int = 52,
displacement: int = 26,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
"""Ichimoku Cloud (Ichimoku Kinko Hyo).
Parameters
----------
high : array-like
low : array-like
close : array-like
tenkan_period : int, default 9
Conversion line (Tenkan-sen) period.
kijun_period : int, default 26
Base line (Kijun-sen) period.
senkou_b_period : int, default 52
Leading Span B period.
displacement : int, default 26
Displacement / cloud offset for Senkou A & B.
Returns
-------
tenkan, kijun, senkou_a, senkou_b, chikou : numpy.ndarray
Each is a 1-D float64 array of the same length as the inputs.
Notes
-----
Implemented in Rust with O(n) monotonic deque for all rolling windows.
"""
h = _to_f64(high)
lo = _to_f64(low)
c = _to_f64(close)
try:
t, k, sa, sb, ch = _rust_ichimoku(
h, lo, c, tenkan_period, kijun_period, senkou_b_period, displacement
)
except ValueError as e:
_normalize_rust_error(e)
return (
np.asarray(t),
np.asarray(k),
np.asarray(sa),
np.asarray(sb),
np.asarray(ch),
)
def DONCHIAN(
high: ArrayLike,
low: ArrayLike,
timeperiod: int = 20,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Donchian Channels — rolling highest high / lowest low.
Parameters
----------
high : array-like
low : array-like
timeperiod : int, default 20
Returns
-------
upper, middle, lower : numpy.ndarray
Rolling highest high, midpoint, and lowest low.
Notes
-----
Implemented in Rust with O(n) monotonic deque (no Python loop).
"""
h = _to_f64(high)
lo = _to_f64(low)
try:
upper, middle, lower = _rust_donchian(h, lo, timeperiod)
except ValueError as e:
_normalize_rust_error(e)
return np.asarray(upper), np.asarray(middle), np.asarray(lower)
def PIVOT_POINTS(
high: ArrayLike,
low: ArrayLike,
close: ArrayLike,
method: str = "classic",
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
"""Pivot Points — support / resistance levels.
Computes pivot points for each bar using the *previous bar's* H/L/C.
The first bar output is NaN.
Parameters
----------
high : array-like
low : array-like
close : array-like
method : {'classic', 'fibonacci', 'camarilla'}, default 'classic'
Returns
-------
pivot, r1, s1, r2, s2 : numpy.ndarray
Notes
-----
**Classic**: P=(H+L+C)/3; R1=2PL; S1=2PH; R2=P+(HL); S2=P(HL)
**Fibonacci**: P=(H+L+C)/3; R1=P+0.382*(HL); S1=P0.382*(HL);
R2=P+0.618*(HL); S2=P0.618*(HL)
**Camarilla**: P=(H+L+C)/3; R1=C+1.1*(HL)/12; S1=C1.1*(HL)/12;
R2=C+1.1*(HL)/6; S2=C1.1*(HL)/6
"""
valid_methods = {"classic", "fibonacci", "camarilla"}
if method.lower() not in valid_methods:
raise FerroTAValueError(
f"Unknown pivot method '{method}'. Use 'classic', 'fibonacci', or 'camarilla'."
)
h = _to_f64(high)
lo = _to_f64(low)
c = _to_f64(close)
try:
pivot, r1, s1, r2, s2 = _rust_pivot_points(h, lo, c, method)
except ValueError as e:
_normalize_rust_error(e)
return (
np.asarray(pivot),
np.asarray(r1),
np.asarray(s1),
np.asarray(r2),
np.asarray(s2),
)
def KELTNER_CHANNELS(
high: ArrayLike,
low: ArrayLike,
close: ArrayLike,
timeperiod: int = 20,
atr_period: int = 10,
multiplier: float = 2.0,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Keltner Channels — EMA ± (multiplier × ATR).
Parameters
----------
high : array-like
low : array-like
close : array-like
timeperiod : int, default 20
EMA period for the middle band.
atr_period : int, default 10
ATR period for band width.
multiplier : float, default 2.0
ATR multiplier.
Returns
-------
upper, middle, lower : numpy.ndarray
Notes
-----
Implemented in Rust — EMA and ATR computed inline without Python calls.
"""
h = _to_f64(high)
lo = _to_f64(low)
c = _to_f64(close)
try:
upper, middle, lower = _rust_keltner_channels(
h, lo, c, timeperiod, atr_period, multiplier
)
except ValueError as e:
_normalize_rust_error(e)
return np.asarray(upper), np.asarray(middle), np.asarray(lower)
def HULL_MA(
close: ArrayLike,
timeperiod: int = 16,
) -> np.ndarray:
"""Hull Moving Average (HMA).
A fast-responding moving average that reduces lag.
Parameters
----------
close : array-like
timeperiod : int, default 16
Returns
-------
numpy.ndarray
Notes
-----
Formula: ``HMA(n) = WMA(2 * WMA(n/2) - WMA(n), sqrt(n))``
Implemented in Rust — all WMA computations are in-process.
"""
c = _to_f64(close)
try:
return np.asarray(_rust_hull_ma(c, timeperiod))
except ValueError as e:
_normalize_rust_error(e)
def CHANDELIER_EXIT(
high: ArrayLike,
low: ArrayLike,
close: ArrayLike,
timeperiod: int = 22,
multiplier: float = 3.0,
) -> tuple[np.ndarray, np.ndarray]:
"""Chandelier Exit — ATR-based trailing stop levels.
Parameters
----------
high : array-like
low : array-like
close : array-like
timeperiod : int, default 22
Lookback period for highest high / lowest low and ATR.
multiplier : float, default 3.0
ATR multiplier.
Returns
-------
long_exit, short_exit : numpy.ndarray
Notes
-----
Implemented in Rust with O(n) monotonic deque for rolling max/min.
"""
h = _to_f64(high)
lo = _to_f64(low)
c = _to_f64(close)
try:
long_exit, short_exit = _rust_chandelier_exit(h, lo, c, timeperiod, multiplier)
except ValueError as e:
_normalize_rust_error(e)
return np.asarray(long_exit), np.asarray(short_exit)
def VWMA(
close: ArrayLike,
volume: ArrayLike,
timeperiod: int = 20,
) -> np.ndarray:
"""Volume Weighted Moving Average.
Parameters
----------
close : array-like
volume : array-like
timeperiod : int, default 20
Returns
-------
numpy.ndarray
Notes
-----
``VWMA = sum(close * volume, n) / sum(volume, n)``
Implemented in Rust with O(n) prefix-sum approach.
"""
c = _to_f64(close)
v = _to_f64(volume)
try:
return np.asarray(_rust_vwma(c, v, timeperiod))
except ValueError as e:
_normalize_rust_error(e)
def CHOPPINESS_INDEX(
high: ArrayLike,
low: ArrayLike,
close: ArrayLike,
timeperiod: int = 14,
) -> np.ndarray:
"""Choppiness Index — measures market choppiness (range-bound vs trending).
Parameters
----------
high : array-like
low : array-like
close : array-like
timeperiod : int, default 14
Returns
-------
numpy.ndarray
Values in ``[0, 100]``. Values near 100 indicate choppy/range-bound
markets; values near 0 indicate strong trends.
Notes
-----
``CI = 100 * log10(sum(ATR(1), n) / (highest_high lowest_low)) / log10(n)``
Implemented in Rust with O(n) monotonic deques (no Python loop).
"""
h = _to_f64(high)
lo = _to_f64(low)
c = _to_f64(close)
try:
return np.asarray(_rust_choppiness_index(h, lo, c, timeperiod))
except ValueError as e:
_normalize_rust_error(e)
__all__ = [
"VWAP",
"SUPERTREND",
"ICHIMOKU",
"DONCHIAN",
"PIVOT_POINTS",
"KELTNER_CHANNELS",
"HULL_MA",
"CHANDELIER_EXIT",
"VWMA",
"CHOPPINESS_INDEX",
]