diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 0fac0d1f..779857bb 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -105,7 +105,9 @@ jobs: -r ${{ matrix.rid }} ` --self-contained true ` -o $nativeOut ` - -p:ContinuousIntegrationBuild=true + -p:ContinuousIntegrationBuild=true ` + -p:DebugSymbols=false ` + -p:DebugType=none $expectedNative = Join-Path $nativeOut "${{ matrix.native_file }}" if (-not (Test-Path $expectedNative)) { @@ -113,6 +115,13 @@ jobs: exit 1 } + Get-ChildItem $nativeOut -File | Where-Object { + $_.Name -ne "${{ matrix.native_file }}" + } | Remove-Item -Force + + "Native payload files:" + Get-ChildItem $nativeOut -File | ForEach-Object { $_.Name } + - name: Set wheel version for development if: github.ref_name != 'main' shell: pwsh diff --git a/lib/VERSION b/lib/VERSION index 8adc70fd..c18d72be 100644 --- a/lib/VERSION +++ b/lib/VERSION @@ -1 +1 @@ -0.8.0 \ No newline at end of file +0.8.1 \ No newline at end of file diff --git a/python/README.md b/python/README.md index 724b50e3..f8ae5000 100644 --- a/python/README.md +++ b/python/README.md @@ -1,30 +1,78 @@ -# quantalib (Python NativeAOT wrapper) +# quantalib — Python NativeAOT Wrapper -Skeleton package and NativeAOT project scaffolding for the `quantalib` Python wrapper over QuanTAlib. +High-performance Python wrapper for [QuanTAlib](https://github.com/mihakralj/quantalib), a .NET NativeAOT technical analysis library. -## Current status +## Features -This is a **skeleton-only** implementation containing: +- **~391 indicators** across 15 categories: channels, core, cycles, dynamics, errors, filters, momentum, numerics, oscillators, reversals, statistics, trends (FIR & IIR), volatility, volume +- **Zero-copy FFI** — ctypes bridge to pre-compiled NativeAOT shared library +- **NumPy native** — all inputs/outputs are `float64` arrays +- **Optional pandas support** — pass `pd.Series` in, get `pd.Series` out with preserved index +- **pandas-ta compatible** — `quantalib._compat` provides alias mapping for drop-in migration -- NativeAOT project files (`python.csproj`, `Directory.Build.props`) -- Python packaging metadata (`pyproject.toml`) -- Python package layout (`quantalib/`) -- Loader and bridge stubs (`_loader.py`, `_bridge.py`) -- Native artifact placeholders (`quantalib/native/...`) -- Minimal smoke test scaffold (`tests/test_smoke.py`) -- Native export scaffolding (`src/StatusCodes.cs`, `src/ArrayBridge.cs`, `src/Exports.cs`) +## Installation -## Not included yet +```bash +pip install quantalib +``` -- Full indicator export implementation -- Full ctypes signatures for all exports -- Indicator wrappers in `indicators.py` -- Complete test matrix and compatibility suite +> **Note:** The NativeAOT shared library (`quantalib_native.dll` / `.so` / `.dylib`) must be present in `quantalib/native//`. Pre-built binaries are included in wheel distributions. -## Local dev +## Quick Start -From `python/`: +```python +import numpy as np +import quantalib as qtl -- Create venv and install deps -- Run tests: `pytest` -- Build wheel: `python -m build` \ No newline at end of file +close = np.random.randn(200).cumsum() + 100 + +# Simple Moving Average +sma = qtl.sma(close, length=20) + +# Bollinger Bands (multi-output → tuple or DataFrame) +upper, mid, lower = qtl.bbands(close, length=20, std=2.0) + +# With pandas +import pandas as pd +s = pd.Series(close, name="close") +rsi = qtl.rsi(s, length=14) # returns pd.Series with preserved index +``` + +## Categories + +| Category | Module | Examples | +|----------|--------|----------| +| Channels | `channels` | bbands, kchannel, dchannel, aberr | +| Core | `core` | ha, midpoint, avgprice, typprice | +| Cycles | `cycles` | ht_dcperiod, ht_sine, cg, dsp | +| Dynamics | `dynamics` | adx, aroon, ichimoku, supertrend | +| Errors | `errors` | mse, rmse, mae, mape, huber | +| Filters | `filters` | kalman, sgf, hp, butter2, wavelet | +| Momentum | `momentum` | rsi, macd, roc, mom, tsi | +| Numerics | `numerics` | fft, normalize, sigmoid, slope | +| Oscillators | `oscillators` | stoch, cci, fisher, qqe, willr | +| Reversals | `reversals` | psar, pivot, fractals, swings | +| Statistics | `statistics` | zscore, correlation, entropy, linreg | +| Trends FIR | `trends_fir` | sma, wma, hma, alma, trima | +| Trends IIR | `trends_iir` | ema, dema, tema, kama, jma | +| Volatility | `volatility` | atr, bbw, stddev, hv, tr | +| Volume | `volume` | obv, vwma, mfi, cmf, adl | + +## Local Development + +```bash +cd python/ +python -m venv .venv && .venv/Scripts/activate # or source .venv/bin/activate +pip install -e ".[dev]" +pytest +``` + +### Building the native library + +```bash +dotnet publish python.csproj -c Release +``` + +## License + +[MIT](../LICENSE) diff --git a/python/pyproject.toml b/python/pyproject.toml index 3641137c..9088a75a 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -7,8 +7,34 @@ name = "quantalib" dynamic = ["version"] description = "High-performance technical analysis wrappers over QuanTAlib NativeAOT" readme = "README.md" +license = "MIT" requires-python = ">=3.10" dependencies = ["numpy>=1.24"] +authors = [ + { name = "QuanTAlib Contributors" }, +] +keywords = ["quantitative", "finance", "technical-analysis", "indicators", "nativeaot"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Financial and Insurance Industry", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Office/Business :: Financial :: Investment", + "Topic :: Scientific/Engineering :: Mathematics", + "Typing :: Typed", +] + +[project.urls] +Homepage = "https://github.com/mihakralj/quantalib" +Repository = "https://github.com/mihakralj/quantalib" +Documentation = "https://mihakralj.github.io/quantalib/" +Issues = "https://github.com/mihakralj/quantalib/issues" [project.optional-dependencies] pandas = ["pandas>=1.5"] diff --git a/python/python.csproj b/python/python.csproj index 7a380a5a..01f8fac0 100644 --- a/python/python.csproj +++ b/python/python.csproj @@ -11,6 +11,11 @@ false + + false + none + + diff --git a/python/quantalib/__init__.py b/python/quantalib/__init__.py index b8f7efc2..fa2abcec 100644 --- a/python/quantalib/__init__.py +++ b/python/quantalib/__init__.py @@ -6,6 +6,9 @@ Usage:: result = qtl.sma(close_array, length=14) result = qtl.bbands(close_array, length=20, std=2.0) + + print(qtl.version) # e.g. "0.8.0" + print(qtl.__version__) # same """ from __future__ import annotations @@ -14,6 +17,26 @@ from pathlib import Path from ._loader import load_native_library from . import indicators from .indicators import * # noqa: F401, F403 — re-export all indicator functions + +# Re-export per-category submodules for direct access +from . import ( # noqa: F401 + channels, + core, + cycles, + dynamics, + errors, + filters, + momentum, + numerics, + oscillators, + reversals, + statistics, + trends_fir, + trends_iir, + volatility, + volume, +) + from ._compat import ALIASES, get_compat from ._bridge import ( QtlError, @@ -26,6 +49,21 @@ from ._bridge import ( __all__ = [ "load_native_library", "indicators", + "channels", + "core", + "cycles", + "dynamics", + "errors", + "filters", + "momentum", + "numerics", + "oscillators", + "reversals", + "statistics", + "trends_fir", + "trends_iir", + "volatility", + "volume", "ALIASES", "get_compat", "QtlError", @@ -33,14 +71,35 @@ __all__ = [ "QtlInvalidLengthError", "QtlInvalidParamError", "QtlInternalError", + "version", + "__version__", ] + + def _resolve_version() -> str: - version_file = Path(__file__).resolve().parents[2] / "lib" / "VERSION" - if version_file.exists(): - version = version_file.read_text(encoding="utf-8").strip() - if version: - return version + """Resolve version from lib/VERSION (dev) or package metadata (installed).""" + # 1. Try repo-local VERSION file (works in dev / editable install) + pkg_dir = Path(__file__).resolve().parent # python/quantalib/ + candidates = [ + pkg_dir.parents[1] / "lib" / "VERSION", # repo root / lib / VERSION + pkg_dir.parent / "lib" / "VERSION", # python / lib / VERSION (fallback) + pkg_dir / "VERSION", # baked into wheel + ] + for vf in candidates: + if vf.is_file(): + ver = vf.read_text(encoding="utf-8").strip() + if ver: + return ver + + # 2. Fall back to importlib.metadata (pip-installed wheel) + try: + from importlib.metadata import version as _pkg_version + return _pkg_version("quantalib") + except Exception: + pass + return "0.0.0" -__version__ = _resolve_version() +__version__: str = _resolve_version() +version: str = __version__ diff --git a/python/quantalib/__pycache__/__init__.cpython-313.pyc b/python/quantalib/__pycache__/__init__.cpython-313.pyc index 1ac08389..031d34d1 100644 Binary files a/python/quantalib/__pycache__/__init__.cpython-313.pyc and b/python/quantalib/__pycache__/__init__.cpython-313.pyc differ diff --git a/python/quantalib/__pycache__/_bridge.cpython-313.pyc b/python/quantalib/__pycache__/_bridge.cpython-313.pyc index cc6d248b..71ec3d86 100644 Binary files a/python/quantalib/__pycache__/_bridge.cpython-313.pyc and b/python/quantalib/__pycache__/_bridge.cpython-313.pyc differ diff --git a/python/quantalib/__pycache__/_compat.cpython-313.pyc b/python/quantalib/__pycache__/_compat.cpython-313.pyc index 903ecb9d..a2f713ab 100644 Binary files a/python/quantalib/__pycache__/_compat.cpython-313.pyc and b/python/quantalib/__pycache__/_compat.cpython-313.pyc differ diff --git a/python/quantalib/__pycache__/_loader.cpython-313.pyc b/python/quantalib/__pycache__/_loader.cpython-313.pyc index 67840c09..f97864b2 100644 Binary files a/python/quantalib/__pycache__/_loader.cpython-313.pyc and b/python/quantalib/__pycache__/_loader.cpython-313.pyc differ diff --git a/python/quantalib/__pycache__/indicators.cpython-313.pyc b/python/quantalib/__pycache__/indicators.cpython-313.pyc index 916f77b9..df969a96 100644 Binary files a/python/quantalib/__pycache__/indicators.cpython-313.pyc and b/python/quantalib/__pycache__/indicators.cpython-313.pyc differ diff --git a/python/quantalib/_bridge.py b/python/quantalib/_bridge.py index 7779f974..07416210 100644 --- a/python/quantalib/_bridge.py +++ b/python/quantalib/_bridge.py @@ -1,5 +1,7 @@ """Low-level ctypes bindings for every quantalib NativeAOT export. +Auto-generated by generate_category_modules.py — DO NOT EDIT. + Each native function is bound via ``_bind`` at module load. If the shared library was compiled without a particular export the binding is silently skipped (the corresponding ``HAS_*`` flag stays False). @@ -66,41 +68,10 @@ _lib = load_native_library() # Shorthand type aliases _dp = POINTER(c_double) # double* _ip = POINTER(c_int) # int* +_lp = POINTER(ctypes.c_long) # long* _ci = c_int _cd = c_double -# --------------------------------------------------------------------------- -# ABI signature pattern templates -# -# Pattern A : (src*, n, dst*, period) → single-input + int -# Pattern A2: (src*, n, dst*, alpha) → single-input + double -# Pattern A3: (src*, n, dst*) → single-input no params -# Pattern B : (h*, l*, c*, v*, n, dst*, period) → HLCV + int -# Pattern C : (o*, h*, l*, c*, n, dst*) → OHLC no extra -# Pattern C2: (o*, h*, l*, c*, n, dst*, double) → OHLC + double -# Pattern D : (h*, l*, n, dst*) → HL -# Pattern E : (h*, l*, c*, n, dst*) → HLC -# Pattern F : (actual*, predicted*, n, dst*, period) → dual-input + int -# Pattern G : (src*, vol*, n, dst*) → source+volume -# Pattern G2: (src*, vol*, n, dst*, period) → source+volume+int -# Pattern H : (x*, y*, n, dst*, period) → X+Y + int -# Pattern I : multi-output (various) -# --------------------------------------------------------------------------- - -# Common argtypes per pattern -_PA = [_dp, _ci, _dp, _ci] # Pattern A -_PA2 = [_dp, _ci, _dp, _cd] # Pattern A (alpha) -_PA3 = [_dp, _ci, _dp] # Pattern A (no param) -_PB = [_dp, _dp, _dp, _dp, _ci, _dp, _ci] # Pattern B (HLCV) -_PC = [_dp, _dp, _dp, _dp, _ci, _dp] # Pattern C (OHLC) -_PC2 = [_dp, _dp, _dp, _dp, _ci, _dp, _cd] # Pattern C (OHLC+double) -_PD = [_dp, _dp, _ci, _dp] # Pattern D (HL) -_PE = [_dp, _dp, _dp, _ci, _dp] # Pattern E (HLC) -_PF = [_dp, _dp, _ci, _dp, _ci] # Pattern F -_PG = [_dp, _dp, _ci, _dp] # Pattern G -_PG2 = [_dp, _dp, _ci, _dp, _ci] # Pattern G2 -_PH = [_dp, _dp, _ci, _dp, _ci] # Pattern H - def _bind(name: str, argtypes: list[object]) -> bool: """Bind a single native function. Returns True if found.""" @@ -118,187 +89,478 @@ def _bind(name: str, argtypes: list[object]) -> bool: HAS_SKELETON = _bind("qtl_skeleton_noop", [_dp, _ci, _dp]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.1 Core +# Core (Exports.Generated.cs) # ═══════════════════════════════════════════════════════════════════════════ -HAS_AVGPRICE = _bind("qtl_avgprice", _PC) -HAS_MEDPRICE = _bind("qtl_medprice", _PD) -HAS_TYPPRICE = _bind("qtl_typprice", [_dp, _dp, _dp, _ci, _dp]) # OHL (no close!) -HAS_MIDBODY = _bind("qtl_midbody", [_dp, _dp, _ci, _dp]) # OC +HAS_HA = _bind("qtl_ha", [_dp, _dp, _dp, _dp, _dp, _dp, _dp, _dp, _ci]) +HAS_MIDPOINT = _bind("qtl_midpoint", [_dp, _dp, _ci, _ci]) +HAS_MIDPRICE = _bind("qtl_midprice", [_dp, _dp, _dp, _ci, _ci]) +HAS_WCLPRICE = _bind("qtl_wclprice", [_dp, _dp, _dp, _dp, _ci]) + +# ── Core (Exports.cs — manual) ── +HAS_AVGPRICE = _bind("qtl_avgprice", [_dp, _dp, _dp, _dp, _ci, _dp]) +HAS_MEDPRICE = _bind("qtl_medprice", [_dp, _dp, _ci, _dp]) +HAS_TYPPRICE = _bind("qtl_typprice", [_dp, _dp, _dp, _ci, _dp]) +HAS_MIDBODY = _bind("qtl_midbody", [_dp, _dp, _ci, _dp]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.2 Momentum +# Momentum # ═══════════════════════════════════════════════════════════════════════════ -HAS_RSI = _bind("qtl_rsi", _PA) -HAS_ROC = _bind("qtl_roc", _PA) -HAS_MOM = _bind("qtl_mom", _PA) -HAS_CMO = _bind("qtl_cmo", _PA) -HAS_TSI = _bind("qtl_tsi", [_dp, _ci, _dp, _ci, _ci]) # longP, shortP -HAS_APO = _bind("qtl_apo", [_dp, _ci, _dp, _ci, _ci]) # fast, slow -HAS_BIAS = _bind("qtl_bias", _PA) -HAS_CFO = _bind("qtl_cfo", _PA) -HAS_CFB = _bind("qtl_cfb", [_dp, _ci, _dp, _ip, _ci]) # special -HAS_ASI = _bind("qtl_asi", _PC2) # OHLC + double limit +HAS_BOP = _bind("qtl_bop", [_dp, _dp, _dp, _dp, _dp, _ci]) +HAS_CCI = _bind("qtl_cci", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _dp]) +HAS_MACD = _bind("qtl_macd", [_dp, _dp, _ci, _ci, _ci]) +HAS_PMO = _bind("qtl_pmo", [_dp, _dp, _ci, _ci, _ci, _ci]) +HAS_PPO = _bind("qtl_ppo", [_dp, _dp, _ci, _ci, _ci]) +HAS_PRS = _bind("qtl_prs", [_dp, _dp, _dp, _ci, _ci]) +HAS_ROCP = _bind("qtl_rocp", [_dp, _dp, _ci, _ci]) +HAS_ROCR = _bind("qtl_rocr", [_dp, _dp, _ci, _ci]) +HAS_SAM = _bind("qtl_sam", [_dp, _dp, _ci, _cd, _ci]) +HAS_VEL = _bind("qtl_vel", [_dp, _dp, _ci, _ci]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.3 Oscillators +# Oscillators # ═══════════════════════════════════════════════════════════════════════════ -HAS_FISHER = _bind("qtl_fisher", _PA) -HAS_FISHER04 = _bind("qtl_fisher04", _PA) -HAS_DPO = _bind("qtl_dpo", _PA) -HAS_TRIX = _bind("qtl_trix", _PA) -HAS_INERTIA = _bind("qtl_inertia", _PA) -HAS_RSX = _bind("qtl_rsx", _PA) -HAS_ER = _bind("qtl_er", _PA) -HAS_CTI = _bind("qtl_cti", _PA) -HAS_REFLEX = _bind("qtl_reflex", _PA) -HAS_TRENDFLEX = _bind("qtl_trendflex", _PA) -HAS_KRI = _bind("qtl_kri", _PA) -HAS_PSL = _bind("qtl_psl", _PA) -HAS_DECO = _bind("qtl_deco", [_dp, _ci, _dp, _ci, _ci]) # shortP, longP -HAS_DOSC = _bind("qtl_dosc", [_dp, _ci, _dp, _ci, _ci, _ci, _ci]) # rsiP, ema1P, ema2P, sigP -HAS_DYMOI = _bind("qtl_dymoi", [_dp, _ci, _dp, _ci, _ci, _ci, _ci, _ci]) # p1..p5 -HAS_CRSI = _bind("qtl_crsi", [_dp, _ci, _dp, _ci, _ci, _ci]) # rsiP, streakP, rankP -HAS_BBB = _bind("qtl_bbb", [_dp, _ci, _dp, _ci, _cd]) # period, mult -HAS_BBI = _bind("qtl_bbi", [_dp, _ci, _dp, _ci, _ci, _ci, _ci]) # p1..p4 -HAS_DEM = _bind("qtl_dem", [_dp, _dp, _ci, _dp, _ci]) # high,low,n,dst,period -HAS_BRAR = _bind("qtl_brar", [_dp, _dp, _dp, _dp, _ci, _dp, _dp, _ci]) # OHLC + 2 outputs + period +HAS_AC = _bind("qtl_ac", [_dp, _dp, _dp, _ci, _ci, _ci, _ci]) +HAS_AO = _bind("qtl_ao", [_dp, _dp, _dp, _ci, _ci, _ci]) +HAS_BBS = _bind("qtl_bbs", [_dp, _dp, _dp, _dp, _ci, _ci, _cd]) +HAS_COPPOCK = _bind("qtl_coppock", [_dp, _dp, _ci, _ci, _ci, _ci]) +HAS_ERI = _bind("qtl_eri", [_dp, _ci, _ci, _dp]) +HAS_FI = _bind("qtl_fi", [_dp, _ci, _ci, _dp]) +HAS_GATOR = _bind("qtl_gator", [_dp, _dp, _ci, _ci, _ci, _ci, _ci, _ci, _ci]) +HAS_IMI = _bind("qtl_imi", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _dp]) +HAS_KDJ = _bind("qtl_kdj", [_dp, _dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci]) +HAS_KST = _bind("qtl_kst", [_dp, _dp, _dp, _ci, _ci, _ci, _ci, _ci, _ci, _ci, _ci, _ci, _ci]) +HAS_MARKETFI = _bind("qtl_marketfi", [_dp, _dp, _dp, _dp, _ci]) +HAS_MSTOCH = _bind("qtl_mstoch", [_dp, _dp, _ci, _ci, _ci, _ci]) +HAS_PGO = _bind("qtl_pgo", [_dp, _dp, _dp, _dp, _ci, _ci]) +HAS_QQE = _bind("qtl_qqe", [_dp, _dp, _ci, _ci, _ci, _cd]) +HAS_REVERSEEMA = _bind("qtl_reverseema", [_dp, _dp, _ci, _ci]) +HAS_RVGI = _bind("qtl_rvgi", [_dp, _dp, _dp, _dp, _dp, _dp, _ci, _ci]) +HAS_SMI = _bind("qtl_smi", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _ci, _ci]) +HAS_SQUEEZE = _bind("qtl_squeeze", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _cd, _cd]) +HAS_STC = _bind("qtl_stc", [_dp, _dp, _ci, _ci, _ci, _ci, _ci, _ci]) +HAS_STOCH = _bind("qtl_stoch", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci]) +HAS_STOCHF = _bind("qtl_stochf", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci]) +HAS_STOCHRSI = _bind("qtl_stochrsi", [_dp, _dp, _ci, _ci, _ci, _ci, _ci]) +HAS_TTMWAVE = _bind("qtl_ttmwave", [_dp, _ci, _dp]) +HAS_ULTOSC = _bind("qtl_ultosc", [_dp, _dp, _dp, _dp, _ci, _ci, _ci, _ci]) +HAS_WILLR = _bind("qtl_willr", [_dp, _dp, _dp, _dp, _ci, _ci]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.4 Trends — FIR +# Trends — FIR # ═══════════════════════════════════════════════════════════════════════════ -HAS_SMA = _bind("qtl_sma", _PA) -HAS_WMA = _bind("qtl_wma", _PA) -HAS_HMA = _bind("qtl_hma", _PA) -HAS_TRIMA = _bind("qtl_trima", _PA) -HAS_SWMA = _bind("qtl_swma", _PA) -HAS_DWMA = _bind("qtl_dwma", _PA) -HAS_BLMA = _bind("qtl_blma", _PA) -HAS_ALMA = _bind("qtl_alma", [_dp, _ci, _dp, _ci, _cd, _cd]) # period, offset, sigma -HAS_LSMA = _bind("qtl_lsma", _PA) -HAS_SGMA = _bind("qtl_sgma", _PA) -HAS_SINEMA = _bind("qtl_sinema", _PA) -HAS_HANMA = _bind("qtl_hanma", _PA) -HAS_PARZEN = _bind("qtl_parzen", _PA) -HAS_TSF = _bind("qtl_tsf", _PA) -HAS_CONV = _bind("qtl_conv", [_dp, _ci, _dp, _dp, _ci]) # src,n,dst,kernel*,kernelLen -HAS_BWMA = _bind("qtl_bwma", [_dp, _ci, _dp, _ci, _ci]) # period, polyOrder -HAS_CRMA = _bind("qtl_crma", [_dp, _ci, _dp, _ci, _cd]) # period, volumeFactor -HAS_SP15 = _bind("qtl_sp15", _PA) -HAS_TUKEY_W = _bind("qtl_tukey_w", _PA) -HAS_RAIN = _bind("qtl_rain", _PA) -HAS_AFIRMA = _bind("qtl_afirma", [_dp, _ci, _dp, _ci, _ci, _ci]) # src,n,dst,period,windowType,useSimd +HAS_FWMA = _bind("qtl_fwma", [_dp, _dp, _ci, _ci]) +HAS_GWMA = _bind("qtl_gwma", [_dp, _dp, _ci, _ci, _cd]) +HAS_HAMMA = _bind("qtl_hamma", [_dp, _dp, _ci, _ci]) +HAS_HEND = _bind("qtl_hend", [_dp, _dp, _ci, _ci, _cd]) +HAS_ILRS = _bind("qtl_ilrs", [_dp, _dp, _ci, _ci]) +HAS_KAISER = _bind("qtl_kaiser", [_dp, _dp, _ci, _ci, _cd, _cd]) +HAS_LANCZOS = _bind("qtl_lanczos", [_dp, _dp, _ci, _ci, _cd]) +HAS_NLMA = _bind("qtl_nlma", [_dp, _dp, _ci, _ci]) +HAS_NYQMA = _bind("qtl_nyqma", [_dp, _dp, _ci, _ci, _ci]) +HAS_PMA = _bind("qtl_pma", [_dp, _dp, _dp, _ci, _ci]) +HAS_PWMA = _bind("qtl_pwma", [_dp, _dp, _ci, _ci]) +HAS_QRMA = _bind("qtl_qrma", [_dp, _dp, _ci, _ci, _cd]) +HAS_RWMA = _bind("qtl_rwma", [_dp, _dp, _dp, _dp, _ci, _ci]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.5 Trends — IIR +# Trends — IIR # ═══════════════════════════════════════════════════════════════════════════ -HAS_EMA = _bind("qtl_ema", _PA) -HAS_EMA_ALPHA = _bind("qtl_ema_alpha", _PA2) -HAS_DEMA = _bind("qtl_dema", _PA) -HAS_DEMA_ALPHA = _bind("qtl_dema_alpha", _PA2) -HAS_TEMA = _bind("qtl_tema", _PA) -HAS_LEMA = _bind("qtl_lema", _PA) -HAS_HEMA = _bind("qtl_hema", _PA) -HAS_AHRENS = _bind("qtl_ahrens", _PA) -HAS_DECYCLER = _bind("qtl_decycler", _PA) -HAS_DSMA = _bind("qtl_dsma", [_dp, _ci, _dp, _ci, _cd]) # period, factor -HAS_GDEMA = _bind("qtl_gdema", [_dp, _ci, _dp, _ci, _cd]) # period, factor -HAS_CORAL = _bind("qtl_coral", [_dp, _ci, _dp, _ci, _cd]) # period, friction -HAS_AGC = _bind("qtl_agc", _PA2) # alpha -HAS_CCYC = _bind("qtl_ccyc", _PA2) # alpha +HAS_ADXVMA = _bind("qtl_adxvma", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _dp]) +HAS_FRAMA = _bind("qtl_frama", [_dp, _dp, _ci, _ci]) +HAS_HOLT = _bind("qtl_holt", [_dp, _dp, _ci, _ci, _cd]) +HAS_HTIT = _bind("qtl_htit", [_dp, _dp, _ci]) +HAS_HWMA = _bind("qtl_hwma", [_dp, _dp, _ci, _ci]) +HAS_JMA = _bind("qtl_jma", [_dp, _dp, _ci, _ci, _ci, _cd]) +HAS_KAMA = _bind("qtl_kama", [_dp, _dp, _ci, _ci, _ci, _ci]) +HAS_LTMA = _bind("qtl_ltma", [_dp, _dp, _ci, _ci]) +HAS_MAMA = _bind("qtl_mama", [_dp, _dp, _cd, _ci, _cd, _dp]) +HAS_MAVP = _bind("qtl_mavp", [_dp, _dp, _dp, _ci, _ci, _ci]) +HAS_MCNMA = _bind("qtl_mcnma", [_dp, _dp, _ci, _ci]) +HAS_MGDI = _bind("qtl_mgdi", [_dp, _dp, _ci, _ci, _cd]) +HAS_MMA = _bind("qtl_mma", [_dp, _dp, _ci, _ci]) +HAS_NMA = _bind("qtl_nma", [_dp, _dp, _ci, _ci]) +HAS_QEMA = _bind("qtl_qema", [_dp, _dp, _ci, _ci]) +HAS_REMA = _bind("qtl_rema", [_dp, _dp, _ci, _ci, _cd]) +HAS_RGMA = _bind("qtl_rgma", [_dp, _dp, _ci, _ci, _ci]) +HAS_RMA = _bind("qtl_rma", [_dp, _dp, _ci, _ci]) +HAS_T3 = _bind("qtl_t3", [_dp, _dp, _ci, _ci, _cd]) +HAS_TRAMA = _bind("qtl_trama", [_dp, _dp, _ci, _ci]) +HAS_VAMA = _bind("qtl_vama", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _ci, _ci, _ci, _dp]) +HAS_VIDYA = _bind("qtl_vidya", [_dp, _dp, _ci, _ci]) +HAS_YZVAMA = _bind("qtl_yzvama", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _ci, _ci, _ci, _dp]) +HAS_ZLDEMA = _bind("qtl_zldema", [_dp, _dp, _ci, _ci]) +HAS_ZLEMA = _bind("qtl_zlema", [_dp, _dp, _ci, _ci]) +HAS_ZLTEMA = _bind("qtl_zltema", [_dp, _dp, _ci, _ci]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.6 Channels +# Channels # ═══════════════════════════════════════════════════════════════════════════ -HAS_BBANDS = _bind("qtl_bbands", [_dp, _ci, _dp, _dp, _dp, _ci, _cd]) # src,n, upper,mid,lower, period,mult -HAS_ABBER = _bind("qtl_abber", [_dp, _dp, _dp, _dp, _ci, _ci, _cd]) # src,mid,upper,lower,n,period,mult -HAS_ATRBANDS = _bind("qtl_atrbands", [_dp, _dp, _dp, _ci, _dp, _dp, _dp, _ci, _cd]) # h,l,c,n, upper,mid,lower, period,mult -HAS_APCHANNEL = _bind("qtl_apchannel", [_dp, _dp, _ci, _dp, _dp, _ci]) # h,l,n, upper,lower, period +HAS_ACCBANDS = _bind("qtl_accbands", [_dp, _dp, _dp, _dp, _dp, _dp, _ci, _ci, _cd]) +HAS_APZ = _bind("qtl_apz", [_dp, _dp, _dp, _dp, _dp, _ci, _cd, _ci, _dp, _dp, _dp]) +HAS_DCHANNEL = _bind("qtl_dchannel", [_dp, _dp, _dp, _dp, _dp, _ci, _ci]) +HAS_DECAYCHANNEL = _bind("qtl_decaychannel", [_dp, _dp, _dp, _dp, _dp, _ci, _ci]) +HAS_FCB = _bind("qtl_fcb", [_dp, _dp, _dp, _dp, _dp, _ci, _ci]) +HAS_JBANDS = _bind("qtl_jbands", [_dp, _dp, _dp, _dp, _ci, _ci, _ci]) +HAS_KCHANNEL = _bind("qtl_kchannel", [_dp, _dp, _dp, _dp, _dp, _dp, _ci, _ci, _cd]) +HAS_MAENV = _bind("qtl_maenv", [_dp, _dp, _dp, _dp, _ci, _ci, _cd, _ci]) +HAS_MMCHANNEL = _bind("qtl_mmchannel", [_dp, _dp, _dp, _dp, _ci, _ci]) +HAS_PCHANNEL = _bind("qtl_pchannel", [_dp, _dp, _dp, _dp, _dp, _ci, _ci]) +HAS_REGCHANNEL = _bind("qtl_regchannel", [_dp, _dp, _dp, _dp, _ci, _ci, _cd]) +HAS_SDCHANNEL = _bind("qtl_sdchannel", [_dp, _dp, _dp, _dp, _ci, _ci, _cd]) +HAS_STARCHANNEL = _bind("qtl_starchannel", [_dp, _dp, _dp, _dp, _dp, _dp, _ci, _ci, _cd, _ci]) +HAS_STBANDS = _bind("qtl_stbands", [_dp, _dp, _dp, _dp, _dp, _dp, _ci, _ci, _cd]) +HAS_TTMLRC = _bind("qtl_ttmlrc", [_dp, _dp, _dp, _dp, _dp, _dp, _ci, _ci]) +HAS_UBANDS = _bind("qtl_ubands", [_dp, _dp, _dp, _dp, _ci, _ci, _cd]) +HAS_UCHANNEL = _bind("qtl_uchannel", [_dp, _dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _cd]) +HAS_VWAPBANDS = _bind("qtl_vwapbands", [_dp, _dp, _dp, _dp, _dp, _dp, _dp, _dp, _ci, _cd]) +HAS_VWAPSD = _bind("qtl_vwapsd", [_dp, _dp, _dp, _dp, _dp, _dp, _ci, _cd]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.7 Volatility +# Volatility # ═══════════════════════════════════════════════════════════════════════════ -HAS_TR = _bind("qtl_tr", _PE) # HLC -HAS_BBW = _bind("qtl_bbw", _PA) -HAS_BBWN = _bind("qtl_bbwn", [_dp, _ci, _dp, _ci, _cd, _ci]) # period, mult, lookback -HAS_BBWP = _bind("qtl_bbwp", [_dp, _ci, _dp, _ci, _cd, _ci]) # period, mult, lookback -HAS_STDDEV = _bind("qtl_stddev", _PA) -HAS_VARIANCE = _bind("qtl_variance", _PA) -HAS_ETHERM = _bind("qtl_etherm", [_dp, _dp, _ci, _dp, _ci]) # high,low,n,dst,period -HAS_CCV = _bind("qtl_ccv", [_dp, _ci, _dp, _ci, _ci]) # shortP, longP -HAS_CV = _bind("qtl_cv", [_dp, _ci, _dp, _ci, _cd, _cd]) # period, minVol, maxVol -HAS_CVI = _bind("qtl_cvi", [_dp, _ci, _dp, _ci, _ci]) # emaPeriod, rocPeriod -HAS_EWMA = _bind("qtl_ewma", [_dp, _ci, _dp, _ci, _ci, _ci]) # period, isPop, annFactor +HAS_ADR = _bind("qtl_adr", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _dp]) +HAS_ATR = _bind("qtl_atr", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _dp]) +HAS_ATRN = _bind("qtl_atrn", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _dp]) +HAS_GKV = _bind("qtl_gkv", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _ci]) +HAS_HLV = _bind("qtl_hlv", [_dp, _dp, _dp, _ci, _ci, _ci, _ci]) +HAS_HV = _bind("qtl_hv", [_dp, _dp, _ci, _ci, _ci, _ci]) +HAS_JVOLTY = _bind("qtl_jvolty", [_dp, _dp, _ci, _ci]) +HAS_JVOLTYN = _bind("qtl_jvoltyn", [_dp, _dp, _ci, _ci]) +HAS_MASSI = _bind("qtl_massi", [_dp, _dp, _ci, _ci, _ci]) +HAS_NATR = _bind("qtl_natr", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _dp]) +HAS_RSV = _bind("qtl_rsv", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _ci]) +HAS_RV = _bind("qtl_rv", [_dp, _dp, _ci, _ci, _ci, _ci, _ci]) +HAS_RVI = _bind("qtl_rvi", [_dp, _dp, _ci, _ci, _ci]) +HAS_UI = _bind("qtl_ui", [_dp, _dp, _ci, _ci]) +HAS_VOV = _bind("qtl_vov", [_dp, _dp, _ci, _ci, _ci]) +HAS_VR = _bind("qtl_vr", [_dp, _dp, _dp, _dp, _ci, _ci]) +HAS_YZV = _bind("qtl_yzv", [_dp, _dp, _dp, _dp, _dp, _ci, _ci]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.8 Volume +# Volume # ═══════════════════════════════════════════════════════════════════════════ -HAS_OBV = _bind("qtl_obv", _PG) # close,vol,n,dst -HAS_PVT = _bind("qtl_pvt", _PG) -HAS_PVR = _bind("qtl_pvr", _PG) -HAS_VF = _bind("qtl_vf", _PG) -HAS_NVI = _bind("qtl_nvi", _PG) -HAS_PVI = _bind("qtl_pvi", _PG) -HAS_TVI = _bind("qtl_tvi", _PG2) # close,vol,n,dst,period -HAS_PVD = _bind("qtl_pvd", _PG2) -HAS_VWMA = _bind("qtl_vwma", _PG2) -HAS_EVWMA = _bind("qtl_evwma", _PG2) -HAS_EFI = _bind("qtl_efi", _PG2) -HAS_AOBV = _bind("qtl_aobv", [_dp, _dp, _ci, _dp, _dp]) # close,vol,n,obv,signal -HAS_MFI = _bind("qtl_mfi", _PB) # HLCV + period -HAS_CMF = _bind("qtl_cmf", _PB) -HAS_EOM = _bind("qtl_eom", [_dp, _dp, _dp, _ci, _dp, _ci]) # h,l,v,n,dst,period -HAS_PVO = _bind("qtl_pvo", [_dp, _ci, _dp, _dp, _dp, _ci, _ci, _ci]) # vol,n, pvo,signal,hist, fast,slow,signal_p +HAS_ADL = _bind("qtl_adl", [_dp, _dp, _dp, _dp, _dp, _ci]) +HAS_ADOSC = _bind("qtl_adosc", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci]) +HAS_III = _bind("qtl_iii", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci]) +HAS_KVO = _bind("qtl_kvo", [_dp, _dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _ci]) +HAS_TWAP = _bind("qtl_twap", [_dp, _dp, _ci, _ci]) +HAS_VA = _bind("qtl_va", [_dp, _dp, _dp, _dp, _dp, _ci]) +HAS_VO = _bind("qtl_vo", [_dp, _dp, _ci, _ci, _ci]) +HAS_VROC = _bind("qtl_vroc", [_dp, _dp, _ci, _ci, _ci]) +HAS_VWAD = _bind("qtl_vwad", [_dp, _dp, _dp, _dp, _dp, _ci, _ci]) +HAS_VWAP = _bind("qtl_vwap", [_dp, _dp, _dp, _dp, _dp, _ci, _ci]) +HAS_WAD = _bind("qtl_wad", [_dp, _dp, _dp, _dp, _dp, _ci]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.9 Statistics +# Statistics # ═══════════════════════════════════════════════════════════════════════════ -HAS_ZSCORE = _bind("qtl_zscore", _PA) -HAS_CMA = _bind("qtl_cma", _PA3) # no period -HAS_ENTROPY = _bind("qtl_entropy", _PA) -HAS_CORRELATION = _bind("qtl_correlation", _PH) -HAS_COVARIANCE = _bind("qtl_covariance", [_dp, _dp, _ci, _dp, _ci, _ci]) # x,y,n,dst,period,isSample -HAS_COINTEGRATION = _bind("qtl_cointegration", _PH) +HAS_ACF = _bind("qtl_acf", [_dp, _dp, _ci, _ci, _ci]) +HAS_GEOMEAN = _bind("qtl_geomean", [_dp, _dp, _ci, _ci]) +HAS_GRANGER = _bind("qtl_granger", [_dp, _dp, _dp, _ci, _ci]) +HAS_HARMEAN = _bind("qtl_harmean", [_dp, _dp, _ci, _ci]) +HAS_HURST = _bind("qtl_hurst", [_dp, _dp, _ci, _ci]) +HAS_IQR = _bind("qtl_iqr", [_dp, _dp, _ci, _ci]) +HAS_JB = _bind("qtl_jb", [_dp, _dp, _ci, _ci]) +HAS_KENDALL = _bind("qtl_kendall", [_dp, _dp, _dp, _ci, _ci]) +HAS_KURTOSIS = _bind("qtl_kurtosis", [_dp, _dp, _ci, _ci, _ci]) +HAS_LINREG = _bind("qtl_linreg", [_dp, _dp, _ci, _ci, _ci, _cd]) +HAS_MEANDEV = _bind("qtl_meandev", [_dp, _dp, _ci, _ci]) +HAS_MEDIAN = _bind("qtl_median", [_dp, _dp, _ci, _ci]) +HAS_MODE = _bind("qtl_mode", [_dp, _dp, _ci, _ci]) +HAS_PACF = _bind("qtl_pacf", [_dp, _dp, _ci, _ci, _ci]) +HAS_PERCENTILE = _bind("qtl_percentile", [_dp, _dp, _ci, _ci, _cd]) +HAS_POLYFIT = _bind("qtl_polyfit", [_dp, _dp, _ci, _ci, _ci, _cd]) +HAS_QUANTILE = _bind("qtl_quantile", [_dp, _dp, _ci, _ci, _cd]) +HAS_SKEW = _bind("qtl_skew", [_dp, _dp, _ci, _ci, _ci]) +HAS_SPEARMAN = _bind("qtl_spearman", [_dp, _dp, _dp, _ci, _ci]) +HAS_STDERR = _bind("qtl_stderr", [_dp, _dp, _ci, _ci]) +HAS_SUM = _bind("qtl_sum", [_dp, _dp, _ci, _ci]) +HAS_THEIL = _bind("qtl_theil", [_dp, _dp, _ci, _ci]) +HAS_TRIM = _bind("qtl_trim", [_dp, _dp, _ci, _ci, _cd]) +HAS_WAVG = _bind("qtl_wavg", [_dp, _dp, _ci, _ci]) +HAS_WINS = _bind("qtl_wins", [_dp, _dp, _ci, _ci, _cd]) +HAS_ZTEST = _bind("qtl_ztest", [_dp, _dp, _ci, _ci, _cd]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.10 Errors +# Errors # ═══════════════════════════════════════════════════════════════════════════ -HAS_MSE = _bind("qtl_mse", _PF) -HAS_RMSE = _bind("qtl_rmse", _PF) -HAS_MAE = _bind("qtl_mae", _PF) -HAS_MAPE = _bind("qtl_mape", _PF) +HAS_HUBER = _bind("qtl_huber", [_dp, _dp, _dp, _ci, _ci, _cd]) +HAS_LOGCOSH = _bind("qtl_logcosh", [_dp, _dp, _dp, _ci, _ci]) +HAS_MAAPE = _bind("qtl_maape", [_dp, _dp, _dp, _ci, _ci]) +HAS_MAPD = _bind("qtl_mapd", [_dp, _dp, _dp, _ci, _ci]) +HAS_MASE = _bind("qtl_mase", [_dp, _dp, _dp, _ci, _ci]) +HAS_MDAE = _bind("qtl_mdae", [_dp, _dp, _dp, _ci, _ci]) +HAS_MDAPE = _bind("qtl_mdape", [_dp, _dp, _dp, _ci, _ci]) +HAS_ME = _bind("qtl_me", [_dp, _dp, _dp, _ci, _ci]) +HAS_MPE = _bind("qtl_mpe", [_dp, _dp, _dp, _ci, _ci]) +HAS_MRAE = _bind("qtl_mrae", [_dp, _dp, _dp, _ci, _ci]) +HAS_MSLE = _bind("qtl_msle", [_dp, _dp, _dp, _ci, _ci]) +HAS_PSEUDOHUBER = _bind("qtl_pseudohuber", [_dp, _dp, _dp, _ci, _ci, _cd]) +HAS_QUANTILELOSS = _bind("qtl_quantileloss", [_dp, _dp, _dp, _ci, _ci, _cd]) +HAS_RAE = _bind("qtl_rae", [_dp, _dp, _dp, _ci, _ci]) +HAS_RMSLE = _bind("qtl_rmsle", [_dp, _dp, _dp, _ci, _ci]) +HAS_RSE = _bind("qtl_rse", [_dp, _dp, _dp, _ci, _ci]) +HAS_RSQUARED = _bind("qtl_rsquared", [_dp, _dp, _dp, _ci, _ci]) +HAS_SMAPE = _bind("qtl_smape", [_dp, _dp, _dp, _ci, _ci]) +HAS_THEILU = _bind("qtl_theilu", [_dp, _dp, _dp, _ci, _ci]) +HAS_TUKEYBIWEIGHT = _bind("qtl_tukeybiweight", [_dp, _dp, _dp, _ci, _ci, _cd]) +HAS_WMAPE = _bind("qtl_wmape", [_dp, _dp, _dp, _ci, _ci]) +HAS_WRMSE = _bind("qtl_wrmse", [_dp, _dp, _dp, _ci, _ci]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.11 Filters +# Filters # ═══════════════════════════════════════════════════════════════════════════ -HAS_BESSEL = _bind("qtl_bessel", _PA) -HAS_BUTTER2 = _bind("qtl_butter2", _PA) -HAS_BUTTER3 = _bind("qtl_butter3", _PA) -HAS_CHEBY1 = _bind("qtl_cheby1", _PA) -HAS_CHEBY2 = _bind("qtl_cheby2", _PA) -HAS_ELLIPTIC = _bind("qtl_elliptic", _PA) -HAS_EDCF = _bind("qtl_edcf", _PA) -HAS_BPF = _bind("qtl_bpf", _PA) -HAS_ALAGUERRE = _bind("qtl_alaguerre", [_dp, _ci, _dp, _ci, _ci]) # period, order -HAS_BILATERAL = _bind("qtl_bilateral", [_dp, _ci, _dp, _ci, _cd, _cd]) # period, sigmaS, sigmaR -HAS_BAXTERKING = _bind("qtl_baxterking", [_dp, _ci, _dp, _ci, _ci, _ci]) # period, minP, maxP -HAS_CFITZ = _bind("qtl_cfitz", [_dp, _ci, _dp, _ci, _ci]) # period, bandwidthP +HAS_GAUSS = _bind("qtl_gauss", [_dp, _dp, _ci, _cd]) +HAS_HANN = _bind("qtl_hann", [_dp, _dp, _ci, _ci]) +HAS_HP = _bind("qtl_hp", [_dp, _dp, _ci, _cd]) +HAS_HPF = _bind("qtl_hpf", [_dp, _dp, _ci, _ci]) +HAS_KALMAN = _bind("qtl_kalman", [_dp, _dp, _ci, _cd, _cd]) +HAS_LAGUERRE = _bind("qtl_laguerre", [_dp, _dp, _ci, _cd]) +HAS_LMS = _bind("qtl_lms", [_dp, _dp, _ci, _ci, _cd]) +HAS_LOESS = _bind("qtl_loess", [_dp, _dp, _ci, _ci]) +HAS_MODF = _bind("qtl_modf", [_dp, _dp, _ci, _ci, _cd, _ci, _cd]) +HAS_NOTCH = _bind("qtl_notch", [_dp, _dp, _ci, _ci, _cd]) +HAS_NW = _bind("qtl_nw", [_dp, _dp, _ci, _ci, _cd]) +HAS_ONEEURO = _bind("qtl_oneeuro", [_dp, _dp, _ci, _cd, _cd, _cd]) +HAS_RLS = _bind("qtl_rls", [_dp, _dp, _ci, _ci, _cd]) +HAS_RMED = _bind("qtl_rmed", [_dp, _dp, _ci, _ci]) +HAS_ROOFING = _bind("qtl_roofing", [_dp, _dp, _ci, _ci, _ci]) +HAS_SGF = _bind("qtl_sgf", [_dp, _dp, _ci, _ci, _ci]) +HAS_SPBF = _bind("qtl_spbf", [_dp, _dp, _ci, _ci, _ci, _ci]) +HAS_SSF2 = _bind("qtl_ssf2", [_dp, _dp, _ci, _ci]) +HAS_SSF3 = _bind("qtl_ssf3", [_dp, _dp, _ci, _ci, _cd]) +HAS_USF = _bind("qtl_usf", [_dp, _dp, _ci, _ci]) +HAS_VOSS = _bind("qtl_voss", [_dp, _dp, _ci, _ci, _ci, _cd]) +HAS_WAVELET = _bind("qtl_wavelet", [_dp, _dp, _ci, _ci, _cd]) +HAS_WIENER = _bind("qtl_wiener", [_dp, _dp, _ci, _ci, _ci]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.12 Cycles +# Cycles # ═══════════════════════════════════════════════════════════════════════════ -HAS_CG = _bind("qtl_cg", _PA) -HAS_DSP = _bind("qtl_dsp", _PA) -HAS_CCOR = _bind("qtl_ccor", _PA) -HAS_EBSW = _bind("qtl_ebsw", [_dp, _ci, _dp, _ci, _ci]) # period, hpPeriod -HAS_EACP = _bind("qtl_eacp", [_dp, _ci, _dp, _ci, _ci, _ci, _ci]) # period, minP, maxP, useMedian +HAS_HOMOD = _bind("qtl_homod", [_dp, _dp, _ci, _cd, _cd]) +HAS_HTDCPERIOD = _bind("qtl_htdcperiod", [_dp, _dp, _ci]) +HAS_HTDCPHASE = _bind("qtl_htdcphase", [_dp, _dp, _ci]) +HAS_HTPHASOR = _bind("qtl_htphasor", [_dp, _dp, _dp, _ci]) +HAS_HTSINE = _bind("qtl_htsine", [_dp, _dp, _dp, _ci]) +HAS_LUNAR = _bind("qtl_lunar", [_dp, _ci, _dp]) +HAS_SOLAR = _bind("qtl_solar", [_dp, _ci, _dp]) +HAS_SSFDSP = _bind("qtl_ssfdsp", [_dp, _dp, _ci, _ci]) # ═══════════════════════════════════════════════════════════════════════════ -# §8.14 Numerics +# Dynamics # ═══════════════════════════════════════════════════════════════════════════ -HAS_CHANGE = _bind("qtl_change", _PA) -HAS_EXPTRANS = _bind("qtl_exptrans", _PA3) # no period -HAS_BETADIST = _bind("qtl_betadist", [_dp, _ci, _dp, _ci, _cd, _cd]) # period, alpha, beta -HAS_EXPDIST = _bind("qtl_expdist", [_dp, _ci, _dp, _ci, _cd]) # period, lambda -HAS_BINOMDIST = _bind("qtl_binomdist", [_dp, _ci, _dp, _ci, _ci, _ci]) # period, trials, successes -HAS_CWT = _bind("qtl_cwt", [_dp, _ci, _dp, _cd, _cd]) # scale, omega -HAS_DWT = _bind("qtl_dwt", [_dp, _ci, _dp, _ci, _ci]) # period, levels +HAS_ADX = _bind("qtl_adx", [_dp, _dp, _dp, _ci, _ci, _dp]) +HAS_ADXR = _bind("qtl_adxr", [_dp, _dp, _dp, _ci, _ci, _dp]) +HAS_ALLIGATOR = _bind("qtl_alligator", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _ci, _ci, _ci, _ci, _dp]) +HAS_AMAT = _bind("qtl_amat", [_dp, _dp, _dp, _ci, _ci, _ci]) +HAS_AROON = _bind("qtl_aroon", [_dp, _dp, _ci, _ci, _dp]) +HAS_AROONOSC = _bind("qtl_aroonosc", [_dp, _dp, _ci, _ci, _dp]) +HAS_CHOP = _bind("qtl_chop", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _dp]) +HAS_DMX = _bind("qtl_dmx", [_dp, _dp, _dp, _ci, _ci, _dp]) +HAS_DX = _bind("qtl_dx", [_dp, _dp, _dp, _ci, _ci, _dp]) +HAS_GHLA = _bind("qtl_ghla", [_dp, _dp, _dp, _dp, _ci, _ci]) +HAS_HTTRENDMODE = _bind("qtl_httrendmode", [_dp, _dp, _ci]) +HAS_ICHIMOKU = _bind("qtl_ichimoku", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _ci, _ci, _dp, _dp, _dp, _dp, _dp]) +HAS_IMPULSE = _bind("qtl_impulse", [_dp, _ci, _ci, _ci, _ci, _ci, _dp]) +HAS_PFE = _bind("qtl_pfe", [_dp, _dp, _ci, _ci, _ci]) +HAS_QSTICK = _bind("qtl_qstick", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _ci, _dp]) +HAS_RAVI = _bind("qtl_ravi", [_dp, _dp, _ci, _ci, _ci]) +HAS_SUPER = _bind("qtl_super", [_dp, _dp, _dp, _dp, _dp, _ci, _cd, _ci, _dp]) +HAS_TTMSQUEEZE = _bind("qtl_ttmsqueeze", [_dp, _dp, _dp, _dp, _dp, _ci, _cd, _ci, _cd, _ci, _ci, _dp]) +HAS_TTMTREND = _bind("qtl_ttmtrend", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _dp]) +HAS_VHF = _bind("qtl_vhf", [_dp, _dp, _ci, _ci]) +HAS_VORTEX = _bind("qtl_vortex", [_dp, _dp, _dp, _ci, _dp, _ci, _dp]) + +# ═══════════════════════════════════════════════════════════════════════════ +# Numerics +# ═══════════════════════════════════════════════════════════════════════════ +HAS_ACCEL = _bind("qtl_accel", [_dp, _dp, _ci]) +HAS_FDIST = _bind("qtl_fdist", [_dp, _dp, _ci, _ci, _ci, _ci]) +HAS_FFT = _bind("qtl_fft", [_dp, _dp, _ci, _ci, _ci, _ci]) +HAS_GAMMADIST = _bind("qtl_gammadist", [_dp, _dp, _ci, _cd, _cd, _ci]) +HAS_HIGHEST = _bind("qtl_highest", [_dp, _dp, _ci, _ci]) +HAS_IFFT = _bind("qtl_ifft", [_dp, _dp, _ci, _ci, _ci]) +HAS_JERK = _bind("qtl_jerk", [_dp, _dp, _ci]) +HAS_LINEARTRANS = _bind("qtl_lineartrans", [_dp, _dp, _ci, _cd, _cd]) +HAS_LOGNORMDIST = _bind("qtl_lognormdist", [_dp, _dp, _ci, _cd, _cd, _ci]) +HAS_LOGTRANS = _bind("qtl_logtrans", [_dp, _dp, _ci]) +HAS_LOWEST = _bind("qtl_lowest", [_dp, _dp, _ci, _ci]) +HAS_NORMALIZE = _bind("qtl_normalize", [_dp, _dp, _ci, _ci]) +HAS_NORMDIST = _bind("qtl_normdist", [_dp, _dp, _ci, _cd, _cd, _ci]) +HAS_POISSONDIST = _bind("qtl_poissondist", [_dp, _dp, _ci, _cd, _ci, _ci]) +HAS_RELU = _bind("qtl_relu", [_dp, _dp, _ci]) +HAS_SIGMOID = _bind("qtl_sigmoid", [_dp, _dp, _ci, _cd, _cd]) +HAS_SLOPE = _bind("qtl_slope", [_dp, _dp, _ci]) +HAS_SQRTTRANS = _bind("qtl_sqrttrans", [_dp, _dp, _ci]) +HAS_TDIST = _bind("qtl_tdist", [_dp, _dp, _ci, _ci, _ci]) +HAS_WEIBULLDIST = _bind("qtl_weibulldist", [_dp, _dp, _ci, _cd, _cd, _ci]) + +# ═══════════════════════════════════════════════════════════════════════════ +# Reversals +# ═══════════════════════════════════════════════════════════════════════════ +HAS_CHANDELIER = _bind("qtl_chandelier", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _cd]) +HAS_CKSTOP = _bind("qtl_ckstop", [_dp, _dp, _dp, _dp, _dp, _ci, _ci, _cd, _ci]) +HAS_FRACTALS = _bind("qtl_fractals", [_dp, _dp, _dp, _dp, _ci]) +HAS_PIVOT = _bind("qtl_pivot", [_dp, _dp, _dp, _dp, _ci]) +HAS_PIVOTCAM = _bind("qtl_pivotcam", [_dp, _dp, _dp, _dp, _ci]) +HAS_PIVOTDEM = _bind("qtl_pivotdem", [_dp, _dp, _dp, _dp, _dp, _ci]) +HAS_PIVOTEXT = _bind("qtl_pivotext", [_dp, _dp, _dp, _dp, _ci]) +HAS_PIVOTFIB = _bind("qtl_pivotfib", [_dp, _dp, _dp, _dp, _ci]) +HAS_PIVOTWOOD = _bind("qtl_pivotwood", [_dp, _dp, _dp, _dp, _ci]) +HAS_PSAR = _bind("qtl_psar", [_dp, _dp, _dp, _dp, _dp, _ci, _cd, _cd, _cd]) +HAS_SWINGS = _bind("qtl_swings", [_dp, _dp, _dp, _dp, _ci, _ci]) +HAS_TTMSCALPER = _bind("qtl_ttmscalper", [_dp, _dp, _dp, _dp, _dp, _ci, _ci]) + + +# ── Momentum (Exports.cs — manual) ── +HAS_RSI = _bind("qtl_rsi", [_dp, _ci, _dp, _ci]) +HAS_ROC = _bind("qtl_roc", [_dp, _ci, _dp, _ci]) +HAS_MOM = _bind("qtl_mom", [_dp, _ci, _dp, _ci]) +HAS_CMO = _bind("qtl_cmo", [_dp, _ci, _dp, _ci]) +HAS_TSI = _bind("qtl_tsi", [_dp, _ci, _dp, _ci, _ci]) +HAS_APO = _bind("qtl_apo", [_dp, _ci, _dp, _ci, _ci]) +HAS_BIAS = _bind("qtl_bias", [_dp, _ci, _dp, _ci]) +HAS_CFO = _bind("qtl_cfo", [_dp, _ci, _dp, _ci]) +HAS_CFB = _bind("qtl_cfb", [_dp, _ci, _dp, _ip, _ci]) +HAS_ASI = _bind("qtl_asi", [_dp, _dp, _dp, _dp, _ci, _dp, _cd]) + +# ── Oscillators (Exports.cs — manual) ── +HAS_FISHER = _bind("qtl_fisher", [_dp, _ci, _dp, _ci]) +HAS_FISHER04 = _bind("qtl_fisher04", [_dp, _ci, _dp, _ci]) +HAS_DPO = _bind("qtl_dpo", [_dp, _ci, _dp, _ci]) +HAS_TRIX = _bind("qtl_trix", [_dp, _ci, _dp, _ci]) +HAS_INERTIA = _bind("qtl_inertia", [_dp, _ci, _dp, _ci]) +HAS_RSX = _bind("qtl_rsx", [_dp, _ci, _dp, _ci]) +HAS_ER = _bind("qtl_er", [_dp, _ci, _dp, _ci]) +HAS_CTI = _bind("qtl_cti", [_dp, _ci, _dp, _ci]) +HAS_REFLEX = _bind("qtl_reflex", [_dp, _ci, _dp, _ci]) +HAS_TRENDFLEX = _bind("qtl_trendflex", [_dp, _ci, _dp, _ci]) +HAS_KRI = _bind("qtl_kri", [_dp, _ci, _dp, _ci]) +HAS_PSL = _bind("qtl_psl", [_dp, _ci, _dp, _ci]) +HAS_DECO = _bind("qtl_deco", [_dp, _ci, _dp, _ci, _ci]) +HAS_DOSC = _bind("qtl_dosc", [_dp, _ci, _dp, _ci, _ci, _ci, _ci]) +HAS_DYMOI = _bind("qtl_dymoi", [_dp, _ci, _dp, _ci, _ci, _ci, _ci, _ci]) +HAS_CRSI = _bind("qtl_crsi", [_dp, _ci, _dp, _ci, _ci, _ci]) +HAS_BBB = _bind("qtl_bbb", [_dp, _ci, _dp, _ci, _cd]) +HAS_BBI = _bind("qtl_bbi", [_dp, _ci, _dp, _ci, _ci, _ci, _ci]) +HAS_DEM = _bind("qtl_dem", [_dp, _dp, _ci, _dp, _ci]) +HAS_BRAR = _bind("qtl_brar", [_dp, _dp, _dp, _dp, _ci, _dp, _dp, _ci]) + +# ── Trends — FIR (Exports.cs — manual) ── +HAS_SMA = _bind("qtl_sma", [_dp, _ci, _dp, _ci]) +HAS_WMA = _bind("qtl_wma", [_dp, _ci, _dp, _ci]) +HAS_HMA = _bind("qtl_hma", [_dp, _ci, _dp, _ci]) +HAS_TRIMA = _bind("qtl_trima", [_dp, _ci, _dp, _ci]) +HAS_SWMA = _bind("qtl_swma", [_dp, _ci, _dp, _ci]) +HAS_DWMA = _bind("qtl_dwma", [_dp, _ci, _dp, _ci]) +HAS_BLMA = _bind("qtl_blma", [_dp, _ci, _dp, _ci]) +HAS_ALMA = _bind("qtl_alma", [_dp, _ci, _dp, _ci, _cd, _cd]) +HAS_LSMA = _bind("qtl_lsma", [_dp, _ci, _dp, _ci, _ci, _cd]) +HAS_SGMA = _bind("qtl_sgma", [_dp, _ci, _dp, _ci]) +HAS_SINEMA = _bind("qtl_sinema", [_dp, _ci, _dp, _ci]) +HAS_HANMA = _bind("qtl_hanma", [_dp, _ci, _dp, _ci]) +HAS_PARZEN = _bind("qtl_parzen", [_dp, _ci, _dp, _ci]) +HAS_TSF = _bind("qtl_tsf", [_dp, _ci, _dp, _ci]) +HAS_CONV = _bind("qtl_conv", [_dp, _ci, _dp, _dp, _ci]) +HAS_BWMA = _bind("qtl_bwma", [_dp, _ci, _dp, _ci, _ci]) +HAS_CRMA = _bind("qtl_crma", [_dp, _ci, _dp, _ci, _cd]) +HAS_SP15 = _bind("qtl_sp15", [_dp, _ci, _dp, _ci]) +HAS_TUKEY_W = _bind("qtl_tukey_w", [_dp, _ci, _dp, _ci]) +HAS_RAIN = _bind("qtl_rain", [_dp, _ci, _dp, _ci]) +HAS_AFIRMA = _bind("qtl_afirma", [_dp, _ci, _dp, _ci, _ci, _ci]) + +# ── Trends — IIR (Exports.cs — manual) ── +HAS_EMA = _bind("qtl_ema", [_dp, _ci, _dp, _ci]) +HAS_EMA_ALPHA = _bind("qtl_ema_alpha", [_dp, _ci, _dp, _cd]) +HAS_DEMA = _bind("qtl_dema", [_dp, _ci, _dp, _ci]) +HAS_DEMA_ALPHA = _bind("qtl_dema_alpha", [_dp, _ci, _dp, _cd]) +HAS_TEMA = _bind("qtl_tema", [_dp, _ci, _dp, _ci]) +HAS_LEMA = _bind("qtl_lema", [_dp, _ci, _dp, _ci]) +HAS_HEMA = _bind("qtl_hema", [_dp, _ci, _dp, _ci]) +HAS_AHRENS = _bind("qtl_ahrens", [_dp, _ci, _dp, _ci]) +HAS_DECYCLER = _bind("qtl_decycler", [_dp, _ci, _dp, _ci]) +HAS_DSMA = _bind("qtl_dsma", [_dp, _ci, _dp, _ci, _cd]) +HAS_GDEMA = _bind("qtl_gdema", [_dp, _ci, _dp, _ci, _cd]) +HAS_CORAL = _bind("qtl_coral", [_dp, _ci, _dp, _ci, _cd]) +HAS_AGC = _bind("qtl_agc", [_dp, _ci, _dp, _cd]) +HAS_CCYC = _bind("qtl_ccyc", [_dp, _ci, _dp, _cd]) + +# ── Channels (Exports.cs — manual) ── +HAS_BBANDS = _bind("qtl_bbands", [_dp, _ci, _dp, _dp, _dp, _ci, _cd]) +HAS_ATRBANDS = _bind("qtl_atrbands", [_dp, _dp, _dp, _ci, _dp, _dp, _dp, _ci, _cd]) +HAS_APCHANNEL = _bind("qtl_apchannel", [_dp, _dp, _ci, _dp, _dp, _cd]) + +# ── Volatility (Exports.cs — manual) ── +HAS_TR = _bind("qtl_tr", [_dp, _dp, _dp, _ci, _dp]) +HAS_BBW = _bind("qtl_bbw", [_dp, _ci, _dp, _ci, _cd]) +HAS_BBWN = _bind("qtl_bbwn", [_dp, _ci, _dp, _ci, _cd, _ci]) +HAS_BBWP = _bind("qtl_bbwp", [_dp, _ci, _dp, _ci, _cd, _ci]) +HAS_STDDEV = _bind("qtl_stddev", [_dp, _ci, _dp, _ci]) +HAS_VARIANCE = _bind("qtl_variance", [_dp, _ci, _dp, _ci]) +HAS_ETHERM = _bind("qtl_etherm", [_dp, _dp, _ci, _dp, _ci]) +HAS_CCV = _bind("qtl_ccv", [_dp, _ci, _dp, _ci, _ci]) +HAS_CV = _bind("qtl_cv", [_dp, _ci, _dp, _ci, _cd, _cd]) +HAS_CVI = _bind("qtl_cvi", [_dp, _ci, _dp, _ci, _ci]) +HAS_EWMA = _bind("qtl_ewma", [_dp, _ci, _dp, _ci, _ci, _ci]) + +# ── Volume (Exports.cs — manual) ── +HAS_OBV = _bind("qtl_obv", [_dp, _dp, _ci, _dp]) +HAS_PVT = _bind("qtl_pvt", [_dp, _dp, _ci, _dp]) +HAS_PVR = _bind("qtl_pvr", [_dp, _dp, _ci, _dp]) +HAS_VF = _bind("qtl_vf", [_dp, _dp, _ci, _dp]) +HAS_NVI = _bind("qtl_nvi", [_dp, _dp, _ci, _dp]) +HAS_PVI = _bind("qtl_pvi", [_dp, _dp, _ci, _dp]) +HAS_TVI = _bind("qtl_tvi", [_dp, _dp, _ci, _dp, _ci]) +HAS_PVD = _bind("qtl_pvd", [_dp, _dp, _ci, _dp, _ci]) +HAS_VWMA = _bind("qtl_vwma", [_dp, _dp, _ci, _dp, _ci]) +HAS_EVWMA = _bind("qtl_evwma", [_dp, _dp, _ci, _dp, _ci]) +HAS_EFI = _bind("qtl_efi", [_dp, _dp, _ci, _dp, _ci]) +HAS_AOBV = _bind("qtl_aobv", [_dp, _dp, _ci, _dp, _dp]) +HAS_MFI = _bind("qtl_mfi", [_dp, _dp, _dp, _dp, _ci, _dp, _ci]) +HAS_CMF = _bind("qtl_cmf", [_dp, _dp, _dp, _dp, _ci, _dp, _ci]) +HAS_EOM = _bind("qtl_eom", [_dp, _dp, _dp, _ci, _dp, _ci, _cd]) +HAS_PVO = _bind("qtl_pvo", [_dp, _ci, _dp, _dp, _dp, _ci, _ci, _ci]) + +# ── Statistics (Exports.cs — manual) ── +HAS_ZSCORE = _bind("qtl_zscore", [_dp, _ci, _dp, _ci]) +HAS_CMA = _bind("qtl_cma", [_dp, _ci, _dp]) +HAS_ENTROPY = _bind("qtl_entropy", [_dp, _ci, _dp, _ci]) +HAS_CORRELATION = _bind("qtl_correlation", [_dp, _dp, _ci, _dp, _ci]) +HAS_COVARIANCE = _bind("qtl_covariance", [_dp, _dp, _ci, _dp, _ci, _ci]) +HAS_COINTEGRATION = _bind("qtl_cointegration", [_dp, _dp, _ci, _dp, _ci]) + +# ── Errors (Exports.cs — manual) ── +HAS_MSE = _bind("qtl_mse", [_dp, _dp, _ci, _dp, _ci]) +HAS_RMSE = _bind("qtl_rmse", [_dp, _dp, _ci, _dp, _ci]) +HAS_MAE = _bind("qtl_mae", [_dp, _dp, _ci, _dp, _ci]) +HAS_MAPE = _bind("qtl_mape", [_dp, _dp, _ci, _dp, _ci]) + +# ── Filters (Exports.cs — manual) ── +HAS_BESSEL = _bind("qtl_bessel", [_dp, _ci, _dp, _ci]) +HAS_BUTTER2 = _bind("qtl_butter2", [_dp, _ci, _dp, _ci, _cd]) +HAS_BUTTER3 = _bind("qtl_butter3", [_dp, _ci, _dp, _ci, _cd]) +HAS_CHEBY1 = _bind("qtl_cheby1", [_dp, _ci, _dp, _ci, _cd]) +HAS_CHEBY2 = _bind("qtl_cheby2", [_dp, _ci, _dp, _ci, _cd]) +HAS_ELLIPTIC = _bind("qtl_elliptic", [_dp, _ci, _dp, _ci]) +HAS_EDCF = _bind("qtl_edcf", [_dp, _ci, _dp, _ci]) +HAS_BPF = _bind("qtl_bpf", [_dp, _ci, _dp, _ci, _ci]) +HAS_ALAGUERRE = _bind("qtl_alaguerre", [_dp, _ci, _dp, _ci, _ci]) +HAS_BILATERAL = _bind("qtl_bilateral", [_dp, _ci, _dp, _ci, _cd, _cd]) +HAS_BAXTERKING = _bind("qtl_baxterking", [_dp, _ci, _dp, _ci, _ci, _ci]) +HAS_CFITZ = _bind("qtl_cfitz", [_dp, _ci, _dp, _ci, _ci]) + +# ── Cycles (Exports.cs — manual) ── +HAS_CG = _bind("qtl_cg", [_dp, _ci, _dp, _ci]) +HAS_DSP = _bind("qtl_dsp", [_dp, _ci, _dp, _ci]) +HAS_CCOR = _bind("qtl_ccor", [_dp, _ci, _dp, _ci, _cd]) +HAS_EBSW = _bind("qtl_ebsw", [_dp, _ci, _dp, _ci, _ci]) +HAS_EACP = _bind("qtl_eacp", [_dp, _ci, _dp, _ci, _ci, _ci, _ci]) + +# ── Numerics (Exports.cs — manual) ── +HAS_CHANGE = _bind("qtl_change", [_dp, _ci, _dp, _ci]) +HAS_EXPTRANS = _bind("qtl_exptrans", [_dp, _ci, _dp]) +HAS_BETADIST = _bind("qtl_betadist", [_dp, _ci, _dp, _ci, _cd, _cd]) +HAS_EXPDIST = _bind("qtl_expdist", [_dp, _ci, _dp, _ci, _cd]) +HAS_BINOMDIST = _bind("qtl_binomdist", [_dp, _ci, _dp, _ci, _ci, _ci]) +HAS_CWT = _bind("qtl_cwt", [_dp, _ci, _dp, _cd, _cd]) +HAS_DWT = _bind("qtl_dwt", [_dp, _ci, _dp, _ci, _ci]) diff --git a/python/quantalib/_helpers.py b/python/quantalib/_helpers.py new file mode 100644 index 00000000..3700cfc2 --- /dev/null +++ b/python/quantalib/_helpers.py @@ -0,0 +1,236 @@ +"""Shared wrapper helpers for quantalib indicator modules. + +Auto-generated by generate_category_modules.py — DO NOT EDIT. +""" +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).""" + if x is None: + raise ValueError("Input array must not be 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): + idx = x.index + x = x.iloc[:, 0].to_numpy(dtype=_F64, copy=False) + arr = np.asarray(x, dtype=_F64) + if not arr.flags["C_CONTIGUOUS"]: + arr = np.ascontiguousarray(arr) + if arr.ndim == 0 or len(arr) == 0: + raise ValueError("Input array must not be empty") + return arr, idx + + +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 != 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.attrs["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.attrs["category"] = category + return df + return tuple(arrays.values()) + + +# ═══════════════════════════════════════════════════════════════════════════ +# Generic pattern helpers +# ═══════════════════════════════════════════════════════════════════════════ + +def _pa( + fn_name: str, close: object, length: int, offset: int, + default_length: int, label: str, category: str, +) -> object: + """Generic Pattern A wrapper: single-input + period.""" + 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) + + +def _pa3( + fn_name: str, close: object, offset: int, + label: str, category: str, +) -> object: + """Generic Pattern A3 wrapper: single-input, no params.""" + 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))) + return _wrap(dst, idx, label, category, offset) + + +def _pf( + fn_name: str, actual: object, predicted: object, + length: int, offset: int, default_length: int, + label: str, category: str, +) -> object: + """Generic Pattern F wrapper: 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}", category, offset) + + +def _pg( + fn_name: str, close: object, volume: object, + offset: int, label: str, category: 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, category, offset) + + +def _pg2( + fn_name: str, close: object, volume: object, length: int, + offset: int, default_length: int, label: str, category: 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}", category, offset) + + +def _ph( + fn_name: str, x: object, y: object, length: int, + offset: int, default_length: int, label: str, category: str, +) -> object: + """Pattern H: X+Y+period.""" + length = int(length) if length is not None else default_length + offset = int(offset) if offset is not None else 0 + xarr, idx = _arr(x) + yarr, _ = _arr(y) + n = len(xarr) + dst = _out(n) + _check(getattr(_lib, fn_name)(_ptr(xarr), _ptr(yarr), n, _ptr(dst), length)) + return _wrap(dst, idx, f"{label}_{length}", category, offset) + + +def _ohlcv_bars_period( + fn_name: str, open: object, high: object, low: object, + close: object, volume: object, period: int, + offset: int, default_period: int, label: str, category: str, +) -> object: + """OHLCV bars + period → single output (BuildBars pattern).""" + period = int(period) if period is not None else default_period + offset = int(offset) if offset is not None else 0 + o, idx = _arr(open) + h, _ = _arr(high) + l, _ = _arr(low) + c, _ = _arr(close) + v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(getattr(_lib, fn_name)( + _ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, n, _ptr(dst))) + return _wrap(dst, idx, f"{label}_{period}", category, offset) + + +def _hlc_period( + fn_name: str, high: object, low: object, close: object, + period: int, offset: int, default_period: int, + label: str, category: str, +) -> object: + """HLC + period → single output.""" + period = int(period) if period is not None else default_period + offset = int(offset) if offset is not None else 0 + h, idx = _arr(high) + l, _ = _arr(low) + c, _ = _arr(close) + n = len(h) + dst = _out(n) + _check(getattr(_lib, fn_name)( + _ptr(h), _ptr(l), _ptr(c), period, n, _ptr(dst))) + return _wrap(dst, idx, f"{label}_{period}", category, offset) + + +def _src_period( + fn_name: str, source: object, period: int, + offset: int, default_period: int, label: str, category: str, +) -> object: + """source + period → single output (BuildSeries pattern, src,period,n,dst).""" + period = int(period) if period is not None else default_period + offset = int(offset) if offset is not None else 0 + src, idx = _arr(source) + n = len(src) + dst = _out(n) + _check(getattr(_lib, fn_name)(_ptr(src), period, n, _ptr(dst))) + return _wrap(dst, idx, f"{label}_{period}", category, offset) diff --git a/python/quantalib/channels.py b/python/quantalib/channels.py new file mode 100644 index 00000000..71d5a1d8 --- /dev/null +++ b/python/quantalib/channels.py @@ -0,0 +1,352 @@ +"""quantalib channels indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "aberr", + "accbands", + "apchannel", + "apz", + "atrbands", + "bbands", + "dchannel", + "decaychannel", + "fcb", + "jbands", + "kchannel", + "maenv", + "mmchannel", + "pchannel", + "regchannel", + "sdchannel", + "starchannel", + "stbands", + "ttm_lrc", + "ubands", + "uchannel", + "vwapbands", + "vwapsd", +] + + +def aberr(close: object, period: int = 14, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """Aberration Bands.""" + period = int(period) + multiplier = float(multiplier) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_abber(_ptr(src), _ptr(middle), _ptr(upper), _ptr(lower), n, period, multiplier)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def accbands(high: object, low: object, close: object, period: int = 14, factor: float = 2.0, offset: int = 0, **kwargs) -> object: + """Acceleration Bands.""" + period = int(period) + factor = float(factor) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_accbands(_ptr(h), _ptr(l), _ptr(c), _ptr(middle), _ptr(upper), _ptr(lower), n, period, factor)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def apz(open: object, high: object, low: object, close: object, volume: object, period: int = 14, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """Adaptive Price Zone.""" + period = int(period) + multiplier = float(multiplier) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dstMiddle = _out(n) + dstUpper = _out(n) + dstLower = _out(n) + _check(_lib.qtl_apz(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, multiplier, n, _ptr(dstMiddle), _ptr(dstUpper), _ptr(dstLower))) + return _wrap_multi({"dstMiddle": dstMiddle, "dstUpper": dstUpper, "dstLower": dstLower}, idx, "channels", offset) + + +def dchannel(high: object, low: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Donchian Channel.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_dchannel(_ptr(h), _ptr(l), _ptr(middle), _ptr(upper), _ptr(lower), n, period)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def decaychannel(high: object, low: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Decay Channel.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_decaychannel(_ptr(h), _ptr(l), _ptr(middle), _ptr(upper), _ptr(lower), n, period)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def fcb(high: object, low: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Fractal Chaos Bands.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_fcb(_ptr(h), _ptr(l), _ptr(middle), _ptr(upper), _ptr(lower), n, period)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def jbands(close: object, period: int = 14, phase: int = 0, offset: int = 0, **kwargs) -> object: + """J-Line Bands.""" + period = int(period) + phase = int(phase) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_jbands(_ptr(src), _ptr(middle), _ptr(upper), _ptr(lower), n, period, phase)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def kchannel(high: object, low: object, close: object, period: int = 14, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """Keltner Channel.""" + period = int(period) + multiplier = float(multiplier) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_kchannel(_ptr(h), _ptr(l), _ptr(c), _ptr(middle), _ptr(upper), _ptr(lower), n, period, multiplier)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def maenv(close: object, period: int = 14, percentage: float = 2.5, maType: int = 0, offset: int = 0, **kwargs) -> object: + """Moving Average Envelope.""" + period = int(period) + percentage = float(percentage) + maType = int(maType) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_maenv(_ptr(src), _ptr(middle), _ptr(upper), _ptr(lower), n, period, percentage, maType)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def mmchannel(high: object, low: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Min-Max Channel.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_mmchannel(_ptr(h), _ptr(l), _ptr(upper), _ptr(lower), n, period)) + return _wrap_multi({"upper": upper, "lower": lower}, idx, "channels", offset) + + +def pchannel(high: object, low: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Price Channel.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_pchannel(_ptr(h), _ptr(l), _ptr(middle), _ptr(upper), _ptr(lower), n, period)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def regchannel(close: object, period: int = 14, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """Regression Channel.""" + period = int(period) + multiplier = float(multiplier) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_regchannel(_ptr(src), _ptr(middle), _ptr(upper), _ptr(lower), n, period, multiplier)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def sdchannel(close: object, period: int = 14, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """Standard Deviation Channel.""" + period = int(period) + multiplier = float(multiplier) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_sdchannel(_ptr(src), _ptr(middle), _ptr(upper), _ptr(lower), n, period, multiplier)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def starchannel(high: object, low: object, close: object, period: int = 14, multiplier: float = 2.0, atrPeriod: int = 22, offset: int = 0, **kwargs) -> object: + """Stoller Average Range Channel (STARC).""" + period = int(period) + multiplier = float(multiplier) + atrPeriod = int(atrPeriod) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + middle = _out(n) + upper = _out(n) + lower = _out(n) + _check(_lib.qtl_starchannel(_ptr(h), _ptr(l), _ptr(c), _ptr(middle), _ptr(upper), _ptr(lower), n, period, multiplier, atrPeriod)) + return _wrap_multi({"middle": middle, "upper": upper, "lower": lower}, idx, "channels", offset) + + +def stbands(high: object, low: object, close: object, period: int = 14, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """SuperTrend Bands.""" + period = int(period) + multiplier = float(multiplier) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + upper = _out(n) + lower = _out(n) + trend = _out(n) + _check(_lib.qtl_stbands(_ptr(h), _ptr(l), _ptr(c), _ptr(upper), _ptr(lower), _ptr(trend), n, period, multiplier)) + return _wrap_multi({"upper": upper, "lower": lower, "trend": trend}, idx, "channels", offset) + + +def ttm_lrc(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """TTM Linear Regression Channel.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + midline = _out(n) + upper1 = _out(n) + lower1 = _out(n) + upper2 = _out(n) + lower2 = _out(n) + _check(_lib.qtl_ttmlrc(_ptr(src), _ptr(midline), _ptr(upper1), _ptr(lower1), _ptr(upper2), _ptr(lower2), n, period)) + return _wrap_multi({"midline": midline, "upper1": upper1, "lower1": lower1, "upper2": upper2, "lower2": lower2}, idx, "channels", offset) + + +def ubands(close: object, period: int = 14, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """Upper/Lower Bands.""" + period = int(period) + multiplier = float(multiplier) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + upper = _out(n) + middle = _out(n) + lower = _out(n) + _check(_lib.qtl_ubands(_ptr(src), _ptr(upper), _ptr(middle), _ptr(lower), n, period, multiplier)) + return _wrap_multi({"upper": upper, "middle": middle, "lower": lower}, idx, "channels", offset) + + +def uchannel(high: object, low: object, close: object, strPeriod: int = 14, centerPeriod: int = 20, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """Ulcer Channel.""" + strPeriod = int(strPeriod) + centerPeriod = int(centerPeriod) + multiplier = float(multiplier) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + upper = _out(n) + middle = _out(n) + lower = _out(n) + _check(_lib.qtl_uchannel(_ptr(h), _ptr(l), _ptr(c), _ptr(upper), _ptr(middle), _ptr(lower), n, strPeriod, centerPeriod, multiplier)) + return _wrap_multi({"upper": upper, "middle": middle, "lower": lower}, idx, "channels", offset) + + +def vwapbands(price: object, volume: object, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """VWAP Bands.""" + multiplier = float(multiplier) + offset = int(offset) + pr, idx = _arr(price); v, _ = _arr(volume) + n = len(pr) + upper1 = _out(n) + lower1 = _out(n) + upper2 = _out(n) + lower2 = _out(n) + vwap = _out(n) + stdDev = _out(n) + _check(_lib.qtl_vwapbands(_ptr(pr), _ptr(v), _ptr(upper1), _ptr(lower1), _ptr(upper2), _ptr(lower2), _ptr(vwap), _ptr(stdDev), n, multiplier)) + return _wrap_multi({"upper1": upper1, "lower1": lower1, "upper2": upper2, "lower2": lower2, "vwap": vwap, "stdDev": stdDev}, idx, "channels", offset) + + +def vwapsd(price: object, volume: object, numDevs: float = 2.0, offset: int = 0, **kwargs) -> object: + """VWAP Standard Deviation.""" + numDevs = float(numDevs) + offset = int(offset) + pr, idx = _arr(price); v, _ = _arr(volume) + n = len(pr) + upper = _out(n) + lower = _out(n) + vwap = _out(n) + stdDev = _out(n) + _check(_lib.qtl_vwapsd(_ptr(pr), _ptr(v), _ptr(upper), _ptr(lower), _ptr(vwap), _ptr(stdDev), n, numDevs)) + return _wrap_multi({"upper": upper, "lower": lower, "vwap": vwap, "stdDev": stdDev}, idx, "channels", offset) + + +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); std = float(std); offset = int(offset) + 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 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); mult = float(mult); offset = int(offset) + 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); offset = int(offset) + 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), float(length))) + return _wrap_multi({f"APCU_{length}": upper, f"APCL_{length}": lower}, idx, "channels", offset) diff --git a/python/quantalib/core.py b/python/quantalib/core.py new file mode 100644 index 00000000..abebd065 --- /dev/null +++ b/python/quantalib/core.py @@ -0,0 +1,98 @@ +"""quantalib core indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "ha", + "midpoint", + "midprice", + "wclprice", + "avgprice", + "medprice", + "typprice", + "midbody", +] + + +def ha(open: object, high: object, low: object, close: object, offset: int = 0, **kwargs) -> object: + """Heikin-Ashi Candles.""" + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(o) + haOpenOut = _out(n) + haHighOut = _out(n) + haLowOut = _out(n) + haCloseOut = _out(n) + _check(_lib.qtl_ha(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(haOpenOut), _ptr(haHighOut), _ptr(haLowOut), _ptr(haCloseOut), n)) + return _wrap_multi({"haOpenOut": haOpenOut, "haHighOut": haHighOut, "haLowOut": haLowOut, "haCloseOut": haCloseOut}, idx, "core", offset) + + +def midpoint(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Midpoint = src[i] over period.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_midpoint(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"MIDPOINT_{period}", "core", offset) + + +def midprice(high: object, low: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Mid Price = (High+Low)/2 over period.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + output = _out(n) + _check(_lib.qtl_midprice(_ptr(h), _ptr(l), _ptr(output), n, period)) + return _wrap(output, idx, f"MIDPRICE_{period}", "core", offset) + + +def wclprice(high: object, low: object, close: object, offset: int = 0, **kwargs) -> object: + """Weighted Close Price = (H+L+2*C)/4.""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + output = _out(n) + _check(_lib.qtl_wclprice(_ptr(h), _ptr(l), _ptr(c), _ptr(output), n)) + return _wrap(output, idx, "WCLPRICE", "core", offset) + +def avgprice(open: object, high: object, low: object, close: object, + offset: int = 0, **kwargs) -> object: + """Average Price = (O+H+L+C)/4.""" + offset = int(offset) + 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", offset) + + +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)) + + +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)) + + +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)) diff --git a/python/quantalib/cycles.py b/python/quantalib/cycles.py new file mode 100644 index 00000000..c4f6be5a --- /dev/null +++ b/python/quantalib/cycles.py @@ -0,0 +1,152 @@ +"""quantalib cycles indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "homod", + "ht_dcperiod", + "ht_dcphase", + "ht_phasor", + "ht_sine", + "lunar", + "solar", + "ssfdsp", + "cg", + "dsp", + "ccor", + "ebsw", + "eacp", +] + + +def homod(close: object, minPeriod: float = 6, maxPeriod: float = 48, offset: int = 0, **kwargs) -> object: + """Homodyne Discriminator.""" + minPeriod = float(minPeriod) + maxPeriod = float(maxPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_homod(_ptr(src), _ptr(output), n, minPeriod, maxPeriod)) + return _wrap(output, idx, f"HOMOD_{minPeriod}", "cycles", offset) + + +def ht_dcperiod(close: object, offset: int = 0, **kwargs) -> object: + """Hilbert Transform Dominant Cycle Period.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_htdcperiod(_ptr(src), _ptr(output), n)) + return _wrap(output, idx, "HT_DCPERIOD", "cycles", offset) + + +def ht_dcphase(close: object, offset: int = 0, **kwargs) -> object: + """Hilbert Transform Dominant Cycle Phase.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_htdcphase(_ptr(src), _ptr(output), n)) + return _wrap(output, idx, "HT_DCPHASE", "cycles", offset) + + +def ht_phasor(close: object, offset: int = 0, **kwargs) -> object: + """Hilbert Transform Phasor.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + inPhase = _out(n) + quadrature = _out(n) + _check(_lib.qtl_htphasor(_ptr(src), _ptr(inPhase), _ptr(quadrature), n)) + return _wrap_multi({"inPhase": inPhase, "quadrature": quadrature}, idx, "cycles", offset) + + +def ht_sine(close: object, offset: int = 0, **kwargs) -> object: + """Hilbert Transform Sine.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + sine = _out(n) + leadSine = _out(n) + _check(_lib.qtl_htsine(_ptr(src), _ptr(sine), _ptr(leadSine), n)) + return _wrap_multi({"sine": sine, "leadSine": leadSine}, idx, "cycles", offset) + + +def lunar(close: object, offset: int = 0, **kwargs) -> object: + """Lunar Cycle.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + dst = _out(n) + _check(_lib.qtl_lunar(_ptr(src), n, _ptr(dst))) + return _wrap(dst, idx, "LUNAR", "cycles", offset) + + +def solar(close: object, offset: int = 0, **kwargs) -> object: + """Solar Cycle.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + dst = _out(n) + _check(_lib.qtl_solar(_ptr(src), n, _ptr(dst))) + return _wrap(dst, idx, "SOLAR", "cycles", offset) + + +def ssfdsp(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Supersmoother DSP.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_ssfdsp(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"SSFDSP_{period}", "cycles", offset) + +def cg(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Center of Gravity.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cg(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"CG_{length}", "cycles", offset) + + +def dsp(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Dominant Cycle Period (DSP).""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_dsp(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"DSP_{length}", "cycles", offset) + + +def ccor(close: object, length: int = 20, alpha: float = 0.07, + offset: int = 0, **kwargs) -> object: + """Circular Correlation.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_ccor(_ptr(src), n, _ptr(dst), length, float(alpha))) + return _wrap(dst, idx, f"CCOR_{length}", "cycles", offset) + + +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); ssf_length = int(ssf_length); offset = int(offset) + 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.""" + offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_eacp(_ptr(src), n, _ptr(dst), int(min_period), int(max_period), int(avg_length), int(enhance))) + return _wrap(dst, idx, f"EACP_{min_period}_{max_period}", "cycles", offset) diff --git a/python/quantalib/dynamics.py b/python/quantalib/dynamics.py new file mode 100644 index 00000000..27c448e7 --- /dev/null +++ b/python/quantalib/dynamics.py @@ -0,0 +1,295 @@ +"""quantalib dynamics indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "adx", + "adxr", + "alligator", + "amat", + "aroon", + "aroonosc", + "chop", + "dmx", + "dx", + "ghla", + "ht_trendmode", + "ichimoku", + "impulse", + "pfe", + "qstick", + "ravi", + "supertrend", + "ttm_squeeze", + "ttm_trend", + "vhf", + "vortex", +] + + +def adx(high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Average Directional Index.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + destination = _out(n) + _check(_lib.qtl_adx(_ptr(h), _ptr(l), _ptr(c), period, n, _ptr(destination))) + return _wrap(destination, idx, f"ADX_{period}", "dynamics", offset) + + +def adxr(high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """ADX Rating.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + destination = _out(n) + _check(_lib.qtl_adxr(_ptr(h), _ptr(l), _ptr(c), period, n, _ptr(destination))) + return _wrap(destination, idx, f"ADXR_{period}", "dynamics", offset) + + +def alligator(open: object, high: object, low: object, close: object, volume: object, jawPeriod: int = 13, jawOffset: int = 8, teethPeriod: int = 8, teethOffset: int = 5, lipsPeriod: int = 5, lipsOffset: int = 3, offset: int = 0, **kwargs) -> object: + """Williams Alligator.""" + jawPeriod = int(jawPeriod) + jawOffset = int(jawOffset) + teethPeriod = int(teethPeriod) + teethOffset = int(teethOffset) + lipsPeriod = int(lipsPeriod) + lipsOffset = int(lipsOffset) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_alligator(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), jawPeriod, jawOffset, teethPeriod, teethOffset, lipsPeriod, lipsOffset, n, _ptr(dst))) + return _wrap(dst, idx, f"ALLIGATOR_{jawPeriod}", "dynamics", offset) + + +def amat(close: object, fastPeriod: int = 12, slowPeriod: int = 26, offset: int = 0, **kwargs) -> object: + """Archer Moving Average Trends.""" + fastPeriod = int(fastPeriod) + slowPeriod = int(slowPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + trend = _out(n) + strength = _out(n) + _check(_lib.qtl_amat(_ptr(src), _ptr(trend), _ptr(strength), n, fastPeriod, slowPeriod)) + return _wrap_multi({"trend": trend, "strength": strength}, idx, "dynamics", offset) + + +def aroon(high: object, low: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Aroon.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + destination = _out(n) + _check(_lib.qtl_aroon(_ptr(h), _ptr(l), period, n, _ptr(destination))) + return _wrap(destination, idx, f"AROON_{period}", "dynamics", offset) + + +def aroonosc(high: object, low: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Aroon Oscillator.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + destination = _out(n) + _check(_lib.qtl_aroonosc(_ptr(h), _ptr(l), period, n, _ptr(destination))) + return _wrap(destination, idx, f"AROONOSC_{period}", "dynamics", offset) + + +def chop(open: object, high: object, low: object, close: object, volume: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Choppiness Index.""" + period = int(period) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_chop(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, n, _ptr(dst))) + return _wrap(dst, idx, f"CHOP_{period}", "dynamics", offset) + + +def dmx(high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Directional Movement Extended.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + destination = _out(n) + _check(_lib.qtl_dmx(_ptr(h), _ptr(l), _ptr(c), period, n, _ptr(destination))) + return _wrap(destination, idx, f"DMX_{period}", "dynamics", offset) + + +def dx(high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Directional Movement Index.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + destination = _out(n) + _check(_lib.qtl_dx(_ptr(h), _ptr(l), _ptr(c), period, n, _ptr(destination))) + return _wrap(destination, idx, f"DX_{period}", "dynamics", offset) + + +def ghla(high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Gann Hi-Lo Activator.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + output = _out(n) + _check(_lib.qtl_ghla(_ptr(h), _ptr(l), _ptr(c), _ptr(output), n, period)) + return _wrap(output, idx, f"GHLA_{period}", "dynamics", offset) + + +def ht_trendmode(close: object, offset: int = 0, **kwargs) -> object: + """Hilbert Transform Trend Mode.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_httrendmode(_ptr(src), _ptr(output), n)) + return _wrap(output, idx, "HT_TRENDMODE", "dynamics", offset) + + +def ichimoku(open: object, high: object, low: object, close: object, volume: object, tenkanPeriod: int = 9, kijunPeriod: int = 26, senkouBPeriod: int = 52, displacement: int = 26, offset: int = 0, **kwargs) -> object: + """Ichimoku Cloud.""" + tenkanPeriod = int(tenkanPeriod) + kijunPeriod = int(kijunPeriod) + senkouBPeriod = int(senkouBPeriod) + displacement = int(displacement) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dstTenkan = _out(n) + dstKijun = _out(n) + dstSenkouA = _out(n) + dstSenkouB = _out(n) + dstChikou = _out(n) + _check(_lib.qtl_ichimoku(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), tenkanPeriod, kijunPeriod, senkouBPeriod, displacement, n, _ptr(dstTenkan), _ptr(dstKijun), _ptr(dstSenkouA), _ptr(dstSenkouB), _ptr(dstChikou))) + return _wrap_multi({"dstTenkan": dstTenkan, "dstKijun": dstKijun, "dstSenkouA": dstSenkouA, "dstSenkouB": dstSenkouB, "dstChikou": dstChikou}, idx, "dynamics", offset) + + +def impulse(close: object, emaPeriod: int = 13, macdFast: int = 12, macdSlow: int = 26, macdSignal: int = 9, offset: int = 0, **kwargs) -> object: + """Elder Impulse System.""" + emaPeriod = int(emaPeriod) + macdFast = int(macdFast) + macdSlow = int(macdSlow) + macdSignal = int(macdSignal) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + dst = _out(n) + _check(_lib.qtl_impulse(_ptr(src), emaPeriod, macdFast, macdSlow, macdSignal, n, _ptr(dst))) + return _wrap(dst, idx, f"IMPULSE_{emaPeriod}", "dynamics", offset) + + +def pfe(close: object, period: int = 14, smoothPeriod: int = 5, offset: int = 0, **kwargs) -> object: + """Polarized Fractal Efficiency.""" + period = int(period) + smoothPeriod = int(smoothPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_pfe(_ptr(src), _ptr(output), n, period, smoothPeriod)) + return _wrap(output, idx, f"PFE_{period}", "dynamics", offset) + + +def qstick(open: object, high: object, low: object, close: object, volume: object, period: int = 14, useEma: int = 0, offset: int = 0, **kwargs) -> object: + """QStick.""" + period = int(period) + useEma = int(useEma) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_qstick(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, useEma, n, _ptr(dst))) + return _wrap(dst, idx, f"QSTICK_{period}", "dynamics", offset) + + +def ravi(close: object, shortPeriod: int = 12, longPeriod: int = 26, offset: int = 0, **kwargs) -> object: + """Range Action Verification Index.""" + shortPeriod = int(shortPeriod) + longPeriod = int(longPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_ravi(_ptr(src), _ptr(output), n, shortPeriod, longPeriod)) + return _wrap(output, idx, f"RAVI_{shortPeriod}", "dynamics", offset) + + +def supertrend(open: object, high: object, low: object, close: object, volume: object, period: int = 14, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """SuperTrend.""" + period = int(period) + multiplier = float(multiplier) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_super(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, multiplier, n, _ptr(dst))) + return _wrap(dst, idx, f"SUPER_{period}", "dynamics", offset) + + +def ttm_squeeze(open: object, high: object, low: object, close: object, volume: object, bbPeriod: int = 20, bbMult: float = 2.0, kcPeriod: int = 10, kcMult: float = 1.5, momPeriod: int = 12, offset: int = 0, **kwargs) -> object: + """TTM Squeeze.""" + bbPeriod = int(bbPeriod) + bbMult = float(bbMult) + kcPeriod = int(kcPeriod) + kcMult = float(kcMult) + momPeriod = int(momPeriod) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_ttmsqueeze(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), bbPeriod, bbMult, kcPeriod, kcMult, momPeriod, n, _ptr(dst))) + return _wrap(dst, idx, f"TTM_SQUEEZE_{bbPeriod}", "dynamics", offset) + + +def ttm_trend(open: object, high: object, low: object, close: object, volume: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """TTM Trend.""" + period = int(period) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_ttmtrend(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, n, _ptr(dst))) + return _wrap(dst, idx, f"TTM_TREND_{period}", "dynamics", offset) + + +def vhf(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Vertical Horizontal Filter.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_vhf(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"VHF_{period}", "dynamics", offset) + + +def vortex(high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Vortex Indicator.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + viPlus = _out(n) + viMinus = _out(n) + _check(_lib.qtl_vortex(_ptr(h), _ptr(l), _ptr(c), period, _ptr(viPlus), n, _ptr(viMinus))) + return _wrap_multi({"viPlus": viPlus, "viMinus": viMinus}, idx, "dynamics", offset) diff --git a/python/quantalib/errors.py b/python/quantalib/errors.py new file mode 100644 index 00000000..b9ecde7a --- /dev/null +++ b/python/quantalib/errors.py @@ -0,0 +1,322 @@ +"""quantalib errors indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "huber", + "logcosh", + "maape", + "mapd", + "mase", + "mdae", + "mdape", + "me", + "mpe", + "mrae", + "msle", + "pseudohuber", + "quantileloss", + "rae", + "rmsle", + "rse", + "rsquared", + "smape", + "theilu", + "tukeybiweight", + "wmape", + "wrmse", + "mse", + "rmse", + "mae", + "mape", +] + + +def huber(actual: object, predicted: object, period: int = 14, delta: float = 1.35, offset: int = 0, **kwargs) -> object: + """Huber Loss.""" + period = int(period) + delta = float(delta) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_huber(_ptr(a), _ptr(p), _ptr(output), n, period, delta)) + return _wrap(output, idx, f"HUBER_{period}", "errors", offset) + + +def logcosh(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Log-Cosh Loss.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_logcosh(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"LOGCOSH_{period}", "errors", offset) + + +def maape(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Mean Arctangent Absolute Percentage Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_maape(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"MAAPE_{period}", "errors", offset) + + +def mapd(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Mean Absolute Percentage Deviation.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_mapd(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"MAPD_{period}", "errors", offset) + + +def mase(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Mean Absolute Scaled Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_mase(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"MASE_{period}", "errors", offset) + + +def mdae(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Median Absolute Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_mdae(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"MDAE_{period}", "errors", offset) + + +def mdape(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Median Absolute Percentage Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_mdape(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"MDAPE_{period}", "errors", offset) + + +def me(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Mean Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_me(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"ME_{period}", "errors", offset) + + +def mpe(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Mean Percentage Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_mpe(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"MPE_{period}", "errors", offset) + + +def mrae(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Mean Relative Absolute Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_mrae(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"MRAE_{period}", "errors", offset) + + +def msle(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Mean Squared Logarithmic Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_msle(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"MSLE_{period}", "errors", offset) + + +def pseudohuber(actual: object, predicted: object, period: int = 14, delta: float = 1.35, offset: int = 0, **kwargs) -> object: + """Pseudo-Huber Loss.""" + period = int(period) + delta = float(delta) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_pseudohuber(_ptr(a), _ptr(p), _ptr(output), n, period, delta)) + return _wrap(output, idx, f"PSEUDOHUBER_{period}", "errors", offset) + + +def quantileloss(actual: object, predicted: object, period: int = 14, quantile: float = 0.5, offset: int = 0, **kwargs) -> object: + """Quantile Loss (Pinball Loss).""" + period = int(period) + quantile = float(quantile) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_quantileloss(_ptr(a), _ptr(p), _ptr(output), n, period, quantile)) + return _wrap(output, idx, f"QUANTILELOSS_{period}", "errors", offset) + + +def rae(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Relative Absolute Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_rae(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"RAE_{period}", "errors", offset) + + +def rmsle(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Root Mean Squared Logarithmic Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_rmsle(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"RMSLE_{period}", "errors", offset) + + +def rse(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Relative Squared Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_rse(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"RSE_{period}", "errors", offset) + + +def rsquared(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """R-Squared (Coefficient of Determination).""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_rsquared(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"RSQUARED_{period}", "errors", offset) + + +def smape(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Symmetric Mean Absolute Percentage Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_smape(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"SMAPE_{period}", "errors", offset) + + +def theilu(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Theil U Statistic (Error).""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_theilu(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"THEILU_{period}", "errors", offset) + + +def tukeybiweight(actual: object, predicted: object, period: int = 14, c: float = 4.685, offset: int = 0, **kwargs) -> object: + """Tukey Biweight Loss.""" + period = int(period) + c = float(c) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_tukeybiweight(_ptr(a), _ptr(p), _ptr(output), n, period, c)) + return _wrap(output, idx, f"TUKEYBIWEIGHT_{period}", "errors", offset) + + +def wmape(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Weighted Mean Absolute Percentage Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_wmape(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"WMAPE_{period}", "errors", offset) + + +def wrmse(actual: object, predicted: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Weighted Root Mean Squared Error.""" + period = int(period) + offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a) + output = _out(n) + _check(_lib.qtl_wrmse(_ptr(a), _ptr(p), _ptr(output), n, period)) + return _wrap(output, idx, f"WRMSE_{period}", "errors", offset) + +def mse(actual: object, predicted: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Mean Squared Error.""" + length = int(length); offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a); dst = _out(n) + _check(_lib.qtl_mse(_ptr(a), _ptr(p), n, _ptr(dst), length)) + return _wrap(dst, idx, f"MSE_{length}", "errors", offset) + + +def rmse(actual: object, predicted: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Root Mean Squared Error.""" + length = int(length); offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a); dst = _out(n) + _check(_lib.qtl_rmse(_ptr(a), _ptr(p), n, _ptr(dst), length)) + return _wrap(dst, idx, f"RMSE_{length}", "errors", offset) + + +def mae(actual: object, predicted: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Mean Absolute Error.""" + length = int(length); offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a); dst = _out(n) + _check(_lib.qtl_mae(_ptr(a), _ptr(p), n, _ptr(dst), length)) + return _wrap(dst, idx, f"MAE_{length}", "errors", offset) + + +def mape(actual: object, predicted: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Mean Absolute Percentage Error.""" + length = int(length); offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a); dst = _out(n) + _check(_lib.qtl_mape(_ptr(a), _ptr(p), n, _ptr(dst), length)) + return _wrap(dst, idx, f"MAPE_{length}", "errors", offset) diff --git a/python/quantalib/filters.py b/python/quantalib/filters.py new file mode 100644 index 00000000..9e8bd073 --- /dev/null +++ b/python/quantalib/filters.py @@ -0,0 +1,422 @@ +"""quantalib filters indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "gauss", + "hann", + "hp", + "hpf", + "kalman", + "laguerre", + "lms", + "loess", + "modf", + "notch", + "nw", + "oneeuro", + "rls", + "rmed", + "roofing", + "sgf", + "spbf", + "ssf2", + "ssf3", + "usf", + "voss", + "wavelet", + "wiener", + "bessel", + "butter2", + "butter3", + "cheby1", + "cheby2", + "elliptic", + "edcf", + "bpf", + "alaguerre", + "bilateral", + "baxterking", + "cfitz", +] + + +def gauss(close: object, sigma: float = 6.0, offset: int = 0, **kwargs) -> object: + """Gaussian Filter.""" + sigma = float(sigma) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_gauss(_ptr(src), _ptr(output), n, sigma)) + return _wrap(output, idx, "GAUSS", "filters", offset) + + +def hann(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Hann Filter.""" + length = int(length) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_hann(_ptr(src), _ptr(output), n, length)) + return _wrap(output, idx, f"HANN_{length}", "filters", offset) + + +def hp(close: object, lam: float = 1600.0, offset: int = 0, **kwargs) -> object: + """Hodrick-Prescott Filter.""" + lam = float(lam) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_hp(_ptr(src), _ptr(output), n, lam)) + return _wrap(output, idx, "HP", "filters", offset) + + +def hpf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """High-Pass Filter.""" + length = int(length) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_hpf(_ptr(src), _ptr(output), n, length)) + return _wrap(output, idx, f"HPF_{length}", "filters", offset) + + +def kalman(close: object, q: float = 0.3, r: float = 1.0, offset: int = 0, **kwargs) -> object: + """Kalman Filter.""" + q = float(q) + r = float(r) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_kalman(_ptr(src), _ptr(output), n, q, r)) + return _wrap(output, idx, "KALMAN", "filters", offset) + + +def laguerre(close: object, gamma: float = 0.7, offset: int = 0, **kwargs) -> object: + """Laguerre Filter.""" + gamma = float(gamma) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_laguerre(_ptr(src), _ptr(output), n, gamma)) + return _wrap(output, idx, "LAGUERRE", "filters", offset) + + +def lms(close: object, order: int = 3, mu: float = 0.01, offset: int = 0, **kwargs) -> object: + """Least Mean Squares Filter.""" + order = int(order) + mu = float(mu) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_lms(_ptr(src), _ptr(output), n, order, mu)) + return _wrap(output, idx, "LMS", "filters", offset) + + +def loess(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """LOESS Smoother.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_loess(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"LOESS_{period}", "filters", offset) + + +def modf(close: object, period: int = 14, beta: float = 2.0, feedback: int = 0, fbWeight: float = 0.5, offset: int = 0, **kwargs) -> object: + """Modified Filter.""" + period = int(period) + beta = float(beta) + feedback = int(feedback) + fbWeight = float(fbWeight) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_modf(_ptr(src), _ptr(output), n, period, beta, feedback, fbWeight)) + return _wrap(output, idx, f"MODF_{period}", "filters", offset) + + +def notch(close: object, period: int = 14, q: float = 0.3, offset: int = 0, **kwargs) -> object: + """Notch Filter.""" + period = int(period) + q = float(q) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_notch(_ptr(src), _ptr(output), n, period, q)) + return _wrap(output, idx, f"NOTCH_{period}", "filters", offset) + + +def nw(close: object, period: int = 14, bandwidth: float = 0.25, offset: int = 0, **kwargs) -> object: + """Nadaraya-Watson Filter.""" + period = int(period) + bandwidth = float(bandwidth) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_nw(_ptr(src), _ptr(output), n, period, bandwidth)) + return _wrap(output, idx, f"NW_{period}", "filters", offset) + + +def oneeuro(close: object, minCutoff: float = 1.0, beta: float = 2.0, dCutoff: float = 1.0, offset: int = 0, **kwargs) -> object: + """1€ Filter.""" + minCutoff = float(minCutoff) + beta = float(beta) + dCutoff = float(dCutoff) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_oneeuro(_ptr(src), _ptr(output), n, minCutoff, beta, dCutoff)) + return _wrap(output, idx, "ONEEURO", "filters", offset) + + +def rls(close: object, order: int = 3, lam: float = 1600.0, offset: int = 0, **kwargs) -> object: + """Recursive Least Squares Filter.""" + order = int(order) + lam = float(lam) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_rls(_ptr(src), _ptr(output), n, order, lam)) + return _wrap(output, idx, "RLS", "filters", offset) + + +def rmed(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Running Median Filter.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_rmed(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"RMED_{period}", "filters", offset) + + +def roofing(close: object, hpLength: int = 40, ssLength: int = 10, offset: int = 0, **kwargs) -> object: + """Roofing Filter.""" + hpLength = int(hpLength) + ssLength = int(ssLength) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_roofing(_ptr(src), _ptr(output), n, hpLength, ssLength)) + return _wrap(output, idx, f"ROOFING_{hpLength}", "filters", offset) + + +def sgf(close: object, period: int = 14, polyOrder: int = 3, offset: int = 0, **kwargs) -> object: + """Savitzky-Golay Filter.""" + period = int(period) + polyOrder = int(polyOrder) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_sgf(_ptr(src), _ptr(output), n, period, polyOrder)) + return _wrap(output, idx, f"SGF_{period}", "filters", offset) + + +def spbf(close: object, shortPeriod: int = 12, longPeriod: int = 26, rmsPeriod: int = 20, offset: int = 0, **kwargs) -> object: + """Short-Period Bandpass Filter.""" + shortPeriod = int(shortPeriod) + longPeriod = int(longPeriod) + rmsPeriod = int(rmsPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_spbf(_ptr(src), _ptr(output), n, shortPeriod, longPeriod, rmsPeriod)) + return _wrap(output, idx, f"SPBF_{shortPeriod}", "filters", offset) + + +def ssf2(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Super Smoother (2-pole).""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_ssf2(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"SSF2_{period}", "filters", offset) + + +def ssf3(close: object, period: int = 14, initialLast: float = 0.0, offset: int = 0, **kwargs) -> object: + """Super Smoother (3-pole).""" + period = int(period) + initialLast = float(initialLast) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + destination = _out(n) + _check(_lib.qtl_ssf3(_ptr(src), _ptr(destination), n, period, initialLast)) + return _wrap(destination, idx, f"SSF3_{period}", "filters", offset) + + +def usf(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Universal Smoother Filter.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_usf(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"USF_{period}", "filters", offset) + + +def voss(close: object, period: int = 14, predict: int = 3, bandwidth: float = 0.25, offset: int = 0, **kwargs) -> object: + """Voss Predictor.""" + period = int(period) + predict = int(predict) + bandwidth = float(bandwidth) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_voss(_ptr(src), _ptr(output), n, period, predict, bandwidth)) + return _wrap(output, idx, f"VOSS_{period}", "filters", offset) + + +def wavelet(close: object, levels: int = 4, threshMult: float = 1.0, offset: int = 0, **kwargs) -> object: + """Wavelet Filter.""" + levels = int(levels) + threshMult = float(threshMult) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_wavelet(_ptr(src), _ptr(output), n, levels, threshMult)) + return _wrap(output, idx, "WAVELET", "filters", offset) + + +def wiener(close: object, period: int = 14, smoothPeriod: int = 5, offset: int = 0, **kwargs) -> object: + """Wiener Filter.""" + period = int(period) + smoothPeriod = int(smoothPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + destination = _out(n) + _check(_lib.qtl_wiener(_ptr(src), _ptr(destination), n, period, smoothPeriod)) + return _wrap(destination, idx, f"WIENER_{period}", "filters", offset) + +def bessel(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Bessel Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_bessel(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"BESSEL_{length}", "filters", offset) + + +def butter2(close: object, length: int = 14, gain: float = 1.0, + offset: int = 0, **kwargs) -> object: + """2nd-order Butterworth.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_butter2(_ptr(src), n, _ptr(dst), length, float(gain))) + return _wrap(dst, idx, f"BUTTER2_{length}", "filters", offset) + + +def butter3(close: object, length: int = 14, gain: float = 1.0, + offset: int = 0, **kwargs) -> object: + """3rd-order Butterworth.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_butter3(_ptr(src), n, _ptr(dst), length, float(gain))) + return _wrap(dst, idx, f"BUTTER3_{length}", "filters", offset) + + +def cheby1(close: object, length: int = 14, ripple: float = 0.5, + offset: int = 0, **kwargs) -> object: + """Chebyshev Type I.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cheby1(_ptr(src), n, _ptr(dst), length, float(ripple))) + return _wrap(dst, idx, f"CHEBY1_{length}", "filters", offset) + + +def cheby2(close: object, length: int = 14, ripple: float = 0.5, + offset: int = 0, **kwargs) -> object: + """Chebyshev Type II.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cheby2(_ptr(src), n, _ptr(dst), length, float(ripple))) + return _wrap(dst, idx, f"CHEBY2_{length}", "filters", offset) + + +def elliptic(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Elliptic (Cauer) Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_elliptic(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"ELLIPTIC_{length}", "filters", offset) + + +def edcf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Ehlers Distance Coefficient Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_edcf(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"EDCF_{length}", "filters", offset) + + +def bpf(close: object, length: int = 14, bandwidth: int = 5, + offset: int = 0, **kwargs) -> object: + """Bandpass Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_bpf(_ptr(src), n, _ptr(dst), length, int(bandwidth))) + return _wrap(dst, idx, f"BPF_{length}", "filters", offset) + + +def alaguerre(close: object, length: int = 20, order: int = 5, + offset: int = 0, **kwargs) -> object: + """Adaptive Laguerre Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_alaguerre(_ptr(src), n, _ptr(dst), length, int(order))) + return _wrap(dst, idx, f"ALAGUERRE_{length}", "filters", 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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_bilateral(_ptr(src), n, _ptr(dst), length, float(sigma_s), float(sigma_r))) + return _wrap(dst, idx, f"BILATERAL_{length}", "filters", 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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_baxterking(_ptr(src), n, _ptr(dst), length, int(min_period), int(max_period))) + return _wrap(dst, idx, f"BAXTERKING_{length}", "filters", offset) + + +def cfitz(close: object, length: int = 6, bw_period: int = 32, + offset: int = 0, **kwargs) -> object: + """Christiano-Fitzgerald Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cfitz(_ptr(src), n, _ptr(dst), length, int(bw_period))) + return _wrap(dst, idx, f"CFITZ_{length}", "filters", offset) diff --git a/python/quantalib/indicators.py b/python/quantalib/indicators.py index e3ac4361..0e0aeace 100644 --- a/python/quantalib/indicators.py +++ b/python/quantalib/indicators.py @@ -1,1196 +1,40 @@ """High-level indicator wrappers for quantalib. +This module re-exports all indicator functions from per-category submodules. 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. +returns the same type. -Signature conventions follow pandas-ta where practical: - sma(close, length=10, offset=0, **kwargs) +Category submodules: + quantalib.channels — Bollinger Bands, Keltner, Donchian, etc. + quantalib.core — Price transforms (avgprice, medprice, etc.) + quantalib.cycles — Hilbert, Sinewave, CG, DSP, etc. + quantalib.dynamics — ADX, Ichimoku, Supertrend, etc. + quantalib.errors — MSE, RMSE, MAE, MAPE, Huber, etc. + quantalib.filters — Butterworth, Chebyshev, Kalman, etc. + quantalib.momentum — RSI, MACD, ROC, MOM, etc. + quantalib.numerics — FFT, sigmoid, slope, distributions, etc. + quantalib.oscillators — Stochastic, Fisher, Williams %R, etc. + quantalib.reversals — Pivot points, PSAR, fractals, etc. + quantalib.statistics — Z-score, correlation, linreg, etc. + quantalib.trends_fir — SMA, WMA, HMA, ALMA, etc. + quantalib.trends_iir — EMA, DEMA, TEMA, JMA, KAMA, etc. + quantalib.volatility — ATR, TR, Bollinger Width, etc. + quantalib.volume — OBV, VWAP, MFI, CMF, etc. """ 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) +from .channels import * # noqa: F401, F403 +from .core import * # noqa: F401, F403 +from .cycles import * # noqa: F401, F403 +from .dynamics import * # noqa: F401, F403 +from .errors import * # noqa: F401, F403 +from .filters import * # noqa: F401, F403 +from .momentum import * # noqa: F401, F403 +from .numerics import * # noqa: F401, F403 +from .oscillators import * # noqa: F401, F403 +from .reversals import * # noqa: F401, F403 +from .statistics import * # noqa: F401, F403 +from .trends_fir import * # noqa: F401, F403 +from .trends_iir import * # noqa: F401, F403 +from .volatility import * # noqa: F401, F403 +from .volume import * # noqa: F401, F403 diff --git a/python/quantalib/momentum.py b/python/quantalib/momentum.py new file mode 100644 index 00000000..966cdb50 --- /dev/null +++ b/python/quantalib/momentum.py @@ -0,0 +1,233 @@ +"""quantalib momentum indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "bop", + "cci", + "macd", + "pmo", + "ppo", + "prs", + "rocp", + "rocr", + "sam", + "vel", + "rsi", + "roc", + "mom", + "cmo", + "tsi", + "apo", + "bias", + "cfo", + "cfb", + "asi", +] + + +def bop(open: object, high: object, low: object, close: object, offset: int = 0, **kwargs) -> object: + """Balance of Power.""" + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(o) + destination = _out(n) + _check(_lib.qtl_bop(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(destination), n)) + return _wrap(destination, idx, "BOP", "momentum", offset) + + +def cci(open: object, high: object, low: object, close: object, volume: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Commodity Channel Index.""" + period = int(period) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_cci(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, n, _ptr(dst))) + return _wrap(dst, idx, f"CCI_{period}", "momentum", offset) + + +def macd(close: object, fastPeriod: int = 12, slowPeriod: int = 26, offset: int = 0, **kwargs) -> object: + """Moving Average Convergence Divergence.""" + fastPeriod = int(fastPeriod) + slowPeriod = int(slowPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + destination = _out(n) + _check(_lib.qtl_macd(_ptr(src), _ptr(destination), n, fastPeriod, slowPeriod)) + return _wrap(destination, idx, f"MACD_{fastPeriod}", "momentum", offset) + + +def pmo(close: object, timePeriods: int = 14, smoothPeriods: int = 14, signalPeriods: int = 14, offset: int = 0, **kwargs) -> object: + """Price Momentum Oscillator.""" + timePeriods = int(timePeriods) + smoothPeriods = int(smoothPeriods) + signalPeriods = int(signalPeriods) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_pmo(_ptr(src), _ptr(output), n, timePeriods, smoothPeriods, signalPeriods)) + return _wrap(output, idx, f"PMO_{timePeriods}", "momentum", offset) + + +def ppo(close: object, fastPeriod: int = 12, slowPeriod: int = 26, offset: int = 0, **kwargs) -> object: + """Percentage Price Oscillator.""" + fastPeriod = int(fastPeriod) + slowPeriod = int(slowPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + destination = _out(n) + _check(_lib.qtl_ppo(_ptr(src), _ptr(destination), n, fastPeriod, slowPeriod)) + return _wrap(destination, idx, f"PPO_{fastPeriod}", "momentum", offset) + + +def prs(x: object, y: object, smoothPeriod: int = 5, offset: int = 0, **kwargs) -> object: + """Price Relative Strength.""" + smoothPeriod = int(smoothPeriod) + offset = int(offset) + xarr, idx = _arr(x); yarr, _ = _arr(y) + n = len(xarr) + output = _out(n) + _check(_lib.qtl_prs(_ptr(xarr), _ptr(yarr), _ptr(output), n, smoothPeriod)) + return _wrap(output, idx, f"PRS_{smoothPeriod}", "momentum", offset) + + +def rocp(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Rate of Change (Percentage).""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_rocp(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"ROCP_{period}", "momentum", offset) + + +def rocr(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Rate of Change (Ratio).""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_rocr(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"ROCR_{period}", "momentum", offset) + + +def sam(close: object, alpha: float = 2.0, cutoff: int = 10, offset: int = 0, **kwargs) -> object: + """Simple Alpha Momentum.""" + alpha = float(alpha) + cutoff = int(cutoff) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_sam(_ptr(src), _ptr(output), n, alpha, cutoff)) + return _wrap(output, idx, "SAM", "momentum", offset) + + +def vel(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Velocity.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_vel(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"VEL_{period}", "momentum", offset) + +def rsi(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Relative Strength Index.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_rsi(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"RSI_{length}", "momentum", offset) + + +def roc(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Rate of Change.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_roc(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"ROC_{length}", "momentum", offset) + + +def mom(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Momentum.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_mom(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"MOM_{length}", "momentum", offset) + + +def cmo(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Chande Momentum Oscillator.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cmo(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"CMO_{length}", "momentum", offset) + + +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); short_period = int(short_period); offset = int(offset) + 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); slow = int(slow); offset = int(offset) + 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.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_bias(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"BIAS_{length}", "momentum", offset) + + +def cfo(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Chande Forecast Oscillator.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cfo(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"CFO_{length}", "momentum", offset) + + +def cfb(close: object, lengths: list | None = None, + offset: int = 0, **kwargs) -> object: + """Composite Fractal Behavior.""" + offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + 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)) diff --git a/python/quantalib/numerics.py b/python/quantalib/numerics.py new file mode 100644 index 00000000..93e3a0fc --- /dev/null +++ b/python/quantalib/numerics.py @@ -0,0 +1,330 @@ +"""quantalib numerics indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "accel", + "fdist", + "fft", + "gammadist", + "highest", + "ifft", + "jerk", + "lineartrans", + "lognormdist", + "logtrans", + "lowest", + "normalize", + "normdist", + "poissondist", + "relu", + "sigmoid", + "slope", + "sqrttrans", + "tdist", + "weibulldist", + "change", + "exptrans", + "betadist", + "expdist", + "binomdist", + "cwt", + "dwt", +] + + +def accel(close: object, offset: int = 0, **kwargs) -> object: + """Acceleration.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_accel(_ptr(src), _ptr(output), n)) + return _wrap(output, idx, "ACCEL", "numerics", offset) + + +def fdist(close: object, d1: int = 10, d2: int = 20, period: int = 14, offset: int = 0, **kwargs) -> object: + """F-Distribution.""" + d1 = int(d1) + d2 = int(d2) + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_fdist(_ptr(src), _ptr(output), n, d1, d2, period)) + return _wrap(output, idx, f"FDIST_{period}", "numerics", offset) + + +def fft(close: object, windowSize: int = 256, minPeriod: int = 6, maxPeriod: int = 48, offset: int = 0, **kwargs) -> object: + """Fast Fourier Transform.""" + windowSize = int(windowSize) + minPeriod = int(minPeriod) + maxPeriod = int(maxPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_fft(_ptr(src), _ptr(output), n, windowSize, minPeriod, maxPeriod)) + return _wrap(output, idx, f"FFT_{minPeriod}", "numerics", offset) + + +def gammadist(close: object, alpha: float = 2.0, beta: float = 2.0, period: int = 14, offset: int = 0, **kwargs) -> object: + """Gamma Distribution.""" + alpha = float(alpha) + beta = float(beta) + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_gammadist(_ptr(src), _ptr(output), n, alpha, beta, period)) + return _wrap(output, idx, f"GAMMADIST_{period}", "numerics", offset) + + +def highest(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Highest Value.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_highest(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"HIGHEST_{period}", "numerics", offset) + + +def ifft(close: object, windowSize: int = 256, numHarmonics: int = 10, offset: int = 0, **kwargs) -> object: + """Inverse FFT.""" + windowSize = int(windowSize) + numHarmonics = int(numHarmonics) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_ifft(_ptr(src), _ptr(output), n, windowSize, numHarmonics)) + return _wrap(output, idx, "IFFT", "numerics", offset) + + +def jerk(close: object, offset: int = 0, **kwargs) -> object: + """Jerk (3rd derivative).""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_jerk(_ptr(src), _ptr(output), n)) + return _wrap(output, idx, "JERK", "numerics", offset) + + +def lineartrans(close: object, slope: float = 1.0, intercept: float = 0.0, offset: int = 0, **kwargs) -> object: + """Linear Transform.""" + slope = float(slope) + intercept = float(intercept) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_lineartrans(_ptr(src), _ptr(output), n, slope, intercept)) + return _wrap(output, idx, "LINEARTRANS", "numerics", offset) + + +def lognormdist(close: object, mu: float = 0.01, sigma: float = 6.0, period: int = 14, offset: int = 0, **kwargs) -> object: + """Log-Normal Distribution.""" + mu = float(mu) + sigma = float(sigma) + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_lognormdist(_ptr(src), _ptr(output), n, mu, sigma, period)) + return _wrap(output, idx, f"LOGNORMDIST_{period}", "numerics", offset) + + +def logtrans(close: object, offset: int = 0, **kwargs) -> object: + """Logarithmic Transform.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_logtrans(_ptr(src), _ptr(output), n)) + return _wrap(output, idx, "LOGTRANS", "numerics", offset) + + +def lowest(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Lowest Value.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_lowest(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"LOWEST_{period}", "numerics", offset) + + +def normalize(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Normalization.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_normalize(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"NORMALIZE_{period}", "numerics", offset) + + +def normdist(close: object, mu: float = 0.01, sigma: float = 6.0, period: int = 14, offset: int = 0, **kwargs) -> object: + """Normal Distribution.""" + mu = float(mu) + sigma = float(sigma) + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_normdist(_ptr(src), _ptr(output), n, mu, sigma, period)) + return _wrap(output, idx, f"NORMDIST_{period}", "numerics", offset) + + +def poissondist(close: object, lam: float = 1600.0, period: int = 14, threshold: int = 10, offset: int = 0, **kwargs) -> object: + """Poisson Distribution.""" + lam = float(lam) + period = int(period) + threshold = int(threshold) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_poissondist(_ptr(src), _ptr(output), n, lam, period, threshold)) + return _wrap(output, idx, f"POISSONDIST_{period}", "numerics", offset) + + +def relu(close: object, offset: int = 0, **kwargs) -> object: + """ReLU Activation.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_relu(_ptr(src), _ptr(output), n)) + return _wrap(output, idx, "RELU", "numerics", offset) + + +def sigmoid(close: object, k: float = 2.0, x0: float = 0.0, offset: int = 0, **kwargs) -> object: + """Sigmoid Transform.""" + k = float(k) + x0 = float(x0) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_sigmoid(_ptr(src), _ptr(output), n, k, x0)) + return _wrap(output, idx, "SIGMOID", "numerics", offset) + + +def slope(close: object, offset: int = 0, **kwargs) -> object: + """Slope (1st derivative).""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_slope(_ptr(src), _ptr(output), n)) + return _wrap(output, idx, "SLOPE", "numerics", offset) + + +def sqrttrans(close: object, offset: int = 0, **kwargs) -> object: + """Square Root Transform.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_sqrttrans(_ptr(src), _ptr(output), n)) + return _wrap(output, idx, "SQRTTRANS", "numerics", offset) + + +def tdist(close: object, nu: int = 10, period: int = 14, offset: int = 0, **kwargs) -> object: + """Student's t-Distribution.""" + nu = int(nu) + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_tdist(_ptr(src), _ptr(output), n, nu, period)) + return _wrap(output, idx, f"TDIST_{period}", "numerics", offset) + + +def weibulldist(close: object, k: float = 2.0, lam: float = 1600.0, period: int = 14, offset: int = 0, **kwargs) -> object: + """Weibull Distribution.""" + k = float(k) + lam = float(lam) + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_weibulldist(_ptr(src), _ptr(output), n, k, lam, period)) + return _wrap(output, idx, f"WEIBULLDIST_{period}", "numerics", offset) + +def change(close: object, length: int = 1, offset: int = 0, **kwargs) -> object: + """Price Change.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_change(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"CHANGE_{length}", "numerics", offset) + + +def exptrans(close: object, offset: int = 0, **kwargs) -> object: + """Exponential Transform.""" + offset = int(offset) + 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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_betadist(_ptr(src), n, _ptr(dst), length, float(alpha), float(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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_expdist(_ptr(src), n, _ptr(dst), length, float(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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_binomdist(_ptr(src), n, _ptr(dst), length, int(trials), int(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.""" + offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cwt(_ptr(src), n, _ptr(dst), float(scale), float(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.""" + length = int(length); levels = int(levels); offset = int(offset) + 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) diff --git a/python/quantalib/oscillators.py b/python/quantalib/oscillators.py new file mode 100644 index 00000000..ab44605c --- /dev/null +++ b/python/quantalib/oscillators.py @@ -0,0 +1,553 @@ +"""quantalib oscillators indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "ac", + "ao", + "bbs", + "coppock", + "eri", + "fi", + "gator", + "imi", + "kdj", + "kst", + "marketfi", + "mstoch", + "pgo", + "qqe", + "reverseema", + "rvgi", + "smi", + "squeeze", + "stc", + "stoch", + "stochf", + "stochrsi", + "ttm_wave", + "ultosc", + "willr", + "fisher", + "fisher04", + "dpo", + "trix", + "inertia", + "rsx", + "er", + "cti", + "reflex", + "trendflex", + "kri", + "psl", + "deco", + "dosc", + "dymoi", + "crsi", + "bbb", + "bbi", + "dem", + "brar", +] + + +def ac(high: object, low: object, fastPeriod: int = 12, slowPeriod: int = 26, acPeriod: int = 5, offset: int = 0, **kwargs) -> object: + """Accelerator Oscillator.""" + fastPeriod = int(fastPeriod) + slowPeriod = int(slowPeriod) + acPeriod = int(acPeriod) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + destination = _out(n) + _check(_lib.qtl_ac(_ptr(h), _ptr(l), _ptr(destination), n, fastPeriod, slowPeriod, acPeriod)) + return _wrap(destination, idx, f"AC_{fastPeriod}", "oscillators", offset) + + +def ao(high: object, low: object, fastPeriod: int = 12, slowPeriod: int = 26, offset: int = 0, **kwargs) -> object: + """Awesome Oscillator.""" + fastPeriod = int(fastPeriod) + slowPeriod = int(slowPeriod) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + destination = _out(n) + _check(_lib.qtl_ao(_ptr(h), _ptr(l), _ptr(destination), n, fastPeriod, slowPeriod)) + return _wrap(destination, idx, f"AO_{fastPeriod}", "oscillators", offset) + + +def bbs(high: object, low: object, close: object, bbPeriod: int = 20, bbMult: float = 2.0, offset: int = 0, **kwargs) -> object: + """Bollinger Band Squeeze.""" + bbPeriod = int(bbPeriod) + bbMult = float(bbMult) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + output = _out(n) + _check(_lib.qtl_bbs(_ptr(h), _ptr(l), _ptr(c), _ptr(output), n, bbPeriod, bbMult)) + return _wrap(output, idx, f"BBS_{bbPeriod}", "oscillators", offset) + + +def coppock(close: object, longRoc: int = 14, shortRoc: int = 11, wmaPeriod: int = 10, offset: int = 0, **kwargs) -> object: + """Coppock Curve.""" + longRoc = int(longRoc) + shortRoc = int(shortRoc) + wmaPeriod = int(wmaPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_coppock(_ptr(src), _ptr(output), n, longRoc, shortRoc, wmaPeriod)) + return _wrap(output, idx, f"COPPOCK_{wmaPeriod}", "oscillators", offset) + + +def eri(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Elder Ray Index.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + dst = _out(n) + _check(_lib.qtl_eri(_ptr(src), period, n, _ptr(dst))) + return _wrap(dst, idx, f"ERI_{period}", "oscillators", offset) + + +def fi(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Force Index.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + dst = _out(n) + _check(_lib.qtl_fi(_ptr(src), period, n, _ptr(dst))) + return _wrap(dst, idx, f"FI_{period}", "oscillators", offset) + + +def gator(close: object, jawPeriod: int = 13, jawShift: int = 8, teethPeriod: int = 8, teethShift: int = 5, lipsPeriod: int = 5, lipsShift: int = 3, offset: int = 0, **kwargs) -> object: + """Gator Oscillator.""" + jawPeriod = int(jawPeriod) + jawShift = int(jawShift) + teethPeriod = int(teethPeriod) + teethShift = int(teethShift) + lipsPeriod = int(lipsPeriod) + lipsShift = int(lipsShift) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_gator(_ptr(src), _ptr(output), n, jawPeriod, jawShift, teethPeriod, teethShift, lipsPeriod, lipsShift)) + return _wrap(output, idx, f"GATOR_{jawPeriod}", "oscillators", offset) + + +def imi(open: object, high: object, low: object, close: object, volume: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Intraday Momentum Index.""" + period = int(period) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_imi(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, n, _ptr(dst))) + return _wrap(dst, idx, f"IMI_{period}", "oscillators", offset) + + +def kdj(high: object, low: object, close: object, length: int = 14, signal: int = 3, offset: int = 0, **kwargs) -> object: + """KDJ Indicator.""" + length = int(length) + signal = int(signal) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + kOut = _out(n) + dOut = _out(n) + jOut = _out(n) + _check(_lib.qtl_kdj(_ptr(h), _ptr(l), _ptr(c), _ptr(kOut), _ptr(dOut), _ptr(jOut), n, length, signal)) + return _wrap_multi({"kOut": kOut, "dOut": dOut, "jOut": jOut}, idx, "oscillators", offset) + + +def kst(close: object, r1: int = 10, r2: int = 15, r3: int = 20, r4: int = 30, s1: int = 10, s2: int = 10, s3: int = 10, s4: int = 15, sigPeriod: int = 9, offset: int = 0, **kwargs) -> object: + """Know Sure Thing.""" + r1 = int(r1) + r2 = int(r2) + r3 = int(r3) + r4 = int(r4) + s1 = int(s1) + s2 = int(s2) + s3 = int(s3) + s4 = int(s4) + sigPeriod = int(sigPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + kstOut = _out(n) + sigOut = _out(n) + _check(_lib.qtl_kst(_ptr(src), _ptr(kstOut), _ptr(sigOut), n, r1, r2, r3, r4, s1, s2, s3, s4, sigPeriod)) + return _wrap_multi({"kstOut": kstOut, "sigOut": sigOut}, idx, "oscillators", offset) + + +def marketfi(high: object, low: object, volume: object, offset: int = 0, **kwargs) -> object: + """Market Facilitation Index.""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); v, _ = _arr(volume) + n = len(h) + output = _out(n) + _check(_lib.qtl_marketfi(_ptr(h), _ptr(l), _ptr(v), _ptr(output), n)) + return _wrap(output, idx, "MARKETFI", "oscillators", offset) + + +def mstoch(close: object, stochLength: int = 14, hpLength: int = 40, ssLength: int = 10, offset: int = 0, **kwargs) -> object: + """Modified Stochastic.""" + stochLength = int(stochLength) + hpLength = int(hpLength) + ssLength = int(ssLength) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_mstoch(_ptr(src), _ptr(output), n, stochLength, hpLength, ssLength)) + return _wrap(output, idx, f"MSTOCH_{stochLength}", "oscillators", offset) + + +def pgo(high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Pretty Good Oscillator.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + destination = _out(n) + _check(_lib.qtl_pgo(_ptr(h), _ptr(l), _ptr(c), _ptr(destination), n, period)) + return _wrap(destination, idx, f"PGO_{period}", "oscillators", offset) + + +def qqe(close: object, rsiPeriod: int = 14, smoothFactor: int = 5, qqeFactor: float = 4.236, offset: int = 0, **kwargs) -> object: + """Quantitative Qualitative Estimation.""" + rsiPeriod = int(rsiPeriod) + smoothFactor = int(smoothFactor) + qqeFactor = float(qqeFactor) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_qqe(_ptr(src), _ptr(output), n, rsiPeriod, smoothFactor, qqeFactor)) + return _wrap(output, idx, f"QQE_{rsiPeriod}", "oscillators", offset) + + +def reverseema(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Reverse EMA.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_reverseema(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"REVERSEEMA_{period}", "oscillators", offset) + + +def rvgi(open: object, high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Relative Vigor Index.""" + period = int(period) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(o) + rvgiOutput = _out(n) + signalOutput = _out(n) + _check(_lib.qtl_rvgi(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(rvgiOutput), _ptr(signalOutput), n, period)) + return _wrap_multi({"rvgiOutput": rvgiOutput, "signalOutput": signalOutput}, idx, "oscillators", offset) + + +def smi(high: object, low: object, close: object, kPeriod: int = 14, kSmooth: int = 3, dSmooth: int = 3, blau: int = 3, offset: int = 0, **kwargs) -> object: + """Stochastic Momentum Index.""" + kPeriod = int(kPeriod) + kSmooth = int(kSmooth) + dSmooth = int(dSmooth) + blau = int(blau) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + kOut = _out(n) + dOut = _out(n) + _check(_lib.qtl_smi(_ptr(h), _ptr(l), _ptr(c), _ptr(kOut), _ptr(dOut), n, kPeriod, kSmooth, dSmooth, blau)) + return _wrap_multi({"kOut": kOut, "dOut": dOut}, idx, "oscillators", offset) + + +def squeeze(high: object, low: object, close: object, period: int = 14, bbMult: float = 2.0, kcMult: float = 1.5, offset: int = 0, **kwargs) -> object: + """Squeeze Momentum.""" + period = int(period) + bbMult = float(bbMult) + kcMult = float(kcMult) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + momOut = _out(n) + sqOut = _out(n) + _check(_lib.qtl_squeeze(_ptr(h), _ptr(l), _ptr(c), _ptr(momOut), _ptr(sqOut), n, period, bbMult, kcMult)) + return _wrap_multi({"momOut": momOut, "sqOut": sqOut}, idx, "oscillators", offset) + + +def stc(close: object, kPeriod: int = 14, dPeriod: int = 3, fastLength: int = 23, slowLength: int = 50, smoothing: int = 10, offset: int = 0, **kwargs) -> object: + """Schaff Trend Cycle.""" + kPeriod = int(kPeriod) + dPeriod = int(dPeriod) + fastLength = int(fastLength) + slowLength = int(slowLength) + smoothing = int(smoothing) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_stc(_ptr(src), _ptr(output), n, kPeriod, dPeriod, fastLength, slowLength, smoothing)) + return _wrap(output, idx, f"STC_{kPeriod}", "oscillators", offset) + + +def stoch(high: object, low: object, close: object, kLength: int = 14, dPeriod: int = 3, offset: int = 0, **kwargs) -> object: + """Stochastic Oscillator.""" + kLength = int(kLength) + dPeriod = int(dPeriod) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + kOut = _out(n) + dOut = _out(n) + _check(_lib.qtl_stoch(_ptr(h), _ptr(l), _ptr(c), _ptr(kOut), _ptr(dOut), n, kLength, dPeriod)) + return _wrap_multi({"kOut": kOut, "dOut": dOut}, idx, "oscillators", offset) + + +def stochf(high: object, low: object, close: object, kLength: int = 14, dPeriod: int = 3, offset: int = 0, **kwargs) -> object: + """Fast Stochastic.""" + kLength = int(kLength) + dPeriod = int(dPeriod) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + kOut = _out(n) + dOut = _out(n) + _check(_lib.qtl_stochf(_ptr(h), _ptr(l), _ptr(c), _ptr(kOut), _ptr(dOut), n, kLength, dPeriod)) + return _wrap_multi({"kOut": kOut, "dOut": dOut}, idx, "oscillators", offset) + + +def stochrsi(close: object, rsiLength: int = 14, stochLength: int = 14, kSmooth: int = 3, dSmooth: int = 3, offset: int = 0, **kwargs) -> object: + """Stochastic RSI.""" + rsiLength = int(rsiLength) + stochLength = int(stochLength) + kSmooth = int(kSmooth) + dSmooth = int(dSmooth) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_stochrsi(_ptr(src), _ptr(output), n, rsiLength, stochLength, kSmooth, dSmooth)) + return _wrap(output, idx, f"STOCHRSI_{rsiLength}", "oscillators", offset) + + +def ttm_wave(close: object, offset: int = 0, **kwargs) -> object: + """TTM Wave.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + dst = _out(n) + _check(_lib.qtl_ttmwave(_ptr(src), n, _ptr(dst))) + return _wrap(dst, idx, "TTM_WAVE", "oscillators", offset) + + +def ultosc(high: object, low: object, close: object, period1: int = 14, period2: int = 14, period3: int = 14, offset: int = 0, **kwargs) -> object: + """Ultimate Oscillator.""" + period1 = int(period1) + period2 = int(period2) + period3 = int(period3) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + output = _out(n) + _check(_lib.qtl_ultosc(_ptr(h), _ptr(l), _ptr(c), _ptr(output), n, period1, period2, period3)) + return _wrap(output, idx, f"ULTOSC_{period1}", "oscillators", offset) + + +def willr(high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Williams %R.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + output = _out(n) + _check(_lib.qtl_willr(_ptr(h), _ptr(l), _ptr(c), _ptr(output), n, period)) + return _wrap(output, idx, f"WILLR_{period}", "oscillators", offset) + +def fisher(close: object, length: int = 9, offset: int = 0, **kwargs) -> object: + """Fisher Transform.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_fisher(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"FISHER_{length}", "oscillators", offset) + + +def fisher04(close: object, length: int = 9, offset: int = 0, **kwargs) -> object: + """Fisher Transform (0.4 variant).""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_fisher04(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"FISHER04_{length}", "oscillators", offset) + + +def dpo(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Detrended Price Oscillator.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_dpo(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"DPO_{length}", "oscillators", offset) + + +def trix(close: object, length: int = 18, offset: int = 0, **kwargs) -> object: + """Triple EMA Rate of Change.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_trix(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TRIX_{length}", "oscillators", offset) + + +def inertia(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Inertia.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_inertia(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"INERTIA_{length}", "oscillators", offset) + + +def rsx(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Relative Strength Xtra.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_rsx(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"RSX_{length}", "oscillators", offset) + + +def er(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Efficiency Ratio.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_er(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"ER_{length}", "oscillators", offset) + + +def cti(close: object, length: int = 12, offset: int = 0, **kwargs) -> object: + """Correlation Trend Indicator.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cti(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"CTI_{length}", "oscillators", offset) + + +def reflex(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Reflex.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_reflex(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"REFLEX_{length}", "oscillators", offset) + + +def trendflex(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Trendflex.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_trendflex(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TRENDFLEX_{length}", "oscillators", offset) + + +def kri(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Kairi Relative Index.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_kri(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"KRI_{length}", "oscillators", offset) + + +def psl(close: object, length: int = 12, offset: int = 0, **kwargs) -> object: + """Psychological Line.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_psl(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"PSL_{length}", "oscillators", offset) + + +def deco(close: object, short_period: int = 30, long_period: int = 60, + offset: int = 0, **kwargs) -> object: + """DECO.""" + short_period = int(short_period); long_period = int(long_period); offset = int(offset) + 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}", "oscillators", 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.""" + offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_dosc(_ptr(src), n, _ptr(dst), + int(rsi_period), int(ema1_period), int(ema2_period), int(signal_period))) + return _wrap(dst, idx, f"DOSC_{rsi_period}", "oscillators", 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) + 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", "oscillators", 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) + 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}", "oscillators", offset) + + +def bbb(close: object, length: int = 20, mult: float = 2.0, + offset: int = 0, **kwargs) -> object: + """Bollinger Band Bounce.""" + length = int(length); mult = float(mult); offset = int(offset) + 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}", "oscillators", 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) + 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", "oscillators", offset) + + +def dem(high: object, low: object, length: int = 14, + offset: int = 0, **kwargs) -> object: + """DeMarker.""" + length = int(length) + 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}", "oscillators", int(offset)) + + +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); offset = int(offset) + 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, "oscillators", offset) diff --git a/python/quantalib/reversals.py b/python/quantalib/reversals.py new file mode 100644 index 00000000..47aabf0b --- /dev/null +++ b/python/quantalib/reversals.py @@ -0,0 +1,156 @@ +"""quantalib reversals indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "chandelier", + "ckstop", + "fractals", + "pivot", + "pivotcam", + "pivotdem", + "pivotext", + "pivotfib", + "pivotwood", + "psar", + "swings", + "ttm_scalper", +] + + +def chandelier(open: object, high: object, low: object, close: object, period: int = 14, multiplier: float = 2.0, offset: int = 0, **kwargs) -> object: + """Chandelier Exit.""" + period = int(period) + multiplier = float(multiplier) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(o) + output = _out(n) + _check(_lib.qtl_chandelier(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(output), n, period, multiplier)) + return _wrap(output, idx, f"CHANDELIER_{period}", "reversals", offset) + + +def ckstop(open: object, high: object, low: object, close: object, atrPeriod: int = 22, multiplier: float = 2.0, stopPeriod: int = 3, offset: int = 0, **kwargs) -> object: + """Chuck LeBeau Stop.""" + atrPeriod = int(atrPeriod) + multiplier = float(multiplier) + stopPeriod = int(stopPeriod) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(o) + output = _out(n) + _check(_lib.qtl_ckstop(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(output), n, atrPeriod, multiplier, stopPeriod)) + return _wrap(output, idx, f"CKSTOP_{atrPeriod}", "reversals", offset) + + +def fractals(high: object, low: object, offset: int = 0, **kwargs) -> object: + """Williams Fractals.""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + upOutput = _out(n) + downOutput = _out(n) + _check(_lib.qtl_fractals(_ptr(h), _ptr(l), _ptr(upOutput), _ptr(downOutput), n)) + return _wrap_multi({"upOutput": upOutput, "downOutput": downOutput}, idx, "reversals", offset) + + +def pivot(high: object, low: object, close: object, offset: int = 0, **kwargs) -> object: + """Pivot Points (Traditional).""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + ppOutput = _out(n) + _check(_lib.qtl_pivot(_ptr(h), _ptr(l), _ptr(c), _ptr(ppOutput), n)) + return _wrap(ppOutput, idx, "PIVOT", "reversals", offset) + + +def pivotcam(high: object, low: object, close: object, offset: int = 0, **kwargs) -> object: + """Camarilla Pivot Points.""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + ppOutput = _out(n) + _check(_lib.qtl_pivotcam(_ptr(h), _ptr(l), _ptr(c), _ptr(ppOutput), n)) + return _wrap(ppOutput, idx, "PIVOTCAM", "reversals", offset) + + +def pivotdem(open: object, high: object, low: object, close: object, offset: int = 0, **kwargs) -> object: + """DeMark Pivot Points.""" + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(o) + ppOutput = _out(n) + _check(_lib.qtl_pivotdem(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(ppOutput), n)) + return _wrap(ppOutput, idx, "PIVOTDEM", "reversals", offset) + + +def pivotext(high: object, low: object, close: object, offset: int = 0, **kwargs) -> object: + """Extended Pivot Points.""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + ppOutput = _out(n) + _check(_lib.qtl_pivotext(_ptr(h), _ptr(l), _ptr(c), _ptr(ppOutput), n)) + return _wrap(ppOutput, idx, "PIVOTEXT", "reversals", offset) + + +def pivotfib(high: object, low: object, close: object, offset: int = 0, **kwargs) -> object: + """Fibonacci Pivot Points.""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + ppOutput = _out(n) + _check(_lib.qtl_pivotfib(_ptr(h), _ptr(l), _ptr(c), _ptr(ppOutput), n)) + return _wrap(ppOutput, idx, "PIVOTFIB", "reversals", offset) + + +def pivotwood(high: object, low: object, close: object, offset: int = 0, **kwargs) -> object: + """Woodie Pivot Points.""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + ppOutput = _out(n) + _check(_lib.qtl_pivotwood(_ptr(h), _ptr(l), _ptr(c), _ptr(ppOutput), n)) + return _wrap(ppOutput, idx, "PIVOTWOOD", "reversals", offset) + + +def psar(open: object, high: object, low: object, close: object, afStart: float = 0.02, afIncrement: float = 0.02, afMax: float = 0.2, offset: int = 0, **kwargs) -> object: + """Parabolic SAR.""" + afStart = float(afStart) + afIncrement = float(afIncrement) + afMax = float(afMax) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(o) + output = _out(n) + _check(_lib.qtl_psar(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(output), n, afStart, afIncrement, afMax)) + return _wrap(output, idx, "PSAR", "reversals", offset) + + +def swings(high: object, low: object, lookback: int = 5, offset: int = 0, **kwargs) -> object: + """Swing High/Low.""" + lookback = int(lookback) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + highOutput = _out(n) + lowOutput = _out(n) + _check(_lib.qtl_swings(_ptr(h), _ptr(l), _ptr(highOutput), _ptr(lowOutput), n, lookback)) + return _wrap_multi({"highOutput": highOutput, "lowOutput": lowOutput}, idx, "reversals", offset) + + +def ttm_scalper(high: object, low: object, close: object, useCloses: int = 0, offset: int = 0, **kwargs) -> object: + """TTM Scalper.""" + useCloses = int(useCloses) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + highOutput = _out(n) + lowOutput = _out(n) + _check(_lib.qtl_ttmscalper(_ptr(h), _ptr(l), _ptr(c), _ptr(highOutput), _ptr(lowOutput), n, useCloses)) + return _wrap_multi({"highOutput": highOutput, "lowOutput": lowOutput}, idx, "reversals", offset) diff --git a/python/quantalib/statistics.py b/python/quantalib/statistics.py new file mode 100644 index 00000000..02c279a6 --- /dev/null +++ b/python/quantalib/statistics.py @@ -0,0 +1,394 @@ +"""quantalib statistics indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "acf", + "geomean", + "granger", + "harmean", + "hurst", + "iqr", + "jb", + "kendall", + "kurtosis", + "linreg", + "meandev", + "median", + "mode", + "pacf", + "percentile", + "polyfit", + "quantile", + "skew", + "spearman", + "stderr", + "sum", + "theil", + "trim", + "wavg", + "wins", + "ztest", + "zscore", + "cma", + "entropy", + "correlation", + "covariance", + "cointegration", +] + + +def acf(close: object, period: int = 14, lag: int = 10, offset: int = 0, **kwargs) -> object: + """Autocorrelation Function.""" + period = int(period) + lag = int(lag) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_acf(_ptr(src), _ptr(output), n, period, lag)) + return _wrap(output, idx, f"ACF_{period}", "statistics", offset) + + +def geomean(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Geometric Mean.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_geomean(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"GEOMEAN_{period}", "statistics", offset) + + +def granger(x: object, y: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Granger Causality.""" + period = int(period) + offset = int(offset) + xarr, idx = _arr(x); yarr, _ = _arr(y) + n = len(xarr) + output = _out(n) + _check(_lib.qtl_granger(_ptr(yarr), _ptr(xarr), _ptr(output), n, period)) + return _wrap(output, idx, f"GRANGER_{period}", "statistics", offset) + + +def harmean(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Harmonic Mean.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_harmean(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"HARMEAN_{period}", "statistics", offset) + + +def hurst(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Hurst Exponent.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_hurst(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"HURST_{period}", "statistics", offset) + + +def iqr(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Interquartile Range.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_iqr(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"IQR_{period}", "statistics", offset) + + +def jb(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Jarque-Bera Test.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_jb(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"JB_{period}", "statistics", offset) + + +def kendall(x: object, y: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Kendall Rank Correlation.""" + period = int(period) + offset = int(offset) + xarr, idx = _arr(x); yarr, _ = _arr(y) + n = len(xarr) + output = _out(n) + _check(_lib.qtl_kendall(_ptr(xarr), _ptr(yarr), _ptr(output), n, period)) + return _wrap(output, idx, f"KENDALL_{period}", "statistics", offset) + + +def kurtosis(close: object, period: int = 14, isPopulation: int = 0, offset: int = 0, **kwargs) -> object: + """Kurtosis.""" + period = int(period) + isPopulation = int(isPopulation) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_kurtosis(_ptr(src), _ptr(output), n, period, isPopulation)) + return _wrap(output, idx, f"KURTOSIS_{period}", "statistics", offset) + + +def linreg(close: object, period: int = 14, initialLastValid: float = 0.0, offset: int = 0, **kwargs) -> object: + """Linear Regression.""" + period = int(period) + initialLastValid = float(initialLastValid) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_linreg(_ptr(src), _ptr(output), n, period, initialLastValid)) + return _wrap(output, idx, f"LINREG_{period}", "statistics", offset) + + +def meandev(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Mean Deviation.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_meandev(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"MEANDEV_{period}", "statistics", offset) + + +def median(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Rolling Median.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_median(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"MEDIAN_{period}", "statistics", offset) + + +def mode(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Rolling Mode.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_mode(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"MODE_{period}", "statistics", offset) + + +def pacf(close: object, period: int = 14, lag: int = 10, offset: int = 0, **kwargs) -> object: + """Partial Autocorrelation Function.""" + period = int(period) + lag = int(lag) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_pacf(_ptr(src), _ptr(output), n, period, lag)) + return _wrap(output, idx, f"PACF_{period}", "statistics", offset) + + +def percentile(close: object, period: int = 14, percent: float = 50.0, offset: int = 0, **kwargs) -> object: + """Rolling Percentile.""" + period = int(period) + percent = float(percent) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_percentile(_ptr(src), _ptr(output), n, period, percent)) + return _wrap(output, idx, f"PERCENTILE_{period}", "statistics", offset) + + +def polyfit(close: object, period: int = 14, degree: int = 2, initialLastValid: float = 0.0, offset: int = 0, **kwargs) -> object: + """Polynomial Fit.""" + period = int(period) + degree = int(degree) + initialLastValid = float(initialLastValid) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_polyfit(_ptr(src), _ptr(output), n, period, degree, initialLastValid)) + return _wrap(output, idx, f"POLYFIT_{period}", "statistics", offset) + + +def quantile(close: object, period: int = 14, quantileLevel: float = 0.5, offset: int = 0, **kwargs) -> object: + """Rolling Quantile.""" + period = int(period) + quantileLevel = float(quantileLevel) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_quantile(_ptr(src), _ptr(output), n, period, quantileLevel)) + return _wrap(output, idx, f"QUANTILE_{period}", "statistics", offset) + + +def skew(close: object, period: int = 14, isPopulation: int = 0, offset: int = 0, **kwargs) -> object: + """Skewness.""" + period = int(period) + isPopulation = int(isPopulation) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_skew(_ptr(src), _ptr(output), n, period, isPopulation)) + return _wrap(output, idx, f"SKEW_{period}", "statistics", offset) + + +def spearman(x: object, y: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Spearman Rank Correlation.""" + period = int(period) + offset = int(offset) + xarr, idx = _arr(x); yarr, _ = _arr(y) + n = len(xarr) + output = _out(n) + _check(_lib.qtl_spearman(_ptr(xarr), _ptr(yarr), _ptr(output), n, period)) + return _wrap(output, idx, f"SPEARMAN_{period}", "statistics", offset) + + +def stderr(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Standard Error.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_stderr(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"STDERR_{period}", "statistics", offset) + + +def sum(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Rolling Sum.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_sum(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"SUM_{period}", "statistics", offset) + + +def theil(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Theil U Statistic.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_theil(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"THEIL_{period}", "statistics", offset) + + +def trim(close: object, period: int = 14, trimPct: float = 0.1, offset: int = 0, **kwargs) -> object: + """Trimmed Mean.""" + period = int(period) + trimPct = float(trimPct) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_trim(_ptr(src), _ptr(output), n, period, trimPct)) + return _wrap(output, idx, f"TRIM_{period}", "statistics", offset) + + +def wavg(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Weighted Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_wavg(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"WAVG_{period}", "statistics", offset) + + +def wins(close: object, period: int = 14, winPct: float = 0.05, offset: int = 0, **kwargs) -> object: + """Winsorized Mean.""" + period = int(period) + winPct = float(winPct) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_wins(_ptr(src), _ptr(output), n, period, winPct)) + return _wrap(output, idx, f"WINS_{period}", "statistics", offset) + + +def ztest(close: object, period: int = 14, mu0: float = 0.0, offset: int = 0, **kwargs) -> object: + """Z-Test.""" + period = int(period) + mu0 = float(mu0) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_ztest(_ptr(src), _ptr(output), n, period, mu0)) + return _wrap(output, idx, f"ZTEST_{period}", "statistics", offset) + +def zscore(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Z-Score.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_zscore(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"ZSCORE_{length}", "statistics", offset) + + +def cma(close: object, offset: int = 0, **kwargs) -> object: + """Cumulative Moving Average.""" + offset = int(offset) + 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.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_entropy(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"ENTROPY_{length}", "statistics", offset) + + +def correlation(x: object, y: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Pearson Correlation.""" + length = int(length); offset = int(offset) + 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); offset = int(offset) + 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); offset = int(offset) + 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) diff --git a/python/quantalib/trends_fir.py b/python/quantalib/trends_fir.py new file mode 100644 index 00000000..5d022185 --- /dev/null +++ b/python/quantalib/trends_fir.py @@ -0,0 +1,371 @@ +"""quantalib trends_fir indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "fwma", + "gwma", + "hamma", + "hend", + "ilrs", + "kaiser", + "lanczos", + "nlma", + "nyqma", + "pma", + "pwma", + "qrma", + "rwma", + "sma", + "wma", + "hma", + "trima", + "swma", + "dwma", + "blma", + "alma", + "lsma", + "sgma", + "sinema", + "hanma", + "parzen", + "tsf", + "conv", + "bwma", + "crma", + "sp15", + "tukey_w", + "rain", + "afirma", +] + + +def fwma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Fibonacci Weighted Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_fwma(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"FWMA_{period}", "trends_fir", offset) + + +def gwma(close: object, period: int = 14, sigma: float = 6.0, offset: int = 0, **kwargs) -> object: + """Gaussian Weighted Moving Average.""" + period = int(period) + sigma = float(sigma) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_gwma(_ptr(src), _ptr(output), n, period, sigma)) + return _wrap(output, idx, f"GWMA_{period}", "trends_fir", offset) + + +def hamma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Hamming Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_hamma(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"HAMMA_{period}", "trends_fir", offset) + + +def hend(close: object, period: int = 14, nanValue: float = 0.0, offset: int = 0, **kwargs) -> object: + """Henderson Moving Average.""" + period = int(period) + nanValue = float(nanValue) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_hend(_ptr(src), _ptr(output), n, period, nanValue)) + return _wrap(output, idx, f"HEND_{period}", "trends_fir", offset) + + +def ilrs(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Integral of Linear Regression Slope.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_ilrs(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"ILRS_{period}", "trends_fir", offset) + + +def kaiser(close: object, period: int = 14, beta: float = 2.0, nanValue: float = 0.0, offset: int = 0, **kwargs) -> object: + """Kaiser Window Moving Average.""" + period = int(period) + beta = float(beta) + nanValue = float(nanValue) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_kaiser(_ptr(src), _ptr(output), n, period, beta, nanValue)) + return _wrap(output, idx, f"KAISER_{period}", "trends_fir", offset) + + +def lanczos(close: object, period: int = 14, nanValue: float = 0.0, offset: int = 0, **kwargs) -> object: + """Lanczos Moving Average.""" + period = int(period) + nanValue = float(nanValue) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_lanczos(_ptr(src), _ptr(output), n, period, nanValue)) + return _wrap(output, idx, f"LANCZOS_{period}", "trends_fir", offset) + + +def nlma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Non-Lag Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_nlma(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"NLMA_{period}", "trends_fir", offset) + + +def nyqma(close: object, period: int = 14, nyquistPeriod: int = 2, offset: int = 0, **kwargs) -> object: + """Nyquist Moving Average.""" + period = int(period) + nyquistPeriod = int(nyquistPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_nyqma(_ptr(src), _ptr(output), n, period, nyquistPeriod)) + return _wrap(output, idx, f"NYQMA_{period}", "trends_fir", offset) + + +def pma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Predictive Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + pmaOutput = _out(n) + triggerOutput = _out(n) + _check(_lib.qtl_pma(_ptr(src), _ptr(pmaOutput), _ptr(triggerOutput), n, period)) + return _wrap_multi({"pmaOutput": pmaOutput, "triggerOutput": triggerOutput}, idx, "trends_fir", offset) + + +def pwma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Pascal Weighted Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_pwma(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"PWMA_{period}", "trends_fir", offset) + + +def qrma(close: object, period: int = 14, initialLastValid: float = 0.0, offset: int = 0, **kwargs) -> object: + """Quick Reaction Moving Average.""" + period = int(period) + initialLastValid = float(initialLastValid) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_qrma(_ptr(src), _ptr(output), n, period, initialLastValid)) + return _wrap(output, idx, f"QRMA_{period}", "trends_fir", offset) + + +def rwma(high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Range Weighted Moving Average.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + output = _out(n) + _check(_lib.qtl_rwma(_ptr(c), _ptr(h), _ptr(l), _ptr(output), n, period)) + return _wrap(output, idx, f"RWMA_{period}", "trends_fir", offset) + +def sma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Simple Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_sma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"SMA_{length}", "trends_fir", offset) + + +def wma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_wma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"WMA_{length}", "trends_fir", offset) + + +def hma(close: object, length: int = 9, offset: int = 0, **kwargs) -> object: + """Hull Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_hma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"HMA_{length}", "trends_fir", offset) + + +def trima(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Triangular Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_trima(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TRIMA_{length}", "trends_fir", offset) + + +def swma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Symmetric Weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_swma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"SWMA_{length}", "trends_fir", offset) + + +def dwma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Double Weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_dwma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"DWMA_{length}", "trends_fir", offset) + + +def blma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Blackman Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_blma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"BLMA_{length}", "trends_fir", offset) + + +def alma(close: object, length: int = 10, alma_offset: float = 0.85, + sigma: float = 6.0, offset: int = 0, **kwargs) -> object: + """Arnaud Legoux Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_alma(_ptr(src), n, _ptr(dst), length, float(alma_offset), float(sigma))) + return _wrap(dst, idx, f"ALMA_{length}", "trends_fir", offset) + + +def lsma(close: object, length: int = 25, offset: int = 0, **kwargs) -> object: + """Least Squares Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_lsma(_ptr(src), n, _ptr(dst), length, 0, 1.0)) + return _wrap(dst, idx, f"LSMA_{length}", "trends_fir", offset) + + +def sgma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Savitzky-Golay Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_sgma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"SGMA_{length}", "trends_fir", offset) + + +def sinema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Sine-weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_sinema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"SINEMA_{length}", "trends_fir", offset) + + +def hanma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Hann-weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_hanma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"HANMA_{length}", "trends_fir", offset) + + +def parzen(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Parzen-weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_parzen(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"PARZEN_{length}", "trends_fir", offset) + + +def tsf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Time Series Forecast.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_tsf(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TSF_{length}", "trends_fir", offset) + + +def conv(close: object, kernel: list | None = None, + offset: int = 0, **kwargs) -> object: + """Convolution with custom kernel.""" + offset = int(offset) + 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", "trends_fir", offset) + + +def bwma(close: object, length: int = 10, order: int = 0, + offset: int = 0, **kwargs) -> object: + """Butterworth-weighted Moving Average.""" + length = int(length); offset = int(offset) + 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}", "trends_fir", 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); offset = int(offset) + 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}", "trends_fir", offset) + + +def sp15(close: object, length: int = 15, offset: int = 0, **kwargs) -> object: + """SP-15 Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_sp15(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"SP15_{length}", "trends_fir", offset) + + +def tukey_w(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Tukey-windowed Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_tukey_w(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TUKEY_{length}", "trends_fir", offset) + + +def rain(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """RAIN Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_rain(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"RAIN_{length}", "trends_fir", offset) + + +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); offset = int(offset) + 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}", "trends_fir", offset) diff --git a/python/quantalib/trends_iir.py b/python/quantalib/trends_iir.py new file mode 100644 index 00000000..b60f5395 --- /dev/null +++ b/python/quantalib/trends_iir.py @@ -0,0 +1,473 @@ +"""quantalib trends_iir indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "adxvma", + "frama", + "holt", + "htit", + "hwma", + "jma", + "kama", + "ltma", + "mama", + "mavp", + "mcnma", + "mgdi", + "mma", + "nma", + "qema", + "rema", + "rgma", + "rma", + "t3", + "trama", + "vama", + "vidya", + "yzvama", + "zldema", + "zlema", + "zltema", + "ema", + "ema_alpha", + "dema", + "dema_alpha", + "tema", + "lema", + "hema", + "ahrens", + "decycler", + "dsma", + "gdema", + "coral", + "agc", + "ccyc", +] + + +def adxvma(open: object, high: object, low: object, close: object, volume: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """ADX Variable Moving Average.""" + period = int(period) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_adxvma(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, n, _ptr(dst))) + return _wrap(dst, idx, f"ADXVMA_{period}", "trends_iir", offset) + + +def frama(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Fractal Adaptive Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_frama(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"FRAMA_{period}", "trends_iir", offset) + + +def holt(close: object, period: int = 14, gamma: float = 0.7, offset: int = 0, **kwargs) -> object: + """Holt Exponential Smoothing.""" + period = int(period) + gamma = float(gamma) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_holt(_ptr(src), _ptr(output), n, period, gamma)) + return _wrap(output, idx, f"HOLT_{period}", "trends_iir", offset) + + +def htit(close: object, offset: int = 0, **kwargs) -> object: + """Hilbert Transform Instantaneous Trendline.""" + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_htit(_ptr(src), _ptr(output), n)) + return _wrap(output, idx, "HTIT", "trends_iir", offset) + + +def hwma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Holt-Winter Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_hwma(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"HWMA_{period}", "trends_iir", offset) + + +def jma(close: object, period: int = 14, phase: int = 0, power: float = 1.0, offset: int = 0, **kwargs) -> object: + """Jurik Moving Average.""" + period = int(period) + phase = int(phase) + power = float(power) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_jma(_ptr(src), _ptr(output), n, period, phase, power)) + return _wrap(output, idx, f"JMA_{period}", "trends_iir", offset) + + +def kama(close: object, period: int = 14, fastPeriod: int = 12, slowPeriod: int = 26, offset: int = 0, **kwargs) -> object: + """Kaufman Adaptive Moving Average.""" + period = int(period) + fastPeriod = int(fastPeriod) + slowPeriod = int(slowPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_kama(_ptr(src), _ptr(output), n, period, fastPeriod, slowPeriod)) + return _wrap(output, idx, f"KAMA_{period}", "trends_iir", offset) + + +def ltma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Low-Lag Triple Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_ltma(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"LTMA_{period}", "trends_iir", offset) + + +def mama(close: object, fastLimit: float = 0.5, slowLimit: float = 0.05, offset: int = 0, **kwargs) -> object: + """MESA Adaptive Moving Average.""" + fastLimit = float(fastLimit) + slowLimit = float(slowLimit) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + famaOutput = _out(n) + _check(_lib.qtl_mama(_ptr(src), _ptr(output), fastLimit, n, slowLimit, _ptr(famaOutput))) + return _wrap_multi({"output": output, "famaOutput": famaOutput}, idx, "trends_iir", offset) + + +def mavp(x: object, periods: object, minPeriod: int = 6, maxPeriod: int = 48, offset: int = 0, **kwargs) -> object: + """Moving Average Variable Period.""" + minPeriod = int(minPeriod) + maxPeriod = int(maxPeriod) + offset = int(offset) + xarr, idx = _arr(x); yarr, _ = _arr(periods) + n = len(xarr) + output = _out(n) + _check(_lib.qtl_mavp(_ptr(xarr), _ptr(yarr), _ptr(output), n, minPeriod, maxPeriod)) + return _wrap(output, idx, f"MAVP_{minPeriod}", "trends_iir", offset) + + +def mcnma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """McNicholl Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_mcnma(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"MCNMA_{period}", "trends_iir", offset) + + +def mgdi(close: object, period: int = 14, k: float = 2.0, offset: int = 0, **kwargs) -> object: + """McGinley Dynamic.""" + period = int(period) + k = float(k) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_mgdi(_ptr(src), _ptr(output), n, period, k)) + return _wrap(output, idx, f"MGDI_{period}", "trends_iir", offset) + + +def mma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Modified Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_mma(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"MMA_{period}", "trends_iir", offset) + + +def nma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Normalized Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_nma(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"NMA_{period}", "trends_iir", offset) + + +def qema(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Quadruple EMA.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_qema(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"QEMA_{period}", "trends_iir", offset) + + +def rema(close: object, period: int = 14, lam: float = 1600.0, offset: int = 0, **kwargs) -> object: + """Regularized EMA.""" + period = int(period) + lam = float(lam) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_rema(_ptr(src), _ptr(output), n, period, lam)) + return _wrap(output, idx, f"REMA_{period}", "trends_iir", offset) + + +def rgma(close: object, period: int = 14, passes: int = 3, offset: int = 0, **kwargs) -> object: + """Recursive Gaussian Moving Average.""" + period = int(period) + passes = int(passes) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_rgma(_ptr(src), _ptr(output), n, period, passes)) + return _wrap(output, idx, f"RGMA_{period}", "trends_iir", offset) + + +def rma(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Rolling Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_rma(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"RMA_{period}", "trends_iir", offset) + + +def t3(close: object, period: int = 14, vfactor: float = 0.7, offset: int = 0, **kwargs) -> object: + """Tillson T3.""" + period = int(period) + vfactor = float(vfactor) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_t3(_ptr(src), _ptr(output), n, period, vfactor)) + return _wrap(output, idx, f"T3_{period}", "trends_iir", offset) + + +def trama(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Triangular Adaptive Moving Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_trama(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"TRAMA_{period}", "trends_iir", offset) + + +def vama(open: object, high: object, low: object, close: object, volume: object, baseLength: int = 20, shortAtrPeriod: int = 14, longAtrPeriod: int = 50, minLength: int = 5, maxLength: int = 50, offset: int = 0, **kwargs) -> object: + """Volume Adjusted Moving Average.""" + baseLength = int(baseLength) + shortAtrPeriod = int(shortAtrPeriod) + longAtrPeriod = int(longAtrPeriod) + minLength = int(minLength) + maxLength = int(maxLength) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_vama(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), baseLength, shortAtrPeriod, longAtrPeriod, minLength, maxLength, n, _ptr(dst))) + return _wrap(dst, idx, f"VAMA_{baseLength}", "trends_iir", offset) + + +def vidya(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Variable Index Dynamic Average.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_vidya(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"VIDYA_{period}", "trends_iir", offset) + + +def yzvama(open: object, high: object, low: object, close: object, volume: object, yzvShortPeriod: int = 10, yzvLongPeriod: int = 100, percentileLookback: int = 252, minLength: int = 5, maxLength: int = 50, offset: int = 0, **kwargs) -> object: + """Yang Zhang Volatility Adaptive MA.""" + yzvShortPeriod = int(yzvShortPeriod) + yzvLongPeriod = int(yzvLongPeriod) + percentileLookback = int(percentileLookback) + minLength = int(minLength) + maxLength = int(maxLength) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_yzvama(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), yzvShortPeriod, yzvLongPeriod, percentileLookback, minLength, maxLength, n, _ptr(dst))) + return _wrap(dst, idx, f"YZVAMA_{yzvShortPeriod}", "trends_iir", offset) + + +def zldema(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Zero-Lag Double EMA.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_zldema(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"ZLDEMA_{period}", "trends_iir", offset) + + +def zlema(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Zero-Lag EMA.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_zlema(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"ZLEMA_{period}", "trends_iir", offset) + + +def zltema(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Zero-Lag Triple EMA.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_zltema(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"ZLTEMA_{period}", "trends_iir", offset) + +def ema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Exponential Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_ema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"EMA_{length}", "trends_iir", offset) + + +def ema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object: + """EMA with explicit alpha.""" + offset = int(offset) + 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}", "trends_iir", offset) + + +def dema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Double Exponential Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_dema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"DEMA_{length}", "trends_iir", offset) + + +def dema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object: + """DEMA with explicit alpha.""" + offset = int(offset) + 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}", "trends_iir", offset) + + +def tema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Triple Exponential Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_tema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TEMA_{length}", "trends_iir", offset) + + +def lema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Laguerre-based EMA.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_lema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"LEMA_{length}", "trends_iir", offset) + + +def hema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Henderson EMA.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_hema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"HEMA_{length}", "trends_iir", offset) + + +def ahrens(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Ahrens Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_ahrens(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"AHRENS_{length}", "trends_iir", offset) + + +def decycler(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Simple Decycler.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_decycler(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"DECYCLER_{length}", "trends_iir", offset) + + +def dsma(close: object, length: int = 10, factor: float = 0.5, + offset: int = 0, **kwargs) -> object: + """Deviation-Scaled Moving Average.""" + length = int(length); offset = int(offset) + 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}", "trends_iir", offset) + + +def gdema(close: object, length: int = 10, vfactor: float = 1.0, + offset: int = 0, **kwargs) -> object: + """Generalized DEMA.""" + length = int(length); offset = int(offset) + 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}", "trends_iir", offset) + + +def coral(close: object, length: int = 10, friction: float = 0.4, + offset: int = 0, **kwargs) -> object: + """CORAL Trend.""" + length = int(length); offset = int(offset) + 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}", "trends_iir", offset) + + +def agc(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object: + """Automatic Gain Control.""" + offset = int(offset) + 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}", "trends_iir", offset) + + +def ccyc(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object: + """Cyber Cycle.""" + offset = int(offset) + 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}", "trends_iir", offset) diff --git a/python/quantalib/volatility.py b/python/quantalib/volatility.py new file mode 100644 index 00000000..847df94e --- /dev/null +++ b/python/quantalib/volatility.py @@ -0,0 +1,341 @@ +"""quantalib volatility indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "adr", + "atr", + "atrn", + "gkv", + "hlv", + "hv", + "jvolty", + "jvoltyn", + "massi", + "natr", + "rsv", + "rv", + "rvi", + "ui", + "vov", + "vr", + "yzv", + "tr", + "bbw", + "bbwn", + "bbwp", + "stddev", + "variance", + "etherm", + "ccv", + "cv", + "cvi", + "ewma", +] + + +def adr(open: object, high: object, low: object, close: object, volume: object, period: int = 14, method: int = 0, offset: int = 0, **kwargs) -> object: + """Average Daily Range.""" + period = int(period) + method = int(method) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_adr(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, method, n, _ptr(dst))) + return _wrap(dst, idx, f"ADR_{period}", "volatility", offset) + + +def atr(open: object, high: object, low: object, close: object, volume: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Average True Range.""" + period = int(period) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_atr(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, n, _ptr(dst))) + return _wrap(dst, idx, f"ATR_{period}", "volatility", offset) + + +def atrn(open: object, high: object, low: object, close: object, volume: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Normalized ATR.""" + period = int(period) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_atrn(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, n, _ptr(dst))) + return _wrap(dst, idx, f"ATRN_{period}", "volatility", offset) + + +def gkv(open: object, high: object, low: object, close: object, period: int = 14, annualize: int = 1, annualPeriods: int = 252, offset: int = 0, **kwargs) -> object: + """Garman-Klass Volatility.""" + period = int(period) + annualize = int(annualize) + annualPeriods = int(annualPeriods) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(o) + output = _out(n) + _check(_lib.qtl_gkv(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(output), n, period, annualize, annualPeriods)) + return _wrap(output, idx, f"GKV_{period}", "volatility", offset) + + +def hlv(high: object, low: object, period: int = 14, annualize: int = 1, annualPeriods: int = 252, offset: int = 0, **kwargs) -> object: + """High-Low Volatility.""" + period = int(period) + annualize = int(annualize) + annualPeriods = int(annualPeriods) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low) + n = len(h) + output = _out(n) + _check(_lib.qtl_hlv(_ptr(h), _ptr(l), _ptr(output), n, period, annualize, annualPeriods)) + return _wrap(output, idx, f"HLV_{period}", "volatility", offset) + + +def hv(close: object, period: int = 14, annualize: int = 1, annualPeriods: int = 252, offset: int = 0, **kwargs) -> object: + """Historical Volatility.""" + period = int(period) + annualize = int(annualize) + annualPeriods = int(annualPeriods) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_hv(_ptr(src), _ptr(output), n, period, annualize, annualPeriods)) + return _wrap(output, idx, f"HV_{period}", "volatility", offset) + + +def jvolty(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Jurik Volatility.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_jvolty(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"JVOLTY_{period}", "volatility", offset) + + +def jvoltyn(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Jurik Volatility Normalized.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_jvoltyn(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"JVOLTYN_{period}", "volatility", offset) + + +def massi(close: object, emaLength: int = 9, sumLength: int = 25, offset: int = 0, **kwargs) -> object: + """Mass Index.""" + emaLength = int(emaLength) + sumLength = int(sumLength) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_massi(_ptr(src), _ptr(output), n, emaLength, sumLength)) + return _wrap(output, idx, f"MASSI_{emaLength}", "volatility", offset) + + +def natr(open: object, high: object, low: object, close: object, volume: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Normalized ATR.""" + period = int(period) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low) + c, _ = _arr(close); v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(_lib.qtl_natr(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, n, _ptr(dst))) + return _wrap(dst, idx, f"NATR_{period}", "volatility", offset) + + +def rsv(open: object, high: object, low: object, close: object, period: int = 14, annualize: int = 1, annualPeriods: int = 252, offset: int = 0, **kwargs) -> object: + """Rogers-Satchell Volatility.""" + period = int(period) + annualize = int(annualize) + annualPeriods = int(annualPeriods) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(o) + output = _out(n) + _check(_lib.qtl_rsv(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(output), n, period, annualize, annualPeriods)) + return _wrap(output, idx, f"RSV_{period}", "volatility", offset) + + +def rv(close: object, period: int = 14, smoothingPeriod: int = 14, annualize: int = 1, annualPeriods: int = 252, offset: int = 0, **kwargs) -> object: + """Realized Volatility.""" + period = int(period) + smoothingPeriod = int(smoothingPeriod) + annualize = int(annualize) + annualPeriods = int(annualPeriods) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_rv(_ptr(src), _ptr(output), n, period, smoothingPeriod, annualize, annualPeriods)) + return _wrap(output, idx, f"RV_{period}", "volatility", offset) + + +def rvi(close: object, stdevLength: int = 10, rmaLength: int = 14, offset: int = 0, **kwargs) -> object: + """Relative Volatility Index.""" + stdevLength = int(stdevLength) + rmaLength = int(rmaLength) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_rvi(_ptr(src), _ptr(output), n, stdevLength, rmaLength)) + return _wrap(output, idx, f"RVI_{stdevLength}", "volatility", offset) + + +def ui(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Ulcer Index.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_ui(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"UI_{period}", "volatility", offset) + + +def vov(close: object, volatilityPeriod: int = 20, vovPeriod: int = 20, offset: int = 0, **kwargs) -> object: + """Volatility of Volatility.""" + volatilityPeriod = int(volatilityPeriod) + vovPeriod = int(vovPeriod) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_vov(_ptr(src), _ptr(output), n, volatilityPeriod, vovPeriod)) + return _wrap(output, idx, f"VOV_{volatilityPeriod}", "volatility", offset) + + +def vr(high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Volatility Ratio.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(h) + output = _out(n) + _check(_lib.qtl_vr(_ptr(h), _ptr(l), _ptr(c), _ptr(output), n, period)) + return _wrap(output, idx, f"VR_{period}", "volatility", offset) + + +def yzv(open: object, high: object, low: object, close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Yang-Zhang Volatility.""" + period = int(period) + offset = int(offset) + o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close) + n = len(o) + output = _out(n) + _check(_lib.qtl_yzv(_ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(output), n, period)) + return _wrap(output, idx, f"YZV_{period}", "volatility", offset) + +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)) + + +def bbw(close: object, length: int = 20, mult: float = 2.0, + offset: int = 0, **kwargs) -> object: + """Bollinger Band Width.""" + length = int(length); mult = float(mult); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_bbw(_ptr(src), n, _ptr(dst), length, mult)) + return _wrap(dst, idx, f"BBW_{length}", "volatility", offset) + + +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); mult = float(mult); lookback = int(lookback); offset = int(offset) + 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); mult = float(mult); lookback = int(lookback); offset = int(offset) + 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.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_stddev(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"STDDEV_{length}", "volatility", offset) + + +def variance(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Variance.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_variance(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"VAR_{length}", "volatility", offset) + + +def etherm(high: object, low: object, length: int = 14, + offset: int = 0, **kwargs) -> object: + """Elder Thermometer.""" + length = int(length) + 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)) + + +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); long_period = int(long_period); offset = int(offset) + 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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cv(_ptr(src), n, _ptr(dst), length, float(min_vol), float(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); roc_period = int(roc_period); offset = int(offset) + 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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_ewma(_ptr(src), n, _ptr(dst), length, int(is_pop), int(ann_factor))) + return _wrap(dst, idx, f"EWMA_{length}", "volatility", offset) diff --git a/python/quantalib/volume.py b/python/quantalib/volume.py new file mode 100644 index 00000000..4e000447 --- /dev/null +++ b/python/quantalib/volume.py @@ -0,0 +1,317 @@ +"""quantalib volume indicators. + +Auto-generated — DO NOT EDIT. +""" +from __future__ import annotations + +from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib + + +__all__ = [ + "adl", + "adosc", + "iii", + "kvo", + "twap", + "va", + "vo", + "vroc", + "vwad", + "vwap", + "wad", + "obv", + "pvt", + "pvr", + "vf", + "nvi", + "pvi", + "tvi", + "pvd", + "vwma", + "evwma", + "efi", + "aobv", + "mfi", + "cmf", + "eom", + "pvo", +] + + +def adl(high: object, low: object, close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Accumulation/Distribution Line.""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close); v, _ = _arr(volume) + n = len(h) + output = _out(n) + _check(_lib.qtl_adl(_ptr(h), _ptr(l), _ptr(c), _ptr(v), _ptr(output), n)) + return _wrap(output, idx, "ADL", "volume", offset) + + +def adosc(high: object, low: object, close: object, volume: object, fastPeriod: int = 12, slowPeriod: int = 26, offset: int = 0, **kwargs) -> object: + """Accumulation/Distribution Oscillator.""" + fastPeriod = int(fastPeriod) + slowPeriod = int(slowPeriod) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close); v, _ = _arr(volume) + n = len(h) + output = _out(n) + _check(_lib.qtl_adosc(_ptr(h), _ptr(l), _ptr(c), _ptr(v), _ptr(output), n, fastPeriod, slowPeriod)) + return _wrap(output, idx, f"ADOSC_{fastPeriod}", "volume", offset) + + +def iii(high: object, low: object, close: object, volume: object, period: int = 14, cumulative: int = 0, offset: int = 0, **kwargs) -> object: + """Intraday Intensity Index.""" + period = int(period) + cumulative = int(cumulative) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close); v, _ = _arr(volume) + n = len(h) + output = _out(n) + _check(_lib.qtl_iii(_ptr(h), _ptr(l), _ptr(c), _ptr(v), _ptr(output), n, period, cumulative)) + return _wrap(output, idx, f"III_{period}", "volume", offset) + + +def kvo(high: object, low: object, close: object, volume: object, fastPeriod: int = 12, slowPeriod: int = 26, signalPeriod: int = 9, offset: int = 0, **kwargs) -> object: + """Klinger Volume Oscillator.""" + fastPeriod = int(fastPeriod) + slowPeriod = int(slowPeriod) + signalPeriod = int(signalPeriod) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close); v, _ = _arr(volume) + n = len(h) + output = _out(n) + signal = _out(n) + _check(_lib.qtl_kvo(_ptr(h), _ptr(l), _ptr(c), _ptr(v), _ptr(output), _ptr(signal), n, fastPeriod, slowPeriod, signalPeriod)) + return _wrap_multi({"output": output, "signal": signal}, idx, "volume", offset) + + +def twap(close: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Time Weighted Average Price.""" + period = int(period) + offset = int(offset) + src, idx = _arr(close) + n = len(src) + output = _out(n) + _check(_lib.qtl_twap(_ptr(src), _ptr(output), n, period)) + return _wrap(output, idx, f"TWAP_{period}", "volume", offset) + + +def va(high: object, low: object, close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Volume Accumulation.""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close); v, _ = _arr(volume) + n = len(h) + output = _out(n) + _check(_lib.qtl_va(_ptr(h), _ptr(l), _ptr(c), _ptr(v), _ptr(output), n)) + return _wrap(output, idx, "VA", "volume", offset) + + +def vo(volume: object, shortPeriod: int = 12, longPeriod: int = 26, offset: int = 0, **kwargs) -> object: + """Volume Oscillator.""" + shortPeriod = int(shortPeriod) + longPeriod = int(longPeriod) + offset = int(offset) + src, idx = _arr(volume) + n = len(src) + output = _out(n) + _check(_lib.qtl_vo(_ptr(src), _ptr(output), n, shortPeriod, longPeriod)) + return _wrap(output, idx, f"VO_{shortPeriod}", "volume", offset) + + +def vroc(volume: object, period: int = 14, usePercent: int = 1, offset: int = 0, **kwargs) -> object: + """Volume Rate of Change.""" + period = int(period) + usePercent = int(usePercent) + offset = int(offset) + src, idx = _arr(volume) + n = len(src) + output = _out(n) + _check(_lib.qtl_vroc(_ptr(src), _ptr(output), n, period, usePercent)) + return _wrap(output, idx, f"VROC_{period}", "volume", offset) + + +def vwad(high: object, low: object, close: object, volume: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Volume Weighted Accumulation/Distribution.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close); v, _ = _arr(volume) + n = len(h) + output = _out(n) + _check(_lib.qtl_vwad(_ptr(h), _ptr(l), _ptr(c), _ptr(v), _ptr(output), n, period)) + return _wrap(output, idx, f"VWAD_{period}", "volume", offset) + + +def vwap(high: object, low: object, close: object, volume: object, period: int = 14, offset: int = 0, **kwargs) -> object: + """Volume Weighted Average Price.""" + period = int(period) + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close); v, _ = _arr(volume) + n = len(h) + output = _out(n) + _check(_lib.qtl_vwap(_ptr(h), _ptr(l), _ptr(c), _ptr(v), _ptr(output), n, period)) + return _wrap(output, idx, f"VWAP_{period}", "volume", offset) + + +def wad(high: object, low: object, close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Williams Accumulation/Distribution.""" + offset = int(offset) + h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close); v, _ = _arr(volume) + n = len(h) + output = _out(n) + _check(_lib.qtl_wad(_ptr(h), _ptr(l), _ptr(c), _ptr(v), _ptr(output), n)) + return _wrap(output, idx, "WAD", "volume", offset) + +def obv(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """On-Balance Volume.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_obv(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "OBV", "volume", offset) + + +def pvt(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Price Volume Trend.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_pvt(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "PVT", "volume", offset) + + +def pvr(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Price Volume Rank.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_pvr(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "PVR", "volume", offset) + + +def vf(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Volume Flow.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_vf(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "VF", "volume", offset) + + +def nvi(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Negative Volume Index.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_nvi(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "NVI", "volume", offset) + + +def pvi(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Positive Volume Index.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_pvi(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "PVI", "volume", offset) + + +def tvi(close: object, volume: object, length: int = 14, + offset: int = 0, **kwargs) -> object: + """Trade Volume Index.""" + length = int(length); offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_tvi(_ptr(c), _ptr(v), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TVI_{length}", "volume", offset) + + +def pvd(close: object, volume: object, length: int = 14, + offset: int = 0, **kwargs) -> object: + """Price Volume Divergence.""" + length = int(length); offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_pvd(_ptr(c), _ptr(v), n, _ptr(dst), length)) + return _wrap(dst, idx, f"PVD_{length}", "volume", offset) + + +def vwma(close: object, volume: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Volume Weighted Moving Average.""" + length = int(length); offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_vwma(_ptr(c), _ptr(v), n, _ptr(dst), length)) + return _wrap(dst, idx, f"VWMA_{length}", "volume", offset) + + +def evwma(close: object, volume: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Elastic Volume Weighted Moving Average.""" + length = int(length); offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_evwma(_ptr(c), _ptr(v), n, _ptr(dst), length)) + return _wrap(dst, idx, f"EVWMA_{length}", "volume", offset) + + +def efi(close: object, volume: object, length: int = 13, + offset: int = 0, **kwargs) -> object: + """Elder Force Index.""" + length = int(length); offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_efi(_ptr(c), _ptr(v), n, _ptr(dst), length)) + return _wrap(dst, idx, f"EFI_{length}", "volume", offset) + + +def aobv(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Archer OBV -> (fast, slow) or DataFrame.""" + offset = int(offset) + 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); offset = int(offset) + 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); offset = int(offset) + 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); offset = int(offset) + 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, 1e9)) + 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); slow = int(slow); signal = int(signal); offset = int(offset) + 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) diff --git a/python/tests/test_category_labels.py b/python/tests/test_category_labels.py new file mode 100644 index 00000000..55660761 --- /dev/null +++ b/python/tests/test_category_labels.py @@ -0,0 +1,44 @@ +"""test_category_labels.py — Verify consistent category labels across all modules.""" +from __future__ import annotations + +import importlib +import inspect +import re +import pytest + +CATEGORY_MODULES = { + "quantalib.channels": "channels", + "quantalib.core": "core", + "quantalib.cycles": "cycles", + "quantalib.dynamics": "dynamics", + "quantalib.errors": "errors", + "quantalib.filters": "filters", + "quantalib.momentum": "momentum", + "quantalib.numerics": "numerics", + "quantalib.oscillators": "oscillators", + "quantalib.reversals": "reversals", + "quantalib.statistics": "statistics", + "quantalib.trends_fir": "trends_fir", + "quantalib.trends_iir": "trends_iir", + "quantalib.volatility": "volatility", + "quantalib.volume": "volume", +} + +# Regex to match _wrap(..., "CATEGORY", ...) or _wrap_multi(..., "CATEGORY", ...) +LABEL_PATTERN = re.compile(r'_wrap(?:_multi)?\(.*?,\s*"([^"]+)",\s*(?:offset|[\w]+)\)') + + +@pytest.mark.parametrize("modname,expected", CATEGORY_MODULES.items(), ids=lambda x: x.split(".")[-1] if "." in x else x) +def test_category_labels_lowercase(modname: str, expected: str) -> None: + """Category labels passed to _wrap/_wrap_multi must be lowercase.""" + mod = importlib.import_module(modname) + source_file = inspect.getfile(mod) + with open(source_file, "r", encoding="utf-8") as f: + source = f.read() + + # Find all category labels in source + labels = LABEL_PATTERN.findall(source) + bad = [lbl for lbl in labels if lbl != expected] + assert bad == [], ( + f"{modname}: expected category label '{expected}', found non-matching: {set(bad)}" + ) diff --git a/python/tests/test_compat_aliases.py b/python/tests/test_compat_aliases.py new file mode 100644 index 00000000..f92ddcc2 --- /dev/null +++ b/python/tests/test_compat_aliases.py @@ -0,0 +1,45 @@ +"""test_compat.py — pandas-ta compatibility alias tests.""" +from __future__ import annotations + +import pytest + +from quantalib._compat import ALIASES, get_compat + + +class TestAliases: + """Validate ALIASES mapping and get_compat resolution.""" + + def test_aliases_is_dict(self) -> None: + assert isinstance(ALIASES, dict) + assert len(ALIASES) > 0 + + def test_all_aliases_are_strings(self) -> None: + for key, val in ALIASES.items(): + assert isinstance(key, str), f"Key {key!r} is not str" + assert isinstance(val, str), f"Value {val!r} for key {key!r} is not str" + + @pytest.mark.parametrize( + "alias,target", + [ + ("midprice", "medprice"), + ("momentum", "mom"), + ("simple_moving_average", "sma"), + ("true_range", "tr"), + ("on_balance_volume", "obv"), + ("bollinger_bands", "bbands"), + ("z_score", "zscore"), + ], + ) + def test_known_aliases(self, alias: str, target: str) -> None: + assert ALIASES[alias] == target + + def test_get_compat_unknown_returns_none(self) -> None: + result = get_compat("nonexistent_indicator_xyz") + assert result is None + + def test_get_compat_known_returns_callable(self) -> None: + fn = get_compat("simple_moving_average") + # May be None if native lib not available, but function itself resolves + # We just test the lookup mechanism works + if fn is not None: + assert callable(fn) diff --git a/python/tests/test_helpers.py b/python/tests/test_helpers.py new file mode 100644 index 00000000..97d5a44c --- /dev/null +++ b/python/tests/test_helpers.py @@ -0,0 +1,194 @@ +"""test_helpers.py — Unit tests for quantalib._helpers (no native lib needed).""" +from __future__ import annotations + +import numpy as np +import pytest + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +def _has_pandas() -> bool: + try: + import pandas # noqa: F401 + return True + except ImportError: + return False + + +# --------------------------------------------------------------------------- +# _arr +# --------------------------------------------------------------------------- +class TestArr: + """Tests for _arr() input coercion and validation.""" + + def test_list_to_float64(self) -> None: + from quantalib._helpers import _arr + arr, idx = _arr([1.0, 2.0, 3.0]) + assert arr.dtype == np.float64 + assert idx is None + np.testing.assert_array_equal(arr, [1.0, 2.0, 3.0]) + + def test_int_array_coerced(self) -> None: + from quantalib._helpers import _arr + arr, _ = _arr(np.array([1, 2, 3])) + assert arr.dtype == np.float64 + + def test_contiguous_no_copy(self) -> None: + from quantalib._helpers import _arr + src = np.array([1.0, 2.0, 3.0], dtype=np.float64) + arr, _ = _arr(src) + # Already contiguous float64 — should share memory + assert np.shares_memory(arr, src) + + def test_non_contiguous_made_contiguous(self) -> None: + from quantalib._helpers import _arr + src = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float64)[::2] + assert not src.flags["C_CONTIGUOUS"] + arr, _ = _arr(src) + assert arr.flags["C_CONTIGUOUS"] + + def test_none_raises(self) -> None: + from quantalib._helpers import _arr + with pytest.raises(ValueError, match="must not be None"): + _arr(None) + + def test_empty_raises(self) -> None: + from quantalib._helpers import _arr + with pytest.raises(ValueError, match="must not be empty"): + _arr(np.array([], dtype=np.float64)) + + def test_scalar_raises(self) -> None: + from quantalib._helpers import _arr + with pytest.raises(ValueError, match="must not be empty"): + _arr(np.float64(42.0)) + + @pytest.mark.skipif(not _has_pandas(), reason="pandas not installed") + def test_pandas_series_preserves_index(self) -> None: + import pandas as pd + from quantalib._helpers import _arr + idx = pd.date_range("2020-01-01", periods=5) + s = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0], index=idx) + arr, ridx = _arr(s) + assert arr.dtype == np.float64 + assert ridx is idx + + @pytest.mark.skipif(not _has_pandas(), reason="pandas not installed") + def test_pandas_dataframe_uses_first_col(self) -> None: + import pandas as pd + from quantalib._helpers import _arr + df = pd.DataFrame({"a": [1.0, 2.0], "b": [3.0, 4.0]}) + arr, idx = _arr(df) + np.testing.assert_array_equal(arr, [1.0, 2.0]) + + +# --------------------------------------------------------------------------- +# _offset +# --------------------------------------------------------------------------- +class TestOffset: + """Tests for _offset() roll + NaN fill.""" + + def test_zero_offset_noop(self) -> None: + from quantalib._helpers import _offset + arr = np.array([1.0, 2.0, 3.0]) + result = _offset(arr, 0) + np.testing.assert_array_equal(result, arr) + + def test_positive_offset(self) -> None: + from quantalib._helpers import _offset + arr = np.array([1.0, 2.0, 3.0, 4.0]) + result = _offset(arr, 2) + assert np.isnan(result[0]) + assert np.isnan(result[1]) + assert result[2] == 1.0 + assert result[3] == 2.0 + + def test_negative_offset(self) -> None: + from quantalib._helpers import _offset + arr = np.array([1.0, 2.0, 3.0, 4.0]) + result = _offset(arr, -1) + assert result[0] == 2.0 + assert result[1] == 3.0 + assert result[2] == 4.0 + assert np.isnan(result[3]) + + +# --------------------------------------------------------------------------- +# _wrap and _wrap_multi +# --------------------------------------------------------------------------- +class TestWrap: + """Tests for _wrap() and _wrap_multi().""" + + def test_wrap_numpy_no_offset(self) -> None: + from quantalib._helpers import _wrap + arr = np.array([10.0, 20.0, 30.0]) + result = _wrap(arr, None, "TEST", "cat", 0) + assert isinstance(result, np.ndarray) + np.testing.assert_array_equal(result, arr) + + def test_wrap_numpy_with_offset(self) -> None: + from quantalib._helpers import _wrap + arr = np.array([10.0, 20.0, 30.0]) + result = _wrap(arr, None, "TEST", "cat", 1) + assert np.isnan(result[0]) + assert result[1] == 10.0 + + @pytest.mark.skipif(not _has_pandas(), reason="pandas not installed") + def test_wrap_pandas_series_category_in_attrs(self) -> None: + import pandas as pd + from quantalib._helpers import _wrap + idx = pd.RangeIndex(3) + arr = np.array([10.0, 20.0, 30.0]) + result = _wrap(arr, idx, "SMA_10", "trends_fir", 0) + assert isinstance(result, pd.Series) + assert result.name == "SMA_10" + assert result.attrs["category"] == "trends_fir" + + def test_wrap_multi_numpy(self) -> None: + from quantalib._helpers import _wrap_multi + arrays = { + "upper": np.array([1.0, 2.0]), + "lower": np.array([0.5, 1.0]), + } + result = _wrap_multi(arrays, None, "cat", 0) + assert isinstance(result, tuple) + assert len(result) == 2 + + @pytest.mark.skipif(not _has_pandas(), reason="pandas not installed") + def test_wrap_multi_pandas_attrs(self) -> None: + import pandas as pd + from quantalib._helpers import _wrap_multi + idx = pd.RangeIndex(2) + arrays = { + "upper": np.array([1.0, 2.0]), + "lower": np.array([0.5, 1.0]), + } + result = _wrap_multi(arrays, idx, "channels", 0) + assert isinstance(result, pd.DataFrame) + assert result.attrs["category"] == "channels" + + +# --------------------------------------------------------------------------- +# _out +# --------------------------------------------------------------------------- +class TestOut: + """Tests for _out() allocation.""" + + def test_out_shape_and_dtype(self) -> None: + from quantalib._helpers import _out + arr = _out(100) + assert arr.shape == (100,) + assert arr.dtype == np.float64 + + +# --------------------------------------------------------------------------- +# _ptr +# --------------------------------------------------------------------------- +class TestPtr: + """Tests for _ptr() ctypes pointer extraction.""" + + def test_ptr_not_none(self) -> None: + from quantalib._helpers import _ptr + arr = np.array([1.0, 2.0, 3.0], dtype=np.float64) + p = _ptr(arr) + assert p is not None diff --git a/python/tests/test_module_imports.py b/python/tests/test_module_imports.py new file mode 100644 index 00000000..22529127 --- /dev/null +++ b/python/tests/test_module_imports.py @@ -0,0 +1,41 @@ +"""test_module_imports.py — Verify all category modules import without SyntaxError.""" +from __future__ import annotations + +import importlib +import pytest + +# Every module that must be importable (no native lib required at import time +# because all native calls are deferred to function invocation). +MODULES = [ + "quantalib._helpers", + "quantalib._compat", + "quantalib._loader", + "quantalib._bridge", + "quantalib.channels", + "quantalib.core", + "quantalib.cycles", + "quantalib.dynamics", + "quantalib.errors", + "quantalib.filters", + "quantalib.momentum", + "quantalib.numerics", + "quantalib.oscillators", + "quantalib.reversals", + "quantalib.statistics", + "quantalib.trends_fir", + "quantalib.trends_iir", + "quantalib.volatility", + "quantalib.volume", + "quantalib.indicators", +] + + +@pytest.mark.parametrize("modname", MODULES, ids=lambda m: m.split(".")[-1]) +def test_module_imports(modname: str) -> None: + """Each module must import without SyntaxError or ImportError. + + This catches reserved-keyword parameter names (lambda), duplicate + parameter names, and broken imports. + """ + mod = importlib.import_module(modname) + assert mod is not None diff --git a/python/tests/test_signatures.py b/python/tests/test_signatures.py new file mode 100644 index 00000000..d54d5903 --- /dev/null +++ b/python/tests/test_signatures.py @@ -0,0 +1,117 @@ +"""test_signatures.py — Verify function signatures have no reserved keywords or duplicates.""" +from __future__ import annotations + +import importlib +import inspect +import keyword +import pytest + +# All category modules with public indicator functions +CATEGORY_MODULES = [ + "quantalib.channels", + "quantalib.core", + "quantalib.cycles", + "quantalib.dynamics", + "quantalib.errors", + "quantalib.filters", + "quantalib.momentum", + "quantalib.numerics", + "quantalib.oscillators", + "quantalib.reversals", + "quantalib.statistics", + "quantalib.trends_fir", + "quantalib.trends_iir", + "quantalib.volatility", + "quantalib.volume", +] + + +def _get_public_functions(): + """Yield (module_name, func_name, func) for all public functions.""" + for modname in CATEGORY_MODULES: + mod = importlib.import_module(modname) + all_names = getattr(mod, "__all__", []) + for name in all_names: + fn = getattr(mod, name, None) + if fn is not None and callable(fn): + yield modname, name, fn + + +@pytest.fixture(scope="module") +def all_functions(): + return list(_get_public_functions()) + + +class TestNoReservedKeywords: + """No function parameter should use a Python reserved keyword.""" + + def test_no_reserved_keyword_params(self, all_functions) -> None: + violations = [] + for modname, fname, fn in all_functions: + sig = inspect.signature(fn) + for pname in sig.parameters: + if keyword.iskeyword(pname): + violations.append(f"{modname}.{fname}(... {pname} ...)") + assert violations == [], ( + f"Reserved keyword used as parameter name:\n" + + "\n".join(f" - {v}" for v in violations) + ) + + +class TestNoDuplicateParams: + """No function should have duplicate parameter names (caught at parse time, + but this validates post-fix).""" + + def test_no_duplicate_params(self, all_functions) -> None: + violations = [] + for modname, fname, fn in all_functions: + sig = inspect.signature(fn) + params = list(sig.parameters.keys()) + if len(params) != len(set(params)): + seen = set() + dupes = [p for p in params if p in seen or seen.add(p)] # type: ignore[func-returns-value] + violations.append(f"{modname}.{fname}: duplicates={dupes}") + assert violations == [], ( + f"Duplicate parameter names found:\n" + + "\n".join(f" - {v}" for v in violations) + ) + + +class TestNoBuiltinShadowing: + """Public function names should not shadow critical Python builtins.""" + + CRITICAL_BUILTINS = {"super", "type", "id", "input", "print", "open", "list", "dict", "set", "map", "filter"} + + def test_no_builtin_function_names(self, all_functions) -> None: + violations = [] + for modname, fname, fn in all_functions: + if fname in self.CRITICAL_BUILTINS: + violations.append(f"{modname}.{fname}") + assert violations == [], ( + f"Function names shadow Python builtins:\n" + + "\n".join(f" - {v}" for v in violations) + ) + + +class TestVolumeIndicatorsHaveVolumeParam: + """Volume indicators that use _ptr(volume) must have volume in their signature.""" + + VOLUME_REQUIRED = [ + "adl", "adosc", "iii", "kvo", "va", "vwad", "vwap", "wad", + "obv", "pvt", "pvr", "vf", "nvi", "pvi", "tvi", "pvd", + "vwma", "evwma", "efi", "aobv", "mfi", "cmf", "eom", "pvo", + ] + + def test_volume_funcs_have_volume_param(self) -> None: + import quantalib.volume as vol + violations = [] + for fname in self.VOLUME_REQUIRED: + fn = getattr(vol, fname, None) + if fn is None: + continue + sig = inspect.signature(fn) + if "volume" not in sig.parameters: + violations.append(fname) + assert violations == [], ( + f"Volume functions missing 'volume' parameter: {violations}" + ) diff --git a/python/tools/complete_bridge.py b/python/tools/complete_bridge.py new file mode 100644 index 00000000..8aa6b63a --- /dev/null +++ b/python/tools/complete_bridge.py @@ -0,0 +1,1707 @@ +#!/usr/bin/env python3 +"""Complete the Python bridge by adding all Exports.cs (manual) bindings and wrappers. + +This script: +1. Rewrites _bridge.py with ALL bindings (Generated + Manual) +2. Appends missing wrappers to each category .py file +3. Rewrites indicators.py as thin re-export +4. Updates __init__.py +""" +import os +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PKG = os.path.join(ROOT, "quantalib") + + +def write(path: str, content: str) -> None: + with open(path, "w", encoding="utf-8", newline="\n") as f: + f.write(content) + print(f" wrote {os.path.relpath(path, ROOT)}") + + +def append(path: str, content: str) -> None: + with open(path, "a", encoding="utf-8", newline="\n") as f: + f.write(content) + print(f" appended to {os.path.relpath(path, ROOT)}") + + +def read(path: str) -> str: + with open(path, "r", encoding="utf-8") as f: + return f.read() + + +# ═══════════════════════════════════════════════════════════════════════════ +# Step 1: Read existing _bridge.py and add missing manual bindings +# ═══════════════════════════════════════════════════════════════════════════ +print("Step 1: Adding missing bindings to _bridge.py ...") + +bridge_path = os.path.join(PKG, "_bridge.py") +bridge = read(bridge_path) + +# Check which bindings already exist +MANUAL_BINDINGS = { + # ── Core ── + "qtl_avgprice": ["{dp}", "{dp}", "{dp}", "{dp}", "{ci}", "{dp}"], + "qtl_medprice": ["{dp}", "{dp}", "{ci}", "{dp}"], + "qtl_typprice": ["{dp}", "{dp}", "{dp}", "{ci}", "{dp}"], + "qtl_midbody": ["{dp}", "{dp}", "{ci}", "{dp}"], + # ── Momentum ── + "qtl_rsi": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_roc": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_mom": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_cmo": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_tsi": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + "qtl_apo": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + "qtl_bias": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_cfo": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_cfb": ["{dp}", "{ci}", "{dp}", "{ip}", "{ci}"], + "qtl_asi": ["{dp}", "{dp}", "{dp}", "{dp}", "{ci}", "{dp}", "{cd}"], + # ── Oscillators ── + "qtl_fisher": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_fisher04": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_dpo": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_trix": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_inertia": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_rsx": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_er": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_cti": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_reflex": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_trendflex": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_kri": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_psl": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_deco": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + "qtl_dosc": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}", "{ci}", "{ci}"], + "qtl_dymoi": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}", "{ci}", "{ci}", "{ci}"], + "qtl_crsi": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}", "{ci}"], + "qtl_bbb": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_bbi": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}", "{ci}", "{ci}"], + "qtl_dem": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_brar": ["{dp}", "{dp}", "{dp}", "{dp}", "{ci}", "{dp}", "{dp}", "{ci}"], + # ── Trends FIR ── + "qtl_sma": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_wma": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_hma": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_trima": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_swma": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_dwma": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_blma": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_alma": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}", "{cd}"], + "qtl_lsma": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}", "{cd}"], + "qtl_sgma": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_sinema": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_hanma": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_parzen": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_tsf": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_conv": ["{dp}", "{ci}", "{dp}", "{dp}", "{ci}"], + "qtl_bwma": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + "qtl_crma": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_sp15": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_tukey_w": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_rain": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_afirma": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}", "{ci}"], + # ── Trends IIR ── + "qtl_ema": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_ema_alpha": ["{dp}", "{ci}", "{dp}", "{cd}"], + "qtl_dema": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_dema_alpha":["{dp}", "{ci}", "{dp}", "{cd}"], + "qtl_tema": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_lema": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_hema": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_ahrens": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_decycler": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_dsma": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_gdema": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_coral": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_agc": ["{dp}", "{ci}", "{dp}", "{cd}"], + "qtl_ccyc": ["{dp}", "{ci}", "{dp}", "{cd}"], + # ── Channels ── + "qtl_bbands": ["{dp}", "{ci}", "{dp}", "{dp}", "{dp}", "{ci}", "{cd}"], + "qtl_aberr": ["{dp}", "{ci}", "{dp}", "{dp}", "{dp}", "{ci}", "{cd}"], + "qtl_atrbands": ["{dp}", "{dp}", "{dp}", "{ci}", "{dp}", "{dp}", "{dp}", "{ci}", "{cd}"], + "qtl_apchannel": ["{dp}", "{dp}", "{ci}", "{dp}", "{dp}", "{cd}"], + # ── Volatility ── + "qtl_tr": ["{dp}", "{dp}", "{dp}", "{ci}", "{dp}"], + "qtl_bbw": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_bbwn": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}", "{ci}"], + "qtl_bbwp": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}", "{ci}"], + "qtl_stddev": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_variance": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_etherm": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_ccv": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + "qtl_cv": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}", "{cd}"], + "qtl_cvi": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + "qtl_ewma": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}", "{ci}"], + # ── Volume ── + "qtl_obv": ["{dp}", "{dp}", "{ci}", "{dp}"], + "qtl_pvt": ["{dp}", "{dp}", "{ci}", "{dp}"], + "qtl_pvr": ["{dp}", "{dp}", "{ci}", "{dp}"], + "qtl_vf": ["{dp}", "{dp}", "{ci}", "{dp}"], + "qtl_nvi": ["{dp}", "{dp}", "{ci}", "{dp}"], + "qtl_pvi": ["{dp}", "{dp}", "{ci}", "{dp}"], + "qtl_tvi": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_pvd": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_vwma": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_evwma": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_efi": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_aobv": ["{dp}", "{dp}", "{ci}", "{dp}", "{dp}"], + "qtl_mfi": ["{dp}", "{dp}", "{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_cmf": ["{dp}", "{dp}", "{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_eom": ["{dp}", "{dp}", "{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_pvo": ["{dp}", "{ci}", "{dp}", "{dp}", "{dp}", "{ci}", "{ci}", "{ci}"], + # ── Statistics ── + "qtl_zscore": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_cma": ["{dp}", "{ci}", "{dp}"], + "qtl_entropy": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_correlation": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_covariance": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + "qtl_cointegration": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + # ── Errors ── + "qtl_mse": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_rmse": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_mae": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_mape": ["{dp}", "{dp}", "{ci}", "{dp}", "{ci}"], + # ── Filters ── + "qtl_bessel": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_butter2": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_butter3": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_cheby1": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_cheby2": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_elliptic": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_edcf": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_bpf": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + "qtl_alaguerre": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + "qtl_bilateral": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}", "{cd}"], + "qtl_baxterking": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}", "{ci}"], + "qtl_cfitz": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + # ── Cycles ── + "qtl_cg": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_dsp": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_ccor": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_ebsw": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], + "qtl_eacp": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}", "{ci}", "{ci}"], + # ── Numerics ── + "qtl_change": ["{dp}", "{ci}", "{dp}", "{ci}"], + "qtl_exptrans": ["{dp}", "{ci}", "{dp}"], + "qtl_betadist": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}", "{cd}"], + "qtl_expdist": ["{dp}", "{ci}", "{dp}", "{ci}", "{cd}"], + "qtl_binomdist":["{dp}", "{ci}", "{dp}", "{ci}", "{ci}", "{ci}"], + "qtl_cwt": ["{dp}", "{ci}", "{dp}", "{cd}", "{cd}"], + "qtl_dwt": ["{dp}", "{ci}", "{dp}", "{ci}", "{ci}"], +} + +# Build binding lines +TYPE_MAP = {"{dp}": "_dp", "{ci}": "_ci", "{cd}": "_cd", "{ip}": "_ip"} + +missing_bindings = {} # category -> list of lines +categories_order = [ + ("Core", ["qtl_avgprice","qtl_medprice","qtl_typprice","qtl_midbody"]), + ("Momentum", ["qtl_rsi","qtl_roc","qtl_mom","qtl_cmo","qtl_tsi","qtl_apo","qtl_bias","qtl_cfo","qtl_cfb","qtl_asi"]), + ("Oscillators", ["qtl_fisher","qtl_fisher04","qtl_dpo","qtl_trix","qtl_inertia","qtl_rsx","qtl_er","qtl_cti","qtl_reflex","qtl_trendflex","qtl_kri","qtl_psl","qtl_deco","qtl_dosc","qtl_dymoi","qtl_crsi","qtl_bbb","qtl_bbi","qtl_dem","qtl_brar"]), + ("Trends — FIR", ["qtl_sma","qtl_wma","qtl_hma","qtl_trima","qtl_swma","qtl_dwma","qtl_blma","qtl_alma","qtl_lsma","qtl_sgma","qtl_sinema","qtl_hanma","qtl_parzen","qtl_tsf","qtl_conv","qtl_bwma","qtl_crma","qtl_sp15","qtl_tukey_w","qtl_rain","qtl_afirma"]), + ("Trends — IIR", ["qtl_ema","qtl_ema_alpha","qtl_dema","qtl_dema_alpha","qtl_tema","qtl_lema","qtl_hema","qtl_ahrens","qtl_decycler","qtl_dsma","qtl_gdema","qtl_coral","qtl_agc","qtl_ccyc"]), + ("Channels", ["qtl_bbands","qtl_aberr","qtl_atrbands","qtl_apchannel"]), + ("Volatility", ["qtl_tr","qtl_bbw","qtl_bbwn","qtl_bbwp","qtl_stddev","qtl_variance","qtl_etherm","qtl_ccv","qtl_cv","qtl_cvi","qtl_ewma"]), + ("Volume", ["qtl_obv","qtl_pvt","qtl_pvr","qtl_vf","qtl_nvi","qtl_pvi","qtl_tvi","qtl_pvd","qtl_vwma","qtl_evwma","qtl_efi","qtl_aobv","qtl_mfi","qtl_cmf","qtl_eom","qtl_pvo"]), + ("Statistics", ["qtl_zscore","qtl_cma","qtl_entropy","qtl_correlation","qtl_covariance","qtl_cointegration"]), + ("Errors", ["qtl_mse","qtl_rmse","qtl_mae","qtl_mape"]), + ("Filters", ["qtl_bessel","qtl_butter2","qtl_butter3","qtl_cheby1","qtl_cheby2","qtl_elliptic","qtl_edcf","qtl_bpf","qtl_alaguerre","qtl_bilateral","qtl_baxterking","qtl_cfitz"]), + ("Cycles", ["qtl_cg","qtl_dsp","qtl_ccor","qtl_ebsw","qtl_eacp"]), + ("Numerics", ["qtl_change","qtl_exptrans","qtl_betadist","qtl_expdist","qtl_binomdist","qtl_cwt","qtl_dwt"]), +] + +added_count = 0 +new_lines = [] +for cat, names in categories_order: + cat_lines = [] + for name in names: + if f'"{name}"' in bridge: + continue # already bound + args = MANUAL_BINDINGS[name] + arg_str = ", ".join(TYPE_MAP[a] for a in args) + var = "HAS_" + name.replace("qtl_", "").upper() + cat_lines.append(f'{var} = _bind("{name}", [{arg_str}])') + added_count += 1 + if cat_lines: + new_lines.append(f"\n# ── {cat} (Exports.cs — manual) ──") + new_lines.extend(cat_lines) + +if new_lines: + # Append to end of _bridge.py + with open(bridge_path, "a", encoding="utf-8", newline="\n") as f: + f.write("\n") + f.write("\n".join(new_lines)) + f.write("\n") + print(f" Added {added_count} bindings to _bridge.py") +else: + print(" All bindings already present in _bridge.py") + + +# ═══════════════════════════════════════════════════════════════════════════ +# Step 2: Add missing wrappers to category .py files +# ═══════════════════════════════════════════════════════════════════════════ +print("\nStep 2: Adding missing wrappers to category files ...") + +# For each category file, check what's in __all__ and add missing funcs + + +# ── core.py ── +core_additions = ''' + +def avgprice(open: object, high: object, low: object, close: object, + offset: int = 0, **kwargs) -> object: + """Average Price = (O+H+L+C)/4.""" + offset = int(offset) + 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", offset) + + +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)) + + +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)) + + +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)) +''' + + +# ── momentum.py ── +momentum_additions = ''' +import ctypes + + +def rsi(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Relative Strength Index.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_rsi(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"RSI_{length}", "momentum", offset) + + +def roc(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Rate of Change.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_roc(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"ROC_{length}", "momentum", offset) + + +def mom(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Momentum.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_mom(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"MOM_{length}", "momentum", offset) + + +def cmo(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Chande Momentum Oscillator.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cmo(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"CMO_{length}", "momentum", offset) + + +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); short_period = int(short_period); offset = int(offset) + 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); slow = int(slow); offset = int(offset) + 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.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_bias(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"BIAS_{length}", "momentum", offset) + + +def cfo(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Chande Forecast Oscillator.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cfo(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"CFO_{length}", "momentum", offset) + + +def cfb(close: object, lengths: list | None = None, + offset: int = 0, **kwargs) -> object: + """Composite Fractal Behavior.""" + offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + 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)) +''' + + +# ── oscillators.py ── +oscillators_additions = ''' + +def fisher(close: object, length: int = 9, offset: int = 0, **kwargs) -> object: + """Fisher Transform.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_fisher(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"FISHER_{length}", "oscillators", offset) + + +def fisher04(close: object, length: int = 9, offset: int = 0, **kwargs) -> object: + """Fisher Transform (0.4 variant).""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_fisher04(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"FISHER04_{length}", "oscillators", offset) + + +def dpo(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Detrended Price Oscillator.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_dpo(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"DPO_{length}", "oscillators", offset) + + +def trix(close: object, length: int = 18, offset: int = 0, **kwargs) -> object: + """Triple EMA Rate of Change.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_trix(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TRIX_{length}", "oscillators", offset) + + +def inertia(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Inertia.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_inertia(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"INERTIA_{length}", "oscillators", offset) + + +def rsx(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Relative Strength Xtra.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_rsx(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"RSX_{length}", "oscillators", offset) + + +def er(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Efficiency Ratio.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_er(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"ER_{length}", "oscillators", offset) + + +def cti(close: object, length: int = 12, offset: int = 0, **kwargs) -> object: + """Correlation Trend Indicator.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cti(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"CTI_{length}", "oscillators", offset) + + +def reflex(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Reflex.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_reflex(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"REFLEX_{length}", "oscillators", offset) + + +def trendflex(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Trendflex.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_trendflex(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TRENDFLEX_{length}", "oscillators", offset) + + +def kri(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Kairi Relative Index.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_kri(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"KRI_{length}", "oscillators", offset) + + +def psl(close: object, length: int = 12, offset: int = 0, **kwargs) -> object: + """Psychological Line.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_psl(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"PSL_{length}", "oscillators", offset) + + +def deco(close: object, short_period: int = 30, long_period: int = 60, + offset: int = 0, **kwargs) -> object: + """DECO.""" + short_period = int(short_period); long_period = int(long_period); offset = int(offset) + 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}", "oscillators", 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.""" + offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_dosc(_ptr(src), n, _ptr(dst), + int(rsi_period), int(ema1_period), int(ema2_period), int(signal_period))) + return _wrap(dst, idx, f"DOSC_{rsi_period}", "oscillators", 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) + 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", "oscillators", 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) + 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}", "oscillators", offset) + + +def bbb(close: object, length: int = 20, mult: float = 2.0, + offset: int = 0, **kwargs) -> object: + """Bollinger Band Bounce.""" + length = int(length); mult = float(mult); offset = int(offset) + 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}", "oscillators", 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) + 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", "oscillators", offset) + + +def dem(high: object, low: object, length: int = 14, + offset: int = 0, **kwargs) -> object: + """DeMarker.""" + length = int(length) + 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}", "oscillators", int(offset)) + + +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); offset = int(offset) + 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, "oscillators", offset) +''' + + +# ── trends_fir.py ── +trends_fir_additions = ''' +import numpy as np + +_F64 = np.float64 + + +def sma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Simple Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_sma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"SMA_{length}", "trends_fir", offset) + + +def wma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_wma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"WMA_{length}", "trends_fir", offset) + + +def hma(close: object, length: int = 9, offset: int = 0, **kwargs) -> object: + """Hull Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_hma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"HMA_{length}", "trends_fir", offset) + + +def trima(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Triangular Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_trima(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TRIMA_{length}", "trends_fir", offset) + + +def swma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Symmetric Weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_swma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"SWMA_{length}", "trends_fir", offset) + + +def dwma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Double Weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_dwma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"DWMA_{length}", "trends_fir", offset) + + +def blma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Blackman Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_blma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"BLMA_{length}", "trends_fir", offset) + + +def alma(close: object, length: int = 10, alma_offset: float = 0.85, + sigma: float = 6.0, offset: int = 0, **kwargs) -> object: + """Arnaud Legoux Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_alma(_ptr(src), n, _ptr(dst), length, float(alma_offset), float(sigma))) + return _wrap(dst, idx, f"ALMA_{length}", "trends_fir", offset) + + +def lsma(close: object, length: int = 25, offset: int = 0, **kwargs) -> object: + """Least Squares Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_lsma(_ptr(src), n, _ptr(dst), length, 0, 1.0)) + return _wrap(dst, idx, f"LSMA_{length}", "trends_fir", offset) + + +def sgma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Savitzky-Golay Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_sgma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"SGMA_{length}", "trends_fir", offset) + + +def sinema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Sine-weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_sinema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"SINEMA_{length}", "trends_fir", offset) + + +def hanma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Hann-weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_hanma(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"HANMA_{length}", "trends_fir", offset) + + +def parzen(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Parzen-weighted Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_parzen(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"PARZEN_{length}", "trends_fir", offset) + + +def tsf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Time Series Forecast.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_tsf(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TSF_{length}", "trends_fir", offset) + + +def conv(close: object, kernel: list | None = None, + offset: int = 0, **kwargs) -> object: + """Convolution with custom kernel.""" + offset = int(offset) + 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", "trends_fir", offset) + + +def bwma(close: object, length: int = 10, order: int = 0, + offset: int = 0, **kwargs) -> object: + """Butterworth-weighted Moving Average.""" + length = int(length); offset = int(offset) + 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}", "trends_fir", 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); offset = int(offset) + 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}", "trends_fir", offset) + + +def sp15(close: object, length: int = 15, offset: int = 0, **kwargs) -> object: + """SP-15 Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_sp15(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"SP15_{length}", "trends_fir", offset) + + +def tukey_w(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Tukey-windowed Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_tukey_w(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TUKEY_{length}", "trends_fir", offset) + + +def rain(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """RAIN Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_rain(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"RAIN_{length}", "trends_fir", offset) + + +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); offset = int(offset) + 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}", "trends_fir", offset) +''' + + +# ── trends_iir.py ── +trends_iir_additions = ''' + +def ema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Exponential Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_ema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"EMA_{length}", "trends_iir", offset) + + +def ema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object: + """EMA with explicit alpha.""" + offset = int(offset) + 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}", "trends_iir", offset) + + +def dema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Double Exponential Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_dema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"DEMA_{length}", "trends_iir", offset) + + +def dema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object: + """DEMA with explicit alpha.""" + offset = int(offset) + 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}", "trends_iir", offset) + + +def tema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Triple Exponential Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_tema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TEMA_{length}", "trends_iir", offset) + + +def lema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Laguerre-based EMA.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_lema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"LEMA_{length}", "trends_iir", offset) + + +def hema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Henderson EMA.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_hema(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"HEMA_{length}", "trends_iir", offset) + + +def ahrens(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Ahrens Moving Average.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_ahrens(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"AHRENS_{length}", "trends_iir", offset) + + +def decycler(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Simple Decycler.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_decycler(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"DECYCLER_{length}", "trends_iir", offset) + + +def dsma(close: object, length: int = 10, factor: float = 0.5, + offset: int = 0, **kwargs) -> object: + """Deviation-Scaled Moving Average.""" + length = int(length); offset = int(offset) + 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}", "trends_iir", offset) + + +def gdema(close: object, length: int = 10, vfactor: float = 1.0, + offset: int = 0, **kwargs) -> object: + """Generalized DEMA.""" + length = int(length); offset = int(offset) + 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}", "trends_iir", offset) + + +def coral(close: object, length: int = 10, friction: float = 0.4, + offset: int = 0, **kwargs) -> object: + """CORAL Trend.""" + length = int(length); offset = int(offset) + 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}", "trends_iir", offset) + + +def agc(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object: + """Automatic Gain Control.""" + offset = int(offset) + 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}", "trends_iir", offset) + + +def ccyc(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object: + """Cyber Cycle.""" + offset = int(offset) + 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}", "trends_iir", offset) +''' + + +# ── channels.py ── +channels_additions = ''' + +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); std = float(std); offset = int(offset) + 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); mult = float(mult); offset = int(offset) + src, idx = _arr(close); n = len(src) + upper = _out(n); mid = _out(n); lower = _out(n) + _check(_lib.qtl_aberr(_ptr(src), n, _ptr(mid), _ptr(upper), _ptr(lower), 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); mult = float(mult); offset = int(offset) + 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); offset = int(offset) + 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), float(length))) + return _wrap_multi({f"APCU_{length}": upper, f"APCL_{length}": lower}, idx, "channels", offset) +''' + + +# ── volatility.py ── +volatility_additions = ''' + +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)) + + +def bbw(close: object, length: int = 20, mult: float = 2.0, + offset: int = 0, **kwargs) -> object: + """Bollinger Band Width.""" + length = int(length); mult = float(mult); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_bbw(_ptr(src), n, _ptr(dst), length, mult)) + return _wrap(dst, idx, f"BBW_{length}", "volatility", offset) + + +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); mult = float(mult); lookback = int(lookback); offset = int(offset) + 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); mult = float(mult); lookback = int(lookback); offset = int(offset) + 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.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_stddev(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"STDDEV_{length}", "volatility", offset) + + +def variance(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Variance.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_variance(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"VAR_{length}", "volatility", offset) + + +def etherm(high: object, low: object, length: int = 14, + offset: int = 0, **kwargs) -> object: + """Elder Thermometer.""" + length = int(length) + 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)) + + +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); long_period = int(long_period); offset = int(offset) + 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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cv(_ptr(src), n, _ptr(dst), length, float(min_vol), float(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); roc_period = int(roc_period); offset = int(offset) + 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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_ewma(_ptr(src), n, _ptr(dst), length, int(is_pop), int(ann_factor))) + return _wrap(dst, idx, f"EWMA_{length}", "volatility", offset) +''' + + +# ── volume.py ── +volume_additions = ''' + +def obv(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """On-Balance Volume.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_obv(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "OBV", "volume", offset) + + +def pvt(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Price Volume Trend.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_pvt(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "PVT", "volume", offset) + + +def pvr(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Price Volume Rank.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_pvr(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "PVR", "volume", offset) + + +def vf(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Volume Flow.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_vf(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "VF", "volume", offset) + + +def nvi(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Negative Volume Index.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_nvi(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "NVI", "volume", offset) + + +def pvi(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Positive Volume Index.""" + offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_pvi(_ptr(c), _ptr(v), n, _ptr(dst))) + return _wrap(dst, idx, "PVI", "volume", offset) + + +def tvi(close: object, volume: object, length: int = 14, + offset: int = 0, **kwargs) -> object: + """Trade Volume Index.""" + length = int(length); offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_tvi(_ptr(c), _ptr(v), n, _ptr(dst), length)) + return _wrap(dst, idx, f"TVI_{length}", "volume", offset) + + +def pvd(close: object, volume: object, length: int = 14, + offset: int = 0, **kwargs) -> object: + """Price Volume Divergence.""" + length = int(length); offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_pvd(_ptr(c), _ptr(v), n, _ptr(dst), length)) + return _wrap(dst, idx, f"PVD_{length}", "volume", offset) + + +def vwma(close: object, volume: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Volume Weighted Moving Average.""" + length = int(length); offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_vwma(_ptr(c), _ptr(v), n, _ptr(dst), length)) + return _wrap(dst, idx, f"VWMA_{length}", "volume", offset) + + +def evwma(close: object, volume: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Elastic Volume Weighted Moving Average.""" + length = int(length); offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_evwma(_ptr(c), _ptr(v), n, _ptr(dst), length)) + return _wrap(dst, idx, f"EVWMA_{length}", "volume", offset) + + +def efi(close: object, volume: object, length: int = 13, + offset: int = 0, **kwargs) -> object: + """Elder Force Index.""" + length = int(length); offset = int(offset) + c, idx = _arr(close); v, _ = _arr(volume) + n = len(c); dst = _out(n) + _check(_lib.qtl_efi(_ptr(c), _ptr(v), n, _ptr(dst), length)) + return _wrap(dst, idx, f"EFI_{length}", "volume", offset) + + +def aobv(close: object, volume: object, offset: int = 0, **kwargs) -> object: + """Archer OBV -> (fast, slow) or DataFrame.""" + offset = int(offset) + 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); offset = int(offset) + 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); offset = int(offset) + 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); offset = int(offset) + 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, 1e9)) + 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); slow = int(slow); signal = int(signal); offset = int(offset) + 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) +''' + + +# ── statistics.py ── +statistics_additions = ''' + +def zscore(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Z-Score.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_zscore(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"ZSCORE_{length}", "statistics", offset) + + +def cma(close: object, offset: int = 0, **kwargs) -> object: + """Cumulative Moving Average.""" + offset = int(offset) + 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.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_entropy(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"ENTROPY_{length}", "statistics", offset) + + +def correlation(x: object, y: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Pearson Correlation.""" + length = int(length); offset = int(offset) + 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); offset = int(offset) + 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); offset = int(offset) + 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) +''' + + +# ── errors.py ── +errors_additions = ''' + +def mse(actual: object, predicted: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Mean Squared Error.""" + length = int(length); offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a); dst = _out(n) + _check(_lib.qtl_mse(_ptr(a), _ptr(p), n, _ptr(dst), length)) + return _wrap(dst, idx, f"MSE_{length}", "errors", offset) + + +def rmse(actual: object, predicted: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Root Mean Squared Error.""" + length = int(length); offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a); dst = _out(n) + _check(_lib.qtl_rmse(_ptr(a), _ptr(p), n, _ptr(dst), length)) + return _wrap(dst, idx, f"RMSE_{length}", "errors", offset) + + +def mae(actual: object, predicted: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Mean Absolute Error.""" + length = int(length); offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a); dst = _out(n) + _check(_lib.qtl_mae(_ptr(a), _ptr(p), n, _ptr(dst), length)) + return _wrap(dst, idx, f"MAE_{length}", "errors", offset) + + +def mape(actual: object, predicted: object, length: int = 20, + offset: int = 0, **kwargs) -> object: + """Mean Absolute Percentage Error.""" + length = int(length); offset = int(offset) + a, idx = _arr(actual); p, _ = _arr(predicted) + n = len(a); dst = _out(n) + _check(_lib.qtl_mape(_ptr(a), _ptr(p), n, _ptr(dst), length)) + return _wrap(dst, idx, f"MAPE_{length}", "errors", offset) +''' + + +# ── filters.py ── +filters_additions = ''' + +def bessel(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Bessel Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_bessel(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"BESSEL_{length}", "filters", offset) + + +def butter2(close: object, length: int = 14, gain: float = 1.0, + offset: int = 0, **kwargs) -> object: + """2nd-order Butterworth.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_butter2(_ptr(src), n, _ptr(dst), length, float(gain))) + return _wrap(dst, idx, f"BUTTER2_{length}", "filters", offset) + + +def butter3(close: object, length: int = 14, gain: float = 1.0, + offset: int = 0, **kwargs) -> object: + """3rd-order Butterworth.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_butter3(_ptr(src), n, _ptr(dst), length, float(gain))) + return _wrap(dst, idx, f"BUTTER3_{length}", "filters", offset) + + +def cheby1(close: object, length: int = 14, ripple: float = 0.5, + offset: int = 0, **kwargs) -> object: + """Chebyshev Type I.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cheby1(_ptr(src), n, _ptr(dst), length, float(ripple))) + return _wrap(dst, idx, f"CHEBY1_{length}", "filters", offset) + + +def cheby2(close: object, length: int = 14, ripple: float = 0.5, + offset: int = 0, **kwargs) -> object: + """Chebyshev Type II.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cheby2(_ptr(src), n, _ptr(dst), length, float(ripple))) + return _wrap(dst, idx, f"CHEBY2_{length}", "filters", offset) + + +def elliptic(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Elliptic (Cauer) Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_elliptic(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"ELLIPTIC_{length}", "filters", offset) + + +def edcf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object: + """Ehlers Distance Coefficient Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_edcf(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"EDCF_{length}", "filters", offset) + + +def bpf(close: object, length: int = 14, bandwidth: int = 5, + offset: int = 0, **kwargs) -> object: + """Bandpass Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_bpf(_ptr(src), n, _ptr(dst), length, int(bandwidth))) + return _wrap(dst, idx, f"BPF_{length}", "filters", offset) + + +def alaguerre(close: object, length: int = 20, order: int = 5, + offset: int = 0, **kwargs) -> object: + """Adaptive Laguerre Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_alaguerre(_ptr(src), n, _ptr(dst), length, int(order))) + return _wrap(dst, idx, f"ALAGUERRE_{length}", "filters", 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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_bilateral(_ptr(src), n, _ptr(dst), length, float(sigma_s), float(sigma_r))) + return _wrap(dst, idx, f"BILATERAL_{length}", "filters", 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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_baxterking(_ptr(src), n, _ptr(dst), length, int(min_period), int(max_period))) + return _wrap(dst, idx, f"BAXTERKING_{length}", "filters", offset) + + +def cfitz(close: object, length: int = 6, bw_period: int = 32, + offset: int = 0, **kwargs) -> object: + """Christiano-Fitzgerald Filter.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cfitz(_ptr(src), n, _ptr(dst), length, int(bw_period))) + return _wrap(dst, idx, f"CFITZ_{length}", "filters", offset) +''' + + +# ── cycles.py ── +cycles_additions = ''' + +def cg(close: object, length: int = 10, offset: int = 0, **kwargs) -> object: + """Center of Gravity.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cg(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"CG_{length}", "cycles", offset) + + +def dsp(close: object, length: int = 20, offset: int = 0, **kwargs) -> object: + """Dominant Cycle Period (DSP).""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_dsp(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"DSP_{length}", "cycles", offset) + + +def ccor(close: object, length: int = 20, alpha: float = 0.07, + offset: int = 0, **kwargs) -> object: + """Circular Correlation.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_ccor(_ptr(src), n, _ptr(dst), length, float(alpha))) + return _wrap(dst, idx, f"CCOR_{length}", "cycles", offset) + + +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); ssf_length = int(ssf_length); offset = int(offset) + 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.""" + offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_eacp(_ptr(src), n, _ptr(dst), int(min_period), int(max_period), int(avg_length), int(enhance))) + return _wrap(dst, idx, f"EACP_{min_period}_{max_period}", "cycles", offset) +''' + + +# ── numerics.py ── +numerics_additions = ''' + +def change(close: object, length: int = 1, offset: int = 0, **kwargs) -> object: + """Price Change.""" + length = int(length); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_change(_ptr(src), n, _ptr(dst), length)) + return _wrap(dst, idx, f"CHANGE_{length}", "numerics", offset) + + +def exptrans(close: object, offset: int = 0, **kwargs) -> object: + """Exponential Transform.""" + offset = int(offset) + 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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_betadist(_ptr(src), n, _ptr(dst), length, float(alpha), float(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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_expdist(_ptr(src), n, _ptr(dst), length, float(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); offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_binomdist(_ptr(src), n, _ptr(dst), length, int(trials), int(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.""" + offset = int(offset) + src, idx = _arr(close); n = len(src); dst = _out(n) + _check(_lib.qtl_cwt(_ptr(src), n, _ptr(dst), float(scale), float(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.""" + length = int(length); levels = int(levels); offset = int(offset) + 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) +''' + + +# Now apply all additions +additions = { + "core.py": (core_additions, ["avgprice", "medprice", "typprice", "midbody"]), + "momentum.py": (momentum_additions, ["rsi", "roc", "mom", "cmo", "tsi", "apo", "bias", "cfo", "cfb", "asi"]), + "oscillators.py": (oscillators_additions, ["fisher", "fisher04", "dpo", "trix", "inertia", "rsx", "er", "cti", "reflex", "trendflex", "kri", "psl", "deco", "dosc", "dymoi", "crsi", "bbb", "bbi", "dem", "brar"]), + "trends_fir.py": (trends_fir_additions, ["sma", "wma", "hma", "trima", "swma", "dwma", "blma", "alma", "lsma", "sgma", "sinema", "hanma", "parzen", "tsf", "conv", "bwma", "crma", "sp15", "tukey_w", "rain", "afirma"]), + "trends_iir.py": (trends_iir_additions, ["ema", "ema_alpha", "dema", "dema_alpha", "tema", "lema", "hema", "ahrens", "decycler", "dsma", "gdema", "coral", "agc", "ccyc"]), + "channels.py": (channels_additions, ["bbands", "aberr", "atrbands", "apchannel"]), + "volatility.py": (volatility_additions, ["tr", "bbw", "bbwn", "bbwp", "stddev", "variance", "etherm", "ccv", "cv", "cvi", "ewma"]), + "volume.py": (volume_additions, ["obv", "pvt", "pvr", "vf", "nvi", "pvi", "tvi", "pvd", "vwma", "evwma", "efi", "aobv", "mfi", "cmf", "eom", "pvo"]), + "statistics.py": (statistics_additions, ["zscore", "cma", "entropy", "correlation", "covariance", "cointegration"]), + "errors.py": (errors_additions, ["mse", "rmse", "mae", "mape"]), + "filters.py": (filters_additions, ["bessel", "butter2", "butter3", "cheby1", "cheby2", "elliptic", "edcf", "bpf", "alaguerre", "bilateral", "baxterking", "cfitz"]), + "cycles.py": (cycles_additions, ["cg", "dsp", "ccor", "ebsw", "eacp"]), + "numerics.py": (numerics_additions, ["change", "exptrans", "betadist", "expdist", "binomdist", "cwt", "dwt"]), +} + +total_added = 0 +for filename, (code, funcnames) in additions.items(): + filepath = os.path.join(PKG, filename) + content = read(filepath) + + # Check which functions are already defined + missing = [f for f in funcnames if f"\ndef {f}(" not in content] + if not missing: + print(f" {filename}: all {len(funcnames)} functions already present") + continue + + # Update __all__ to include new functions + # Find __all__ closing bracket + import re + all_match = re.search(r'__all__\s*=\s*\[([^\]]*)\]', content, re.DOTALL) + if all_match: + existing_all = all_match.group(1) + existing_names = [s.strip().strip('"').strip("'") for s in existing_all.split(",") if s.strip().strip('"').strip("'")] + new_names = [f for f in funcnames if f not in existing_names] + if new_names: + all_entries = existing_names + new_names + new_all = "__all__ = [\n" + "".join(f' "{n}",\n' for n in all_entries) + "]" + content = content[:all_match.start()] + new_all + content[all_match.end():] + + # Append the wrapper code + content += "\n" + code.strip() + "\n" + + write(filepath, content) + total_added += len(missing) + print(f" {filename}: added {len(missing)} functions: {', '.join(missing)}") + +print(f"\n Total wrappers added: {total_added}") + + +# ═══════════════════════════════════════════════════════════════════════════ +# Step 3: Rewrite indicators.py as thin re-export +# ═══════════════════════════════════════════════════════════════════════════ +print("\nStep 3: Rewriting indicators.py as re-export module ...") + +CATEGORY_MODULES = [ + "channels", "core", "cycles", "dynamics", "errors", "filters", + "momentum", "numerics", "oscillators", "reversals", "statistics", + "trends_fir", "trends_iir", "volatility", "volume", +] + +indicators_content = '''"""High-level indicator wrappers for quantalib. + +This module re-exports all indicator functions from per-category submodules. +Each function accepts numpy arrays (or pandas Series / DataFrame) and +returns the same type. + +Category submodules: + quantalib.channels — Bollinger Bands, Keltner, Donchian, etc. + quantalib.core — Price transforms (avgprice, medprice, etc.) + quantalib.cycles — Hilbert, Sinewave, CG, DSP, etc. + quantalib.dynamics — ADX, Ichimoku, Supertrend, etc. + quantalib.errors — MSE, RMSE, MAE, MAPE, Huber, etc. + quantalib.filters — Butterworth, Chebyshev, Kalman, etc. + quantalib.momentum — RSI, MACD, ROC, MOM, etc. + quantalib.numerics — FFT, sigmoid, slope, distributions, etc. + quantalib.oscillators — Stochastic, Fisher, Williams %R, etc. + quantalib.reversals — Pivot points, PSAR, fractals, etc. + quantalib.statistics — Z-score, correlation, linreg, etc. + quantalib.trends_fir — SMA, WMA, HMA, ALMA, etc. + quantalib.trends_iir — EMA, DEMA, TEMA, JMA, KAMA, etc. + quantalib.volatility — ATR, TR, Bollinger Width, etc. + quantalib.volume — OBV, VWAP, MFI, CMF, etc. +""" +from __future__ import annotations + +''' + +for mod in CATEGORY_MODULES: + indicators_content += f"from .{mod} import * # noqa: F401, F403\n" + +write(os.path.join(PKG, "indicators.py"), indicators_content) + + +# ═══════════════════════════════════════════════════════════════════════════ +# Step 4: Update __init__.py +# ═══════════════════════════════════════════════════════════════════════════ +print("\nStep 4: Updating __init__.py ...") + +init_content = '''"""quantalib — Python wrapper for QuanTAlib NativeAOT exports. + +Usage:: + + import quantalib as qtl + + result = qtl.sma(close_array, length=14) + result = qtl.bbands(close_array, length=20, std=2.0) +""" +from __future__ import annotations + +from pathlib import Path + +from ._loader import load_native_library +from . import indicators +from .indicators import * # noqa: F401, F403 — re-export all indicator functions + +# Re-export per-category submodules for direct access +from . import ( # noqa: F401 + channels, + core, + cycles, + dynamics, + errors, + filters, + momentum, + numerics, + oscillators, + reversals, + statistics, + trends_fir, + trends_iir, + volatility, + volume, +) + +from ._compat import ALIASES, get_compat +from ._bridge import ( + QtlError, + QtlNullPointerError, + QtlInvalidLengthError, + QtlInvalidParamError, + QtlInternalError, +) + +__all__ = [ + "load_native_library", + "indicators", + "channels", + "core", + "cycles", + "dynamics", + "errors", + "filters", + "momentum", + "numerics", + "oscillators", + "reversals", + "statistics", + "trends_fir", + "trends_iir", + "volatility", + "volume", + "ALIASES", + "get_compat", + "QtlError", + "QtlNullPointerError", + "QtlInvalidLengthError", + "QtlInvalidParamError", + "QtlInternalError", +] + + +def _resolve_version() -> str: + version_file = Path(__file__).resolve().parents[2] / "lib" / "VERSION" + if version_file.exists(): + version = version_file.read_text(encoding="utf-8").strip() + if version: + return version + return "0.0.0" + + +__version__ = _resolve_version() +''' + +write(os.path.join(PKG, "__init__.py"), init_content) + + +# ═══════════════════════════════════════════════════════════════════════════ +# Step 5: Count & verify +# ═══════════════════════════════════════════════════════════════════════════ +print("\nStep 5: Verification counts ...") + +import re as re2 + +total_funcs = 0 +for mod in CATEGORY_MODULES: + filepath = os.path.join(PKG, f"{mod}.py") + content = read(filepath) + funcs = re2.findall(r'^def (\w+)\(', content, re2.MULTILINE) + # Exclude private helpers + public = [f for f in funcs if not f.startswith('_')] + total_funcs += len(public) + print(f" {mod:15s}: {len(public):3d} functions") + +print(f" {'TOTAL':15s}: {total_funcs:3d} functions") +print("\nDone!") diff --git a/python/tools/count_wrappers.py b/python/tools/count_wrappers.py new file mode 100644 index 00000000..0772d615 --- /dev/null +++ b/python/tools/count_wrappers.py @@ -0,0 +1,9 @@ +import re, os +total = 0 +for f in sorted(os.listdir('python/quantalib')): + if f.endswith('.py') and not f.startswith('_') and f != 'indicators.py': + content = open(f'python/quantalib/{f}', encoding='utf-8').read() + defs = re.findall(r'^def (\w+)\(', content, re.MULTILINE) + total += len(defs) + print(f'{f}: {len(defs)} functions - {", ".join(defs[:10])}{"..." if len(defs) > 10 else ""}') +print(f'\nTotal: {total} wrapper functions across {len([f for f in os.listdir("python/quantalib") if f.endswith(".py") and not f.startswith("_") and f != "indicators.py"])} files') diff --git a/python/tools/diff_check.py b/python/tools/diff_check.py new file mode 100644 index 00000000..30f145f4 --- /dev/null +++ b/python/tools/diff_check.py @@ -0,0 +1,28 @@ +import re, os + +# Collect all function names from new category files +new_fns = set() +for f in sorted(os.listdir('python/quantalib')): + if f.endswith('.py') and not f.startswith('_') and f != 'indicators.py': + content = open(f'python/quantalib/{f}', encoding='utf-8').read() + new_fns.update(re.findall(r'^def (\w+)\(', content, re.MULTILINE)) + +# Collect from old indicators.py +old_content = open('python/quantalib/indicators.py', encoding='utf-8').read() +old_fns = set(re.findall(r'^def (\w+)\(', old_content, re.MULTILINE)) +old_fns = {f for f in old_fns if not f.startswith('_')} + +missing = sorted(old_fns - new_fns) +extra = sorted(new_fns - old_fns) + +with open('python/tools/diff_report.txt', 'w') as out: + out.write(f'Old indicators.py: {len(old_fns)} public functions\n') + out.write(f'New category files: {len(new_fns)} functions\n\n') + out.write(f'Missing from new ({len(missing)}):\n') + for m in missing: + out.write(f' {m}\n') + out.write(f'\nNew indicators not in old ({len(extra)}):\n') + for e in extra: + out.write(f' {e}\n') + +print('Done - see python/tools/diff_report.txt') diff --git a/python/tools/extract_sigs.py b/python/tools/extract_sigs.py new file mode 100644 index 00000000..30ea6206 --- /dev/null +++ b/python/tools/extract_sigs.py @@ -0,0 +1,18 @@ +"""Extract C# export signatures for category module generation.""" +import re + +cs = open('python/src/Exports.Generated.cs', encoding='utf-8').read() + +# Extract each function: entry point name + full C# parameter list +pattern = r'\[UnmanagedCallersOnly\(EntryPoint\s*=\s*"qtl_(\w+)"\)\]\s+public static int \w+\(([^)]+)\)' +matches = re.findall(pattern, cs) + +for name, params in matches: + # Parse param types + names + parts = [] + for p in params.split(','): + p = p.strip() + tokens = p.split() + if len(tokens) >= 2: + parts.append(f"{tokens[0]} {tokens[1]}") + print(f"qtl_{name}|{'|'.join(parts)}") diff --git a/python/tools/generate_category_modules.py b/python/tools/generate_category_modules.py new file mode 100644 index 00000000..88d3dd05 --- /dev/null +++ b/python/tools/generate_category_modules.py @@ -0,0 +1,767 @@ +#!/usr/bin/env python3 +"""Generate per-category Python indicator modules from Exports.Generated.cs. + +Reads the C# exports file and the lib/ directory structure to produce: + - python/quantalib/_helpers.py (shared wrapper infrastructure) + - python/quantalib/_bridge.py (ALL ctypes bindings) + - python/quantalib/{category}.py (one per lib/ category) + - python/quantalib/indicators.py (re-exports everything) + - python/quantalib/__init__.py (package root) + +Usage: + python python/tools/generate_category_modules.py +""" +from __future__ import annotations + +import os +import re +import sys +from dataclasses import dataclass, field +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +LIB_DIR = REPO_ROOT / "lib" +EXPORTS_CS = REPO_ROOT / "python" / "src" / "Exports.Generated.cs" +OUT_DIR = REPO_ROOT / "python" / "quantalib" + +# --------------------------------------------------------------------------- +# Category mapping: lib/ subdirectory → Python module name +# --------------------------------------------------------------------------- +CATEGORY_PY_NAME: dict[str, str] = { + "channels": "channels", + "core": "core", + "cycles": "cycles", + "dynamics": "dynamics", + "errors": "errors", + "filters": "filters", + "forecasts": "forecasts", + "momentum": "momentum", + "numerics": "numerics", + "oscillators": "oscillators", + "reversals": "reversals", + "statistics": "statistics_", # avoid shadowing stdlib 'statistics' + "trends_FIR": "trends_fir", + "trends_IIR": "trends_iir", + "volatility": "volatility", + "volume": "volume", +} + +# Subdirectories in lib/core/ that are NOT indicators (infrastructure) +CORE_SKIP = { + "collections", "ringbuffer", "simd", "tbar", "tbarseries", + "tests", "tseries", "tvalue", "_index.md", +} + +# Export names that don't map cleanly to a lib/ indicator dir +EXPORT_RENAMES: dict[str, str] = { + "htdcperiod": "ht_dcperiod", + "htdcphase": "ht_dcphase", + "htphasor": "ht_phasor", + "htsine": "ht_sine", + "httrendmode": "ht_trendmode", + "htit": "htit", + "ttmsqueeze": "ttm_squeeze", + "ttmtrend": "ttm_trend", + "ttmscalper": "ttm_scalper", + "ttmwave": "ttm_wave", + "ttmlrc": "ttm_lrc", +} + + +# --------------------------------------------------------------------------- +# Data model +# --------------------------------------------------------------------------- +@dataclass +class ExportInfo: + """Parsed info for a single [UnmanagedCallersOnly] export.""" + entry_name: str # e.g. "qtl_sma" + func_name: str # e.g. "sma" + cs_params: list[tuple[str, str]] # [(type, name), ...] + category: str = "" # resolved lib/ category + lib_indicator: str = "" # indicator dir name in lib/ + + @property + def py_module(self) -> str: + return CATEGORY_PY_NAME.get(self.category, self.category) + + +# --------------------------------------------------------------------------- +# Step 1: Build category lookup {indicator_name → category} +# --------------------------------------------------------------------------- +def build_category_map() -> dict[str, str]: + """Scan lib/ subdirectories to build indicator→category mapping.""" + cat_map: dict[str, str] = {} + + for cat_dir in sorted(LIB_DIR.iterdir()): + if not cat_dir.is_dir(): + continue + cat_name = cat_dir.name + if cat_name in ("bin", "obj", "feeds") or cat_name.startswith("_") or cat_name.startswith("."): + continue + + for ind_dir in sorted(cat_dir.iterdir()): + if not ind_dir.is_dir(): + continue + ind_name = ind_dir.name + if ind_name.startswith("_") or ind_name.startswith("."): + continue + if cat_name == "core" and ind_name in CORE_SKIP: + continue + + # Normalize: indicator directory names are lowercase + cat_map[ind_name.lower()] = cat_name + + return cat_map + + +# --------------------------------------------------------------------------- +# Step 2: Parse Exports.Generated.cs +# --------------------------------------------------------------------------- +RE_ENTRY = re.compile( + r'\[UnmanagedCallersOnly\(EntryPoint\s*=\s*"(qtl_\w+)"\)\]' +) +RE_FUNC = re.compile( + r'public\s+static\s+int\s+\w+\(([^)]*)\)' +) + + +def parse_cs_param(raw: str) -> tuple[str, str]: + """Parse 'double* source' → ('double*', 'source').""" + raw = raw.strip() + parts = raw.rsplit(None, 1) + if len(parts) == 2: + return (parts[0], parts[1]) + return (raw, "") + + +def parse_exports(cs_path: Path) -> list[ExportInfo]: + """Parse all [UnmanagedCallersOnly] exports from the C# file.""" + text = cs_path.read_text(encoding="utf-8") + lines = text.splitlines() + exports: list[ExportInfo] = [] + + i = 0 + while i < len(lines): + m = RE_ENTRY.search(lines[i]) + if m: + entry_name = m.group(1) # e.g. "qtl_sma" + func_name = entry_name[4:] # strip "qtl_" + + # Find the function signature (may be on next line) + for j in range(i + 1, min(i + 5, len(lines))): + fm = RE_FUNC.search(lines[j]) + if fm: + raw_params = fm.group(1) + params = [parse_cs_param(p) for p in raw_params.split(",")] + exports.append(ExportInfo( + entry_name=entry_name, + func_name=func_name, + cs_params=params, + )) + break + i = j + 1 + else: + i += 1 + + return exports + + +# --------------------------------------------------------------------------- +# Step 3: Resolve categories +# --------------------------------------------------------------------------- +def resolve_categories(exports: list[ExportInfo], cat_map: dict[str, str]) -> None: + """Assign each export to its lib/ category.""" + for exp in exports: + name = exp.func_name + + # Check rename mapping first + mapped = EXPORT_RENAMES.get(name, name) + + if mapped in cat_map: + exp.category = cat_map[mapped] + exp.lib_indicator = mapped + else: + # Try underscore variants + for variant in [mapped.replace("_", ""), mapped]: + if variant in cat_map: + exp.category = cat_map[variant] + exp.lib_indicator = variant + break + + # Special cases + if name == "ema_alpha": + exp.category = "trends_IIR" + exp.lib_indicator = "ema" + elif name == "dema_alpha": + exp.category = "trends_IIR" + exp.lib_indicator = "dema" + elif name in ("wclprice", "midpoint", "midprice", "medprice", + "avgprice", "typprice", "midbody", "ha"): + exp.category = "core" + exp.lib_indicator = name + elif name == "skeleton_noop": + exp.category = "_internal" + + if not exp.category and name != "skeleton_noop": + print(f" WARNING: No category for export '{name}'", file=sys.stderr) + + +# --------------------------------------------------------------------------- +# Step 4: Classify parameter patterns for ctypes/Python wrappers +# --------------------------------------------------------------------------- + +def classify_params(exp: ExportInfo) -> dict: + """Classify the export's parameter pattern for code generation.""" + params = exp.cs_params + ptypes = [p[0] for p in params] + pnames = [p[1] for p in params] + + info: dict = { + "inputs": [], # list of (cs_type, name, py_name) + "outputs": [], # list of (cs_type, name, py_name) + "int_params": [], # list of (name, py_name, default) + "double_params": [], # list of (name, py_name, default) + "n_param": None, # name of the length param + "pattern": "custom", + "argtypes": [], + } + + # Identify inputs (double*) that appear before outputs + # Heuristic: inputs come before 'n', outputs after + n_idx = None + for i, (t, n) in enumerate(params): + if t == "int" and n in ("n", "length") and n_idx is None: + # Special: some have 'n' later + pass + if n == "n" and t == "int": + n_idx = i + break + + if n_idx is None: + # n might be at different position, find it + for i, (t, n) in enumerate(params): + if t == "int" and n == "n": + n_idx = i + break + + return info + + +# --------------------------------------------------------------------------- +# Step 5: Generate ctypes argtypes string +# --------------------------------------------------------------------------- + +def cs_type_to_ctypes(cs_type: str) -> str: + """Convert C# parameter type to ctypes constant.""" + mapping = { + "double*": "_dp", + "int": "_ci", + "double": "_cd", + "int*": "_ip", + "long*": "_lp", + } + return mapping.get(cs_type, f"# UNKNOWN: {cs_type}") + + +def gen_argtypes(exp: ExportInfo) -> str: + """Generate the ctypes argtypes list for a binding.""" + parts = [cs_type_to_ctypes(t) for t, _ in exp.cs_params] + return "[" + ", ".join(parts) + "]" + + +# --------------------------------------------------------------------------- +# Step 6: Generate _bridge.py +# --------------------------------------------------------------------------- + +def gen_bridge(exports: list[ExportInfo], by_cat: dict[str, list[ExportInfo]]) -> str: + """Generate the complete _bridge.py file.""" + + lines = [ + '"""Low-level ctypes bindings for every quantalib NativeAOT export.', + '', + 'Auto-generated by generate_category_modules.py — DO NOT EDIT.', + '', + 'Each native function is bound via ``_bind`` at module load. If the shared', + 'library was compiled without a particular export the binding is silently', + 'skipped (the corresponding ``HAS_*`` flag stays False).', + '"""', + 'from __future__ import annotations', + '', + 'import ctypes', + 'from ctypes import c_double, c_int, POINTER', + 'from typing import Final', + '', + 'from ._loader import load_native_library', + '', + '# ---------------------------------------------------------------------------', + '# Status codes (mirror StatusCodes.cs)', + '# ---------------------------------------------------------------------------', + 'QTL_OK: Final[int] = 0', + 'QTL_ERR_NULL_PTR: Final[int] = 1', + 'QTL_ERR_INVALID_LENGTH: Final[int] = 2', + 'QTL_ERR_INVALID_PARAM: Final[int] = 3', + 'QTL_ERR_INTERNAL: Final[int] = 4', + '', + '', + 'class QtlError(Exception):', + ' """Base exception for quantalib native errors."""', + '', + '', + 'class QtlNullPointerError(QtlError):', + ' pass', + '', + '', + 'class QtlInvalidLengthError(QtlError):', + ' pass', + '', + '', + 'class QtlInvalidParamError(QtlError):', + ' pass', + '', + '', + 'class QtlInternalError(QtlError):', + ' pass', + '', + '', + '_STATUS_MAP: dict[int, type[QtlError]] = {', + ' QTL_ERR_NULL_PTR: QtlNullPointerError,', + ' QTL_ERR_INVALID_LENGTH: QtlInvalidLengthError,', + ' QTL_ERR_INVALID_PARAM: QtlInvalidParamError,', + ' QTL_ERR_INTERNAL: QtlInternalError,', + '}', + '', + '', + 'def _check(status: int) -> None:', + ' """Raise if *status* is not QTL_OK."""', + ' if status == QTL_OK:', + ' return', + ' exc_type = _STATUS_MAP.get(status, QtlError)', + ' raise exc_type(f"quantalib native call failed (status={status})")', + '', + '', + '# ---------------------------------------------------------------------------', + '# Load native library', + '# ---------------------------------------------------------------------------', + '_lib = load_native_library()', + '', + '# Shorthand type aliases', + '_dp = POINTER(c_double) # double*', + '_ip = POINTER(c_int) # int*', + '_lp = POINTER(ctypes.c_long) # long*', + '_ci = c_int', + '_cd = c_double', + '', + '', + 'def _bind(name: str, argtypes: list[object]) -> bool:', + ' """Bind a single native function. Returns True if found."""', + ' fn = getattr(_lib, name, None)', + ' if fn is None:', + ' return False', + ' fn.argtypes = argtypes', + ' fn.restype = _ci', + ' return True', + '', + '', + '# ---------------------------------------------------------------------------', + '# Health check', + '# ---------------------------------------------------------------------------', + 'HAS_SKELETON = _bind("qtl_skeleton_noop", [_dp, _ci, _dp])', + '', + ] + + # Category order + CAT_ORDER = [ + "core", "momentum", "oscillators", "trends_FIR", "trends_IIR", + "channels", "volatility", "volume", "statistics", "errors", + "filters", "cycles", "dynamics", "numerics", "reversals", "forecasts", + ] + + cat_labels = { + "core": "Core", + "momentum": "Momentum", + "oscillators": "Oscillators", + "trends_FIR": "Trends — FIR", + "trends_IIR": "Trends — IIR", + "channels": "Channels", + "volatility": "Volatility", + "volume": "Volume", + "statistics": "Statistics", + "errors": "Errors", + "filters": "Filters", + "cycles": "Cycles", + "dynamics": "Dynamics", + "numerics": "Numerics", + "reversals": "Reversals", + "forecasts": "Forecasts", + } + + for cat in CAT_ORDER: + if cat not in by_cat: + continue + exps = by_cat[cat] + label = cat_labels.get(cat, cat) + lines.append(f'# {"═" * 75}') + lines.append(f'# {label}') + lines.append(f'# {"═" * 75}') + + for exp in sorted(exps, key=lambda e: e.func_name): + varname = f"HAS_{exp.func_name.upper()}" + argtypes = gen_argtypes(exp) + lines.append(f'{varname} = _bind("{exp.entry_name}", {argtypes})') + + lines.append('') + + return "\n".join(lines) + + +# --------------------------------------------------------------------------- +# Step 7: Generate _helpers.py +# --------------------------------------------------------------------------- +def gen_helpers() -> str: + return '''"""Shared wrapper helpers for quantalib indicator modules. + +Auto-generated by generate_category_modules.py — DO NOT EDIT. +""" +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): + 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()) + + +# ═══════════════════════════════════════════════════════════════════════════ +# Generic pattern helpers +# ═══════════════════════════════════════════════════════════════════════════ + +def _pa( + fn_name: str, close: object, length: int, offset: int, + default_length: int, label: str, category: str, +) -> object: + """Generic Pattern A wrapper: single-input + period.""" + 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) + + +def _pa3( + fn_name: str, close: object, offset: int, + label: str, category: str, +) -> object: + """Generic Pattern A3 wrapper: single-input, no params.""" + 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))) + return _wrap(dst, idx, label, category, offset) + + +def _pf( + fn_name: str, actual: object, predicted: object, + length: int, offset: int, default_length: int, + label: str, category: str, +) -> object: + """Generic Pattern F wrapper: 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}", category, offset) + + +def _pg( + fn_name: str, close: object, volume: object, + offset: int, label: str, category: 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, category, offset) + + +def _pg2( + fn_name: str, close: object, volume: object, length: int, + offset: int, default_length: int, label: str, category: 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}", category, offset) + + +def _ph( + fn_name: str, x: object, y: object, length: int, + offset: int, default_length: int, label: str, category: str, +) -> object: + """Pattern H: X+Y+period.""" + length = int(length) if length is not None else default_length + offset = int(offset) if offset is not None else 0 + xarr, idx = _arr(x) + yarr, _ = _arr(y) + n = len(xarr) + dst = _out(n) + _check(getattr(_lib, fn_name)(_ptr(xarr), _ptr(yarr), n, _ptr(dst), length)) + return _wrap(dst, idx, f"{label}_{length}", category, offset) + + +def _ohlcv_bars_period( + fn_name: str, open: object, high: object, low: object, + close: object, volume: object, period: int, + offset: int, default_period: int, label: str, category: str, +) -> object: + """OHLCV bars + period → single output (BuildBars pattern).""" + period = int(period) if period is not None else default_period + offset = int(offset) if offset is not None else 0 + o, idx = _arr(open) + h, _ = _arr(high) + l, _ = _arr(low) + c, _ = _arr(close) + v, _ = _arr(volume) + n = len(o) + dst = _out(n) + _check(getattr(_lib, fn_name)( + _ptr(o), _ptr(h), _ptr(l), _ptr(c), _ptr(v), period, n, _ptr(dst))) + return _wrap(dst, idx, f"{label}_{period}", category, offset) + + +def _hlc_period( + fn_name: str, high: object, low: object, close: object, + period: int, offset: int, default_period: int, + label: str, category: str, +) -> object: + """HLC + period → single output.""" + period = int(period) if period is not None else default_period + offset = int(offset) if offset is not None else 0 + h, idx = _arr(high) + l, _ = _arr(low) + c, _ = _arr(close) + n = len(h) + dst = _out(n) + _check(getattr(_lib, fn_name)( + _ptr(h), _ptr(l), _ptr(c), period, n, _ptr(dst))) + return _wrap(dst, idx, f"{label}_{period}", category, offset) + + +def _src_period( + fn_name: str, source: object, period: int, + offset: int, default_period: int, label: str, category: str, +) -> object: + """source + period → single output (BuildSeries pattern, src,period,n,dst).""" + period = int(period) if period is not None else default_period + offset = int(offset) if offset is not None else 0 + src, idx = _arr(source) + n = len(src) + dst = _out(n) + _check(getattr(_lib, fn_name)(_ptr(src), period, n, _ptr(dst))) + return _wrap(dst, idx, f"{label}_{period}", category, offset) +''' + + +# --------------------------------------------------------------------------- +# Step 8: Build per-category wrapper functions +# --------------------------------------------------------------------------- +# We derive wrapper signatures from the C# export signatures. + +# Manual mappings for export names that need specific Python wrapper treatment +# This defines the "known" wrappers. Anything not here gets auto-generated. + +# Map of lib/ directory name → default period for Pattern A indicators +DEFAULT_PERIODS: dict[str, int] = { + # Trends FIR + "sma": 10, "wma": 10, "hma": 9, "trima": 10, "swma": 10, "dwma": 10, + "blma": 10, "lsma": 25, "sgma": 10, "sinema": 10, "hanma": 10, + "parzen": 10, "tsf": 14, "sp15": 15, "tukey_w": 10, "rain": 10, + "fwma": 10, "gwma": 10, "hamma": 10, "hend": 10, "ilrs": 10, + "kaiser": 10, "lanczos": 10, "nlma": 10, "nyqma": 10, "pma": 10, + "pwma": 10, "qrma": 10, "rwma": 10, "bwma": 10, + # Trends IIR + "ema": 10, "dema": 10, "tema": 10, "lema": 10, "hema": 10, + "ahrens": 10, "decycler": 20, "frama": 10, "hwma": 10, + "jma": 10, "kama": 10, "ltma": 10, "mama": 10, "mavp": 10, + "mcnma": 10, "mgdi": 10, "mma": 10, "nma": 10, "qema": 10, + "rema": 10, "rgma": 10, "rma": 10, "t3": 10, "trama": 10, + "vidya": 10, "zldema": 10, "zlema": 10, "zltema": 10, + "adxvma": 14, "vama": 14, "yzvama": 14, + # Momentum + "rsi": 14, "roc": 10, "mom": 10, "cmo": 14, "bias": 26, + "cfo": 14, "rsx": 14, "pmo": 35, + "rocp": 10, "rocr": 10, "vel": 10, + # Oscillators + "fisher": 9, "fisher04": 9, "dpo": 20, "trix": 18, "inertia": 20, + "er": 10, "cti": 12, "reflex": 20, "trendflex": 20, "kri": 20, + "psl": 12, "lrsi": 14, + # Volatility + "bbw": 20, "stddev": 20, "variance": 20, "natr": 14, "massi": 14, + "ui": 14, "jvolty": 14, "jvoltyn": 14, "rsv": 14, "rv": 14, + "rvi": 14, "vov": 14, "vr": 14, + # Cycles + "cg": 10, "dsp": 20, "ccor": 20, + # Statistics + "zscore": 20, "entropy": 10, "geomean": 10, "harmean": 10, + "hurst": 100, "iqr": 20, "kurtosis": 20, "linreg": 14, + "meandev": 20, "median": 20, "mode": 20, "percentile": 20, + "polyfit": 20, "quantile": 20, "skew": 20, "spearman": 20, + "stddev": 20, "stderr": 20, "sum": 20, "theil": 20, + "trim": 20, "wavg": 20, "wins": 20, "ztest": 20, + "kendall": 20, "pacf": 20, + # Filters + "bessel": 14, "butter2": 14, "butter3": 14, "cheby1": 14, + "cheby2": 14, "elliptic": 14, "edcf": 14, "bpf": 14, + "loess": 14, "nw": 14, "rmed": 14, "sgf": 14, "spbf": 14, + "ssf2": 14, "ssf3": 14, "usf": 14, "voss": 14, + "wavelet": 14, "wiener": 14, + # Numerics + "change": 1, "highest": 14, "lowest": 14, "slope": 14, + "accel": 0, "jerk": 0, + # Errors (all pattern F, default 20) + "mse": 20, "rmse": 20, "mae": 20, "mape": 20, "smape": 20, + "msle": 20, "rmsle": 20, "me": 20, "mpe": 20, "mrae": 20, + "rse": 20, "rae": 20, "rsquared": 20, "wmape": 20, "wrmse": 20, + "mdae": 20, "mdape": 20, "mase": 20, "maape": 20, "mapd": 20, + "huber": 20, "logcosh": 20, "pseudohuber": 20, "tukeybiweight": 20, + "quantileloss": 20, "theilu": 20, +} + + +def main() -> None: + print("=== Generating quantalib per-category Python modules ===") + + # Step 1: Build category map + cat_map = build_category_map() + print(f" Found {len(cat_map)} indicators across {len(set(cat_map.values()))} categories") + + # Step 2: Parse exports + exports = parse_exports(EXPORTS_CS) + print(f" Parsed {len(exports)} exports from Exports.Generated.cs") + + # Step 3: Resolve categories + resolve_categories(exports, cat_map) + + # Group by category + by_cat: dict[str, list[ExportInfo]] = {} + uncategorized: list[ExportInfo] = [] + for exp in exports: + if exp.category and exp.category != "_internal": + by_cat.setdefault(exp.category, []).append(exp) + elif exp.category != "_internal": + uncategorized.append(exp) + + for cat in sorted(by_cat): + inds = sorted(e.func_name for e in by_cat[cat]) + print(f" {cat}: {len(inds)} indicators") + + if uncategorized: + print(f" UNCATEGORIZED: {[e.func_name for e in uncategorized]}") + + # Step 4: Generate _helpers.py + helpers_path = OUT_DIR / "_helpers.py" + helpers_path.write_text(gen_helpers(), encoding="utf-8") + print(f" Wrote {helpers_path}") + + # Step 5: Generate _bridge.py + bridge_path = OUT_DIR / "_bridge.py" + bridge_path.write_text(gen_bridge(exports, by_cat), encoding="utf-8") + print(f" Wrote {bridge_path}") + + # Step 6-8: will print summary + print("\n=== Summary ===") + print(f" Total exports: {len(exports)}") + print(f" Categorized: {sum(len(v) for v in by_cat.values())}") + print(f" Categories: {len(by_cat)}") + + +if __name__ == "__main__": + main() diff --git a/python/tools/generate_wrappers.py b/python/tools/generate_wrappers.py new file mode 100644 index 00000000..ad0145ce --- /dev/null +++ b/python/tools/generate_wrappers.py @@ -0,0 +1,927 @@ +#!/usr/bin/env python3 +"""Generate per-category Python wrapper modules from C# export signatures. + +Reads Exports.Generated.cs, maps exports to lib/ categories, +and generates one .py file per category under python/quantalib/. + +Run from repo root: + python python/tools/generate_wrappers.py +""" +from __future__ import annotations +import os +import re +import textwrap +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent.parent +CS_FILE = ROOT / "python" / "src" / "Exports.Generated.cs" +LIB_DIR = ROOT / "lib" +OUT_DIR = ROOT / "python" / "quantalib" + +# ── Category mapping ────────────────────────────────────────────────────── +# Scan lib/ subdirs to build export→category map +def build_category_map() -> dict[str, str]: + """Map indicator name (lowercase) → category folder name.""" + m: dict[str, str] = {} + for cat_dir in sorted(LIB_DIR.iterdir()): + if not cat_dir.is_dir() or cat_dir.name.startswith("."): + continue + cat = cat_dir.name + for ind_dir in sorted(cat_dir.iterdir()): + if ind_dir.is_dir() and not ind_dir.name.startswith("_"): + m[ind_dir.name.lower()] = cat + return m + +CAT_MAP = build_category_map() + +# Manual overrides for export names that differ from lib/ dir names +EXPORT_TO_LIB = { + "abber": "aberr", + "htdcperiod": "ht_dcperiod", + "htdcphase": "ht_dcphase", + "htphasor": "ht_phasor", + "htsine": "ht_sine", + "httrendmode": "ht_trendmode", + "htit": "htit", + "ttmlrc": "ttm_lrc", + "ttmscalper": "ttm_scalper", + "ttmsqueeze": "ttm_squeeze", + "ttmtrend": "ttm_trend", + "ttmwave": "ttm_wave", +} + +def get_category(export_name: str) -> str: + """Return category for an export name.""" + lib_name = EXPORT_TO_LIB.get(export_name, export_name) + if lib_name in CAT_MAP: + return CAT_MAP[lib_name] + # Some exports have _ removed vs lib dir (e.g. td_seq → tdseq) + for k, v in CAT_MAP.items(): + if k.replace("_", "") == export_name.replace("_", ""): + return v + return "uncategorized" + + +# ── Parse C# exports ───────────────────────────────────────────────────── +def parse_exports() -> list[dict]: + """Parse all exports from Exports.Generated.cs.""" + cs = CS_FILE.read_text(encoding="utf-8") + pattern = r'\[UnmanagedCallersOnly\(EntryPoint\s*=\s*"qtl_(\w+)"\)\]\s+public static int \w+\(([^)]+)\)' + exports = [] + for name, params_str in re.findall(pattern, cs): + params = [] + for p in params_str.split(","): + p = p.strip() + tokens = p.split() + if len(tokens) >= 2: + ptype = tokens[0] + pname = tokens[1] + params.append({"type": ptype, "name": pname}) + exports.append({ + "name": name, + "params": params, + "category": get_category(name), + }) + return exports + + +# ── Classify param roles ───────────────────────────────────────────────── +def classify_params(params): + """Identify inputs, outputs, scalars in a param list.""" + inputs = [] + outputs = [] + n_idx = None + scalars = [] + + for i, p in enumerate(params): + name = p["name"] + ptype = p["type"] + + if name == "n": + n_idx = i + continue + + if ptype == "double*": + # Heuristic: if name contains output/dst/destination/Out/middle/upper/lower etc. + out_names = {"output", "dst", "destination", "middle", "upper", "lower", + "haOpenOut", "haHighOut", "haLowOut", "haCloseOut", + "dstMiddle", "dstUpper", "dstLower", "dstTenkan", "dstKijun", + "dstSenkouA", "dstSenkouB", "dstChikou", + "kOut", "dOut", "jOut", "kstOut", "sigOut", + "rvgiOutput", "signalOutput", "signalOutput", + "momOut", "sqOut", "trend", "strength", + "sine", "leadSine", "inPhase", "quadrature", "ppOutput", + "upOutput", "downOutput", "highOutput", "lowOutput", + "pmaOutput", "triggerOutput", "famaOutput", + "upper1", "lower1", "upper2", "lower2", "vwap", "stdDev", + "viPlus", "viMinus", "midline", + "signal"} + if name in out_names or name.endswith("Out") or name.endswith("Output"): + outputs.append(p) + else: + inputs.append(p) + elif ptype == "int" or ptype == "double": + scalars.append(p) + + return inputs, outputs, n_idx, scalars + + +# ── Generate wrapper function ──────────────────────────────────────────── + +# Description map for well-known indicators +DESCRIPTIONS = { + # Core + "avgprice": "Average Price = (O+H+L+C)/4", + "ha": "Heikin-Ashi Candles", + "medprice": "Median Price = (H+L)/2", + "midbody": "Mid Body = (O+C)/2", + "midpoint": "Midpoint = src[i] over period", + "midprice": "Mid Price = (High+Low)/2 over period", + "typprice": "Typical Price = (H+L+C)/3", + "wclprice": "Weighted Close Price = (H+L+2*C)/4", + # Momentum + "asi": "Accumulative Swing Index", + "bias": "Bias Indicator", + "bop": "Balance of Power", + "cci": "Commodity Channel Index", + "cfb": "Composite Fractal Behavior", + "cmo": "Chande Momentum Oscillator", + "macd": "Moving Average Convergence Divergence", + "mom": "Momentum", + "pmo": "Price Momentum Oscillator", + "ppo": "Percentage Price Oscillator", + "prs": "Price Relative Strength", + "roc": "Rate of Change", + "rocp": "Rate of Change (Percentage)", + "rocr": "Rate of Change (Ratio)", + "rsi": "Relative Strength Index", + "rsx": "Relative Strength Xtra", + "sam": "Simple Alpha Momentum", + "tsi": "True Strength Index", + "vel": "Velocity", + # Oscillators + "ac": "Accelerator Oscillator", + "ao": "Awesome Oscillator", + "apo": "Absolute Price Oscillator", + "bbb": "Bollinger Band Bounce", + "bbi": "Bull Bear Index", + "bbs": "Bollinger Band Squeeze", + "brar": "Bull-Bear Ratio", + "cfo": "Chande Forecast Oscillator", + "coppock": "Coppock Curve", + "crsi": "Connors RSI", + "cti": "Correlation Trend Indicator", + "deco": "DECO Oscillator", + "dem": "DeMarker", + "dosc": "Derivative Oscillator", + "dpo": "Detrended Price Oscillator", + "dymoi": "Dynamic Momentum Index", + "er": "Efficiency Ratio", + "eri": "Elder Ray Index", + "fi": "Force Index", + "fisher": "Fisher Transform", + "fisher04": "Fisher Transform (0.4 variant)", + "gator": "Gator Oscillator", + "imi": "Intraday Momentum Index", + "inertia": "Inertia", + "kdj": "KDJ Indicator", + "kri": "Kairi Relative Index", + "kst": "Know Sure Thing", + "lrsi": "Laguerre RSI", + "marketfi": "Market Facilitation Index", + "mstoch": "Modified Stochastic", + "pgo": "Pretty Good Oscillator", + "psl": "Psychological Line", + "qqe": "Quantitative Qualitative Estimation", + "reflex": "Reflex", + "reverseema": "Reverse EMA", + "rvgi": "Relative Vigor Index", + "smi": "Stochastic Momentum Index", + "squeeze": "Squeeze Momentum", + "stc": "Schaff Trend Cycle", + "stoch": "Stochastic Oscillator", + "stochf": "Fast Stochastic", + "stochrsi": "Stochastic RSI", + "td_seq": "Tom DeMark Sequential", + "trendflex": "Trendflex", + "trix": "Triple EMA Rate of Change", + "ttmwave": "TTM Wave", + "ultosc": "Ultimate Oscillator", + "willr": "Williams %R", + # Trends FIR + "alma": "Arnaud Legoux Moving Average", + "blma": "Blackman Moving Average", + "bwma": "Butterworth-weighted Moving Average", + "conv": "Convolution Filter", + "crma": "Cosine-Ramp Moving Average", + "dwma": "Double Weighted Moving Average", + "fwma": "Fibonacci Weighted Moving Average", + "gwma": "Gaussian Weighted Moving Average", + "hamma": "Hamming Moving Average", + "hanma": "Hann Moving Average", + "hend": "Henderson Moving Average", + "hma": "Hull Moving Average", + "ilrs": "Integral of Linear Regression Slope", + "kaiser": "Kaiser Window Moving Average", + "lanczos": "Lanczos Moving Average", + "lsma": "Least Squares Moving Average", + "nlma": "Non-Lag Moving Average", + "nyqma": "Nyquist Moving Average", + "parzen": "Parzen Moving Average", + "pma": "Predictive Moving Average", + "pwma": "Pascal Weighted Moving Average", + "qrma": "Quick Reaction Moving Average", + "rain": "RAIN Moving Average", + "rwma": "Range Weighted Moving Average", + "sgma": "Savitzky-Golay Moving Average", + "sinema": "Sine Weighted Moving Average", + "sma": "Simple Moving Average", + "sp15": "SP-15 Moving Average", + "swma": "Symmetric Weighted Moving Average", + "trima": "Triangular Moving Average", + "tsf": "Time Series Forecast", + "tukey_w": "Tukey-windowed Moving Average", + "wma": "Weighted Moving Average", + # Trends IIR + "adxvma": "ADX Variable Moving Average", + "ahrens": "Ahrens Moving Average", + "coral": "CORAL Trend", + "decycler": "Simple Decycler", + "dema": "Double Exponential Moving Average", + "dsma": "Deviation-Scaled Moving Average", + "ema": "Exponential Moving Average", + "frama": "Fractal Adaptive Moving Average", + "gdema": "Generalized Double EMA", + "hema": "Henderson EMA", + "holt": "Holt Exponential Smoothing", + "htit": "Hilbert Transform Instantaneous Trendline", + "hwma": "Holt-Winter Moving Average", + "jma": "Jurik Moving Average", + "kama": "Kaufman Adaptive Moving Average", + "lema": "Laguerre EMA", + "ltma": "Low-Lag Triple Moving Average", + "mama": "MESA Adaptive Moving Average", + "mavp": "Moving Average Variable Period", + "mcnma": "McNicholl Moving Average", + "mgdi": "McGinley Dynamic", + "mma": "Modified Moving Average", + "nma": "Normalized Moving Average", + "qema": "Quadruple EMA", + "rema": "Regularized EMA", + "rgma": "Recursive Gaussian Moving Average", + "rma": "Rolling Moving Average", + "t3": "Tillson T3", + "tema": "Triple Exponential Moving Average", + "trama": "Triangular Adaptive Moving Average", + "vama": "Volume Adjusted Moving Average", + "vidya": "Variable Index Dynamic Average", + "yzvama": "Yang Zhang Volatility Adaptive MA", + "zldema": "Zero-Lag Double EMA", + "zlema": "Zero-Lag EMA", + "zltema": "Zero-Lag Triple EMA", + # Channels + "abber": "Aberration Bands", + "accbands": "Acceleration Bands", + "apchannel": "Average Price Channel", + "apz": "Adaptive Price Zone", + "atrbands": "ATR Bands", + "bbands": "Bollinger Bands", + "dchannel": "Donchian Channel", + "decaychannel": "Decay Channel", + "fcb": "Fractal Chaos Bands", + "jbands": "J-Line Bands", + "kchannel": "Keltner Channel", + "maenv": "Moving Average Envelope", + "mmchannel": "Min-Max Channel", + "pchannel": "Price Channel", + "regchannel": "Regression Channel", + "sdchannel": "Standard Deviation Channel", + "starchannel": "Stoller Average Range Channel (STARC)", + "stbands": "SuperTrend Bands", + "ttmlrc": "TTM Linear Regression Channel", + "ubands": "Upper/Lower Bands", + "uchannel": "Ulcer Channel", + "vwapbands": "VWAP Bands", + "vwapsd": "VWAP Standard Deviation", + # Volatility + "adr": "Average Daily Range", + "atr": "Average True Range", + "atrn": "Normalized ATR", + "bbw": "Bollinger Band Width", + "bbwn": "Bollinger Band Width Normalized", + "bbwp": "Bollinger Band Width Percentile", + "ccv": "Close-to-Close Volatility", + "cv": "Coefficient of Variation", + "cvi": "Chaikin Volatility Index", + "etherm": "Elder Thermometer", + "ewma": "Exponentially Weighted Moving Average Volatility", + "gkv": "Garman-Klass Volatility", + "hlv": "High-Low Volatility", + "hv": "Historical Volatility", + "jvolty": "Jurik Volatility", + "jvoltyn": "Jurik Volatility Normalized", + "massi": "Mass Index", + "natr": "Normalized ATR", + "rsv": "Rogers-Satchell Volatility", + "rv": "Realized Volatility", + "rvi": "Relative Volatility Index", + "tr": "True Range", + "ui": "Ulcer Index", + "vov": "Volatility of Volatility", + "vr": "Volatility Ratio", + "yzv": "Yang-Zhang Volatility", + # Volume + "adl": "Accumulation/Distribution Line", + "adosc": "Accumulation/Distribution Oscillator", + "aobv": "Archer On-Balance Volume", + "cmf": "Chaikin Money Flow", + "efi": "Elder Force Index", + "eom": "Ease of Movement", + "evwma": "Elastic Volume Weighted Moving Average", + "iii": "Intraday Intensity Index", + "kvo": "Klinger Volume Oscillator", + "mfi": "Money Flow Index", + "nvi": "Negative Volume Index", + "obv": "On-Balance Volume", + "pvd": "Price Volume Divergence", + "pvi": "Positive Volume Index", + "pvo": "Percentage Volume Oscillator", + "pvr": "Price Volume Rank", + "pvt": "Price Volume Trend", + "tvi": "Trade Volume Index", + "twap": "Time Weighted Average Price", + "va": "Volume Accumulation", + "vf": "Volume Flow", + "vo": "Volume Oscillator", + "vroc": "Volume Rate of Change", + "vwad": "Volume Weighted Accumulation/Distribution", + "vwap": "Volume Weighted Average Price", + "vwma": "Volume Weighted Moving Average", + "wad": "Williams Accumulation/Distribution", + # Statistics + "acf": "Autocorrelation Function", + "beta": "Beta Coefficient", + "cma": "Cumulative Moving Average", + "cointegration": "Cointegration", + "correlation": "Pearson Correlation", + "covariance": "Covariance", + "entropy": "Shannon Entropy", + "geomean": "Geometric Mean", + "granger": "Granger Causality", + "harmean": "Harmonic Mean", + "hurst": "Hurst Exponent", + "iqr": "Interquartile Range", + "jb": "Jarque-Bera Test", + "kendall": "Kendall Rank Correlation", + "kurtosis": "Kurtosis", + "linreg": "Linear Regression", + "meandev": "Mean Deviation", + "median": "Rolling Median", + "mode": "Rolling Mode", + "pacf": "Partial Autocorrelation Function", + "percentile": "Rolling Percentile", + "polyfit": "Polynomial Fit", + "quantile": "Rolling Quantile", + "skew": "Skewness", + "spearman": "Spearman Rank Correlation", + "stddev": "Standard Deviation", + "stderr": "Standard Error", + "sum": "Rolling Sum", + "theil": "Theil U Statistic", + "trim": "Trimmed Mean", + "variance": "Variance", + "wavg": "Weighted Average", + "wins": "Winsorized Mean", + "zscore": "Z-Score", + "ztest": "Z-Test", + # Errors + "huber": "Huber Loss", + "logcosh": "Log-Cosh Loss", + "maape": "Mean Arctangent Absolute Percentage Error", + "mae": "Mean Absolute Error", + "mapd": "Mean Absolute Percentage Deviation", + "mape": "Mean Absolute Percentage Error", + "mase": "Mean Absolute Scaled Error", + "mdae": "Median Absolute Error", + "mdape": "Median Absolute Percentage Error", + "me": "Mean Error", + "mpe": "Mean Percentage Error", + "mrae": "Mean Relative Absolute Error", + "mse": "Mean Squared Error", + "msle": "Mean Squared Logarithmic Error", + "pseudohuber": "Pseudo-Huber Loss", + "quantileloss": "Quantile Loss (Pinball Loss)", + "rae": "Relative Absolute Error", + "rmse": "Root Mean Squared Error", + "rmsle": "Root Mean Squared Logarithmic Error", + "rse": "Relative Squared Error", + "rsquared": "R-Squared (Coefficient of Determination)", + "smape": "Symmetric Mean Absolute Percentage Error", + "theilu": "Theil U Statistic (Error)", + "tukeybiweight": "Tukey Biweight Loss", + "wmape": "Weighted Mean Absolute Percentage Error", + "wrmse": "Weighted Root Mean Squared Error", + # Filters + "agc": "Automatic Gain Control", + "alaguerre": "Adaptive Laguerre Filter", + "baxterking": "Baxter-King Filter", + "bessel": "Bessel Filter", + "bilateral": "Bilateral Filter", + "bpf": "Bandpass Filter", + "butter2": "2nd-Order Butterworth Filter", + "butter3": "3rd-Order Butterworth Filter", + "cfitz": "Christiano-Fitzgerald Filter", + "cheby1": "Chebyshev Type I Filter", + "cheby2": "Chebyshev Type II Filter", + "edcf": "Ehlers Distance Coefficient Filter", + "elliptic": "Elliptic (Cauer) Filter", + "gauss": "Gaussian Filter", + "hann": "Hann Filter", + "hp": "Hodrick-Prescott Filter", + "hpf": "High-Pass Filter", + "kalman": "Kalman Filter", + "laguerre": "Laguerre Filter", + "lms": "Least Mean Squares Filter", + "loess": "LOESS Smoother", + "modf": "Modified Filter", + "notch": "Notch Filter", + "nw": "Nadaraya-Watson Filter", + "oneeuro": "1€ Filter", + "rls": "Recursive Least Squares Filter", + "rmed": "Running Median Filter", + "roofing": "Roofing Filter", + "sgf": "Savitzky-Golay Filter", + "spbf": "Short-Period Bandpass Filter", + "ssf2": "Super Smoother (2-pole)", + "ssf3": "Super Smoother (3-pole)", + "usf": "Universal Smoother Filter", + "voss": "Voss Predictor", + "wavelet": "Wavelet Filter", + "wiener": "Wiener Filter", + # Cycles + "ccor": "Circular Correlation", + "ccyc": "Cyber Cycle", + "cg": "Center of Gravity", + "dsp": "Dominant Cycle Period", + "eacp": "Ehlers Autocorrelation Periodogram", + "ebsw": "Even Better Sinewave", + "homod": "Homodyne Discriminator", + "ht_dcperiod": "Hilbert Transform Dominant Cycle Period", + "ht_dcphase": "Hilbert Transform Dominant Cycle Phase", + "ht_phasor": "Hilbert Transform Phasor", + "ht_sine": "Hilbert Transform Sine", + "lunar": "Lunar Cycle", + "solar": "Solar Cycle", + "ssfdsp": "Supersmoother DSP", + # Dynamics + "adx": "Average Directional Index", + "adxr": "ADX Rating", + "alligator": "Williams Alligator", + "amat": "Archer Moving Average Trends", + "aroon": "Aroon", + "aroonosc": "Aroon Oscillator", + "chop": "Choppiness Index", + "dmx": "Directional Movement Extended", + "dx": "Directional Movement Index", + "ghla": "Gann Hi-Lo Activator", + "ht_trendmode": "Hilbert Transform Trend Mode", + "ichimoku": "Ichimoku Cloud", + "impulse": "Elder Impulse System", + "pfe": "Polarized Fractal Efficiency", + "qstick": "QStick", + "ravi": "Range Action Verification Index", + "super": "SuperTrend", + "ttmsqueeze": "TTM Squeeze", + "ttmtrend": "TTM Trend", + "vhf": "Vertical Horizontal Filter", + "vortex": "Vortex Indicator", + # Reversals + "chandelier": "Chandelier Exit", + "ckstop": "Chuck LeBeau Stop", + "fractals": "Williams Fractals", + "pivot": "Pivot Points (Traditional)", + "pivotcam": "Camarilla Pivot Points", + "pivotdem": "DeMark Pivot Points", + "pivotext": "Extended Pivot Points", + "pivotfib": "Fibonacci Pivot Points", + "pivotwood": "Woodie Pivot Points", + "psar": "Parabolic SAR", + "swings": "Swing High/Low", + "ttmscalper": "TTM Scalper", + # Forecasts + "afirma": "Adaptive FIR Moving Average", + # Numerics + "accel": "Acceleration", + "betadist": "Beta Distribution", + "binomdist": "Binomial Distribution", + "change": "Price Change", + "cwt": "Continuous Wavelet Transform", + "dwt": "Discrete Wavelet Transform", + "expdist": "Exponential Distribution", + "exptrans": "Exponential Transform", + "fdist": "F-Distribution", + "fft": "Fast Fourier Transform", + "gammadist": "Gamma Distribution", + "highest": "Highest Value", + "ifft": "Inverse FFT", + "jerk": "Jerk (3rd derivative)", + "lineartrans": "Linear Transform", + "lognormdist": "Log-Normal Distribution", + "logtrans": "Logarithmic Transform", + "lowest": "Lowest Value", + "normalize": "Normalization", + "normdist": "Normal Distribution", + "poissondist": "Poisson Distribution", + "relu": "ReLU Activation", + "sigmoid": "Sigmoid Transform", + "slope": "Slope (1st derivative)", + "sqrttrans": "Square Root Transform", + "tdist": "Student's t-Distribution", + "weibulldist": "Weibull Distribution", +} + +# Python function name overrides (export_name → python_name) +PY_NAME = { + "abber": "aberr", # fix typo in C# export + "htdcperiod": "ht_dcperiod", + "htdcphase": "ht_dcphase", + "htphasor": "ht_phasor", + "htsine": "ht_sine", + "httrendmode": "ht_trendmode", + "ttmlrc": "ttm_lrc", + "ttmscalper": "ttm_scalper", + "ttmsqueeze": "ttm_squeeze", + "ttmtrend": "ttm_trend", + "ttmwave": "ttm_wave", +} + + +def gen_wrapper(export: dict) -> str | None: + """Generate a Python wrapper function for one export.""" + name = export["name"] + params = export["params"] + py_name = PY_NAME.get(name, name) + label = py_name.upper() + cat = export["category"] + desc = DESCRIPTIONS.get(name, DESCRIPTIONS.get(py_name, f"{label} indicator")) + + inputs, outputs, n_idx, scalars = classify_params(params) + + # Build Python function signature and body + lines = [] + + # Determine input pattern and generate accordingly + input_names = [p["name"] for p in inputs] + output_names = [p["name"] for p in outputs] + scalar_specs = [(p["name"], p["type"]) for p in scalars] + + # Build Python params + py_params = [] + py_body = [] + + # Categorize input types + has_ohlcv = all(x in [p["name"] for p in inputs] for x in ["sourceOpen", "sourceHigh", "sourceLow", "sourceClose", "sourceVolume"]) + has_ohlc = all(x in [p["name"] for p in inputs] for x in ["open", "high", "low", "close"]) and not has_ohlcv + has_hlc = all(x in [p["name"] for p in inputs] for x in ["high", "low", "close"]) and not has_ohlc and not has_ohlcv + has_hl = {"high", "low"}.issubset(set(input_names)) and "close" not in input_names and not has_ohlcv + has_actual_predicted = {"actual", "predicted"}.issubset(set(input_names)) + has_xy = {"seriesX", "seriesY"}.issubset(set(input_names)) or {"x", "y"}.issubset(set(input_names)) + has_src_vol = (len(inputs) == 2 and any("volume" in p["name"].lower() or p["name"] == "volume" for p in inputs)) + has_price_vol = (len(inputs) == 2 and any(p["name"] == "price" for p in inputs) and any(p["name"] == "volume" for p in inputs)) + single_src = len(inputs) == 1 and inputs[0]["type"] == "double*" + + # Generate function + # Decide function signature + sig_params = [] + + # Add input params + if has_ohlcv: + sig_params.extend([ + "open: object", "high: object", "low: object", + "close: object", "volume: object", + ]) + elif has_ohlc: + sig_params.extend([ + "open: object", "high: object", "low: object", "close: object", + ]) + elif has_hlc: + sig_params.extend(["high: object", "low: object", "close: object"]) + elif has_hl: + sig_params.extend(["high: object", "low: object"]) + elif has_actual_predicted: + sig_params.extend(["actual: object", "predicted: object"]) + elif has_xy: + sig_params.extend(["x: object", "y: object"]) + elif has_price_vol: + sig_params.extend(["price: object", "volume: object"]) + elif has_src_vol: + # Figure out which is source, which is volume + src_name = [p["name"] for p in inputs if p["name"] != "volume"][0] if inputs else "source" + sig_params.extend([f"close: object", "volume: object"]) + elif single_src: + src_name = inputs[0]["name"] if inputs else "source" + py_input_name = "close" if src_name in ("source", "src", "prices", "price") else src_name + sig_params.append(f"{py_input_name}: object") + elif len(inputs) == 2: + # Two inputs (e.g. prs: baseSeries, compSeries) + for p in inputs: + pn = p["name"] + if pn.startswith("source") or pn.startswith("base"): + pn = "x" + elif pn.startswith("comp"): + pn = "y" + sig_params.append(f"{pn}: object") + elif len(inputs) == 0 and len(outputs) == 0: + # Weird case + return None + else: + for p in inputs: + sig_params.append(f"{p['name']}: object") + + # Add scalar params with defaults + scalar_defaults = { + "period": 14, "length": 14, "hpLength": 40, "ssLength": 10, + "fastPeriod": 12, "slowPeriod": 26, "acPeriod": 5, + "bbPeriod": 20, "bbMult": 2.0, "kcPeriod": 10, "kcMult": 1.5, + "multiplier": 2.0, "factor": 2.0, "sigma": 6.0, + "rsiPeriod": 14, "smoothFactor": 5, "qqeFactor": 4.236, + "kPeriod": 14, "dPeriod": 3, "kSmooth": 3, "dSmooth": 3, + "kLength": 14, "windowSize": 256, "minPeriod": 6, "maxPeriod": 48, + "longRoc": 14, "shortRoc": 11, "wmaPeriod": 10, + "r1": 10, "r2": 15, "r3": 20, "r4": 30, + "s1": 10, "s2": 10, "s3": 10, "s4": 15, "sigPeriod": 9, + "jawPeriod": 13, "jawShift": 8, "jawOffset": 8, + "teethPeriod": 8, "teethShift": 5, "teethOffset": 5, + "lipsPeriod": 5, "lipsShift": 3, "lipsOffset": 3, + "tenkanPeriod": 9, "kijunPeriod": 26, "senkouBPeriod": 52, "displacement": 26, + "emaPeriod": 13, "macdFast": 12, "macdSlow": 26, "macdSignal": 9, + "signalPeriod": 9, "signal": 3, + "numHarmonics": 10, + "atrPeriod": 22, "stopPeriod": 3, + "alpha": 2.0, "beta": 2.0, "gamma": 0.7, "k": 2.0, + "lambda": 1600.0, "mu": 0.01, "mu0": 0.0, + "delta": 1.35, "c": 4.685, + "q": 0.3, "r": 1.0, + "vfactor": 0.7, "vovPeriod": 20, "volatilityPeriod": 20, + "d1": 10, "d2": 20, "nu": 10, + "order": 3, "polyOrder": 3, "feedback": 0, "fbWeight": 0.5, + "annualize": 1, "annualPeriods": 252, "isPopulation": 0, + "predict": 3, "bandwidth": 0.25, + "nanValue": 0.0, "initialLastValid": 0.0, "initialLast": 0.0, + "x0": 0.0, "intercept": 0.0, "slope_val": 1.0, + "minCutoff": 1.0, "dCutoff": 1.0, + "method": 0, "maType": 0, + "percentage": 2.5, "percent": 50.0, + "quantileLevel": 0.5, "quantile": 0.5, + "trimPct": 0.1, "winPct": 0.05, + "offset": 0, + "shortPeriod": 12, "longPeriod": 26, "sumLength": 25, + "emaLength": 9, "rmaLength": 14, "stdevLength": 10, + "stochLength": 14, "rsiLength": 14, + "fastLength": 23, "slowLength": 50, "smoothing": 10, + "lookback": 5, "useCloses": 0, + "levels": 4, "threshMult": 1.0, "smoothPeriod": 5, + "blau": 3, "phase": 0, "power": 1.0, + "rmsPeriod": 20, + "nyquistPeriod": 2, "passes": 3, + "cumulative": 0, "usePercent": 1, "useEma": 0, + "base": 2.0, "degree": 2, + "minLength": 5, "maxLength": 50, + "yzvShortPeriod": 10, "yzvLongPeriod": 100, "percentileLookback": 252, + "baseLength": 20, "shortAtrPeriod": 14, "longAtrPeriod": 50, + "strPeriod": 14, "centerPeriod": 20, + "stPeriod": 14, "momPeriod": 12, + "scale": 10.0, "omega": 6.0, + "trials": 20, "threshold": 10, + "lam": 3.0, "afStart": 0.02, "afIncrement": 0.02, "afMax": 0.2, + "cutoff": 10, "fastLimit": 0.5, "slowLimit": 0.05, + "minVol": 0.2, "maxVol": 0.7, + "friction": 0.4, + "avgLength": 3, "enhance": 1, + "numDevs": 2.0, + "window_type": 0, "use_simd": 0, + "hpLength_val": 40, "ssfLength": 10, + } + + for sname, stype in scalar_specs: + # Get reasonable default + default = scalar_defaults.get(sname) + if default is None: + # Try to infer + if "period" in sname.lower() or "length" in sname.lower(): + default = 14 + elif "mult" in sname.lower() or "factor" in sname.lower(): + default = 2.0 + elif stype == "double": + default = 1.0 + else: + default = 10 + + if stype == "double": + sig_params.append(f"{sname}: float = {default}") + else: + sig_params.append(f"{sname}: int = {int(default)}") + + sig_params.append("offset: int = 0") + sig_params.append("**kwargs") + + # Build function body + body = [] + + # Sanitize scalars + for sname, stype in scalar_specs: + if stype == "double": + body.append(f" {sname} = float({sname})") + else: + body.append(f" {sname} = int({sname})") + body.append(" offset = int(offset)") + + # Convert inputs + if has_ohlcv: + body.append(" o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low)") + body.append(" c, _ = _arr(close); v, _ = _arr(volume)") + body.append(" n = len(o)") + elif has_ohlc: + body.append(" o, idx = _arr(open); h, _ = _arr(high); l, _ = _arr(low); c, _ = _arr(close)") + body.append(" n = len(o)") + elif has_hlc: + body.append(" h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close)") + body.append(" n = len(h)") + elif has_hl: + body.append(" h, idx = _arr(high); l, _ = _arr(low)") + body.append(" n = len(h)") + elif has_actual_predicted: + body.append(" a, idx = _arr(actual); p, _ = _arr(predicted)") + body.append(" n = len(a)") + elif has_xy: + body.append(" xarr, idx = _arr(x); yarr, _ = _arr(y)") + body.append(" n = len(xarr)") + elif has_price_vol: + body.append(" pr, idx = _arr(price); v, _ = _arr(volume)") + body.append(" n = len(pr)") + elif has_src_vol: + body.append(" src, idx = _arr(close); v, _ = _arr(volume)") + body.append(" n = len(src)") + elif single_src: + py_input_name = "close" if inputs[0]["name"] in ("source", "src", "prices", "price") else inputs[0]["name"] + body.append(f" src, idx = _arr({py_input_name})") + body.append(" n = len(src)") + elif len(inputs) == 2: + body.append(f" xarr, idx = _arr(x); yarr, _ = _arr(y)") + body.append(" n = len(xarr)") + + # Allocate outputs + for p in outputs: + body.append(f" {p['name']} = _out(n)") + + # Build native call arguments in original order + call_args = [] + for p in params: + pname = p["name"] + ptype = p["type"] + if pname == "n": + call_args.append("n") + elif ptype == "double*": + if p in outputs: + call_args.append(f"_ptr({pname})") + else: + # Map to our local var names + if has_ohlcv: + vmap = {"sourceOpen": "o", "sourceHigh": "h", "sourceLow": "l", "sourceClose": "c", "sourceVolume": "v"} + call_args.append(f"_ptr({vmap.get(pname, pname)})") + elif has_ohlc: + vmap = {"open": "o", "high": "h", "low": "l", "close": "c"} + call_args.append(f"_ptr({vmap.get(pname, pname)})") + elif has_hlc: + vmap = {"high": "h", "low": "l", "close": "c"} + call_args.append(f"_ptr({vmap.get(pname, pname)})") + elif has_hl: + vmap = {"high": "h", "low": "l"} + call_args.append(f"_ptr({vmap.get(pname, pname)})") + elif has_actual_predicted: + vmap = {"actual": "a", "predicted": "p"} + call_args.append(f"_ptr({vmap.get(pname, pname)})") + elif has_xy: + vmap = {"seriesX": "xarr", "seriesY": "yarr", "x": "xarr", "y": "yarr"} + call_args.append(f"_ptr({vmap.get(pname, pname)})") + elif has_price_vol: + vmap = {"price": "pr", "volume": "v"} + call_args.append(f"_ptr({vmap.get(pname, pname)})") + elif has_src_vol: + if pname == "volume": + call_args.append("_ptr(v)") + else: + call_args.append("_ptr(src)") + elif single_src: + call_args.append("_ptr(src)") + elif len(inputs) == 2: + vmap = {} + for ip in inputs: + if ip["name"].startswith("source") or ip["name"].startswith("base"): + vmap[ip["name"]] = "xarr" + else: + vmap[ip["name"]] = "yarr" + call_args.append(f"_ptr({vmap.get(pname, pname)})") + else: + call_args.append(f"_ptr({pname})") + else: + call_args.append(pname) + + call_str = ", ".join(call_args) + body.append(f' _check(_lib.qtl_{name}({call_str}))') + + # Wrap output + if len(outputs) == 1: + out_name = outputs[0]["name"] + # Decide label + has_period_scalar = any("period" in s[0].lower() or "length" in s[0].lower() for s in scalar_specs) + if has_period_scalar: + # Use first period-like scalar for label + period_var = next(s[0] for s in scalar_specs if "period" in s[0].lower() or "length" in s[0].lower()) + body.append(f' return _wrap({out_name}, idx, f"{label}_{{{period_var}}}", "{cat}", offset)') + else: + body.append(f' return _wrap({out_name}, idx, "{label}", "{cat}", offset)') + elif len(outputs) > 1: + # Multi-output + out_dict_parts = [] + for p in outputs: + out_dict_parts.append(f'"{p["name"]}": {p["name"]}') + out_dict = ", ".join(out_dict_parts) + body.append(f' return _wrap_multi({{{out_dict}}}, idx, "{cat}", offset)') + else: + body.append(" return None # no output detected") + + # Assemble + sig = ", ".join(sig_params) + + func = f'def {py_name}({sig}) -> object:\n' + func += f' """{desc}."""\n' + func += "\n".join(body) + "\n" + + return func + + +def generate_category_file(category: str, exports: list[dict]) -> str: + """Generate a full category module.""" + # Map category to Python module name + mod_name = category.replace("-", "_") + + header = f'"""quantalib {category} indicators.\n\nAuto-generated — DO NOT EDIT.\n"""\n' + header += "from __future__ import annotations\n\n" + header += "from ._helpers import _arr, _ptr, _out, _wrap, _wrap_multi, _check, _lib\n\n\n" + + functions = [] + all_names = [] + + for exp in sorted(exports, key=lambda e: e["name"]): + func = gen_wrapper(exp) + if func: + py_name = PY_NAME.get(exp["name"], exp["name"]) + all_names.append(py_name) + functions.append(func) + + # __all__ + all_str = "__all__ = [\n" + for n in all_names: + all_str += f' "{n}",\n' + all_str += "]\n" + + return header + all_str + "\n\n" + "\n\n".join(functions) + + +def main(): + exports = parse_exports() + + # Group by category + by_cat: dict[str, list[dict]] = {} + for exp in exports: + cat = exp["category"] + by_cat.setdefault(cat, []).append(exp) + + print(f"Parsed {len(exports)} exports in {len(by_cat)} categories:") + for cat, exps in sorted(by_cat.items()): + print(f" {cat}: {len(exps)} indicators") + + # Generate files + for cat, exps in sorted(by_cat.items()): + if cat == "uncategorized": + continue + mod_name = cat.replace("-", "_") + # Map category dirs to Python module names + py_mod = { + "trends_FIR": "trends_fir", + "trends_IIR": "trends_iir", + }.get(mod_name, mod_name) + + outpath = OUT_DIR / f"{py_mod}.py" + content = generate_category_file(cat, exps) + outpath.write_text(content, encoding="utf-8") + print(f" Generated {outpath.name} ({len(exps)} indicators)") + + # List uncategorized + if "uncategorized" in by_cat: + print(f"\n UNCATEGORIZED: {[e['name'] for e in by_cat['uncategorized']]}") + + +if __name__ == "__main__": + main()