"""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 = 3.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, length: int = 14, offset: int = 0, **kwargs) -> object: """DeMarker.""" length = int(length) if length is not None else 14 h, idx = _arr(high); l, _ = _arr(low) n = len(h); dst = _out(n) _check(_lib.qtl_dem(_ptr(h), _ptr(l), n, _ptr(dst), length)) return _wrap(dst, idx, f"DEM_{length}", "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, alma_offset: float = 0.85, sigma: float = 6.0, **kwargs) -> object: """Arnaud Legoux Moving Average.""" length = int(length) if length is not None else 10 offset = int(offset) if offset is not None else 0 alma_offset = float(alma_offset) if alma_offset is not None else 0.85 sigma = float(sigma) if sigma is not None else 6.0 src, idx = _arr(close) n = len(src); dst = _out(n) _check(_lib.qtl_alma(_ptr(src), n, _ptr(dst), length, alma_offset, sigma)) return _wrap(dst, idx, f"ALMA_{length}", "trend", offset) 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, length: int = 14, offset: int = 0, **kwargs) -> object: """Elder Thermometer.""" length = int(length) if length is not None else 14 h, idx = _arr(high); l, _ = _arr(low) n = len(h); dst = _out(n) _check(_lib.qtl_etherm(_ptr(h), _ptr(l), n, _ptr(dst), length)) return _wrap(dst, idx, f"ETHERM_{length}", "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)