43 KiB
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-contiguousfloat64; 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
Batchmethods 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.csmapping allBatchmethods to[UnmanagedCallersOnly]exports - Python package
quantalibwith 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
- ABI correctness — pointer, length, and parameter contracts are explicit and validated on both sides of the boundary.
- Performance — call QuanTAlib
Batchmethods directly; no managed allocations in the export shim beyond span construction. - ABI stability — export names are prefixed (
qtl_*) and versioned; existing symbols never change within a major version. - Deterministic behavior — warmup slots filled with
NaN; invalid inputs produce defined status codes, never exceptions. - pandas-ta compatibility — Python names and signatures mirror pandas-ta where feasible, with documented deltas.
- 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
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
intstatus 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/catchthat returnsQTL_ERR_INTERNALon 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:
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:
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:
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:
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:
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:
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:
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: Correlation.Batch(ReadOnlySpan<double> seriesX, ..seriesY, Span<double> output, int period)
C ABI:
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:
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 |
| KChannel | 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— takesint period - Alpha export:
qtl_ema_alpha— takesdouble 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:
- Null checks for all required pointer parameters →
QTL_ERR_NULL_PTR n > 0→QTL_ERR_INVALID_LENGTH- Range checks on scalar parameters (e.g.,
period > 0) →QTL_ERR_INVALID_PARAM - Semantic checks (e.g.,
fastPeriod < slowPeriodwhere required) →QTL_ERR_INVALID_PARAM try/catcharoundBatchcall →QTL_ERR_INTERNALon 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
nvalues 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 (A–I 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
// 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
OSErrorwith 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
argtypesandrestypefor 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:
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:
| 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 |
- 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.
6.5 Concrete Python wrapper example
# 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
NaNby the C#Batchmethods. - 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 pandas fallback policy
When pandas is not installed:
- Functions accept and return
np.ndarrayonly. pd.Series/pd.DataFrameinput raisesImportErrorwith message"pandas required for Series/DataFrame input".numpyis a hard dependency;pandasis an optional extra.
7) Build and packaging
7.1 .NET project (python.csproj)
<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)
<Project>
<PropertyGroup>
<GitVersionSkip>true</GitVersionSkip>
<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)
[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
Batchmethods are reachable via explicit calls inExports.cs, so nord.xmlneeded. - Reflection: QuanTAlib indicators do not use reflection in
Batchpaths. - SIMD: NativeAOT preserves hardware intrinsics.
Avx2.IsSupportedworks 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 |
| prs | Prs |
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 |
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 |
| dosc | Dosc |
A | rsiPeriod, ema1Period, ema2Period, sigPeriod |
| dymoi | Dymoi |
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 | — |
| 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 |
| smi | Smi |
I (HLC→2+) | period, smoothK, smoothD |
| squeeze | Squeeze |
I (HLC→multi) | bbPeriod, kcPeriod... |
| 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 |
| htit | Htit |
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 |
| dchannel | Dchannel |
D (HL) | period |
| decaychannel | Decaychannel |
B (HLC) | period |
| fcb | Fcb |
D (HL) | period |
| jbands | Jbands |
A | period, phase, power |
| kchannel | Kchannel |
B (HLC) | period, multiplier |
| maenv | Maenv |
A | period, pct |
| mmchannel | Mmchannel |
D (HL) | period |
| pchannel | Pchannel |
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 |
|---|---|---|---|
| adl | Adl |
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 | — |
| correlation | Correlation |
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 | — |
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 |
|---|---|---|---|
| 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) | — |
| psar | Psar |
C (OHLC) | accelStart, accelMax |
| swings | Swings |
D (HL→multi) | period |
| ttm_scalper | TtmScalper |
D (HL→multi) | period |
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=Trueto 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.
offsetparameter: quantalib supports it vianp.rollin the Python layer.fillna/fill_methodkwargs: 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
Batchcalls
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
dotnet publish -r <rid> -c Releaseper RID- Stage artifacts into
quantalib/native/<platform>/ python -m pytest tests/per platform- Build wheel with
python -m build - 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.mdupdated- 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 (0–4)
- 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
- Skeleton:
python.csproj,Directory.Build.props, package layout,_loader.py,_bridge.pyscaffolding,pyproject.toml - ABI layer:
StatusCodes.cs,ArrayBridge.cs,Exports.cs(all ~290) - Python layer:
indicators.py(all wrappers),_compat.py(aliases) - Tests:
test_smoke.py,test_shapes.py,test_status_codes.py,test_golden.py,test_compat.py - Publish:
publish.ps1, CI workflow, wheel packaging - Docs:
README.mdwith full indicator list and compatibility table
15) Open decisions
- Error mapping granularity:
ValueErrorfor bad params vs separate exception classes per status code (current spec uses separate classes). - Alpha overloads: Export as
qtl_ema_alphaor defer to v2? - Code generation: Generate
Exports.cs+_bridge.py+indicators.pyfrom a manifest file, or write manually? - Linux wheel tag:
manylinux2014_x86_64vsmanylinux_2_17_x86_64? - Version sync: Should
quantalibPython version track QuanTAlib NuGet version, or version independently?
16) Review findings and prioritized refinements
High priority
- Finalize ABI evolution policy
- Keep
qtl_immutable for v1 and reserveqtl2_for first break.
- Keep
- Adopt manifest-driven generation
- Eliminate manual drift risk across native exports, ctypes bridge, and wrappers.
- Clarify output validity on internal failures
- Treat outputs as invalid on
QTL_ERR_INTERNAL.
- Treat outputs as invalid on
Medium priority
- Lock pandas fallback behavior
- NumPy-only mode should be deterministic and explicitly tested.
- Define parity subset for L3
- Freeze exact Wave 1 L3 indicator list before coding starts.
- Set measurable perf gates
- Keep CI benchmarks with comparable overlap indicators and stable datasets.
Low priority
- Catalog automation
- Generate indicator catalog tables from manifest to avoid stale docs.
- Release metadata policy
- Define whether Python package version mirrors or decouples from NuGet version.