Files
QuanTAlib/python/quantalib/indicators.py
T
2026-02-28 14:14:35 -08:00

1185 lines
55 KiB
Python

"""High-level indicator wrappers for quantalib.
Each function accepts numpy arrays (or pandas Series / DataFrame) and
returns the same type. Multi-output indicators return a tuple of arrays
or a DataFrame depending on input type.
Signature conventions follow pandas-ta where practical:
sma(close, length=10, offset=0, **kwargs)
"""
from __future__ import annotations
import numpy as np
from numpy.typing import NDArray
from ._bridge import _lib, _check, _dp, _ci, _cd
# Optional pandas support
try:
import pandas as pd # type: ignore[import-untyped]
except ImportError: # pragma: no cover
pd = None # type: ignore[assignment]
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
_F64 = np.float64
def _arr(x: object) -> tuple[NDArray[np.float64], object]:
"""Return (contiguous float64 array, original_index_or_None)."""
idx = None
if pd is not None and isinstance(x, pd.Series):
idx = x.index
x = x.to_numpy(dtype=_F64, copy=False)
elif pd is not None and isinstance(x, pd.DataFrame):
# Use first column
idx = x.index
x = x.iloc[:, 0].to_numpy(dtype=_F64, copy=False)
return np.ascontiguousarray(x, dtype=_F64), idx # type: ignore[arg-type]
def _ptr(a: NDArray[np.float64]): # noqa: ANN202
"""Get ctypes double* from array."""
return a.ctypes.data_as(_dp)
def _out(n: int) -> NDArray[np.float64]:
"""Allocate output array."""
return np.empty(n, dtype=_F64)
def _offset(arr: NDArray[np.float64], off: int) -> NDArray[np.float64]:
"""Apply offset (roll + NaN fill)."""
if off and off != 0:
arr = np.roll(arr, off)
if off > 0:
arr[:off] = np.nan
else:
arr[off:] = np.nan
return arr
def _wrap(
arr: NDArray[np.float64],
idx: object,
name: str,
category: str,
offset: int = 0,
):
"""Wrap result: apply offset, optionally convert to pd.Series."""
arr = _offset(arr, offset)
if idx is not None and pd is not None:
s = pd.Series(arr, index=idx, name=name)
s.category = category
return s
return arr
def _wrap_multi(
arrays: dict[str, NDArray[np.float64]],
idx: object,
category: str,
offset: int = 0,
):
"""Wrap multi-output result into tuple or DataFrame."""
for k in arrays:
arrays[k] = _offset(arrays[k], offset)
if idx is not None and pd is not None:
df = pd.DataFrame(arrays, index=idx)
df.category = category
return df
return tuple(arrays.values())
# ═══════════════════════════════════════════════════════════════════════════
# Pattern A: single-input + period (most common)
# ═══════════════════════════════════════════════════════════════════════════
def _pa(
fn_name: str, close: object, length: int, offset: int,
default_length: int, label: str, category: str,
) -> object:
"""Generic Pattern A wrapper."""
length = int(length) if length is not None else default_length
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src)
dst = _out(n)
_check(getattr(_lib, fn_name)(_ptr(src), n, _ptr(dst), length))
return _wrap(dst, idx, f"{label}_{length}", category, offset)
# ═══════════════════════════════════════════════════════════════════════════
# §8.1 Core
# ═══════════════════════════════════════════════════════════════════════════
def avgprice(open: object, high: object, low: object, close: object,
offset: int = 0, **kwargs) -> object:
"""Average Price = (O+H+L+C)/4."""
o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close)
n = len(o); dst = _out(n)
_check(_lib.qtl_avgprice(_ptr(o), _ptr(h), _ptr(l), _ptr(c), n, _ptr(dst)))
return _wrap(dst, idx, "AVGPRICE", "core", int(offset) if offset is not None else 0)
def medprice(high: object, low: object, offset: int = 0, **kwargs) -> object:
"""Median Price = (H+L)/2."""
h, idx = _arr(high); l, _ = _arr(low)
n = len(h); dst = _out(n)
_check(_lib.qtl_medprice(_ptr(h), _ptr(l), n, _ptr(dst)))
return _wrap(dst, idx, "MEDPRICE", "core", int(offset) if offset is not None else 0)
def typprice(open: object, high: object, low: object,
offset: int = 0, **kwargs) -> object:
"""Typical Price = (O+H+L)/3 (QuanTAlib variant)."""
o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low)
n = len(o); dst = _out(n)
_check(_lib.qtl_typprice(_ptr(o), _ptr(h), _ptr(l), n, _ptr(dst)))
return _wrap(dst, idx, "TYPPRICE", "core", int(offset) if offset is not None else 0)
def midbody(open: object, close: object, offset: int = 0, **kwargs) -> object:
"""Mid Body = (O+C)/2."""
o, idx = _arr(open); c, _ = _arr(close)
n = len(o); dst = _out(n)
_check(_lib.qtl_midbody(_ptr(o), _ptr(c), n, _ptr(dst)))
return _wrap(dst, idx, "MIDBODY", "core", int(offset) if offset is not None else 0)
# ═══════════════════════════════════════════════════════════════════════════
# §8.2 Momentum
# ═══════════════════════════════════════════════════════════════════════════
def rsi(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Relative Strength Index."""
return _pa("qtl_rsi", close, length, offset, 14, "RSI", "momentum")
def roc(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Rate of Change."""
return _pa("qtl_roc", close, length, offset, 10, "ROC", "momentum")
def mom(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Momentum."""
return _pa("qtl_mom", close, length, offset, 10, "MOM", "momentum")
def cmo(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Chande Momentum Oscillator."""
return _pa("qtl_cmo", close, length, offset, 14, "CMO", "momentum")
def tsi(close: object, long_period: int = 25, short_period: int = 13,
offset: int = 0, **kwargs) -> object:
"""True Strength Index."""
long_period = int(long_period) if long_period is not None else 25
short_period = int(short_period) if short_period is not None else 13
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_tsi(_ptr(src), n, _ptr(dst), long_period, short_period))
return _wrap(dst, idx, f"TSI_{long_period}_{short_period}", "momentum", offset)
def apo(close: object, fast: int = 12, slow: int = 26,
offset: int = 0, **kwargs) -> object:
"""Absolute Price Oscillator."""
fast = int(fast) if fast is not None else 12
slow = int(slow) if slow is not None else 26
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_apo(_ptr(src), n, _ptr(dst), fast, slow))
return _wrap(dst, idx, f"APO_{fast}_{slow}", "momentum", offset)
def bias(close: object, length: int = 26, offset: int = 0, **kwargs) -> object:
"""Bias."""
return _pa("qtl_bias", close, length, offset, 26, "BIAS", "momentum")
def cfo(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Chande Forecast Oscillator."""
return _pa("qtl_cfo", close, length, offset, 14, "CFO", "momentum")
def cfb(close: object, lengths: list[int] | None = None,
offset: int = 0, **kwargs) -> object:
"""Composite Fractal Behavior."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
import ctypes
if lengths:
arr_t = (ctypes.c_int * len(lengths))(*lengths)
_check(_lib.qtl_cfb(_ptr(src), n, _ptr(dst), arr_t, len(lengths)))
else:
_check(_lib.qtl_cfb(_ptr(src), n, _ptr(dst), None, 0))
return _wrap(dst, idx, "CFB", "momentum", offset)
def asi(open: object, high: object, low: object, close: object,
limit: float = 0.0, offset: int = 0, **kwargs) -> object:
"""Accumulative Swing Index."""
o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close)
n = len(o); dst = _out(n)
_check(_lib.qtl_asi(_ptr(o), _ptr(h), _ptr(l), _ptr(c), n, _ptr(dst), float(limit)))
return _wrap(dst, idx, "ASI", "momentum", int(offset) if offset is not None else 0)
# ═══════════════════════════════════════════════════════════════════════════
# §8.3 Oscillators
# ═══════════════════════════════════════════════════════════════════════════
def fisher(close: object, length: int = 9, offset: int = 0, **kwargs) -> object:
"""Fisher Transform."""
return _pa("qtl_fisher", close, length, offset, 9, "FISHER", "oscillator")
def fisher04(close: object, length: int = 9, offset: int = 0, **kwargs) -> object:
"""Fisher Transform (0.4 variant)."""
return _pa("qtl_fisher04", close, length, offset, 9, "FISHER04", "oscillator")
def dpo(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Detrended Price Oscillator."""
return _pa("qtl_dpo", close, length, offset, 20, "DPO", "oscillator")
def trix(close: object, length: int = 18, offset: int = 0, **kwargs) -> object:
"""Triple EMA Rate of Change."""
return _pa("qtl_trix", close, length, offset, 18, "TRIX", "oscillator")
def inertia(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Inertia."""
return _pa("qtl_inertia", close, length, offset, 20, "INERTIA", "oscillator")
def rsx(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Relative Strength Xtra."""
return _pa("qtl_rsx", close, length, offset, 14, "RSX", "oscillator")
def er(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Efficiency Ratio."""
return _pa("qtl_er", close, length, offset, 10, "ER", "oscillator")
def cti(close: object, length: int = 12, offset: int = 0, **kwargs) -> object:
"""Correlation Trend Indicator."""
return _pa("qtl_cti", close, length, offset, 12, "CTI", "oscillator")
def reflex(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Reflex."""
return _pa("qtl_reflex", close, length, offset, 20, "REFLEX", "oscillator")
def trendflex(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Trendflex."""
return _pa("qtl_trendflex", close, length, offset, 20, "TRENDFLEX", "oscillator")
def kri(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Kairi Relative Index."""
return _pa("qtl_kri", close, length, offset, 20, "KRI", "oscillator")
def psl(close: object, length: int = 12, offset: int = 0, **kwargs) -> object:
"""Psychological Line."""
return _pa("qtl_psl", close, length, offset, 12, "PSL", "oscillator")
def deco(close: object, short_period: int = 30, long_period: int = 60,
offset: int = 0, **kwargs) -> object:
"""DECO."""
short_period = int(short_period) if short_period is not None else 30
long_period = int(long_period) if long_period is not None else 60
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_deco(_ptr(src), n, _ptr(dst), short_period, long_period))
return _wrap(dst, idx, f"DECO_{short_period}_{long_period}", "oscillator", offset)
def dosc(close: object, rsi_period: int = 14, ema1_period: int = 5,
ema2_period: int = 3, signal_period: int = 9,
offset: int = 0, **kwargs) -> object:
"""DeMarker Oscillator."""
rsi_period = int(rsi_period) if rsi_period is not None else 14
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_dosc(_ptr(src), n, _ptr(dst),
rsi_period, int(ema1_period), int(ema2_period), int(signal_period)))
return _wrap(dst, idx, f"DOSC_{rsi_period}", "oscillator", offset)
def dymoi(close: object, base_period: int = 14, short_period: int = 5,
long_period: int = 10, min_period: int = 3, max_period: int = 30,
offset: int = 0, **kwargs) -> object:
"""Dynamic Momentum Index."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_dymoi(_ptr(src), n, _ptr(dst),
int(base_period), int(short_period), int(long_period),
int(min_period), int(max_period)))
return _wrap(dst, idx, "DYMOI", "oscillator", offset)
def crsi(close: object, rsi_period: int = 3, streak_period: int = 2,
rank_period: int = 100, offset: int = 0, **kwargs) -> object:
"""Connors RSI."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_crsi(_ptr(src), n, _ptr(dst),
int(rsi_period), int(streak_period), int(rank_period)))
return _wrap(dst, idx, f"CRSI_{rsi_period}", "oscillator", offset)
def bbb(close: object, length: int = 20, mult: float = 2.0,
offset: int = 0, **kwargs) -> object:
"""Bollinger Band Bounce."""
length = int(length) if length is not None else 20
mult = float(mult) if mult is not None else 2.0
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_bbb(_ptr(src), n, _ptr(dst), length, mult))
return _wrap(dst, idx, f"BBB_{length}", "oscillator", offset)
def bbi(close: object, p1: int = 3, p2: int = 6, p3: int = 12, p4: int = 24,
offset: int = 0, **kwargs) -> object:
"""Bull Bear Index."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_bbi(_ptr(src), n, _ptr(dst), int(p1), int(p2), int(p3), int(p4)))
return _wrap(dst, idx, "BBI", "oscillator", offset)
def dem(high: object, low: object, offset: int = 0, **kwargs) -> object:
"""DeMarker."""
h, idx = _arr(high); l, _ = _arr(low)
n = len(h); dst = _out(n)
_check(_lib.qtl_dem(_ptr(h), _ptr(l), n, _ptr(dst)))
return _wrap(dst, idx, "DEM", "oscillator", int(offset) if offset is not None else 0)
def brar(open: object, high: object, low: object, close: object,
length: int = 26, offset: int = 0, **kwargs) -> object:
"""Bull-Bear Ratio (BRAR)."""
length = int(length) if length is not None else 26
offset = int(offset) if offset is not None else 0
o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close)
n = len(o); br = _out(n); ar = _out(n)
_check(_lib.qtl_brar(_ptr(o), _ptr(h), _ptr(l), _ptr(c), n, _ptr(br), _ptr(ar), length))
return _wrap_multi(
{f"BR_{length}": br, f"AR_{length}": ar},
idx, "oscillator", offset,
)
# ═══════════════════════════════════════════════════════════════════════════
# §8.4 Trends — FIR
# ═══════════════════════════════════════════════════════════════════════════
def sma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Simple Moving Average."""
return _pa("qtl_sma", close, length, offset, 10, "SMA", "trend")
def wma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Weighted Moving Average."""
return _pa("qtl_wma", close, length, offset, 10, "WMA", "trend")
def hma(close: object, length: int = 9, offset: int = 0, **kwargs) -> object:
"""Hull Moving Average."""
return _pa("qtl_hma", close, length, offset, 9, "HMA", "trend")
def trima(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Triangular Moving Average."""
return _pa("qtl_trima", close, length, offset, 10, "TRIMA", "trend")
def swma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Symmetric Weighted Moving Average."""
return _pa("qtl_swma", close, length, offset, 10, "SWMA", "trend")
def dwma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Double Weighted Moving Average."""
return _pa("qtl_dwma", close, length, offset, 10, "DWMA", "trend")
def blma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Blackman Moving Average."""
return _pa("qtl_blma", close, length, offset, 10, "BLMA", "trend")
def alma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Arnaud Legoux Moving Average."""
return _pa("qtl_alma", close, length, offset, 10, "ALMA", "trend")
def lsma(close: object, length: int = 25, offset: int = 0, **kwargs) -> object:
"""Least Squares Moving Average."""
return _pa("qtl_lsma", close, length, offset, 25, "LSMA", "trend")
def sgma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Savitzky-Golay Moving Average."""
return _pa("qtl_sgma", close, length, offset, 10, "SGMA", "trend")
def sinema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Sine-weighted Moving Average."""
return _pa("qtl_sinema", close, length, offset, 10, "SINEMA", "trend")
def hanma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Hann-weighted Moving Average."""
return _pa("qtl_hanma", close, length, offset, 10, "HANMA", "trend")
def parzen(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Parzen-weighted Moving Average."""
return _pa("qtl_parzen", close, length, offset, 10, "PARZEN", "trend")
def tsf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Time Series Forecast."""
return _pa("qtl_tsf", close, length, offset, 14, "TSF", "trend")
def conv(close: object, kernel: list[float] | NDArray[np.float64] | None = None,
offset: int = 0, **kwargs) -> object:
"""Convolution with custom kernel."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
if kernel is None:
kernel = [1.0]
k = np.ascontiguousarray(kernel, dtype=_F64)
_check(_lib.qtl_conv(_ptr(src), n, _ptr(dst), _ptr(k), len(k)))
return _wrap(dst, idx, "CONV", "trend", offset)
def bwma(close: object, length: int = 10, order: int = 0,
offset: int = 0, **kwargs) -> object:
"""Butterworth-weighted Moving Average."""
length = int(length) if length is not None else 10
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_bwma(_ptr(src), n, _ptr(dst), length, int(order)))
return _wrap(dst, idx, f"BWMA_{length}", "trend", offset)
def crma(close: object, length: int = 10, volume_factor: float = 1.0,
offset: int = 0, **kwargs) -> object:
"""Cosine-Ramp Moving Average."""
length = int(length) if length is not None else 10
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_crma(_ptr(src), n, _ptr(dst), length, float(volume_factor)))
return _wrap(dst, idx, f"CRMA_{length}", "trend", offset)
def sp15(close: object, length: int = 15, offset: int = 0, **kwargs) -> object:
"""SP-15 Moving Average."""
return _pa("qtl_sp15", close, length, offset, 15, "SP15", "trend")
def tukey_w(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Tukey-windowed Moving Average."""
return _pa("qtl_tukey_w", close, length, offset, 10, "TUKEY", "trend")
def rain(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""RAIN Moving Average."""
return _pa("qtl_rain", close, length, offset, 10, "RAIN", "trend")
def afirma(close: object, length: int = 10, window_type: int = 0,
use_simd: bool = False, offset: int = 0, **kwargs) -> object:
"""Adaptive FIR Moving Average."""
length = int(length) if length is not None else 10
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_afirma(_ptr(src), n, _ptr(dst), length, int(window_type), int(use_simd)))
return _wrap(dst, idx, f"AFIRMA_{length}", "trend", offset)
# ═══════════════════════════════════════════════════════════════════════════
# §8.5 Trends — IIR
# ═══════════════════════════════════════════════════════════════════════════
def ema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Exponential Moving Average."""
return _pa("qtl_ema", close, length, offset, 10, "EMA", "trend")
def ema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object:
"""EMA with explicit alpha."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_ema_alpha(_ptr(src), n, _ptr(dst), float(alpha)))
return _wrap(dst, idx, f"EMA_a{alpha:.4f}", "trend", offset)
def dema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Double Exponential Moving Average."""
return _pa("qtl_dema", close, length, offset, 10, "DEMA", "trend")
def dema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object:
"""DEMA with explicit alpha."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_dema_alpha(_ptr(src), n, _ptr(dst), float(alpha)))
return _wrap(dst, idx, f"DEMA_a{alpha:.4f}", "trend", offset)
def tema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Triple Exponential Moving Average."""
return _pa("qtl_tema", close, length, offset, 10, "TEMA", "trend")
def lema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Laguerre-based EMA."""
return _pa("qtl_lema", close, length, offset, 10, "LEMA", "trend")
def hema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Henderson EMA."""
return _pa("qtl_hema", close, length, offset, 10, "HEMA", "trend")
def ahrens(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Ahrens Moving Average."""
return _pa("qtl_ahrens", close, length, offset, 10, "AHRENS", "trend")
def decycler(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Simple Decycler."""
return _pa("qtl_decycler", close, length, offset, 20, "DECYCLER", "trend")
def dsma(close: object, length: int = 10, factor: float = 0.5,
offset: int = 0, **kwargs) -> object:
"""Deviation-Scaled Moving Average."""
length = int(length) if length is not None else 10
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_dsma(_ptr(src), n, _ptr(dst), length, float(factor)))
return _wrap(dst, idx, f"DSMA_{length}", "trend", offset)
def gdema(close: object, length: int = 10, vfactor: float = 1.0,
offset: int = 0, **kwargs) -> object:
"""Generalized DEMA."""
length = int(length) if length is not None else 10
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_gdema(_ptr(src), n, _ptr(dst), length, float(vfactor)))
return _wrap(dst, idx, f"GDEMA_{length}", "trend", offset)
def coral(close: object, length: int = 10, friction: float = 0.4,
offset: int = 0, **kwargs) -> object:
"""CORAL Trend."""
length = int(length) if length is not None else 10
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_coral(_ptr(src), n, _ptr(dst), length, float(friction)))
return _wrap(dst, idx, f"CORAL_{length}", "trend", offset)
def agc(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object:
"""Automatic Gain Control."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_agc(_ptr(src), n, _ptr(dst), float(alpha)))
return _wrap(dst, idx, f"AGC_a{alpha:.4f}", "trend", offset)
def ccyc(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object:
"""Cyber Cycle."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_ccyc(_ptr(src), n, _ptr(dst), float(alpha)))
return _wrap(dst, idx, f"CCYC_a{alpha:.4f}", "trend", offset)
# ═══════════════════════════════════════════════════════════════════════════
# §8.6 Channels
# ═══════════════════════════════════════════════════════════════════════════
def bbands(close: object, length: int = 20, std: float = 2.0,
offset: int = 0, **kwargs) -> object:
"""Bollinger Bands → (upper, mid, lower) or DataFrame."""
length = int(length) if length is not None else 20
std = float(std) if std is not None else 2.0
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src)
upper = _out(n); mid = _out(n); lower = _out(n)
_check(_lib.qtl_bbands(
_ptr(src), n, _ptr(upper), _ptr(mid), _ptr(lower), length, std,
))
return _wrap_multi(
{
f"BBU_{length}_{std}": upper,
f"BBM_{length}_{std}": mid,
f"BBL_{length}_{std}": lower,
},
idx, "channels", offset,
)
def aberr(close: object, length: int = 20, mult: float = 2.0,
offset: int = 0, **kwargs) -> object:
"""Aberration Bands → (upper, mid, lower) or DataFrame."""
length = int(length) if length is not None else 20
mult = float(mult) if mult is not None else 2.0
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src)
upper = _out(n); mid = _out(n); lower = _out(n)
_check(_lib.qtl_abber(
_ptr(src), _ptr(mid), _ptr(upper), _ptr(lower), n, length, mult,
))
return _wrap_multi(
{
f"ABERRU_{length}_{mult}": upper,
f"ABERRM_{length}_{mult}": mid,
f"ABERRL_{length}_{mult}": lower,
},
idx, "channels", offset,
)
def atrbands(high: object, low: object, close: object,
length: int = 14, mult: float = 2.0,
offset: int = 0, **kwargs) -> object:
"""ATR Bands → (upper, mid, lower) or DataFrame."""
length = int(length) if length is not None else 14
mult = float(mult) if mult is not None else 2.0
offset = int(offset) if offset is not None else 0
h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close)
n = len(h)
upper = _out(n); mid = _out(n); lower = _out(n)
_check(_lib.qtl_atrbands(
_ptr(h), _ptr(l), _ptr(c), n, _ptr(upper), _ptr(mid), _ptr(lower), length, mult,
))
return _wrap_multi(
{
f"ATRBU_{length}_{mult}": upper,
f"ATRBM_{length}_{mult}": mid,
f"ATRBL_{length}_{mult}": lower,
},
idx, "channels", offset,
)
def apchannel(high: object, low: object, length: int = 20,
offset: int = 0, **kwargs) -> object:
"""Average Price Channel → (upper, lower) or DataFrame."""
length = int(length) if length is not None else 20
offset = int(offset) if offset is not None else 0
h, idx = _arr(high); l, _ = _arr(low)
n = len(h)
upper = _out(n); lower = _out(n)
_check(_lib.qtl_apchannel(_ptr(h), _ptr(l), n, _ptr(upper), _ptr(lower), length))
return _wrap_multi(
{f"APCU_{length}": upper, f"APCL_{length}": lower},
idx, "channels", offset,
)
# ═══════════════════════════════════════════════════════════════════════════
# §8.7 Volatility
# ═══════════════════════════════════════════════════════════════════════════
def tr(high: object, low: object, close: object, offset: int = 0, **kwargs) -> object:
"""True Range."""
h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close)
n = len(h); dst = _out(n)
_check(_lib.qtl_tr(_ptr(h), _ptr(l), _ptr(c), n, _ptr(dst)))
return _wrap(dst, idx, "TR", "volatility", int(offset) if offset is not None else 0)
def bbw(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Bollinger Band Width."""
return _pa("qtl_bbw", close, length, offset, 20, "BBW", "volatility")
def bbwn(close: object, length: int = 20, mult: float = 2.0,
lookback: int = 252, offset: int = 0, **kwargs) -> object:
"""Bollinger Band Width Normalized."""
length = int(length) if length is not None else 20
mult = float(mult) if mult is not None else 2.0
lookback = int(lookback) if lookback is not None else 252
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_bbwn(_ptr(src), n, _ptr(dst), length, mult, lookback))
return _wrap(dst, idx, f"BBWN_{length}", "volatility", offset)
def bbwp(close: object, length: int = 20, mult: float = 2.0,
lookback: int = 252, offset: int = 0, **kwargs) -> object:
"""Bollinger Band Width Percentile."""
length = int(length) if length is not None else 20
mult = float(mult) if mult is not None else 2.0
lookback = int(lookback) if lookback is not None else 252
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_bbwp(_ptr(src), n, _ptr(dst), length, mult, lookback))
return _wrap(dst, idx, f"BBWP_{length}", "volatility", offset)
def stddev(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Standard Deviation."""
return _pa("qtl_stddev", close, length, offset, 20, "STDDEV", "volatility")
def variance(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Variance."""
return _pa("qtl_variance", close, length, offset, 20, "VARIANCE", "volatility")
def etherm(high: object, low: object, offset: int = 0, **kwargs) -> object:
"""Elder Thermometer."""
h, idx = _arr(high); l, _ = _arr(low)
n = len(h); dst = _out(n)
_check(_lib.qtl_etherm(_ptr(h), _ptr(l), n, _ptr(dst)))
return _wrap(dst, idx, "ETHERM", "volatility", int(offset) if offset is not None else 0)
def ccv(close: object, short_period: int = 20, long_period: int = 1,
offset: int = 0, **kwargs) -> object:
"""Close-to-Close Volatility."""
short_period = int(short_period) if short_period is not None else 20
long_period = int(long_period) if long_period is not None else 1
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_ccv(_ptr(src), n, _ptr(dst), short_period, long_period))
return _wrap(dst, idx, f"CCV_{short_period}", "volatility", offset)
def cv(close: object, length: int = 20, min_vol: float = 0.2,
max_vol: float = 0.7, offset: int = 0, **kwargs) -> object:
"""Coefficient of Variation."""
length = int(length) if length is not None else 20
min_vol = float(min_vol) if min_vol is not None else 0.2
max_vol = float(max_vol) if max_vol is not None else 0.7
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_cv(_ptr(src), n, _ptr(dst), length, min_vol, max_vol))
return _wrap(dst, idx, f"CV_{length}", "volatility", offset)
def cvi(close: object, ema_period: int = 10, roc_period: int = 10,
offset: int = 0, **kwargs) -> object:
"""Chaikin Volatility Index."""
ema_period = int(ema_period) if ema_period is not None else 10
roc_period = int(roc_period) if roc_period is not None else 10
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_cvi(_ptr(src), n, _ptr(dst), ema_period, roc_period))
return _wrap(dst, idx, f"CVI_{ema_period}", "volatility", offset)
def ewma(close: object, length: int = 20, is_pop: int = 1,
ann_factor: int = 252, offset: int = 0, **kwargs) -> object:
"""Exponentially Weighted Moving Average (volatility)."""
length = int(length) if length is not None else 20
is_pop = int(is_pop) if is_pop is not None else 1
ann_factor = int(ann_factor) if ann_factor is not None else 252
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_ewma(_ptr(src), n, _ptr(dst), length, is_pop, ann_factor))
return _wrap(dst, idx, f"EWMA_{length}", "volatility", offset)
# ═══════════════════════════════════════════════════════════════════════════
# §8.8 Volume
# ═══════════════════════════════════════════════════════════════════════════
def _pg(fn_name: str, close: object, volume: object,
offset: int, label: str) -> object:
"""Pattern G (source+volume, no period)."""
offset = int(offset) if offset is not None else 0
c, idx = _arr(close); v, _ = _arr(volume)
n = len(c); dst = _out(n)
_check(getattr(_lib, fn_name)(_ptr(c), _ptr(v), n, _ptr(dst)))
return _wrap(dst, idx, label, "volume", offset)
def _pg2(fn_name: str, close: object, volume: object, length: int,
offset: int, default_length: int, label: str) -> object:
"""Pattern G2 (source+volume+period)."""
length = int(length) if length is not None else default_length
offset = int(offset) if offset is not None else 0
c, idx = _arr(close); v, _ = _arr(volume)
n = len(c); dst = _out(n)
_check(getattr(_lib, fn_name)(_ptr(c), _ptr(v), n, _ptr(dst), length))
return _wrap(dst, idx, f"{label}_{length}", "volume", offset)
def obv(close: object, volume: object, offset: int = 0, **kwargs) -> object:
"""On-Balance Volume."""
return _pg("qtl_obv", close, volume, offset, "OBV")
def pvt(close: object, volume: object, offset: int = 0, **kwargs) -> object:
"""Price Volume Trend."""
return _pg("qtl_pvt", close, volume, offset, "PVT")
def pvr(close: object, volume: object, offset: int = 0, **kwargs) -> object:
"""Price Volume Rank."""
return _pg("qtl_pvr", close, volume, offset, "PVR")
def vf(close: object, volume: object, offset: int = 0, **kwargs) -> object:
"""Volume Flow."""
return _pg("qtl_vf", close, volume, offset, "VF")
def nvi(close: object, volume: object, offset: int = 0, **kwargs) -> object:
"""Negative Volume Index."""
return _pg("qtl_nvi", close, volume, offset, "NVI")
def pvi(close: object, volume: object, offset: int = 0, **kwargs) -> object:
"""Positive Volume Index."""
return _pg("qtl_pvi", close, volume, offset, "PVI")
def tvi(close: object, volume: object, length: int = 14,
offset: int = 0, **kwargs) -> object:
"""Trade Volume Index."""
return _pg2("qtl_tvi", close, volume, length, offset, 14, "TVI")
def pvd(close: object, volume: object, length: int = 14,
offset: int = 0, **kwargs) -> object:
"""Price Volume Divergence."""
return _pg2("qtl_pvd", close, volume, length, offset, 14, "PVD")
def vwma(close: object, volume: object, length: int = 20,
offset: int = 0, **kwargs) -> object:
"""Volume Weighted Moving Average."""
return _pg2("qtl_vwma", close, volume, length, offset, 20, "VWMA")
def evwma(close: object, volume: object, length: int = 20,
offset: int = 0, **kwargs) -> object:
"""Elastic Volume Weighted Moving Average."""
return _pg2("qtl_evwma", close, volume, length, offset, 20, "EVWMA")
def efi(close: object, volume: object, length: int = 13,
offset: int = 0, **kwargs) -> object:
"""Elder's Force Index."""
return _pg2("qtl_efi", close, volume, length, offset, 13, "EFI")
def aobv(close: object, volume: object, offset: int = 0, **kwargs) -> object:
"""Archer OBV → (obv, signal) or DataFrame."""
offset = int(offset) if offset is not None else 0
c, idx = _arr(close); v, _ = _arr(volume)
n = len(c); obv_out = _out(n); sig = _out(n)
_check(_lib.qtl_aobv(_ptr(c), _ptr(v), n, _ptr(obv_out), _ptr(sig)))
return _wrap_multi({"AOBV": obv_out, "AOBV_SIG": sig}, idx, "volume", offset)
def mfi(high: object, low: object, close: object, volume: object,
length: int = 14, offset: int = 0, **kwargs) -> object:
"""Money Flow Index."""
length = int(length) if length is not None else 14
offset = int(offset) if offset is not None else 0
h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close); v, _ = _arr(volume)
n = len(h); dst = _out(n)
_check(_lib.qtl_mfi(_ptr(h), _ptr(l), _ptr(c), _ptr(v), n, _ptr(dst), length))
return _wrap(dst, idx, f"MFI_{length}", "volume", offset)
def cmf(high: object, low: object, close: object, volume: object,
length: int = 20, offset: int = 0, **kwargs) -> object:
"""Chaikin Money Flow."""
length = int(length) if length is not None else 20
offset = int(offset) if offset is not None else 0
h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close); v, _ = _arr(volume)
n = len(h); dst = _out(n)
_check(_lib.qtl_cmf(_ptr(h), _ptr(l), _ptr(c), _ptr(v), n, _ptr(dst), length))
return _wrap(dst, idx, f"CMF_{length}", "volume", offset)
def eom(high: object, low: object, volume: object,
length: int = 14, offset: int = 0, **kwargs) -> object:
"""Ease of Movement."""
length = int(length) if length is not None else 14
offset = int(offset) if offset is not None else 0
h, idx = _arr(high); l, _ = _arr(low); v, _ = _arr(volume)
n = len(h); dst = _out(n)
_check(_lib.qtl_eom(_ptr(h), _ptr(l), _ptr(v), n, _ptr(dst), length))
return _wrap(dst, idx, f"EOM_{length}", "volume", offset)
def pvo(volume: object, fast: int = 12, slow: int = 26, signal: int = 9,
offset: int = 0, **kwargs) -> object:
"""Percentage Volume Oscillator → (pvo, signal, histogram) or DataFrame."""
fast = int(fast) if fast is not None else 12
slow = int(slow) if slow is not None else 26
signal = int(signal) if signal is not None else 9
offset = int(offset) if offset is not None else 0
v, idx = _arr(volume)
n = len(v); pvo_out = _out(n); sig = _out(n); hist = _out(n)
_check(_lib.qtl_pvo(
_ptr(v), n, _ptr(pvo_out), _ptr(sig), _ptr(hist), fast, slow, signal,
))
return _wrap_multi(
{
f"PVO_{fast}_{slow}_{signal}": pvo_out,
f"PVOs_{fast}_{slow}_{signal}": sig,
f"PVOh_{fast}_{slow}_{signal}": hist,
},
idx, "volume", offset,
)
# ═══════════════════════════════════════════════════════════════════════════
# §8.9 Statistics
# ═══════════════════════════════════════════════════════════════════════════
def zscore(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Z-Score."""
return _pa("qtl_zscore", close, length, offset, 20, "ZSCORE", "statistics")
def cma(close: object, offset: int = 0, **kwargs) -> object:
"""Cumulative Moving Average."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_cma(_ptr(src), n, _ptr(dst)))
return _wrap(dst, idx, "CMA", "statistics", offset)
def entropy(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Shannon Entropy."""
return _pa("qtl_entropy", close, length, offset, 10, "ENTROPY", "statistics")
def correlation(x: object, y: object, length: int = 20,
offset: int = 0, **kwargs) -> object:
"""Pearson Correlation."""
length = int(length) if length is not None else 20
offset = int(offset) if offset is not None else 0
xarr, idx = _arr(x); yarr, _ = _arr(y)
n = len(xarr); dst = _out(n)
_check(_lib.qtl_correlation(_ptr(xarr), _ptr(yarr), n, _ptr(dst), length))
return _wrap(dst, idx, f"CORR_{length}", "statistics", offset)
def covariance(x: object, y: object, length: int = 20,
is_sample: bool = True, offset: int = 0, **kwargs) -> object:
"""Covariance."""
length = int(length) if length is not None else 20
offset = int(offset) if offset is not None else 0
xarr, idx = _arr(x); yarr, _ = _arr(y)
n = len(xarr); dst = _out(n)
_check(_lib.qtl_covariance(
_ptr(xarr), _ptr(yarr), n, _ptr(dst), length, int(is_sample),
))
return _wrap(dst, idx, f"COV_{length}", "statistics", offset)
def cointegration(x: object, y: object, length: int = 20,
offset: int = 0, **kwargs) -> object:
"""Cointegration."""
length = int(length) if length is not None else 20
offset = int(offset) if offset is not None else 0
xarr, idx = _arr(x); yarr, _ = _arr(y)
n = len(xarr); dst = _out(n)
_check(_lib.qtl_cointegration(_ptr(xarr), _ptr(yarr), n, _ptr(dst), length))
return _wrap(dst, idx, f"COINT_{length}", "statistics", offset)
# ═══════════════════════════════════════════════════════════════════════════
# §8.10 Errors
# ═══════════════════════════════════════════════════════════════════════════
def _pf(fn_name: str, actual: object, predicted: object,
length: int, offset: int, default_length: int, label: str) -> object:
"""Pattern F (actual+predicted+period)."""
length = int(length) if length is not None else default_length
offset = int(offset) if offset is not None else 0
a, idx = _arr(actual); p, _ = _arr(predicted)
n = len(a); dst = _out(n)
_check(getattr(_lib, fn_name)(_ptr(a), _ptr(p), n, _ptr(dst), length))
return _wrap(dst, idx, f"{label}_{length}", "errors", offset)
def mse(actual: object, predicted: object, length: int = 20,
offset: int = 0, **kwargs) -> object:
"""Mean Squared Error."""
return _pf("qtl_mse", actual, predicted, length, offset, 20, "MSE")
def rmse(actual: object, predicted: object, length: int = 20,
offset: int = 0, **kwargs) -> object:
"""Root Mean Squared Error."""
return _pf("qtl_rmse", actual, predicted, length, offset, 20, "RMSE")
def mae(actual: object, predicted: object, length: int = 20,
offset: int = 0, **kwargs) -> object:
"""Mean Absolute Error."""
return _pf("qtl_mae", actual, predicted, length, offset, 20, "MAE")
def mape(actual: object, predicted: object, length: int = 20,
offset: int = 0, **kwargs) -> object:
"""Mean Absolute Percentage Error."""
return _pf("qtl_mape", actual, predicted, length, offset, 20, "MAPE")
# ═══════════════════════════════════════════════════════════════════════════
# §8.11 Filters
# ═══════════════════════════════════════════════════════════════════════════
def bessel(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Bessel Filter."""
return _pa("qtl_bessel", close, length, offset, 14, "BESSEL", "filter")
def butter2(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""2nd-order Butterworth."""
return _pa("qtl_butter2", close, length, offset, 14, "BUTTER2", "filter")
def butter3(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""3rd-order Butterworth."""
return _pa("qtl_butter3", close, length, offset, 14, "BUTTER3", "filter")
def cheby1(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Chebyshev Type I."""
return _pa("qtl_cheby1", close, length, offset, 14, "CHEBY1", "filter")
def cheby2(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Chebyshev Type II."""
return _pa("qtl_cheby2", close, length, offset, 14, "CHEBY2", "filter")
def elliptic(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Elliptic (Cauer) Filter."""
return _pa("qtl_elliptic", close, length, offset, 14, "ELLIPTIC", "filter")
def edcf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Ehlers Distance Coefficient Filter."""
return _pa("qtl_edcf", close, length, offset, 14, "EDCF", "filter")
def bpf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
"""Bandpass Filter."""
return _pa("qtl_bpf", close, length, offset, 14, "BPF", "filter")
def alaguerre(close: object, length: int = 20, order: int = 5,
offset: int = 0, **kwargs) -> object:
"""Adaptive Laguerre Filter."""
length = int(length) if length is not None else 20
order = int(order) if order is not None else 5
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_alaguerre(_ptr(src), n, _ptr(dst), length, order))
return _wrap(dst, idx, f"ALAGUERRE_{length}", "filter", offset)
def bilateral(close: object, length: int = 14, sigma_s: float = 0.5,
sigma_r: float = 1.0, offset: int = 0, **kwargs) -> object:
"""Bilateral Filter."""
length = int(length) if length is not None else 14
sigma_s = float(sigma_s) if sigma_s is not None else 0.5
sigma_r = float(sigma_r) if sigma_r is not None else 1.0
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_bilateral(_ptr(src), n, _ptr(dst), length, sigma_s, sigma_r))
return _wrap(dst, idx, f"BILATERAL_{length}", "filter", offset)
def baxterking(close: object, length: int = 12, min_period: int = 6,
max_period: int = 32, offset: int = 0, **kwargs) -> object:
"""Baxter-King Filter."""
length = int(length) if length is not None else 12
min_period = int(min_period) if min_period is not None else 6
max_period = int(max_period) if max_period is not None else 32
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_baxterking(_ptr(src), n, _ptr(dst), length, min_period, max_period))
return _wrap(dst, idx, f"BAXTERKING_{length}", "filter", offset)
def cfitz(close: object, length: int = 6, bw_period: int = 32,
offset: int = 0, **kwargs) -> object:
"""Christiano-Fitzgerald Filter."""
length = int(length) if length is not None else 6
bw_period = int(bw_period) if bw_period is not None else 32
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_cfitz(_ptr(src), n, _ptr(dst), length, bw_period))
return _wrap(dst, idx, f"CFITZ_{length}", "filter", offset)
# ═══════════════════════════════════════════════════════════════════════════
# §8.12 Cycles
# ═══════════════════════════════════════════════════════════════════════════
def cg(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
"""Center of Gravity."""
return _pa("qtl_cg", close, length, offset, 10, "CG", "cycles")
def dsp(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Dominant Cycle Period (DSP)."""
return _pa("qtl_dsp", close, length, offset, 20, "DSP", "cycles")
def ccor(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
"""Circular Correlation."""
return _pa("qtl_ccor", close, length, offset, 20, "CCOR", "cycles")
def ebsw(close: object, hp_length: int = 40, ssf_length: int = 10,
offset: int = 0, **kwargs) -> object:
"""Even Better Sinewave."""
hp_length = int(hp_length) if hp_length is not None else 40
ssf_length = int(ssf_length) if ssf_length is not None else 10
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_ebsw(_ptr(src), n, _ptr(dst), hp_length, ssf_length))
return _wrap(dst, idx, f"EBSW_{hp_length}", "cycles", offset)
def eacp(close: object, min_period: int = 8, max_period: int = 48,
avg_length: int = 3, enhance: int = 1,
offset: int = 0, **kwargs) -> object:
"""Ehlers Autocorrelation Periodogram."""
min_period = int(min_period) if min_period is not None else 8
max_period = int(max_period) if max_period is not None else 48
avg_length = int(avg_length) if avg_length is not None else 3
enhance = int(enhance) if enhance is not None else 1
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_eacp(_ptr(src), n, _ptr(dst), min_period, max_period, avg_length, enhance))
return _wrap(dst, idx, f"EACP_{min_period}_{max_period}", "cycles", offset)
# ═══════════════════════════════════════════════════════════════════════════
# §8.14 Numerics
# ═══════════════════════════════════════════════════════════════════════════
def change(close: object, length: int = 1, offset: int = 0, **kwargs) -> object:
"""Price Change."""
return _pa("qtl_change", close, length, offset, 1, "CHANGE", "numerics")
def exptrans(close: object, offset: int = 0, **kwargs) -> object:
"""Exponential Transform."""
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_exptrans(_ptr(src), n, _ptr(dst)))
return _wrap(dst, idx, "EXPTRANS", "numerics", offset)
def betadist(close: object, length: int = 50, alpha: float = 2.0,
beta: float = 2.0, offset: int = 0, **kwargs) -> object:
"""Beta Distribution."""
length = int(length) if length is not None else 50
alpha = float(alpha) if alpha is not None else 2.0
beta = float(beta) if beta is not None else 2.0
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_betadist(_ptr(src), n, _ptr(dst), length, alpha, beta))
return _wrap(dst, idx, f"BETADIST_{length}", "numerics", offset)
def expdist(close: object, length: int = 50, lam: float = 3.0,
offset: int = 0, **kwargs) -> object:
"""Exponential Distribution."""
length = int(length) if length is not None else 50
lam = float(lam) if lam is not None else 3.0
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_expdist(_ptr(src), n, _ptr(dst), length, lam))
return _wrap(dst, idx, f"EXPDIST_{length}", "numerics", offset)
def binomdist(close: object, length: int = 50, trials: int = 20,
threshold: int = 10, offset: int = 0, **kwargs) -> object:
"""Binomial Distribution."""
length = int(length) if length is not None else 50
trials = int(trials) if trials is not None else 20
threshold = int(threshold) if threshold is not None else 10
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_binomdist(_ptr(src), n, _ptr(dst), length, trials, threshold))
return _wrap(dst, idx, f"BINOMDIST_{length}", "numerics", offset)
def cwt(close: object, scale: float = 10.0, omega: float = 6.0,
offset: int = 0, **kwargs) -> object:
"""Continuous Wavelet Transform."""
scale = float(scale) if scale is not None else 10.0
omega = float(omega) if omega is not None else 6.0
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_cwt(_ptr(src), n, _ptr(dst), scale, omega))
return _wrap(dst, idx, "CWT", "numerics", offset)
def dwt(close: object, length: int = 4, levels: int = 0,
offset: int = 0, **kwargs) -> object:
"""Discrete Wavelet Transform.
Parameters
----------
length : int
Number of decomposition levels (1-8). Default 4.
levels : int
Output component: 0 = approximation, 1..length = detail level.
"""
length = int(length) if length is not None else 4
levels = int(levels) if levels is not None else 0
offset = int(offset) if offset is not None else 0
src, idx = _arr(close)
n = len(src); dst = _out(n)
_check(_lib.qtl_dwt(_ptr(src), n, _ptr(dst), length, levels))
return _wrap(dst, idx, f"DWT_{length}", "numerics", offset)