Files
Miha Kralj eb9e41fc2e feat: add RRSI (Rocket RSI) — Ehlers TASC May 2018
Algorithm: SuperSmoother-filtered momentum → Ehlers RSI → Fisher Transform
- 2-pole Butterworth IIR pre-filter removes noise
- Ehlers RSI (raw summation, not Wilder) outputs [-1,1]
- arctanh produces Gaussian-distributed zero-mean oscillator

Files: Rrsi.cs, Rrsi.Quantower.cs, Rrsi.md, 31+7 tests
Integration: sidebar, indices, Python bridge (Exports, _bridge, oscillators, SPEC)
Build: 0 warnings, 0 errors | Tests: 15,963 passed, 0 failed
2026-03-17 09:25:32 -07:00

1246 lines
45 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# quantalib Python NativeAOT Wrapper — Specification v2
## 1) Objective
Deliver a Python package named **`quantalib`** that exposes the entire QuanTAlib
indicator library through a stable, versioned C ABI compiled via .NET NativeAOT.
Python ergonomics follow pandas-ta conventions where practical.
### Primary goals
- Near-zero-copy interop via `ctypes` + contiguous NumPy buffers (zero-copy when inputs are already C-contiguous `float64`; otherwise one normalization copy is allowed)
- Stable C ABI surface (`qtl_*`) with explicit major-version evolution policy for future non-Python consumers
- Full library coverage target: all supported `Batch` methods exported using a generated manifest and validated rollout gates
- pandas-ta-compatible function signatures for common indicators
### Non-goals (initial release)
- Rewriting indicator math in Python
- pybind11 / Cython extension modules
- GPU acceleration
- Streaming / stateful Python classes per indicator
- Alternate numeric dtypes (`float32`, decimal)
- Full pandas-ta edge-case parity on day one
---
## 2) Scope
### In scope
- `python/` directory at repository root
- NativeAOT shared library (`quantalib.dll` / `quantalib.so` / `quantalib.dylib`)
- Single `Exports.cs` mapping all `Batch` methods to `[UnmanagedCallersOnly]` exports
- Python package `quantalib` with loader, bridge, and indicator wrappers
- Compatibility matrix documenting pandas-ta parity levels
- CI matrix for `win-x64`, `linux-x64`, `osx-arm64`, `osx-x64`
### Out of scope
- Windows ARM64 packaging
- Async / streaming Python APIs
- TBarSeries / BatchInputs overloads (use flat span-based overloads only)
---
## 3) Design requirements
1. **ABI correctness** — pointer, length, and parameter contracts are explicit and validated on both sides of the boundary.
2. **Performance** — call QuanTAlib `Batch` methods directly; no managed allocations in the export shim beyond span construction.
3. **ABI stability** — export names are prefixed (`qtl_*`) and versioned; existing symbols never change within a major version.
4. **Deterministic behavior** — warmup slots filled with `NaN`; invalid inputs produce defined status codes, never exceptions.
5. **pandas-ta compatibility** — Python names and signatures mirror pandas-ta where feasible, with documented deltas.
6. **Thread safety** — exports are designed to be stateless and reentrant; concurrent calls are supported when each call uses distinct buffers. This must be validated by stress tests in CI.
---
## 4) Repository layout
```text
python/
SPEC.md # this file
python.csproj # NativeAOT shared lib project
Directory.Build.props # local build overrides
publish.ps1 # multi-RID publish + stage script
pyproject.toml # Python package metadata
README.md # usage docs + indicator list
src/
StatusCodes.cs # QTL_OK / QTL_ERR_* constants
ArrayBridge.cs # unsafe ptr→span helpers + validation
Exports.cs # ALL [UnmanagedCallersOnly] entry points
quantalib/
__init__.py # re-export indicators; __version__
_loader.py # platform+arch dispatch; ctypes.CDLL
_bridge.py # ctypes argtypes/restype declarations
indicators.py # pandas-ta-compatible wrappers
_compat.py # pandas-ta name aliases + helpers
py.typed # PEP 561 marker
native/
win_amd64/.gitkeep
linux_x86_64/.gitkeep
macosx_arm64/.gitkeep
macosx_x86_64/.gitkeep
tests/
test_smoke.py # import + load + call one indicator
test_shapes.py # output length == input length
test_status_codes.py # null ptr, bad length, bad params
test_golden.py # golden-value vs managed QuanTAlib
test_compat.py # pandas-ta parity subset
```
**Design note:** `Exports.cs` and `indicators.py` stay as single flat files.
Internal splitting is allowed later without changing any public API or ABI.
---
## 5) Native ABI contract
### 5.1 Calling convention
- All exports use `[UnmanagedCallersOnly(EntryPoint = "qtl_<name>")]`.
- All exports return `int` status code.
- All pointer parameters use C ABI-compatible primitive types only.
- No managed exceptions ever cross the ABI boundary. Every export wraps its body
in a `try/catch` that returns `QTL_ERR_INTERNAL` on unhandled exceptions.
### 5.2 Status codes
| Code | Name | Meaning |
|------|------|---------|
| `0` | `QTL_OK` | Success |
| `1` | `QTL_ERR_NULL_PTR` | Required pointer is null |
| `2` | `QTL_ERR_INVALID_LENGTH` | `n <= 0` or output length mismatch |
| `3` | `QTL_ERR_INVALID_PARAM` | Parameter out of valid range |
| `4` | `QTL_ERR_INTERNAL` | Unhandled exception in managed code |
Failure guarantees:
- For validation failures (`QTL_ERR_NULL_PTR`, `QTL_ERR_INVALID_LENGTH`, `QTL_ERR_INVALID_PARAM`), outputs are untouched by contract.
- For `QTL_ERR_INTERNAL`, output buffers are unspecified and must be treated as invalid by the caller.
### 5.3 ABI signature patterns
The ~290 `Batch` methods in QuanTAlib follow 9 distinct signature patterns.
Each pattern maps to a specific C ABI template:
#### Pattern A: Single-input, single-output + scalar params
**C# source:** `Sma.Batch(ReadOnlySpan<double> source, Span<double> output, int period)`
**C ABI:**
```c
int qtl_sma(double* src, int n, double* out, int period);
```
**Indicators (~130):** SMA, EMA, DEMA, TEMA, HMA, WMA, RSI, ROC, MOM, CMO,
TSI, APO, PPO, DPO, TRIX, Fisher, Inertia, Zscore, StdDev, Variance, etc.
#### Pattern B: Multi-input HLCV, single-output
**C# source:** `Mfi.Batch(ReadOnlySpan<double> high, ..low, ..close, ..volume, Span<double> output, int period)`
**C ABI:**
```c
int qtl_mfi(double* high, double* low, double* close, double* volume,
int n, double* out, int period);
```
**Indicators (~25):** MFI, ADL, CMF, VWAP, VWAD, ADOsc, KVO, III, WAD, VA, EOM.
#### Pattern C: Multi-input OHLC (no volume), single-output
**C# source:** `Bop.Batch(ReadOnlySpan<double> open, ..high, ..low, ..close, Span<double> destination)`
**C ABI:**
```c
int qtl_bop(double* open, double* high, double* low, double* close,
int n, double* out);
```
**Indicators (~20):** BOP, ASI, BRAR, Avgprice, Typprice, Wclprice, Midbody,
HA, ReverseEMA variants requiring OHLC, RVGI, DEM, etc.
#### Pattern D: Multi-input HL, single-output
**C# source:** `Ao.Batch(ReadOnlySpan<double> high, ReadOnlySpan<double> low, Span<double> destination, int fast, int slow)`
**C ABI:**
```c
int qtl_ao(double* high, double* low, int n, double* out,
int fast_period, int slow_period);
```
**Indicators (~10):** AO, AC, Medprice, Frama (HL overload), WillR, etc.
#### Pattern E: Multi-input HLC (no volume), single-output
**C# source:** `Tr.Batch(ReadOnlySpan<double> high, ..low, ..close, Span<double> output)`
**C ABI:**
```c
int qtl_tr(double* high, double* low, double* close, int n, double* out);
```
**Indicators (~15):** TR, ATR, Etherm, GHLA, PGO, RWMA, etc.
#### Pattern F: Dual-input (actual + predicted), single-output
**C# source:** `Mse.Batch(ReadOnlySpan<double> actual, ..predicted, Span<double> output, int period)`
**C ABI:**
```c
int qtl_mse(double* actual, double* predicted, int n, double* out, int period);
```
**Indicators (~25):** MSE, RMSE, MAE, MAPE, SMAPE, RMSLE, Rsquared, Huber,
PseudoHuber, TheilU, WMAPE, LogCosh, QuantileLoss, TukeyBiweight, etc.
#### Pattern G: Dual-input (source + volume), single-output
**C# source:** `Obv.Batch(ReadOnlySpan<double> close, ReadOnlySpan<double> volume, Span<double> output)`
**C ABI:**
```c
int qtl_obv(double* close, double* volume, int n, double* out);
```
**Indicators (~12):** OBV, PVT, PVR, VF, EFI, NVI, PVI, VWMA, EVWMA, TVI, PVD.
#### Pattern H: Dual-input statistics (seriesX + seriesY)
**C# source:** `Correl.Batch(ReadOnlySpan<double> seriesX, ..seriesY, Span<double> output, int period)`
**C ABI:**
```c
int qtl_correlation(double* x, double* y, int n, double* out, int period);
```
**Indicators (~6):** Correlation, Covariance, Spearman, Kendall, Granger,
Cointegration.
#### Pattern I: Multi-output indicators
**C# source (example):** `Pvo.Batch(ReadOnlySpan<double> volume, Span<double> output, Span<double> signal, Span<double> histogram, ...)`
**C ABI:** Each output gets its own pointer parameter:
```c
int qtl_pvo(double* volume, int n,
double* out_pvo, double* out_signal, double* out_hist,
int fast, int slow, int signal_period);
```
**Multi-output indicators:**
| Indicator | Outputs | Names |
|-----------|---------|-------|
| PVO | 3 | pvo, signal, histogram |
| PMA | 2 | pma, trigger |
| MAMA | 2 | mama, fama |
| HtPhasor | 2 | inPhase, quadrature |
| HtSine | 2 | sine, leadSine |
| AOBV | 2 | fast, slow |
| AMAT | 2 | trend, strength |
| Stoch | 2 | K, D |
| Stochf | 2 | K, D |
| KDJ | 3 | K, D, J |
| SMI | 2+ | smi, signal |
| BBands | 3 | upper, middle, lower |
| KC | 3 | upper, middle, lower |
| DCChannel | 3 | upper, middle, lower |
| AccBands | 3 | upper, middle, lower |
| AtrBands | 3 | upper, middle, lower |
| All channel indicators | 3 | upper, middle, lower |
| Vortex | 2 | viPlus, viMinus |
| Aroon | 1* | combined |
### 5.4 Special parameter mappings
Several `Batch` methods use types that cannot cross the C ABI directly.
Each requires a specific mapping:
| C# type | C ABI type | Strategy |
|---------|-----------|----------|
| `enum StcSmoothing` | `int` | 0 = SMA, 1 = EMA |
| `enum WindowType` (Afirma) | `int` | Map enum ordinals |
| `bool` (isPopulation, annualize, etc.) | `int` | 0 = false, 1 = true |
| `int[]? lengths` (Cfb) | `int* lengths, int lengths_count` | Null-safe; null → use defaults |
| `double[] kernel` (Conv) | `double* kernel, int kernel_len` | Caller allocates |
| `ReadOnlySpan<long>` (Solar, Lunar) | `long* timestamps` | Direct pointer cast |
| `TBarSeries` overloads | **Skip** | Use flat span-based overload instead |
| `BatchInputs`/`BatchOutput` struct overloads | **Skip** | Use flat span-based overload instead |
### 5.5 Dual overloads (period vs alpha)
Many IIR filters expose both `Batch(src, out, int period)` and
`Batch(src, out, double alpha)`. Export strategy:
- Primary export: `qtl_ema` — takes `int period`
- Alpha export: `qtl_ema_alpha` — takes `double alpha`
Both are exported; Python wrapper defaults to the period variant.
### 5.6 Validation in each export
Every export function validates before calling the inner `Batch`:
1. Null checks for all required pointer parameters → `QTL_ERR_NULL_PTR`
2. `n > 0``QTL_ERR_INVALID_LENGTH`
3. Range checks on scalar parameters (e.g., `period > 0`) → `QTL_ERR_INVALID_PARAM`
4. Semantic checks (e.g., `fastPeriod < slowPeriod` where required) → `QTL_ERR_INVALID_PARAM`
5. `try/catch` around `Batch` call → `QTL_ERR_INTERNAL` on any managed exception
### 5.7 Memory ownership rules
- Input pointers are **read-only** by contract (cast to `ReadOnlySpan<double>`).
- Caller allocates **all** output buffers with length `n`.
- Native layer writes exactly `n` values per output buffer on success.
- No ownership transfer across boundary. No heap allocation in the shim.
### 5.8 ABI versioning policy
- All exports use the flat `qtl_` prefix for v1.
- Existing published symbols are immutable within ABI major v1.
- New optional parameters require new symbol names (e.g. `qtl_ema_alpha`).
- Deprecation policy: old symbols remain exported for at least one minor release after replacement and are marked deprecated in Python wrappers.
- First breaking ABI revision introduces `qtl2_` prefix and parallel support window.
### 5.9 Export manifest (authoritative source)
To reduce drift across `Exports.cs`, `_bridge.py`, and `indicators.py`, maintain one machine-readable manifest (e.g., JSON/YAML) containing:
- indicator name and export symbol
- input pattern (AI or special)
- parameter schema (name, type, defaults, constraints)
- output schema (count, names, order)
Generated artifacts from this manifest are preferred over hand-maintained parallel lists.
### 5.10 Concrete export example
```csharp
// StatusCodes.cs
file static class StatusCodes
{
public const int QTL_OK = 0;
public const int QTL_ERR_NULL_PTR = 1;
public const int QTL_ERR_INVALID_LENGTH = 2;
public const int QTL_ERR_INVALID_PARAM = 3;
public const int QTL_ERR_INTERNAL = 4;
}
// Exports.cs (excerpt)
[UnmanagedCallersOnly(EntryPoint = "qtl_sma")]
public static unsafe int Sma(double* src, int n, double* output, int period)
{
if (src == null || output == null) return StatusCodes.QTL_ERR_NULL_PTR;
if (n <= 0) return StatusCodes.QTL_ERR_INVALID_LENGTH;
if (period <= 0) return StatusCodes.QTL_ERR_INVALID_PARAM;
try
{
var srcSpan = new ReadOnlySpan<double>(src, n);
var outSpan = new Span<double>(output, n);
QuanTAlib.Sma.Batch(srcSpan, outSpan, period);
return StatusCodes.QTL_OK;
}
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
[UnmanagedCallersOnly(EntryPoint = "qtl_bbands")]
public static unsafe int Bbands(
double* src, int n,
double* outUpper, double* outMiddle, double* outLower,
int period, double multiplier)
{
if (src == null || outUpper == null || outMiddle == null || outLower == null)
return StatusCodes.QTL_ERR_NULL_PTR;
if (n <= 0) return StatusCodes.QTL_ERR_INVALID_LENGTH;
if (period <= 0) return StatusCodes.QTL_ERR_INVALID_PARAM;
try
{
var srcSpan = new ReadOnlySpan<double>(src, n);
var upper = new Span<double>(outUpper, n);
var middle = new Span<double>(outMiddle, n);
var lower = new Span<double>(outLower, n);
QuanTAlib.Bbands.Batch(srcSpan, upper, middle, lower, period, multiplier);
return StatusCodes.QTL_OK;
}
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
```
---
## 6) Python contract
### 6.1 Package identity
| Property | Value |
|----------|-------|
| PyPI name | `quantalib` |
| Import name | `quantalib` |
| AssemblyName | `quantalib` |
### 6.2 Loader (`_loader.py`)
- Detect OS + architecture at import time.
- Load bundled shared library from `quantalib/native/<platform>/`.
- Platform directory mapping:
| Platform | Directory |
|----------|-----------|
| Windows x64 | `native/win_amd64/quantalib.dll` |
| Linux x64 | `native/linux_x86_64/quantalib.so` |
| macOS ARM64 | `native/macosx_arm64/quantalib.dylib` |
| macOS x64 | `native/macosx_x86_64/quantalib.dylib` |
- On failure: raise `OSError` with actionable message listing expected path
and detected platform.
- Use explicit package-path loading; never rely on system library search order.
### 6.3 Bridge (`_bridge.py`)
- Declare `argtypes` and `restype` for every native function at module level.
- Validate function presence at import time (graceful `AttributeError` → skip).
- Input normalization: `np.ascontiguousarray(arr, dtype=np.float64)`.
- Status code checking: helper raises Python exception mapped from return code:
```python
class QtlError(Exception):
"""Base exception for quantalib native errors."""
class QtlNullPointerError(QtlError): ... # status 1
class QtlInvalidLengthError(QtlError): ... # status 2
class QtlInvalidParamError(QtlError): ... # status 3
class QtlInternalError(QtlError): ... # status 4
```
### 6.4 Indicator wrappers (`indicators.py`)
- Expose pandas-ta-compatible function signatures where practical.
- Return types adapt to the input container type (**"same-type-in, same-type-out"**):
| Input type | Output (single) | Output (multi) |
|------------|-----------------|----------------|
| `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
```python
# indicators.py (excerpt)
import numpy as np
import pandas as pd
from quantalib._bridge import _lib, _check
def sma(close, length=None, offset=None, **kwargs):
"""Simple Moving Average."""
length = int(length) if length else 10
offset = int(offset) if offset else 0
# Extract numpy array
index = None
if isinstance(close, pd.Series):
index = close.index
close = close.to_numpy(dtype=np.float64, copy=False)
close = np.ascontiguousarray(close, dtype=np.float64)
n = len(close)
out = np.empty(n, dtype=np.float64)
_check(_lib.qtl_sma(
close.ctypes.data_as(c_double_p), n,
out.ctypes.data_as(c_double_p), length
))
if offset:
out = np.roll(out, offset)
out[:offset] = np.nan
if index is not None:
result = pd.Series(out, index=index, name=f"SMA_{length}")
result.category = "trend"
return result
return out
```
### 6.6 NaN / warmup behavior
- Warmup slots are filled with `NaN` by the C# `Batch` methods.
- This matches pandas-ta's convention for most indicators.
- Known deltas from pandas-ta warmup lengths are documented in the
compatibility table in `README.md`.
### 6.7 Optional dependency policy
`numpy` is the only **hard** dependency. All DataFrame libraries are optional extras:
| 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.
---
## 7) Build and packaging
### 7.1 .NET project (`python.csproj`)
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Library</OutputType>
<PublishAot>true</PublishAot>
<AssemblyName>quantalib</AssemblyName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<InvariantGlobalization>true</InvariantGlobalization>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<Compile Include="src\**\*.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\lib\quantalib.csproj" />
</ItemGroup>
</Project>
```
### 7.2 Local build props (`Directory.Build.props`)
```xml
<Project>
<PropertyGroup>
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
<RunAnalyzers>false</RunAnalyzers>
<ErrorReport>none</ErrorReport>
</PropertyGroup>
</Project>
```
### 7.3 Publish script (`publish.ps1`)
Requirements:
- Publish for RIDs: `win-x64`, `linux-x64`, `osx-arm64`, `osx-x64`
- Clean target directory before each publish
- Verify expected artifact exists per RID (`.dll`, `.so`, `.dylib`)
- Copy artifact into `quantalib/native/<platform>/`
- Exit non-zero on any missing artifact
### 7.4 Python packaging (`pyproject.toml`)
```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "quantalib"
version = "0.1.0"
description = "High-performance technical analysis via QuanTAlib NativeAOT"
requires-python = ">=3.10"
dependencies = ["numpy>=1.24"]
[project.optional-dependencies]
pandas = ["pandas>=1.5"]
dev = ["pytest", "pandas>=1.5"]
[tool.hatch.build.targets.wheel]
packages = ["quantalib"]
```
### 7.5 NativeAOT considerations
- **Trimming:** NativeAOT trims unreachable code. All `Batch` methods are
reachable via explicit calls in `Exports.cs`, so no `rd.xml` needed.
- **Reflection:** QuanTAlib indicators do not use reflection in `Batch` paths.
- **SIMD:** NativeAOT preserves hardware intrinsics. `Avx2.IsSupported` works
at runtime in the compiled native binary.
- **Startup:** NativeAOT has near-zero startup overhead. No JIT warmup.
---
## 8) Full indicator catalog by category
### 8.1 Core (7 indicators)
| Indicator | C# Class | Pattern | Params |
|-----------|----------|---------|--------|
| avgprice | `Avgprice` | C (OHLC) | — |
| ha | `Ha` | C (OHLC) | — |
| medprice | `Medprice` | D (HL) | — |
| midbody | `Midbody` | C (OC) | — |
| midpoint | `Midpoint` | A | period |
| midprice | `Midprice` | D (HL) | period |
| typprice | `Typprice` | C (OHLC) | — |
| wclprice | `Wclprice` | E (HLC) | — |
### 8.2 Momentum (18 indicators)
| Indicator | C# Class | Pattern | Params |
|-----------|----------|---------|--------|
| asi | `Asi` | C (OHLC) | period, limitMove |
| bias | `Bias` | A | period |
| bop | `Bop` | C (OHLC) | — |
| cfb | `Cfb` | A + int[]* | lengths |
| cmo | `Cmo` | A | period |
| macd | `Macd` | A | fastPeriod, slowPeriod |
| mom | `Mom` | A | period |
| pmo | `Pmo` | A | timePeriods, smoothPeriods, signalPeriods |
| ppo | `Ppo` | A | fastPeriod, slowPeriod |
| rs | `Rs` | H (dual) | period |
| roc | `Roc` | A | period |
| rocp | `Rocp` | A | period |
| rocr | `Rocr` | A | period |
| rsi | `Rsi` | A | period |
| rsx | `Rsx` | A | period |
| sam | `Sam` | A | alpha, cutoff |
| tsi | `Tsi` | A | longPeriod, shortPeriod |
| vel | `Vel` | A | period |
| vwmacd | `Vwmacd` | multi | fastPeriod, slowPeriod, signalPeriod |
### 8.3 Oscillators (~35 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| ac | `Ac` | D (HL) | fastPeriod, slowPeriod, acPeriod |
| ao | `Ao` | D (HL) | fastPeriod, slowPeriod |
| apo | `Apo` | A | fastPeriod, slowPeriod |
| bbb | `Bbb` | A | period, multiplier |
| bbi | `Bbi` | A | periods... |
| bbs | `Bbs` | E (HLC) | multi-output |
| brar | `Brar` | C (OHLC) | period |
| cfo | `Cfo` | A | period |
| coppock | `Coppock` | A | roc1, roc2, wma |
| crsi | `Crsi` | A | rsiPeriod, streakPeriod, rankPeriod |
| cti | `Cti` | A | period |
| deco | `Deco` | A | shortPeriod, longPeriod |
| dem | `Dem` | E (HLC) | period |
| dpo | `Dpo` | A | period |
| dstoch | `Dstoch` | E (HLC) | period |
| dosc | `Dosc` | A | rsiPeriod, ema1Period, ema2Period, sigPeriod |
| dymi | `Dymi` | A | basePeriod, shortPeriod, longPeriod... |
| er | `Er` | A | period |
| fisher | `Fisher` | A | period, alpha |
| fisher04 | `Fisher04` | A | period |
| gator | `Gator` | A | jawPeriod... |
| imi | `Imi` | C (OHLC) | period |
| inertia | `Inertia` | A | period |
| kdj | `Kdj` | I (HLC→3) | period, kSmooth, dSmooth |
| kri | `Kri` | A | period |
| kst | `Kst` | A | multiple roc/ma periods |
| marketfi | `Marketfi` | D (HL) + vol | — |
| bw_mfi | `BwMfi` | D (HL) + vol → 2 | — |
| mstoch | `Mstoch` | A | period |
| pgo | `Pgo` | E (HLC) | period |
| psl | `Psl` | A | period |
| qqe | `Qqe` | A | rsiPeriod, smoothFactor, qqeFactor |
| reflex | `Reflex` | A | period |
| reverseema | `ReverseEma` | A | period |
| rvgi | `Rvgi` | C (OHLC) | period |
| rrsi | `Rrsi` | A | smoothLength, rsiLength |
| smi | `Smi` | I (HLC→2+) | period, smoothK, smoothD |
| squeeze | `Squeeze` | I (HLC→multi) | bbPeriod, kcPeriod... |
| squeeze_pro | `SqueezePro` | I (HLC→multi) | period, bbMult, kcMultWide/Normal/Narrow |
| stc | `Stc` | A + enum | kPeriod, dPeriod, fastLen, slowLen, smoothing(int) |
| stoch | `Stoch` | I (HLC→2) | kPeriod, kSmooth, dSmooth |
| stochf | `Stochf` | I (HLC→2) | kPeriod, dPeriod |
| stochrsi | `Stochrsi` | A | rsiLen, stochLen, kSmooth, dSmooth |
| td_seq | `Td_seq` | C (OHLC) | — |
| trendflex | `Trendflex` | A | period |
| trix | `Trix` | A | period |
| ttm_wave | `TtmWave` | I (HLC→multi) | — |
| ultosc | `Ultosc` | E (HLC) | period1, period2, period3 |
| willr | `Willr` | E (HLC) | period |
### 8.4 Trends — FIR (~30 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| alma | `Alma` | A | period, offset, sigma |
| blma | `Blma` | A | period |
| bwma | `Bwma` | A | period, order |
| conv | `Conv` | A + double[]* | kernel |
| crma | `Crma` | A | period |
| dwma | `Dwma` | A | period |
| fwma | `Fwma` | A | period |
| gwma | `Gwma` | A | period, sigma |
| hamma | `Hamma` | A | period |
| hanma | `Hanma` | A | period |
| hend | `Hend` | A | period |
| hma | `Hma` | A | period |
| ilrs | `Ilrs` | A | period |
| kaiser | `Kaiser` | A | period, beta |
| lanczos | `Lanczos` | A | period |
| lsma | `Lsma` | A | period, offset |
| nlma | `Nlma` | A | period |
| nyqma | `Nyqma` | A | period, nyquistPeriod |
| parzen | `Parzen` | A | period |
| pma | `Pma` | A / I (2) | period |
| pwma | `Pwma` | A | period |
| qrma | `Qrma` | A | period |
| rain | `Rain` | A | period |
| rwma | `Rwma` | D (CHL) | period |
| sgma | `Sgma` | A | period, degree |
| sinema | `Sinema` | A | period |
| sma | `Sma` | A | period |
| sp15 | `Sp15` | A | — |
| swma | `Swma` | A | period |
| trima | `Trima` | A | period |
| tsf | `Tsf` | A | period |
| tukey_w | `Tukey_w` | A | period, alpha |
| wma | `Wma` | A | period |
### 8.5 Trends — IIR (~30 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| ahrens | `Ahrens` | A | period |
| coral | `Coral` | A | period, cd |
| decycler | `Decycler` | A | period |
| dema | `Dema` | A (+ alpha) | period |
| dsma | `Dsma` | A | period |
| ema | `Ema` | A (+ alpha) | period |
| frama | `Frama` | A / D (HL) | period |
| gdema | `Gdema` | A | period, vfactor |
| hema | `Hema` | A | period |
| holt | `Holt` | A | period, gamma |
| ht_trendline | `HtTrendline` | A | — |
| hwma | `Hwma` | A | period |
| jma | `Jma` | A | period, phase, power |
| kama | `Kama` | A | period, fastPeriod, slowPeriod |
| lema | `Lema` | A (+ alpha) | period |
| ltma | `Ltma` | A | period |
| mama | `Mama` | I (2) | fastLimit, slowLimit |
| mavp | `Mavp` | H (src+periods) | minPeriod, maxPeriod |
| mcnma | `Mcnma` | A (+ alpha) | period |
| mgdi | `Mgdi` | A | period, k |
| mma | `Mma` | A | period |
| nma | `Nma` | A | period |
| qema | `Qema` | A | period |
| rema | `Rema` | A | period, lambda |
| rgma | `Rgma` | A | period, passes |
| rma | `Rma` | A | period |
| t3 | `T3` | A | period, vfactor |
| tema | `Tema` | A (+ alpha) | period |
| trama | `Trama` | A | period |
| vama | `Vama` | A | period |
| vidya | `Vidya` | A | period |
| yzvama | `Yzvama` | A | period |
| zldema | `Zldema` | A (+ alpha) | period |
| zlema | `Zlema` | A (+ alpha) | period |
| zltema | `Zltema` | A (+ alpha) | period |
### 8.6 Channels (~20 indicators)
All channel indicators output 3 spans: upper, middle, lower (Pattern I).
| Indicator | C# Class | Input pattern | Key params |
|-----------|----------|---------------|------------|
| abber | `Abber` | A | period |
| accbands | `AccBands` | B (HLC) | period |
| apchannel | `Apchannel` | D (HL) | period |
| apz | `Apz` | B (HLC) | period |
| atrbands | `AtrBands` | B (HLC) | period, multiplier |
| bbands | `Bbands` | A | period, multiplier |
| dc | `Dc` | D (HL) | period |
| decaychannel | `Decaychannel` | B (HLC) | period |
| fcb | `Fcb` | D (HL) | period |
| hwc | `Hwc` | A→multi | period, multiplier |
| jbands | `Jbands` | A | period, phase, power |
| kc | `Kc` | B (HLC) | period, multiplier |
| maenv | `Maenv` | A | period, pct |
| mmchannel | `Mmchannel` | D (HL) | period |
| pc | `Pc` | D (HL) | period |
| regchannel | `Regchannel` | A | period |
| sdchannel | `Sdchannel` | A | period |
| starchannel | `Starchannel` | B (HLC) | period |
| stbands | `Stbands` | B (HLC) | period |
| ttm_lrc | `TtmLrc` | A | period |
| ubands | `Ubands` | A | period |
| uchannel | `Uchannel` | D (HL) | period |
| vwapbands | `Vwapbands` | A + vol | period |
| vwapsd | `Vwapsd` | A + vol | period |
### 8.7 Volatility (~20 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| bbw | `Bbw` | A | period, multiplier |
| bbwn | `Bbwn` | A | period, multiplier, lookback |
| bbwp | `Bbwp` | A | period, multiplier, lookback |
| ccv | `Ccv` | A | period, method |
| cv | `Cv` | A | period, alpha, beta |
| cvi | `Cvi` | A | rocLength, smoothLength |
| etherm | `Etherm` | E (HLC) | period |
| ewma | `Ewma` | A | period, annualize, annualPeriods |
| gkv | `Gkv` | C (OHLC) | period, annualPeriods |
| hlv | `Hlv` | D (HL) | period, annualPeriods |
| hv | `Hv` | A | period, annualPeriods |
| jvolty | `Jvolty` | A | period, phase, power |
| jvoltyn | `Jvoltyn` | A | period, phase, power |
| massi | `Massi` | A | emaLength, sumLength |
| rsv | `Rsv` | C (OHLC) | period, annualPeriods |
| rv | `Rv` | A | period, annualPeriods |
| rvi | `Rvi` | A | period |
| tr | `Tr` | E (HLC) | — |
| ui | `Ui` | A | period |
| vov | `Vov` | A | period, vovPeriod |
| vr | `Vr` | E (HLC) | period |
| yzv | `Yzv` | C (OHLC) | period, annualPeriods |
### 8.8 Volume (~30 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| ad | `Ad` | B (HLCV) | — |
| adosc | `Adosc` | B (HLCV) | fastPeriod, slowPeriod |
| aobv | `Aobv` | I (CV→2) | — |
| cmf | `Cmf` | B (HLCV) | period |
| efi | `Efi` | G (CV) | period |
| eom | `Eom` | B (HLV) | period, volumeScale |
| evwma | `Evwma` | G (SV) | period |
| iii | `Iii` | B (HLCV) | period |
| kvo | `Kvo` | B (HLCV) | fastPeriod, slowPeriod |
| mfi | `Mfi` | B (HLCV) | period |
| nvi | `Nvi` | G (CV) | startValue |
| obv | `Obv` | G (CV) | — |
| pvd | `Pvd` | G (CV) | pricePeriod, volumePeriod, smoothingPeriod |
| pvi | `Pvi` | G (CV) | startValue |
| pvo | `Pvo` | I (V→3) | fastPeriod, slowPeriod, signalPeriod |
| pvr | `Pvr` | G (PV) | — |
| pvt | `Pvt` | G (CV) | — |
| tvi | `Tvi` | G (PV) | minTick |
| twap | `Twap` | A | period |
| va | `Va` | B (HLCV) | — |
| vf | `Vf` | G (CV) | period |
| vo | `Vo` | A (volume) | shortPeriod, longPeriod |
| vroc | `Vroc` | A (volume) | period, usePercent |
| vwad | `Vwad` | B (HLCV) | period |
| vwap | `Vwap` | B (HLCV) | period |
| vwma | `Vwma` | G (SV) | period |
| wad | `Wad` | B (HLCV) | — |
### 8.9 Statistics (~30 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| acf | `Acf` | A | period, lag |
| cma | `Cma` | A | — |
| correl | `Correl` | H | period |
| covariance | `Covariance` | H | period, isPopulation |
| entropy | `Entropy` | A | period |
| geomean | `Geomean` | A | period |
| granger | `Granger` | H | period, maxlag |
| harmean | `Harmean` | A | period |
| hurst | `Hurst` | A | period |
| iqr | `Iqr` | A | period |
| jb | `Jb` | A | period |
| kendall | `Kendall` | H | period |
| kurtosis | `Kurtosis` | A | period, isPopulation |
| linreg | `LinReg` | A | period, offset |
| meandev | `MeanDev` | A | period |
| median | `Median` | A | period |
| mode | `Mode` | A | period |
| pacf | `Pacf` | A | period, lag |
| percentile | `Percentile` | A | period, percent |
| polyfit | `Polyfit` | A | period, degree |
| quantile | `Quantile` | A | period, quantileLevel |
| skew | `Skew` | A | period, isPopulation |
| spearman | `Spearman` | H | period |
| stddev | `StdDev` | A | period, isPopulation |
| stderr | `Stderr` | A | period |
| sum | `Sum` | A | period |
| theil | `Theil` | A | period |
| trim | `Trim` | A | period, trimPct |
| variance | `Variance` | A | period, isPopulation |
| wavg | `Wavg` | A | period |
| wins | `Wins` | A | period, winPct |
| zscore | `Zscore` | A | period |
| ztest | `Ztest` | A | period, mu0 |
| cointegration | `Cointegration` | H | — |
| convexity | `Convexity` | multi | period |
### 8.10 Errors (~25 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| huber | `Huber` | F | period, delta |
| logcosh | `LogCosh` | F | period |
| mae | `Mae` | F | period |
| maape | `Maape` | F | period |
| mape | `Mape` | F | period |
| mapd | `Mapd` | F | period |
| mase | `Mase` | F | period |
| mdae | `Mdae` | F | period |
| mdape | `Mdape` | F | period |
| me | `Me` | F | period |
| mpe | `Mpe` | F | period |
| mrae | `Mrae` | F | period |
| mse | `Mse` | F | period |
| msle | `Msle` | F | period |
| pseudohuber | `PseudoHuber` | F | period, delta |
| quantileloss | `QuantileLoss` | F | period, quantile |
| rae | `Rae` | F | period |
| rmse | `Rmse` | F | period |
| rmsle | `Rmsle` | F | period |
| rse | `Rse` | F | period |
| rsquared | `Rsquared` | F | period |
| smape | `Smape` | F | period |
| theilu | `TheilU` | F | period |
| tukeybiweight | `TukeyBiweight` | F | period, c |
| wmape | `Wmape` | F | period |
| wrmse | `Wrmse` | F | period (+ weighted variant) |
### 8.11 Filters (~30 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| agc | `Agc` | A | decay |
| alaguerre | `ALaguerre` | A | length, medianLength |
| baxterking | `BaxterKing` | A | pLow, pHigh, k |
| bessel | `Bessel` | A | length |
| bilateral | `Bilateral` | A | period, sigmaSRatio, sigmaRMult |
| bpf | `Bpf` | A | lowerPeriod, upperPeriod |
| butter2 | `Butter2` | A | period |
| butter3 | `Butter3` | A | period |
| cfitz | `Cfitz` | A | pLow, pHigh |
| cheby1 | `Cheby1` | A | period, ripple |
| cheby2 | `Cheby2` | A | period, attenuation |
| edcf | `Edcf` | A | length |
| elliptic | `Elliptic` | A | period |
| gauss | `Gauss` | A | sigma |
| hann | `Hann` | A | length |
| hp | `Hp` | A | lambda |
| hpf | `Hpf` | A | length |
| kalman | `Kalman` | A | q, r (or period, gain) |
| laguerre | `Laguerre` | A | gamma |
| lms | `Lms` | A | order, mu |
| loess | `Loess` | A | period |
| modf | `Modf` | A | period, beta, feedback, fbWeight |
| notch | `Notch` | A | period, q |
| nw | `Nw` | A | period, bandwidth |
| oneeuro | `OneEuro` | A | minCutoff, beta, dCutoff |
| rls | `Rls` | A | order, lambda |
| rmed | `Rmed` | A | period |
| roofing | `Roofing` | A | hpLength, ssLength |
| sgf | `Sgf` | A | period, polyOrder |
| spbf | `Spbf` | A | shortPeriod, longPeriod, rmsPeriod |
| ssf2 | `Ssf2` | A | period |
| ssf3 | `Ssf3` | A | period |
| usf | `Usf` | A | period |
| voss | `Voss` | A | period, predict, bandwidth |
| wavelet | `Wavelet` | A | levels, threshMult |
| wiener | `Wiener` | A | period, smoothPeriod |
### 8.12 Cycles (~14 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| ccor | `Ccor` | A | period, threshold |
| ccyc | `Ccyc` | A | alpha |
| cg | `Cg` | A | period |
| dsp | `Dsp` | A | period |
| eacp | `Eacp` | A | minPeriod, maxPeriod, avgLength, enhance |
| ebsw | `Ebsw` | A | hpLength, ssfLength |
| homod | `Homod` | A | minPeriod, maxPeriod |
| ht_dcperiod | `HtDcperiod` | A | — |
| ht_dcphase | `HtDcphase` | A | — |
| ht_phasor | `HtPhasor` | I (→2) | — |
| ht_sine | `HtSine` | I (→2) | — |
| lunar | `Lunar` | Special (long*) | — |
| solar | `Solar` | Special (long*) | — |
| ssfdsp | `Ssfdsp` | A | period |
### 8.13 Dynamics (~10 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| amat | `Amat` | I (→2) | fastPeriod, slowPeriod |
| aroon | `Aroon` | D (HL) | period |
| ghla | `Ghla` | E (HLC) | period |
| ht_trendmode | `HtTrendmode` | A | — |
| pfe | `Pfe` | A | period, smoothPeriod |
| ravi | `Ravi` | A | shortPeriod, longPeriod |
| vhf | `Vhf` | A | period |
| vortex | `Vortex` | I (HLC→2) | period |
| qstick | `Qstick` | C (OC) | period |
### 8.14 Reversals (~10 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| atrstop | `Atrstop` | E (HLC→1) | period, multiplier |
| chandelier | `Chandelier` | C (OHLC→2) | period, multiplier |
| ckstop | `Ckstop` | C (OHLC→2) | period, multiplier |
| fractals | `Fractals` | D (HL→2) | period |
| pivot | `Pivot` | D (HL→multi) | — |
| pivotcam | `Pivotcam` | E (HLC→multi) | — |
| pivotdem | `Pivotdem` | C (OHLC→multi) | — |
| pivotext | `Pivotext` | E (HLC→multi) | — |
| pivotfib | `Pivotfib` | E (HLC→multi) | — |
| pivotwood | `Pivotwood` | E (HLC→multi) | — |
| sar | `Sar` | C (OHLC) | accelStart, accelMax |
| swings | `Swings` | D (HL→multi) | period |
| ttm_scalper | `TtmScalper` | D (HL→multi) | period |
| vstop | `Vstop` | E (HLC→1) | period, multiplier |
### 8.15 Numerics (~15 indicators)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| accel | `Accel` | A | — |
| betadist | `Betadist` | A | alpha, beta params |
| binomdist | `Binomdist` | A | n, p |
| change | `Change` | A | period |
| dwt | `Dwt` | A | levels |
| expdist | `Expdist` | A | lambda |
| exptrans | `Exptrans` | A | — |
| fdist | `Fdist` | A | df1, df2 |
| fft | `Fft` | A | — |
| gammadist | `Gammadist` | A | alpha, beta |
| highest | `Highest` | A | period |
| ifft | `Ifft` | A | — |
| jerk | `Jerk` | A | — |
| lineartrans | `Lineartrans` | A | slope, intercept |
| lognormdist | `Lognormdist` | A | mu, sigma |
| logtrans | `Logtrans` | A | — |
| lowest | `Lowest` | A | period |
| normdist | `Normdist` | A | mu, sigma |
| normalize | `Normalize` | A | period |
| poissondist | `Poissondist` | A | lambda |
| relu | `Relu` | A | — |
| sigmoid | `Sigmoid` | A | k, x0 |
| slope | `Slope` | A | — |
| sqrttrans | `Sqrttrans` | A | — |
| tdist | `Tdist` | A | df |
| weibulldist | `Weibulldist` | A | k, lambda |
### 8.16 Forecasts (1 indicator)
| Indicator | C# Class | Pattern | Key params |
|-----------|----------|---------|------------|
| afirma | `Afirma` | A + enum | period, window(int), leastSquares(bool→int) |
---
## 9) Compatibility strategy with pandas-ta
### 9.1 Compatibility levels
| Level | Description |
|-------|-------------|
| **L1** | Signature-compatible: same parameter names and defaults |
| **L2** | Return-compatible: same return container shape and type |
| **L3** | Behavior-compatible: matching warmup NaN count, naming, core semantics |
### 9.2 pandas-ta overlap indicators
These indicators exist in both pandas-ta and QuanTAlib. All target L2 minimum,
L3 where QuanTAlib's algorithm matches:
**Trend:** SMA, EMA, DEMA, TEMA, HMA, WMA, KAMA, FWMA, T3, TRIMA, ALMA, LSMA,
SINEMA, VIDYA, ZLEMA, FRAMA, HWMA
**Momentum:** RSI, ROC, MOM, CMO, MACD, PPO, TSI, RSX
**Oscillators:** STOCH, STOCHF, STOCHRSI, WILLR, AO, APO, TRIX, FISHER, DPO,
ULTOSC, KDJ, QQE, SQUEEZE, STC, RVGI
**Volatility:** ATR, TR, BBANDS, BBW, MASSI, UI, EWMA (realized vol)
**Volume:** OBV, ADL, ADOSC, CMF, MFI, NVI, PVI, PVT, VWAP, VWMA, EFI, KVO
**Statistics:** ZSCORE, VARIANCE, STDDEV, SKEW, KURTOSIS, MEDIAN, ENTROPY,
LINREG
### 9.3 Known deltas
- pandas-ta uses `talib=True` to delegate to TA-Lib C library; quantalib uses
its own C# implementations.
- Warmup NaN counts may differ for recursive indicators (EMA, RSI) due to
different convergence thresholds.
- `offset` parameter: quantalib supports it via `np.roll` in the Python layer.
- `fillna`/`fill_method` kwargs: supported in Python layer, not in native.
---
## 10) Testing protocol
### 10.1 Native ABI tests (C# xUnit in lib project)
- Status-code contract tests for all exports
- Null pointer, zero length, negative period → correct error codes
- Known golden values vs managed `Batch` calls
### 10.2 Python tests (pytest)
| Suite | Description |
|-------|-------------|
| `test_smoke.py` | Import, load native lib, call SMA, check output is ndarray |
| `test_shapes.py` | `len(output) == len(input)` for all single-output indicators |
| `test_status_codes.py` | Null ptr, bad length, bad params raise correct exceptions |
| `test_golden.py` | Compare outputs vs known golden values from managed QuanTAlib |
| `test_compat.py` | pandas-ta parity for overlap indicators (Series/DataFrame) |
### 10.3 Performance tests
Track per-indicator:
- Python wrapper overhead (µs per call for 10k bars)
- Throughput (rows/sec)
- Compare vs pandas-ta for overlap indicators
Acceptance (initial baseline):
- Median wrapper overhead does not exceed pandas-ta by more than 20% on overlap indicators where algorithms are directly comparable.
- No unbounded memory growth across repeated runs.
- Performance report is produced per release candidate and checked into CI artifacts.
---
## 11) CI and release
### 11.1 Build matrix
| RID | Runner | Artifact |
|-----|--------|----------|
| `win-x64` | `windows-latest` | `quantalib.dll` |
| `linux-x64` | `ubuntu-latest` | `quantalib.so` |
| `osx-arm64` | `macos-14` | `quantalib.dylib` |
| `osx-x64` | `macos-13` | `quantalib.dylib` |
### 11.2 Pipeline stages
1. `dotnet publish -r <rid> -c Release` per RID
2. Stage artifacts into `quantalib/native/<platform>/`
3. `python -m pytest tests/` per platform
4. Build wheel with `python -m build`
5. Upload to PyPI (manual trigger for releases)
### 11.3 Release checklist
- [ ] All RID artifacts present and loadable
- [ ] All pytest suites pass on all platforms
- [ ] Performance benchmark logged (no regressions)
- [ ] `CHANGELOG.md` updated
- [ ] Version bumped in `pyproject.toml`
---
## 12) Risk register
| # | Risk | Impact | Mitigation |
|---|------|--------|------------|
| 1 | ABI breakage on update | High | Versioned symbols (`qtl_*`) + ABI contract tests |
| 2 | NativeAOT trims used code | High | Explicit calls in `Exports.cs` guarantee reachability |
| 3 | Platform loader failures | Medium | Explicit package-path loading + startup diagnostics |
| 4 | Behavior drift vs pandas-ta | Medium | Compatibility suite + documented deltas |
| 5 | Enum/array params over ABI | Medium | int/ptr mapping with documented contracts |
| 6 | Large export surface (~290) | Medium | Code generation considered for `Exports.cs` if manual is too error-prone |
| 7 | Cross-compilation failures | Medium | CI matrix catches per-platform issues early |
| 8 | Thread safety | Low | `Batch` methods are stateless; no shared mutable state |
---
## 13) Review checklist
- [ ] Naming accepted: PyPI/import/binary all `quantalib`
- [ ] Versioned ABI prefix accepted (`qtl_*`)
- [ ] Flat structure accepted (`Exports.cs`, `indicators.py`)
- [ ] Status-code model accepted (04)
- [ ] 9 ABI signature patterns accepted
- [ ] Special parameter mappings accepted (enum→int, bool→int, array→ptr+len)
- [ ] Full indicator catalog reviewed
- [ ] Compatibility levels accepted (L1/L2/L3)
- [ ] RID matrix accepted (4 platforms)
- [ ] NaN/warmup policy accepted
- [ ] Thread safety model accepted
- [ ] CI pipeline accepted
---
## 14) Implementation order
1. **Skeleton:** `python.csproj`, `Directory.Build.props`, package layout,
`_loader.py`, `_bridge.py` scaffolding, `pyproject.toml`
2. **ABI layer:** `StatusCodes.cs`, `ArrayBridge.cs`, `Exports.cs` (all ~290)
3. **Python layer:** `indicators.py` (all wrappers), `_compat.py` (aliases)
4. **Tests:** `test_smoke.py`, `test_shapes.py`, `test_status_codes.py`,
`test_golden.py`, `test_compat.py`
5. **Publish:** `publish.ps1`, CI workflow, wheel packaging
6. **Docs:** `README.md` with full indicator list and compatibility table
---
## 15) Open decisions
1. **Error mapping granularity:** `ValueError` for bad params vs separate
exception classes per status code (current spec uses separate classes).
2. **Alpha overloads:** Export as `qtl_ema_alpha` or defer to v2?
3. **Code generation:** Generate `Exports.cs` + `_bridge.py` + `indicators.py`
from a manifest file, or write manually?
4. **Linux wheel tag:** `manylinux2014_x86_64` vs `manylinux_2_17_x86_64`?
5. **Version sync:** Should `quantalib` Python version track QuanTAlib NuGet
version, or version independently?
## 16) Review findings and prioritized refinements
### High priority
1. **Finalize ABI evolution policy**
- Keep `qtl_` immutable for v1 and reserve `qtl2_` for first break.
2. **Adopt manifest-driven generation**
- Eliminate manual drift risk across native exports, ctypes bridge, and wrappers.
3. **Clarify output validity on internal failures**
- Treat outputs as invalid on `QTL_ERR_INTERNAL`.
### Medium priority
4. **Lock pandas fallback behavior**
- NumPy-only mode should be deterministic and explicitly tested.
5. **Define parity subset for L3**
- Freeze exact Wave 1 L3 indicator list before coding starts.
6. **Set measurable perf gates**
- Keep CI benchmarks with comparable overlap indicators and stable datasets.
### Low priority
7. **Catalog automation**
- Generate indicator catalog tables from manifest to avoid stale docs.
8. **Release metadata policy**
- Define whether Python package version mirrors or decouples from NuGet version.