docs: add license rationale, Python/PineScript guides, API updates

- Add docs/license.md with Apache 2.0 rationale and patent protection analysis
- Add docs/python.md and docs/pinescript.md platform guides
- Expand README license section with disclosure and link to rationale
- Update docs/api.md and docs/architecture.md
- Update Python bindings: helpers, all indicator modules, pyproject.toml
- Add Python tests for Arrow and Polars integration
- Update TValue core type and documentation
- Add fix_length_to_period tooling script
This commit is contained in:
Miha Kralj
2026-03-03 22:11:35 -08:00
parent 6f4e083811
commit f10baa6dfb
28 changed files with 2050 additions and 542 deletions
+59 -33
View File
@@ -1,47 +1,74 @@
# quantalib — Python NativeAOT Wrapper
# quantalib
High-performance Python wrapper for [QuanTAlib](https://github.com/mihakralj/quantalib), a .NET NativeAOT technical analysis library.
[![PyPI](https://img.shields.io/pypi/v/quantalib?style=flat-square)](https://pypi.org/project/quantalib/)
[![Python](https://img.shields.io/pypi/pyversions/quantalib?style=flat-square)](https://pypi.org/project/quantalib/)
[![License](https://img.shields.io/pypi/l/quantalib?style=flat-square)](https://github.com/mihakralj/quantalib/blob/main/LICENSE)
## Features
- **~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
## Installation
393 technical analysis indicators compiled to native code via .NET NativeAOT, called from Python through `ctypes`. Same SIMD-accelerated engine as the [QuanTAlib](https://github.com/mihakralj/quantalib) .NET package. Zero Python math reimplementation.
```bash
pip install quantalib
```
> **Note:** The NativeAOT shared library (`quantalib_native.dll` / `.so` / `.dylib`) must be present in `quantalib/native/<platform>/`. Pre-built binaries are included in wheel distributions.
## Quick Start
```python
import numpy as np
import quantalib as qtl
close = np.random.randn(200).cumsum() + 100
close = np.random.default_rng(42).normal(100, 2, size=500)
# Simple Moving Average
sma = qtl.sma(close, length=20)
sma = qtl.sma(close, period=20)
rsi = qtl.rsi(close, period=14)
upper, mid, lower = qtl.bbands(close, period=20, std=2.0)
```
# Bollinger Bands (multi-output → tuple or DataFrame)
upper, mid, lower = qtl.bbands(close, length=20, std=2.0)
Works with **pandas**, **polars**, and **pyarrow** — same-type-in, same-type-out:
# With pandas
```python
# pandas — preserves index
import pandas as pd
s = pd.Series(close, name="close")
rsi = qtl.rsi(s, length=14) # returns pd.Series with preserved index
rsi = qtl.rsi(s, period=14) # → pd.Series
# polars — zero-copy-friendly
import polars as pl
s = pl.Series("close", close)
rsi = qtl.rsi(s, period=14) # → pl.Series
bb = qtl.bbands(s, period=20) # → pl.DataFrame (upper, mid, lower)
# pyarrow — for Arrow-native pipelines
import pyarrow as pa
a = pa.array(close, type=pa.float64())
rsi = qtl.rsi(a, period=14) # → pa.Array
```
> **pandas-ta users:** `length=` is accepted everywhere as an alias for `period=`.
Install optional backends:
```bash
pip install quantalib[pandas] # pandas / pd.Series support
pip install quantalib[polars] # polars / pl.Series support
pip install quantalib[pyarrow] # pyarrow / pa.Array support
pip install quantalib[all] # all three
```
## Performance (500,000 bars, AVX-512)
| Indicator | quantalib | pandas-ta | Ratio |
| --------- | --------: | --------: | ----: |
| SMA | 328 μs | ~50 ms | ~150× |
| EMA | 421 μs | ~45 ms | ~107× |
| WMA | 302 μs | ~60 ms | ~199× |
| RSI | 517 μs | ~80 ms | ~155× |
The `ctypes` call adds 5-15 μs overhead. For arrays above a few hundred bars, NativeAOT wins by two orders of magnitude.
## Categories
| Category | Module | Examples |
|----------|--------|----------|
| -------- | ------ | -------- |
| Channels | `channels` | bbands, kchannel, dchannel, aberr |
| Core | `core` | ha, midpoint, avgprice, typprice |
| Cycles | `cycles` | ht_dcperiod, ht_sine, cg, dsp |
@@ -58,21 +85,20 @@ rsi = qtl.rsi(s, length=14) # returns pd.Series with preserved index
| Volatility | `volatility` | atr, bbw, stddev, hv, tr |
| Volume | `volume` | obv, vwma, mfi, cmf, adl |
## Local Development
## Requirements
```bash
cd python/
python -m venv .venv && .venv/Scripts/activate # or source .venv/bin/activate
pip install -e ".[dev]"
pytest
```
- Python 3.10+
- NumPy >= 1.24
- Pre-built wheels: `win-x64`, `linux-x64`, `osx-x64`, `osx-arm64`
### Building the native library
### Optional dependencies
```bash
dotnet publish python.csproj -c Release
```
| Extra | Minimum version | Enables |
| ----- | --------------- | ------- |
| `pandas` | ≥ 1.5 | `pd.Series` / `pd.DataFrame` round-trip |
| `polars` | ≥ 0.20 | `pl.Series` / `pl.DataFrame` round-trip |
| `pyarrow` | ≥ 14.0 | `pa.Array` / `pa.ChunkedArray` round-trip |
## License
[MIT](../LICENSE)
[MIT](https://github.com/mihakralj/quantalib/blob/main/LICENSE)
+27 -9
View File
@@ -429,16 +429,22 @@ class QtlInternalError(QtlError): ... # status 4
### 6.4 Indicator wrappers (`indicators.py`)
- Expose pandas-ta-compatible function signatures where practical.
- Return types:
- Return types adapt to the input container type (**"same-type-in, same-type-out"**):
| Input type | Output (single) | Output (multi) |
|------------|-----------------|----------------|
| `pd.Series` | `pd.Series` | `pd.DataFrame` |
| `pd.DataFrame` | `pd.Series` | `pd.DataFrame` |
| `np.ndarray` | `np.ndarray` | tuple of `np.ndarray` |
| `pd.Series` | `pd.Series` | `pd.DataFrame` |
| `pd.DataFrame` | `pd.Series` (1st col) | `pd.DataFrame` |
| `pl.Series` | `pl.Series` | `pl.DataFrame` |
| `pl.DataFrame` | `pl.Series` (1st col) | `pl.DataFrame` |
| `pa.Array` | `pa.Array` (float64) | `dict[str, pa.Array]` |
| `pa.ChunkedArray` | `pa.Array` (float64) | `dict[str, pa.Array]` |
- Series names follow pandas-ta conventions: `SMA_14`, `BBU_20_2.0`, etc.
- DataFrame columns for multi-output: `BBU_20_2.0`, `BBM_20_2.0`, `BBL_20_2.0`.
- Polars Series `.name` is set to the indicator name (e.g. `"SMA_14"`).
- PyArrow arrays are always returned as `pa.float64()` type.
### 6.5 Concrete Python wrapper example
@@ -485,14 +491,26 @@ def sma(close, length=None, offset=None, **kwargs):
- Known deltas from pandas-ta warmup lengths are documented in the
compatibility table in `README.md`.
### 6.7 pandas fallback policy
### 6.7 Optional dependency policy
When pandas is not installed:
`numpy` is the only **hard** dependency. All DataFrame libraries are optional extras:
- Functions accept and return `np.ndarray` only.
- `pd.Series` / `pd.DataFrame` input raises `ImportError` with message
`"pandas required for Series/DataFrame input"`.
- `numpy` is a hard dependency; `pandas` is an optional extra.
| Extra | Install command | Enables |
|-------|----------------|---------|
| `pandas` | `pip install quantalib[pandas]` | `pd.Series` / `pd.DataFrame` I/O |
| `polars` | `pip install quantalib[polars]` | `pl.Series` / `pl.DataFrame` I/O |
| `pyarrow` | `pip install quantalib[pyarrow]` | `pa.Array` / `pa.ChunkedArray` I/O |
| `all` | `pip install quantalib[all]` | All of the above |
When a library is **not** installed, its input types are silently unsupported:
the `_arr()` helper falls through to `np.asarray()`, which may produce an
error or unexpected result. Because detection uses `isinstance`, there is no
import overhead when a library is absent.
**Origin token pattern:** `_arr()` returns an opaque `_Origin` token that
`_wrap()` / `_wrap_multi()` use to reconstruct the original container type.
Category modules never inspect this token — they pass it through unchanged.
This keeps all 15 category modules free of any polars/pyarrow awareness.
---
+4 -1
View File
@@ -38,7 +38,10 @@ Issues = "https://github.com/mihakralj/quantalib/issues"
[project.optional-dependencies]
pandas = ["pandas>=1.5"]
dev = ["pytest>=8.0", "pandas>=1.5"]
polars = ["polars>=0.20"]
pyarrow = ["pyarrow>=14.0"]
all = ["pandas>=1.5", "polars>=0.20", "pyarrow>=14.0"]
dev = ["pytest>=8.0", "pandas>=1.5", "polars>=0.20", "pyarrow>=14.0"]
[tool.pytest.ini_options]
testpaths = ["tests"]
+146 -38
View File
@@ -9,35 +9,103 @@ from numpy.typing import NDArray
from ._bridge import _lib, _check, _dp, _ci, _cd
# Optional pandas support
# ---------------------------------------------------------------------------
# Optional dataframe library imports (all optional; numpy is the only hard dep)
# ---------------------------------------------------------------------------
try:
import pandas as pd # type: ignore[import-untyped]
except ImportError: # pragma: no cover
pd = None # type: ignore[assignment]
try:
import polars as pl # type: ignore[import-untyped]
except ImportError: # pragma: no cover
pl = None # type: ignore[assignment]
try:
import pyarrow as pa # type: ignore[import-untyped]
except ImportError: # pragma: no cover
pa = None # type: ignore[assignment]
# ---------------------------------------------------------------------------
# Origin token — opaque marker passed from _arr() → _wrap()/_wrap_multi()
#
# Callers in category modules simply do:
# src, origin = _arr(close)
# ...
# return _wrap(dst, origin, name, category, offset)
#
# The token carries enough info to reconstruct the original container type.
# ---------------------------------------------------------------------------
_ORIGIN_NUMPY = "numpy"
_ORIGIN_PANDAS = "pandas"
_ORIGIN_POLARS = "polars"
_ORIGIN_PYARROW = "pyarrow"
class _Origin:
"""Lightweight tag recording the input container type + metadata."""
__slots__ = ("kind", "meta")
def __init__(self, kind: str, meta: object = None) -> None:
self.kind = kind
self.meta = meta # pandas.Index | polars series name | None
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
_F64 = np.float64
def _arr(x: object) -> tuple[NDArray[np.float64], object]:
"""Return (contiguous float64 array, original_index_or_None)."""
def _arr(x: object) -> tuple[NDArray[np.float64], _Origin | None]:
"""Return *(contiguous float64 array, origin_token)*.
Supported input types
---------------------
* ``numpy.ndarray``
* ``pandas.Series`` / ``pandas.DataFrame`` (first column)
* ``polars.Series`` / ``polars.DataFrame`` (first column)
* ``pyarrow.Array`` / ``pyarrow.ChunkedArray``
* Any sequence coercible via ``np.asarray``
"""
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)
origin: _Origin | None = None
# ── pandas ──────────────────────────────────────────────────────────
if pd is not None and isinstance(x, (pd.Series, pd.DataFrame)):
if isinstance(x, pd.DataFrame):
origin = _Origin(_ORIGIN_PANDAS, x.index)
x = x.iloc[:, 0].to_numpy(dtype=_F64, copy=False)
else:
origin = _Origin(_ORIGIN_PANDAS, x.index)
x = x.to_numpy(dtype=_F64, copy=False)
# ── polars ──────────────────────────────────────────────────────────
elif pl is not None and isinstance(x, (pl.Series, pl.DataFrame)):
if isinstance(x, pl.DataFrame):
col = x.get_column(x.columns[0])
origin = _Origin(_ORIGIN_POLARS, col.name)
x = col.cast(pl.Float64).to_numpy(allow_copy=True)
else:
origin = _Origin(_ORIGIN_POLARS, x.name)
x = x.cast(pl.Float64).to_numpy(allow_copy=True)
# ── pyarrow ─────────────────────────────────────────────────────────
elif pa is not None and isinstance(x, (pa.Array, pa.ChunkedArray)):
origin = _Origin(_ORIGIN_PYARROW)
x = x.to_numpy(zero_copy_only=False).astype(_F64, copy=False)
# ── coerce to numpy ────────────────────────────────────────────────
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
return arr, origin
def _ptr(a: NDArray[np.float64]): # noqa: ANN202
@@ -63,33 +131,73 @@ def _offset(arr: NDArray[np.float64], off: int) -> NDArray[np.float64]:
def _wrap(
arr: NDArray[np.float64],
idx: object,
origin: _Origin | None,
name: str,
category: str,
offset: int = 0,
):
"""Wrap result: apply offset, optionally convert to pd.Series."""
"""Wrap result: apply offset, reconstruct the original container type.
Return types by origin
----------------------
* numpy → ``np.ndarray``
* pandas → ``pd.Series`` with original index
* polars → ``pl.Series``
* pyarrow → ``pa.Array`` (float64)
"""
arr = _offset(arr, offset)
if idx is not None and pd is not None:
s = pd.Series(arr, index=idx, name=name)
if origin is None:
return arr
if origin.kind == _ORIGIN_PANDAS and pd is not None:
s = pd.Series(arr, index=origin.meta, name=name)
s.attrs["category"] = category
return s
if origin.kind == _ORIGIN_POLARS and pl is not None:
return pl.Series(name=name, values=arr)
if origin.kind == _ORIGIN_PYARROW and pa is not None:
return pa.array(arr, type=pa.float64())
return arr
def _wrap_multi(
arrays: dict[str, NDArray[np.float64]],
idx: object,
origin: _Origin | None,
category: str,
offset: int = 0,
):
"""Wrap multi-output result into tuple or DataFrame."""
"""Wrap multi-output result into the original container type.
Return types by origin
----------------------
* numpy → ``tuple[np.ndarray, ...]``
* pandas → ``pd.DataFrame`` with original index
* polars → ``pl.DataFrame``
* pyarrow → ``dict[str, pa.Array]``
"""
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)
if origin is None:
return tuple(arrays.values())
if origin.kind == _ORIGIN_PANDAS and pd is not None:
df = pd.DataFrame(arrays, index=origin.meta)
df.attrs["category"] = category
return df
if origin.kind == _ORIGIN_POLARS and pl is not None:
return pl.DataFrame(
{k: pl.Series(name=k, values=v) for k, v in arrays.items()}
)
if origin.kind == _ORIGIN_PYARROW and pa is not None:
return {k: pa.array(v, type=pa.float64()) for k, v in arrays.items()}
return tuple(arrays.values())
@@ -98,17 +206,17 @@ def _wrap_multi(
# ═══════════════════════════════════════════════════════════════════════════
def _pa(
fn_name: str, close: object, length: int, offset: int,
default_length: int, label: str, category: str,
fn_name: str, close: object, period: int, offset: int,
default_period: int, label: str, category: str,
) -> object:
"""Generic Pattern A wrapper: single-input + period."""
length = int(length) if length is not None else default_length
period = int(period) if period is not None else default_period
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)
_check(getattr(_lib, fn_name)(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"{label}_{period}", category, offset)
def _pa3(
@@ -126,18 +234,18 @@ def _pa3(
def _pf(
fn_name: str, actual: object, predicted: object,
length: int, offset: int, default_length: int,
period: int, offset: int, default_period: int,
label: str, category: str,
) -> object:
"""Generic Pattern F wrapper: actual+predicted+period."""
length = int(length) if length is not None else default_length
period = int(period) if period is not None else default_period
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)
_check(getattr(_lib, fn_name)(_ptr(a), _ptr(p), n, _ptr(dst), period))
return _wrap(dst, idx, f"{label}_{period}", category, offset)
def _pg(
@@ -155,33 +263,33 @@ def _pg(
def _pg2(
fn_name: str, close: object, volume: object, length: int,
offset: int, default_length: int, label: str, category: str,
fn_name: str, close: object, volume: object, period: int,
offset: int, default_period: int, label: str, category: str,
) -> object:
"""Pattern G2: source+volume+period."""
length = int(length) if length is not None else default_length
period = int(period) if period is not None else default_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), length))
return _wrap(dst, idx, f"{label}_{length}", category, offset)
_check(getattr(_lib, fn_name)(_ptr(c), _ptr(v), n, _ptr(dst), period))
return _wrap(dst, idx, f"{label}_{period}", category, offset)
def _ph(
fn_name: str, x: object, y: object, length: int,
offset: int, default_length: int, label: str, category: str,
fn_name: str, x: object, y: object, period: int,
offset: int, default_period: int, label: str, category: str,
) -> object:
"""Pattern H: X+Y+period."""
length = int(length) if length is not None else default_length
period = int(period) if period is not None else default_period
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)
_check(getattr(_lib, fn_name)(_ptr(xarr), _ptr(yarr), n, _ptr(dst), period))
return _wrap(dst, idx, f"{label}_{period}", category, offset)
def _ohlcv_bars_period(
+8 -8
View File
@@ -315,15 +315,15 @@ def vwapsd(price: object, volume: object, numDevs: float = 2.0, offset: int = 0,
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,
def bbands(close: object, period: 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)
period = int(kwargs.get("length", period)); 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))
_check(_lib.qtl_bbands(_ptr(src), n, _ptr(upper), _ptr(mid), _ptr(lower), period, std))
return _wrap_multi(
{f"BBU_{length}_{std}": upper, f"BBM_{length}_{std}": mid, f"BBL_{length}_{std}": lower},
{f"BBU_{period}_{std}": upper, f"BBM_{period}_{std}": mid, f"BBL_{period}_{std}": lower},
idx, "channels", offset)
@@ -341,12 +341,12 @@ def atrbands(high: object, low: object, close: object,
idx, "channels", offset)
def apchannel(high: object, low: object, length: int = 20,
def apchannel(high: object, low: object, period: int = 20,
offset: int = 0, **kwargs) -> object:
"""Average Price Channel -> (upper, lower) or DataFrame."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_apchannel(_ptr(h), _ptr(l), n, _ptr(upper), _ptr(lower), float(period)))
return _wrap_multi({f"APCU_{period}": upper, f"APCL_{period}": lower}, idx, "channels", offset)
+12 -12
View File
@@ -108,29 +108,29 @@ def ssfdsp(close: object, period: int = 14, offset: int = 0, **kwargs) -> object
_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:
def cg(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Center of Gravity."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_cg(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"CG_{period}", "cycles", offset)
def dsp(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
def dsp(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
"""Dominant Cycle Period (DSP)."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_dsp(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"DSP_{period}", "cycles", offset)
def ccor(close: object, length: int = 20, alpha: float = 0.07,
def ccor(close: object, period: int = 20, alpha: float = 0.07,
offset: int = 0, **kwargs) -> object:
"""Circular Correlation."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_ccor(_ptr(src), n, _ptr(dst), period, float(alpha)))
return _wrap(dst, idx, f"CCOR_{period}", "cycles", offset)
def ebsw(close: object, hp_length: int = 40, ssf_length: int = 10,
+16 -16
View File
@@ -282,41 +282,41 @@ def wrmse(actual: object, predicted: object, period: int = 14, offset: int = 0,
_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,
def mse(actual: object, predicted: object, period: int = 20,
offset: int = 0, **kwargs) -> object:
"""Mean Squared Error."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_mse(_ptr(a), _ptr(p), n, _ptr(dst), period))
return _wrap(dst, idx, f"MSE_{period}", "errors", offset)
def rmse(actual: object, predicted: object, length: int = 20,
def rmse(actual: object, predicted: object, period: int = 20,
offset: int = 0, **kwargs) -> object:
"""Root Mean Squared Error."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_rmse(_ptr(a), _ptr(p), n, _ptr(dst), period))
return _wrap(dst, idx, f"RMSE_{period}", "errors", offset)
def mae(actual: object, predicted: object, length: int = 20,
def mae(actual: object, predicted: object, period: int = 20,
offset: int = 0, **kwargs) -> object:
"""Mean Absolute Error."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_mae(_ptr(a), _ptr(p), n, _ptr(dst), period))
return _wrap(dst, idx, f"MAE_{period}", "errors", offset)
def mape(actual: object, predicted: object, length: int = 20,
def mape(actual: object, predicted: object, period: int = 20,
offset: int = 0, **kwargs) -> object:
"""Mean Absolute Percentage Error."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_mape(_ptr(a), _ptr(p), n, _ptr(dst), period))
return _wrap(dst, idx, f"MAPE_{period}", "errors", offset)
+56 -56
View File
@@ -57,15 +57,15 @@ def gauss(close: object, sigma: float = 1.0, offset: int = 0, **kwargs) -> objec
return _wrap(output, idx, "GAUSS", "filters", offset)
def hann(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
def hann(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
"""Hann Filter."""
length = int(length)
period = int(kwargs.get("length", period))
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)
_check(_lib.qtl_hann(_ptr(src), _ptr(output), n, period))
return _wrap(output, idx, f"HANN_{period}", "filters", offset)
def hp(close: object, lam: float = 1600.0, offset: int = 0, **kwargs) -> object:
@@ -79,15 +79,15 @@ def hp(close: object, lam: float = 1600.0, offset: int = 0, **kwargs) -> object:
return _wrap(output, idx, "HP", "filters", offset)
def hpf(close: object, length: int = 40, offset: int = 0, **kwargs) -> object:
def hpf(close: object, period: int = 40, offset: int = 0, **kwargs) -> object:
"""High-Pass Filter."""
length = int(length)
period = int(kwargs.get("length", period))
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)
_check(_lib.qtl_hpf(_ptr(src), _ptr(output), n, period))
return _wrap(output, idx, f"HPF_{period}", "filters", offset)
def kalman(close: object, q: float = 0.01, r: float = 0.1, offset: int = 0, **kwargs) -> object:
@@ -317,106 +317,106 @@ def wiener(close: object, period: int = 14, smoothPeriod: int = 10, offset: int
_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:
def bessel(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
"""Bessel Filter."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_bessel(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"BESSEL_{period}", "filters", offset)
def butter2(close: object, length: int = 14, gain: float = 1.0,
def butter2(close: object, period: int = 14, gain: float = 1.0,
offset: int = 0, **kwargs) -> object:
"""2nd-order Butterworth."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_butter2(_ptr(src), n, _ptr(dst), period, float(gain)))
return _wrap(dst, idx, f"BUTTER2_{period}", "filters", offset)
def butter3(close: object, length: int = 14, gain: float = 1.0,
def butter3(close: object, period: int = 14, gain: float = 1.0,
offset: int = 0, **kwargs) -> object:
"""3rd-order Butterworth."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_butter3(_ptr(src), n, _ptr(dst), period, float(gain)))
return _wrap(dst, idx, f"BUTTER3_{period}", "filters", offset)
def cheby1(close: object, length: int = 14, ripple: float = 0.5,
def cheby1(close: object, period: int = 14, ripple: float = 0.5,
offset: int = 0, **kwargs) -> object:
"""Chebyshev Type I."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_cheby1(_ptr(src), n, _ptr(dst), period, float(ripple)))
return _wrap(dst, idx, f"CHEBY1_{period}", "filters", offset)
def cheby2(close: object, length: int = 14, ripple: float = 0.5,
def cheby2(close: object, period: int = 14, ripple: float = 0.5,
offset: int = 0, **kwargs) -> object:
"""Chebyshev Type II."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_cheby2(_ptr(src), n, _ptr(dst), period, float(ripple)))
return _wrap(dst, idx, f"CHEBY2_{period}", "filters", offset)
def elliptic(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
def elliptic(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
"""Elliptic (Cauer) Filter."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_elliptic(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"ELLIPTIC_{period}", "filters", offset)
def edcf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
def edcf(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
"""Ehlers Distance Coefficient Filter."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_edcf(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"EDCF_{period}", "filters", offset)
def bpf(close: object, length: int = 14, bandwidth: int = 5,
def bpf(close: object, period: int = 14, bandwidth: int = 5,
offset: int = 0, **kwargs) -> object:
"""Bandpass Filter."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_bpf(_ptr(src), n, _ptr(dst), period, int(bandwidth)))
return _wrap(dst, idx, f"BPF_{period}", "filters", offset)
def alaguerre(close: object, length: int = 20, order: int = 5,
def alaguerre(close: object, period: int = 20, order: int = 5,
offset: int = 0, **kwargs) -> object:
"""Adaptive Laguerre Filter."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_alaguerre(_ptr(src), n, _ptr(dst), period, int(order)))
return _wrap(dst, idx, f"ALAGUERRE_{period}", "filters", offset)
def bilateral(close: object, length: int = 14, sigma_s: float = 0.5,
def bilateral(close: object, period: 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)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_bilateral(_ptr(src), n, _ptr(dst), period, float(sigma_s), float(sigma_r)))
return _wrap(dst, idx, f"BILATERAL_{period}", "filters", offset)
def baxterking(close: object, length: int = 12, min_period: int = 6,
def baxterking(close: object, period: int = 12, min_period: int = 6,
max_period: int = 32, offset: int = 0, **kwargs) -> object:
"""Baxter-King Filter."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_baxterking(_ptr(src), n, _ptr(dst), period, int(min_period), int(max_period)))
return _wrap(dst, idx, f"BAXTERKING_{period}", "filters", offset)
def cfitz(close: object, length: int = 6, bw_period: int = 32,
def cfitz(close: object, period: int = 6, bw_period: int = 32,
offset: int = 0, **kwargs) -> object:
"""Christiano-Fitzgerald Filter."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_cfitz(_ptr(src), n, _ptr(dst), period, int(bw_period)))
return _wrap(dst, idx, f"CFITZ_{period}", "filters", offset)
+24 -24
View File
@@ -145,36 +145,36 @@ def vel(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
_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:
def rsi(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
"""Relative Strength Index."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_rsi(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"RSI_{period}", "momentum", offset)
def roc(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def roc(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Rate of Change."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_roc(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"ROC_{period}", "momentum", offset)
def mom(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def mom(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Momentum."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_mom(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"MOM_{period}", "momentum", offset)
def cmo(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
def cmo(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
"""Chande Momentum Oscillator."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_cmo(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"CMO_{period}", "momentum", offset)
def tsi(close: object, long_period: int = 25, short_period: int = 13,
@@ -195,20 +195,20 @@ def apo(close: object, fast: int = 12, slow: int = 26,
return _wrap(dst, idx, f"APO_{fast}_{slow}", "momentum", offset)
def bias(close: object, length: int = 26, offset: int = 0, **kwargs) -> object:
def bias(close: object, period: int = 26, offset: int = 0, **kwargs) -> object:
"""Bias."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_bias(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"BIAS_{period}", "momentum", offset)
def cfo(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
def cfo(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
"""Chande Forecast Oscillator."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_cfo(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"CFO_{period}", "momentum", offset)
def cfb(close: object, lengths: list | None = None,
+20 -20
View File
@@ -269,12 +269,12 @@ def weibulldist(close: object, k: float = 1.5, lam: float = 1.0, period: int = 1
_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:
def change(close: object, period: int = 1, offset: int = 0, **kwargs) -> object:
"""Price Change."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_change(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"CHANGE_{period}", "numerics", offset)
def exptrans(close: object, offset: int = 0, **kwargs) -> object:
@@ -285,31 +285,31 @@ def exptrans(close: object, offset: int = 0, **kwargs) -> object:
return _wrap(dst, idx, "EXPTRANS", "numerics", offset)
def betadist(close: object, length: int = 50, alpha: float = 2.0,
def betadist(close: object, period: int = 50, alpha: float = 2.0,
beta: float = 2.0, offset: int = 0, **kwargs) -> object:
"""Beta Distribution."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_betadist(_ptr(src), n, _ptr(dst), period, float(alpha), float(beta)))
return _wrap(dst, idx, f"BETADIST_{period}", "numerics", offset)
def expdist(close: object, length: int = 50, lam: float = 3.0,
def expdist(close: object, period: int = 50, lam: float = 3.0,
offset: int = 0, **kwargs) -> object:
"""Exponential Distribution."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_expdist(_ptr(src), n, _ptr(dst), period, float(lam)))
return _wrap(dst, idx, f"EXPDIST_{period}", "numerics", offset)
def binomdist(close: object, length: int = 50, trials: int = 20,
def binomdist(close: object, period: int = 50, trials: int = 20,
threshold: int = 10, offset: int = 0, **kwargs) -> object:
"""Binomial Distribution."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_binomdist(_ptr(src), n, _ptr(dst), period, int(trials), int(threshold)))
return _wrap(dst, idx, f"BINOMDIST_{period}", "numerics", offset)
def cwt(close: object, scale: float = 10.0, omega: float = 6.0,
@@ -321,10 +321,10 @@ def cwt(close: object, scale: float = 10.0, omega: float = 6.0,
return _wrap(dst, idx, "CWT", "numerics", offset)
def dwt(close: object, length: int = 4, levels: int = 0,
def dwt(close: object, period: int = 4, levels: int = 0,
offset: int = 0, **kwargs) -> object:
"""Discrete Wavelet Transform."""
length = int(length); levels = int(levels); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_dwt(_ptr(src), n, _ptr(dst), period, levels))
return _wrap(dst, idx, f"DWT_{period}", "numerics", offset)
+59 -59
View File
@@ -156,9 +156,9 @@ def imi(open: object, high: object, low: object, close: object, volume: object,
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:
def kdj(high: object, low: object, close: object, period: int = 14, signal: int = 3, offset: int = 0, **kwargs) -> object:
"""KDJ Indicator."""
length = int(length)
period = int(kwargs.get("length", period))
signal = int(signal)
offset = int(offset)
h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close)
@@ -166,7 +166,7 @@ def kdj(high: object, low: object, close: object, length: int = 14, signal: int
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))
_check(_lib.qtl_kdj(_ptr(h), _ptr(l), _ptr(c), _ptr(kOut), _ptr(dOut), _ptr(jOut), n, period, signal))
return _wrap_multi({"kOut": kOut, "dOut": dOut, "jOut": jOut}, idx, "oscillators", offset)
@@ -377,100 +377,100 @@ def willr(high: object, low: object, close: object, period: int = 14, offset: in
_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:
def fisher(close: object, period: int = 9, offset: int = 0, **kwargs) -> object:
"""Fisher Transform."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_fisher(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"FISHER_{period}", "oscillators", offset)
def fisher04(close: object, length: int = 9, offset: int = 0, **kwargs) -> object:
def fisher04(close: object, period: int = 9, offset: int = 0, **kwargs) -> object:
"""Fisher Transform (0.4 variant)."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_fisher04(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"FISHER04_{period}", "oscillators", offset)
def dpo(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
def dpo(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
"""Detrended Price Oscillator."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_dpo(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"DPO_{period}", "oscillators", offset)
def trix(close: object, length: int = 18, offset: int = 0, **kwargs) -> object:
def trix(close: object, period: int = 18, offset: int = 0, **kwargs) -> object:
"""Triple EMA Rate of Change."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_trix(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"TRIX_{period}", "oscillators", offset)
def inertia(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
def inertia(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
"""Inertia."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_inertia(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"INERTIA_{period}", "oscillators", offset)
def rsx(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
def rsx(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
"""Relative Strength Xtra."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_rsx(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"RSX_{period}", "oscillators", offset)
def er(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def er(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Efficiency Ratio."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_er(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"ER_{period}", "oscillators", offset)
def cti(close: object, length: int = 12, offset: int = 0, **kwargs) -> object:
def cti(close: object, period: int = 12, offset: int = 0, **kwargs) -> object:
"""Correlation Trend Indicator."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_cti(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"CTI_{period}", "oscillators", offset)
def reflex(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
def reflex(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
"""Reflex."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_reflex(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"REFLEX_{period}", "oscillators", offset)
def trendflex(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
def trendflex(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
"""Trendflex."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_trendflex(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"TRENDFLEX_{period}", "oscillators", offset)
def kri(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
def kri(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
"""Kairi Relative Index."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_kri(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"KRI_{period}", "oscillators", offset)
def psl(close: object, length: int = 12, offset: int = 0, **kwargs) -> object:
def psl(close: object, period: int = 12, offset: int = 0, **kwargs) -> object:
"""Psychological Line."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_psl(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"PSL_{period}", "oscillators", offset)
def deco(close: object, short_period: int = 30, long_period: int = 60,
@@ -515,13 +515,13 @@ def crsi(close: object, rsi_period: int = 3, streak_period: int = 2,
return _wrap(dst, idx, f"CRSI_{rsi_period}", "oscillators", offset)
def bbb(close: object, length: int = 20, mult: float = 2.0,
def bbb(close: object, period: int = 20, mult: float = 2.0,
offset: int = 0, **kwargs) -> object:
"""Bollinger Band Bounce."""
length = int(length); mult = float(mult); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_bbb(_ptr(src), n, _ptr(dst), period, mult))
return _wrap(dst, idx, f"BBB_{period}", "oscillators", offset)
def bbi(close: object, p1: int = 3, p2: int = 6, p3: int = 12, p4: int = 24,
@@ -533,14 +533,14 @@ def bbi(close: object, p1: int = 3, p2: int = 6, p3: int = 12, p4: int = 24,
return _wrap(dst, idx, "BBI", "oscillators", offset)
def dem(high: object, low: object, length: int = 14,
def dem(high: object, low: object, period: int = 14,
offset: int = 0, **kwargs) -> object:
"""DeMarker."""
length = int(length)
period = int(kwargs.get("length", period))
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))
_check(_lib.qtl_dem(_ptr(h), _ptr(l), n, _ptr(dst), period))
return _wrap(dst, idx, f"DEM_{period}", "oscillators", int(offset))
def brar(open: object, high: object, low: object, close: object,
+20 -20
View File
@@ -340,12 +340,12 @@ def ztest(close: object, period: int = 14, mu0: float = 0.0, offset: int = 0, **
_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:
def zscore(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
"""Z-Score."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_zscore(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"ZSCORE_{period}", "statistics", offset)
def cma(close: object, offset: int = 0, **kwargs) -> object:
@@ -356,39 +356,39 @@ def cma(close: object, offset: int = 0, **kwargs) -> object:
return _wrap(dst, idx, "CMA", "statistics", offset)
def entropy(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def entropy(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Shannon Entropy."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_entropy(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"ENTROPY_{period}", "statistics", offset)
def correlation(x: object, y: object, length: int = 20,
def correlation(x: object, y: object, period: int = 20,
offset: int = 0, **kwargs) -> object:
"""Pearson Correlation."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_correlation(_ptr(xarr), _ptr(yarr), n, _ptr(dst), period))
return _wrap(dst, idx, f"CORR_{period}", "statistics", offset)
def covariance(x: object, y: object, length: int = 20,
def covariance(x: object, y: object, period: int = 20,
is_sample: bool = True, offset: int = 0, **kwargs) -> object:
"""Covariance."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_covariance(_ptr(xarr), _ptr(yarr), n, _ptr(dst), period, int(is_sample)))
return _wrap(dst, idx, f"COV_{period}", "statistics", offset)
def cointegration(x: object, y: object, length: int = 20,
def cointegration(x: object, y: object, period: int = 20,
offset: int = 0, **kwargs) -> object:
"""Cointegration."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_cointegration(_ptr(xarr), _ptr(yarr), n, _ptr(dst), period))
return _wrap(dst, idx, f"COINT_{period}", "statistics", offset)
+80 -80
View File
@@ -195,117 +195,117 @@ def rwma(high: object, low: object, close: object, period: int = 14, offset: int
_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:
def sma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Simple Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_sma(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"SMA_{period}", "trends_fir", offset)
def wma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def wma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Weighted Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_wma(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"WMA_{period}", "trends_fir", offset)
def hma(close: object, length: int = 9, offset: int = 0, **kwargs) -> object:
def hma(close: object, period: int = 9, offset: int = 0, **kwargs) -> object:
"""Hull Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_hma(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"HMA_{period}", "trends_fir", offset)
def trima(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def trima(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Triangular Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_trima(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"TRIMA_{period}", "trends_fir", offset)
def swma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def swma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Symmetric Weighted Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_swma(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"SWMA_{period}", "trends_fir", offset)
def dwma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def dwma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Double Weighted Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_dwma(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"DWMA_{period}", "trends_fir", offset)
def blma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def blma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Blackman Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_blma(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"BLMA_{period}", "trends_fir", offset)
def alma(close: object, length: int = 10, alma_offset: float = 0.85,
def alma(close: object, period: 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)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_alma(_ptr(src), n, _ptr(dst), period, float(alma_offset), float(sigma)))
return _wrap(dst, idx, f"ALMA_{period}", "trends_fir", offset)
def lsma(close: object, length: int = 25, offset: int = 0, **kwargs) -> object:
def lsma(close: object, period: int = 25, offset: int = 0, **kwargs) -> object:
"""Least Squares Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_lsma(_ptr(src), n, _ptr(dst), period, 0, 1.0))
return _wrap(dst, idx, f"LSMA_{period}", "trends_fir", offset)
def sgma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def sgma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Savitzky-Golay Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_sgma(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"SGMA_{period}", "trends_fir", offset)
def sinema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def sinema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Sine-weighted Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_sinema(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"SINEMA_{period}", "trends_fir", offset)
def hanma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def hanma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Hann-weighted Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_hanma(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"HANMA_{period}", "trends_fir", offset)
def parzen(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def parzen(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Parzen-weighted Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_parzen(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"PARZEN_{period}", "trends_fir", offset)
def tsf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
def tsf(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
"""Time Series Forecast."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_tsf(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"TSF_{period}", "trends_fir", offset)
def conv(close: object, kernel: list | None = None,
@@ -320,52 +320,52 @@ def conv(close: object, kernel: list | None = None,
return _wrap(dst, idx, "CONV", "trends_fir", offset)
def bwma(close: object, length: int = 10, order: int = 0,
def bwma(close: object, period: int = 10, order: int = 0,
offset: int = 0, **kwargs) -> object:
"""Butterworth-weighted Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_bwma(_ptr(src), n, _ptr(dst), period, int(order)))
return _wrap(dst, idx, f"BWMA_{period}", "trends_fir", offset)
def crma(close: object, length: int = 10, volume_factor: float = 1.0,
def crma(close: object, period: int = 10, volume_factor: float = 1.0,
offset: int = 0, **kwargs) -> object:
"""Cosine-Ramp Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_crma(_ptr(src), n, _ptr(dst), period, float(volume_factor)))
return _wrap(dst, idx, f"CRMA_{period}", "trends_fir", offset)
def sp15(close: object, length: int = 15, offset: int = 0, **kwargs) -> object:
def sp15(close: object, period: int = 15, offset: int = 0, **kwargs) -> object:
"""SP-15 Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_sp15(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"SP15_{period}", "trends_fir", offset)
def tukey_w(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def tukey_w(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Tukey-windowed Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_tukey_w(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"TUKEY_{period}", "trends_fir", offset)
def rain(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def rain(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""RAIN Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_rain(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"RAIN_{period}", "trends_fir", offset)
def afirma(close: object, length: int = 10, window_type: int = 0,
def afirma(close: object, period: 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)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_afirma(_ptr(src), n, _ptr(dst), period, int(window_type), int(use_simd)))
return _wrap(dst, idx, f"AFIRMA_{period}", "trends_fir", offset)
+40 -40
View File
@@ -357,12 +357,12 @@ def zltema(close: object, period: int = 14, offset: int = 0, **kwargs) -> object
_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:
def ema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Exponential Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_ema(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"EMA_{period}", "trends_iir", offset)
def ema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object:
@@ -373,12 +373,12 @@ def ema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> o
return _wrap(dst, idx, f"EMA_a{alpha:.4f}", "trends_iir", offset)
def dema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def dema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Double Exponential Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_dema(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"DEMA_{period}", "trends_iir", offset)
def dema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object:
@@ -389,71 +389,71 @@ def dema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) ->
return _wrap(dst, idx, f"DEMA_a{alpha:.4f}", "trends_iir", offset)
def tema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def tema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Triple Exponential Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_tema(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"TEMA_{period}", "trends_iir", offset)
def lema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def lema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Laguerre-based EMA."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_lema(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"LEMA_{period}", "trends_iir", offset)
def hema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def hema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Henderson EMA."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_hema(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"HEMA_{period}", "trends_iir", offset)
def ahrens(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
def ahrens(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
"""Ahrens Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_ahrens(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"AHRENS_{period}", "trends_iir", offset)
def decycler(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
def decycler(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
"""Simple Decycler."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_decycler(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"DECYCLER_{period}", "trends_iir", offset)
def dsma(close: object, length: int = 10, factor: float = 0.5,
def dsma(close: object, period: int = 10, factor: float = 0.5,
offset: int = 0, **kwargs) -> object:
"""Deviation-Scaled Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_dsma(_ptr(src), n, _ptr(dst), period, float(factor)))
return _wrap(dst, idx, f"DSMA_{period}", "trends_iir", offset)
def gdema(close: object, length: int = 10, vfactor: float = 1.0,
def gdema(close: object, period: int = 10, vfactor: float = 1.0,
offset: int = 0, **kwargs) -> object:
"""Generalized DEMA."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_gdema(_ptr(src), n, _ptr(dst), period, float(vfactor)))
return _wrap(dst, idx, f"GDEMA_{period}", "trends_iir", offset)
def coral(close: object, length: int = 10, friction: float = 0.4,
def coral(close: object, period: int = 10, friction: float = 0.4,
offset: int = 0, **kwargs) -> object:
"""CORAL Trend."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_coral(_ptr(src), n, _ptr(dst), period, float(friction)))
return _wrap(dst, idx, f"CORAL_{period}", "trends_iir", offset)
def agc(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object:
+32 -32
View File
@@ -252,57 +252,57 @@ def tr(high: object, low: object, close: object, offset: int = 0, **kwargs) -> o
return _wrap(dst, idx, "TR", "volatility", int(offset))
def bbw(close: object, length: int = 20, mult: float = 2.0,
def bbw(close: object, period: int = 20, mult: float = 2.0,
offset: int = 0, **kwargs) -> object:
"""Bollinger Band Width."""
length = int(length); mult = float(mult); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_bbw(_ptr(src), n, _ptr(dst), period, mult))
return _wrap(dst, idx, f"BBW_{period}", "volatility", offset)
def bbwn(close: object, length: int = 20, mult: float = 2.0,
def bbwn(close: object, period: 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)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_bbwn(_ptr(src), n, _ptr(dst), period, mult, lookback))
return _wrap(dst, idx, f"BBWN_{period}", "volatility", offset)
def bbwp(close: object, length: int = 20, mult: float = 2.0,
def bbwp(close: object, period: 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)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_bbwp(_ptr(src), n, _ptr(dst), period, mult, lookback))
return _wrap(dst, idx, f"BBWP_{period}", "volatility", offset)
def stddev(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
def stddev(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
"""Standard Deviation."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_stddev(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"STDDEV_{period}", "volatility", offset)
def variance(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
def variance(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
"""Variance."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_variance(_ptr(src), n, _ptr(dst), period))
return _wrap(dst, idx, f"VAR_{period}", "volatility", offset)
def etherm(high: object, low: object, length: int = 14,
def etherm(high: object, low: object, period: int = 14,
offset: int = 0, **kwargs) -> object:
"""Elder Thermometer."""
length = int(length)
period = int(kwargs.get("length", period))
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))
_check(_lib.qtl_etherm(_ptr(h), _ptr(l), n, _ptr(dst), period))
return _wrap(dst, idx, f"ETHERM_{period}", "volatility", int(offset))
def ccv(close: object, short_period: int = 20, long_period: int = 1,
@@ -314,13 +314,13 @@ def ccv(close: object, short_period: int = 20, long_period: int = 1,
return _wrap(dst, idx, f"CCV_{short_period}", "volatility", offset)
def cv(close: object, length: int = 20, min_vol: float = 0.2,
def cv(close: object, period: 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)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_cv(_ptr(src), n, _ptr(dst), period, float(min_vol), float(max_vol)))
return _wrap(dst, idx, f"CV_{period}", "volatility", offset)
def cvi(close: object, ema_period: int = 10, roc_period: int = 10,
@@ -332,10 +332,10 @@ def cvi(close: object, ema_period: int = 10, roc_period: int = 10,
return _wrap(dst, idx, f"CVI_{ema_period}", "volatility", offset)
def ewma(close: object, length: int = 20, is_pop: int = 1,
def ewma(close: object, period: 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)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_ewma(_ptr(src), n, _ptr(dst), period, int(is_pop), int(ann_factor)))
return _wrap(dst, idx, f"EWMA_{period}", "volatility", offset)
+20 -20
View File
@@ -216,54 +216,54 @@ def pvi(close: object, volume: object, offset: int = 0, **kwargs) -> object:
return _wrap(dst, idx, "PVI", "volume", offset)
def tvi(close: object, volume: object, length: int = 14,
def tvi(close: object, volume: object, period: int = 14,
offset: int = 0, **kwargs) -> object:
"""Trade Volume Index."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_tvi(_ptr(c), _ptr(v), n, _ptr(dst), period))
return _wrap(dst, idx, f"TVI_{period}", "volume", offset)
def pvd(close: object, volume: object, length: int = 14,
def pvd(close: object, volume: object, period: int = 14,
offset: int = 0, **kwargs) -> object:
"""Price Volume Divergence."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_pvd(_ptr(c), _ptr(v), n, _ptr(dst), period))
return _wrap(dst, idx, f"PVD_{period}", "volume", offset)
def vwma(close: object, volume: object, length: int = 20,
def vwma(close: object, volume: object, period: int = 20,
offset: int = 0, **kwargs) -> object:
"""Volume Weighted Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_vwma(_ptr(c), _ptr(v), n, _ptr(dst), period))
return _wrap(dst, idx, f"VWMA_{period}", "volume", offset)
def evwma(close: object, volume: object, length: int = 20,
def evwma(close: object, volume: object, period: int = 20,
offset: int = 0, **kwargs) -> object:
"""Elastic Volume Weighted Moving Average."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_evwma(_ptr(c), _ptr(v), n, _ptr(dst), period))
return _wrap(dst, idx, f"EVWMA_{period}", "volume", offset)
def efi(close: object, volume: object, length: int = 13,
def efi(close: object, volume: object, period: int = 13,
offset: int = 0, **kwargs) -> object:
"""Elder Force Index."""
length = int(length); offset = int(offset)
period = int(kwargs.get("length", period)); 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)
_check(_lib.qtl_efi(_ptr(c), _ptr(v), n, _ptr(dst), period))
return _wrap(dst, idx, f"EFI_{period}", "volume", offset)
def aobv(close: object, volume: object, offset: int = 0, **kwargs) -> object:
+164
View File
@@ -0,0 +1,164 @@
"""Round-trip tests for PyArrow Array / ChunkedArray input → output.
Requires: ``pip install quantalib[pyarrow]``
"""
from __future__ import annotations
import numpy as np
import pytest
pa = pytest.importorskip("pyarrow", minversion="14.0")
import quantalib as qtl # noqa: E402
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture()
def close_array() -> pa.Array:
"""100-bar random close prices as a PyArrow float64 Array."""
rng = np.random.default_rng(42)
return pa.array(rng.random(100) * 100 + 50, type=pa.float64())
@pytest.fixture()
def close_chunked() -> pa.ChunkedArray:
"""100-bar random close prices as a PyArrow ChunkedArray (2 chunks)."""
rng = np.random.default_rng(42)
data = rng.random(100) * 100 + 50
chunk1 = pa.array(data[:50], type=pa.float64())
chunk2 = pa.array(data[50:], type=pa.float64())
return pa.chunked_array([chunk1, chunk2])
# ---------------------------------------------------------------------------
# Single-output: pa.Array in → pa.Array out
# ---------------------------------------------------------------------------
class TestSingleOutput:
def test_sma_returns_arrow_array(self, close_array: pa.Array) -> None:
result = qtl.sma(close_array, length=14)
assert isinstance(result, pa.Array)
assert len(result) == len(close_array)
assert result.type == pa.float64()
def test_ema_returns_arrow_array(self, close_array: pa.Array) -> None:
result = qtl.ema(close_array, length=14)
assert isinstance(result, pa.Array)
assert len(result) == len(close_array)
def test_rsi_returns_arrow_array(self, close_array: pa.Array) -> None:
result = qtl.rsi(close_array, length=14)
assert isinstance(result, pa.Array)
assert len(result) == len(close_array)
def test_stddev_returns_arrow_array(self, close_array: pa.Array) -> None:
result = qtl.stddev(close_array, length=14)
assert isinstance(result, pa.Array)
assert len(result) == len(close_array)
def test_mom_returns_arrow_array(self, close_array: pa.Array) -> None:
result = qtl.mom(close_array, length=10)
assert isinstance(result, pa.Array)
assert len(result) == len(close_array)
# ---------------------------------------------------------------------------
# ChunkedArray input
# ---------------------------------------------------------------------------
class TestChunkedArray:
def test_chunked_array_accepted(self, close_chunked: pa.ChunkedArray) -> None:
result = qtl.sma(close_chunked, length=14)
assert isinstance(result, pa.Array)
assert len(result) == len(close_chunked)
def test_chunked_values_match_flat(self, close_chunked: pa.ChunkedArray) -> None:
flat = close_chunked.combine_chunks()
result_chunked = qtl.sma(close_chunked, length=14)
result_flat = qtl.sma(flat, length=14)
np.testing.assert_allclose(
result_chunked.to_numpy(zero_copy_only=False),
result_flat.to_numpy(zero_copy_only=False),
rtol=1e-12,
)
# ---------------------------------------------------------------------------
# Multi-output: pa.Array in → dict[str, pa.Array] out
# ---------------------------------------------------------------------------
class TestMultiOutput:
def test_bbands_returns_dict_of_arrays(self, close_array: pa.Array) -> None:
result = qtl.bbands(close_array, length=20, std=2.0)
assert isinstance(result, dict)
assert all(isinstance(v, pa.Array) for v in result.values())
assert len(result) == 3 # upper, mid, lower
for v in result.values():
assert len(v) == len(close_array)
assert v.type == pa.float64()
# ---------------------------------------------------------------------------
# Numerical equivalence: Arrow vs numpy should produce identical values
# ---------------------------------------------------------------------------
class TestNumericalEquivalence:
def test_sma_values_match_numpy(self, close_array: pa.Array) -> None:
np_arr = close_array.to_numpy(zero_copy_only=False)
result_pa = qtl.sma(close_array, length=14)
result_np = qtl.sma(np_arr, length=14)
np.testing.assert_allclose(
result_pa.to_numpy(zero_copy_only=False),
result_np,
rtol=1e-12,
)
def test_rsi_values_match_numpy(self, close_array: pa.Array) -> None:
np_arr = close_array.to_numpy(zero_copy_only=False)
result_pa = qtl.rsi(close_array, length=14)
result_np = qtl.rsi(np_arr, length=14)
np.testing.assert_allclose(
result_pa.to_numpy(zero_copy_only=False),
result_np,
rtol=1e-12,
equal_nan=True,
)
def test_ema_values_match_numpy(self, close_array: pa.Array) -> None:
np_arr = close_array.to_numpy(zero_copy_only=False)
result_pa = qtl.ema(close_array, length=14)
result_np = qtl.ema(np_arr, length=14)
np.testing.assert_allclose(
result_pa.to_numpy(zero_copy_only=False),
result_np,
rtol=1e-12,
equal_nan=True,
)
# ---------------------------------------------------------------------------
# Type coercion
# ---------------------------------------------------------------------------
class TestTypeCoercion:
def test_int32_array_coerced(self) -> None:
arr = pa.array(list(range(1, 101)), type=pa.int32())
result = qtl.sma(arr, length=5)
assert isinstance(result, pa.Array)
assert result.type == pa.float64()
assert len(result) == 100
def test_float32_array_coerced(self) -> None:
rng = np.random.default_rng(42)
arr = pa.array(rng.random(100).astype(np.float32), type=pa.float32())
result = qtl.sma(arr, length=5)
assert isinstance(result, pa.Array)
assert result.type == pa.float64()
assert len(result) == 100
# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------
class TestEdgeCases:
def test_empty_array_raises(self) -> None:
empty = pa.array([], type=pa.float64())
with pytest.raises(ValueError, match="must not be empty"):
qtl.sma(empty, length=14)
+142
View File
@@ -0,0 +1,142 @@
"""Round-trip tests for Polars Series / DataFrame input → output.
Requires: ``pip install quantalib[polars]``
"""
from __future__ import annotations
import numpy as np
import pytest
pl = pytest.importorskip("polars", minversion="0.20")
import quantalib as qtl # noqa: E402
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture()
def close_series() -> pl.Series:
"""100-bar random close prices as a Polars Series."""
rng = np.random.default_rng(42)
return pl.Series(name="close", values=rng.random(100) * 100 + 50)
@pytest.fixture()
def ohlcv_df() -> pl.DataFrame:
"""100-bar OHLCV DataFrame."""
rng = np.random.default_rng(42)
c = rng.random(100) * 100 + 50
return pl.DataFrame({
"open": c + rng.uniform(-2, 2, 100),
"high": c + rng.uniform(0, 5, 100),
"low": c - rng.uniform(0, 5, 100),
"close": c,
"volume": rng.uniform(1e4, 1e6, 100),
})
# ---------------------------------------------------------------------------
# Single-output: Polars Series in → Polars Series out
# ---------------------------------------------------------------------------
class TestSingleOutput:
def test_sma_returns_polars_series(self, close_series: pl.Series) -> None:
result = qtl.sma(close_series, length=14)
assert isinstance(result, pl.Series)
assert len(result) == len(close_series)
def test_ema_returns_polars_series(self, close_series: pl.Series) -> None:
result = qtl.ema(close_series, length=14)
assert isinstance(result, pl.Series)
assert len(result) == len(close_series)
def test_rsi_returns_polars_series(self, close_series: pl.Series) -> None:
result = qtl.rsi(close_series, length=14)
assert isinstance(result, pl.Series)
assert len(result) == len(close_series)
def test_series_name_follows_convention(self, close_series: pl.Series) -> None:
result = qtl.sma(close_series, length=20)
assert isinstance(result, pl.Series)
assert result.name == "SMA_20"
def test_stddev_returns_polars_series(self, close_series: pl.Series) -> None:
result = qtl.stddev(close_series, length=14)
assert isinstance(result, pl.Series)
assert len(result) == len(close_series)
def test_mom_returns_polars_series(self, close_series: pl.Series) -> None:
result = qtl.mom(close_series, length=10)
assert isinstance(result, pl.Series)
assert len(result) == len(close_series)
# ---------------------------------------------------------------------------
# DataFrame input: first column extracted
# ---------------------------------------------------------------------------
class TestDataFrameInput:
def test_dataframe_first_col_used(self, ohlcv_df: pl.DataFrame) -> None:
close_col = ohlcv_df.select("close")
result = qtl.sma(close_col, length=14)
assert isinstance(result, pl.Series)
assert len(result) == len(ohlcv_df)
# ---------------------------------------------------------------------------
# Multi-output: Polars Series in → Polars DataFrame out
# ---------------------------------------------------------------------------
class TestMultiOutput:
def test_bbands_returns_polars_dataframe(self, close_series: pl.Series) -> None:
result = qtl.bbands(close_series, length=20, std=2.0)
assert isinstance(result, pl.DataFrame)
assert result.shape[0] == len(close_series)
assert result.shape[1] == 3 # upper, mid, lower
# ---------------------------------------------------------------------------
# Numerical equivalence: Polars vs numpy should produce identical values
# ---------------------------------------------------------------------------
class TestNumericalEquivalence:
def test_sma_values_match_numpy(self, close_series: pl.Series) -> None:
np_arr = close_series.to_numpy()
result_pl = qtl.sma(close_series, length=14)
result_np = qtl.sma(np_arr, length=14)
np.testing.assert_allclose(
result_pl.to_numpy(), result_np, rtol=1e-12
)
def test_rsi_values_match_numpy(self, close_series: pl.Series) -> None:
np_arr = close_series.to_numpy()
result_pl = qtl.rsi(close_series, length=14)
result_np = qtl.rsi(np_arr, length=14)
np.testing.assert_allclose(
result_pl.to_numpy(), result_np, rtol=1e-12, equal_nan=True
)
def test_ema_values_match_numpy(self, close_series: pl.Series) -> None:
np_arr = close_series.to_numpy()
result_pl = qtl.ema(close_series, length=14)
result_np = qtl.ema(np_arr, length=14)
np.testing.assert_allclose(
result_pl.to_numpy(), result_np, rtol=1e-12, equal_nan=True
)
# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------
class TestEdgeCases:
def test_none_input_raises(self) -> None:
with pytest.raises(ValueError, match="must not be None"):
qtl.sma(None, length=14)
def test_empty_series_raises(self) -> None:
empty = pl.Series(name="empty", values=[], dtype=pl.Float64)
with pytest.raises(ValueError, match="must not be empty"):
qtl.sma(empty, length=14)
def test_int_series_coerced_to_float(self) -> None:
int_series = pl.Series(name="ints", values=list(range(1, 101)))
result = qtl.sma(int_series, length=5)
assert isinstance(result, pl.Series)
assert len(result) == 100
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""Transform new-style functions from `length` → `period` (primary) with `length` as kwargs alias.
Target: all category module .py files in python/quantalib/ that have functions
using bare `length` as a parameter name.
Rules:
1. `def foo(close, length: int = X, ...)` `def foo(close, period: int = X, ...)`
2. `length = int(length)` (standalone assignment) `period = int(kwargs.get("length", period))`
3. Any remaining standalone `length` in the function body `period`
4. Compound names like hpLength, ssLength, minLength, etc. are NOT touched.
5. `lengths` (plural) is NOT touched.
6. `default_length` in _helpers.py pattern helpers is NOT touched (separate ticket).
Also fixes _helpers.py pattern helpers (_pa, _pf, _pg2, _ph) the same way.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
QUANTALIB = Path(__file__).resolve().parent.parent / "quantalib"
# Files to process (category modules + _helpers.py)
TARGET_FILES = [
"channels.py",
"core.py",
"cycles.py",
"dynamics.py",
"errors.py",
"filters.py",
"momentum.py",
"numerics.py",
"oscillators.py",
"reversals.py",
"statistics.py",
"trends_fir.py",
"trends_iir.py",
"volatility.py",
"volume.py",
"_helpers.py",
]
# Pattern: standalone `length` as a word — NOT preceded or followed by
# alphanumeric or underscore (i.e., not part of hpLength, minLength, etc.)
# Also NOT `lengths` (plural).
STANDALONE_LENGTH = re.compile(r'(?<![a-zA-Z0-9_])length(?![a-zA-Z0-9_])')
# Pattern for the signature line: `length: int = <default>`
SIG_PATTERN = re.compile(r'(?<![a-zA-Z0-9_])length(\s*:\s*int\s*=\s*\d+)')
# Pattern for the assignment line: `length = int(length)` possibly with `;`
ASSIGN_PATTERN = re.compile(
r'^(\s*)length\s*=\s*int\(length\)\s*;?\s*'
)
# Pattern for assignment in _helpers.py: `length = int(length) if length is not None else default_length`
HELPERS_ASSIGN = re.compile(
r'^(\s*)length\s*=\s*int\(length\)\s+if\s+length\s+is\s+not\s+None\s+else\s+default_length'
)
def has_standalone_length_param(line: str) -> bool:
"""Check if a def line has standalone `length` as a parameter."""
if not line.lstrip().startswith("def "):
return False
# Must have `length` as standalone word (not part of compound)
return bool(STANDALONE_LENGTH.search(line))
def transform_file(filepath: Path) -> tuple[int, int]:
"""Transform a single file. Returns (functions_changed, lines_changed)."""
text = filepath.read_text(encoding="utf-8")
lines = text.split("\n")
new_lines: list[str] = []
in_target_func = False
func_indent = ""
funcs_changed = 0
lines_changed = 0
assignment_done = False
i = 0
while i < len(lines):
line = lines[i]
stripped = line.lstrip()
# Detect start of a function with `length` parameter
if stripped.startswith("def ") and has_standalone_length_param(line):
in_target_func = True
func_indent = line[: len(line) - len(stripped)]
assignment_done = False
funcs_changed += 1
# Handle multi-line def (continuation lines)
full_def = line
while i < len(lines) - 1 and line.rstrip().endswith(","):
# Replace standalone length in this line
new_line = STANDALONE_LENGTH.sub("period", line)
if new_line != line:
lines_changed += 1
new_lines.append(new_line)
i += 1
line = lines[i]
# Last line of def (or single-line def)
new_line = STANDALONE_LENGTH.sub("period", line)
if new_line != line:
lines_changed += 1
new_lines.append(new_line)
i += 1
continue
# Inside a target function?
if in_target_func:
# Detect end of function: non-empty line at or less than func indent,
# or a new def/class
if stripped and not line.startswith(func_indent + " ") and not line.startswith(func_indent + "\t"):
if not stripped.startswith('"""') and not stripped.startswith("'"):
# Could be the docstring continuation; check differently
if stripped.startswith("def ") or stripped.startswith("class ") or (len(line) - len(stripped) <= len(func_indent) and stripped and not stripped.startswith('#')):
in_target_func = False
if in_target_func:
# Check for _helpers.py style assignment:
# `length = int(length) if length is not None else default_length`
m_helpers = HELPERS_ASSIGN.match(line)
if m_helpers and not assignment_done:
indent = m_helpers.group(1)
new_line = f"{indent}period = int(kwargs.get(\"length\", period)) if period is not None else default_length"
new_lines.append(new_line)
lines_changed += 1
assignment_done = True
i += 1
continue
# Check for standard assignment: `length = int(length);`
m_assign = ASSIGN_PATTERN.match(line)
if m_assign and not assignment_done:
indent = m_assign.group(1)
# Preserve anything after the semicolon on the same line
rest_of_line = ASSIGN_PATTERN.sub("", line)
# Check if there's more after (e.g., "; offset = int(offset)")
remaining = line[m_assign.end():]
new_assignment = f'{indent}period = int(kwargs.get("length", period))'
if remaining.strip():
new_assignment += "; " + remaining.strip()
new_lines.append(new_assignment)
lines_changed += 1
assignment_done = True
i += 1
continue
# Replace any remaining standalone `length` references
new_line = STANDALONE_LENGTH.sub("period", line)
if new_line != line:
lines_changed += 1
new_lines.append(new_line)
i += 1
continue
new_lines.append(line)
i += 1
new_text = "\n".join(new_lines)
if new_text != text:
filepath.write_text(new_text, encoding="utf-8")
return funcs_changed, lines_changed
def main() -> None:
total_funcs = 0
total_lines = 0
for fname in TARGET_FILES:
fpath = QUANTALIB / fname
if not fpath.exists():
print(f" SKIP {fname} (not found)")
continue
funcs, lines = transform_file(fpath)
if funcs > 0:
print(f" {fname}: {funcs} functions, {lines} lines changed")
total_funcs += funcs
total_lines += lines
else:
print(f" {fname}: no changes")
print(f"\nTotal: {total_funcs} functions, {total_lines} lines changed")
if __name__ == "__main__":
main()