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
BIN
View File
Binary file not shown.
+53
View File
@@ -10,6 +10,27 @@ Traditional object-oriented design stores data as arrays of objects: `List<Price
QuanTAlib stores timestamps and values in separate contiguous arrays. When calculating an average, the CPU loads a cache line filled entirely with price values, without wasting space on interleaved timestamps or object headers.
``` mermaid
graph LR
subgraph AoS [Array of Structures: CPU chokes on interleaved data]
direction LR
A1[Time] --- A2[Price] --- A3[Time] --- A4[Price] --- A5[Time]
style A1 fill:#550000
style A3 fill:#550000
style A5 fill:#550000
end
subgraph SoA [Structure of Arrays: Contiguous SIMD pipeline]
direction LR
S1[Price] --- S2[Price] --- S3[Price] --- S4[Price] --- S5[Price]
style S1 fill:#005500
style S2 fill:#005500
style S3 fill:#005500
style S4 fill:#005500
style S5 fill:#005500
end
```
The performance difference is measurable:
| Operation | SoA Layout | AoS Layout | Improvement |
@@ -62,6 +83,17 @@ The math to calculate averages works with limited history. A 14-period SMA with
Trading systems have different data flow patterns. Backtesting engines process years of historical data in batch. Real-time systems update indicators bar-by-bar. Event-driven architectures react to changes asynchronously. QuanTAlib provides four modes optimized for these patterns.
``` mermaid
graph TD
Event[Eventing Mode<br/>Reactive Chain] -->|Adds pub/sub overhead| Stream
Stream[Streaming Mode<br/>Stateful O1 Updates] -->|Maintains state across| Span
Batch[Batch Mode<br/>Time-Aligned TSeries] -->|Unwraps to| Span
Span[Span Mode<br/>Stackalloc / Raw Memory]
style Span fill:#003300,stroke:#00ff00,stroke-width:2px
```
### Span Mode
Operates directly on `Span<double>` without allocating objects. Raw arrays in, calculated arrays out. Zero garbage collection pressure, maximum speed, minimal abstraction.
@@ -170,6 +202,27 @@ else
CalculateScalar(source, output);
```
``` mermaid
graph TD
Start{JIT Hardware Detection}
AVX512[AVX-512 Vectorization<br/>8 doubles per instruction]
AVX2[AVX2 Vectorization<br/>4 doubles per instruction]
NEON[ARM NEON / AdvSimd<br/>2 doubles per instruction]
Scalar[Scalar Fallback<br/>1 double per instruction]
Start -->|Avx512F.IsSupported| AVX512
Start -->|Avx2.IsSupported| AVX2
Start -->|AdvSimd.IsSupported| NEON
Start -->|Instruction Set Missing| Scalar
style Start fill:#333
style AVX512 fill:#004400
style AVX2 fill:#444400
style NEON fill:#003366
style Scalar fill:#440000
```
The library checks hardware support at runtime. Systems without AVX2 fall back to scalar implementations. The code runs everywhere; speed varies with hardware capability.
### Allocation Discipline
+117
View File
@@ -0,0 +1,117 @@
# Why Apache 2.0
QuanTAlib grinds financial math at the instruction-cycle level. Circular buffers, incremental O(1) computation, hardware-aligned memory. Over 300 indicators, each designed to sprint through streaming data without allocating so much as a sneeze on the managed heap.
That kind of optimization is precisely what proprietary firms like to absorb, repackage, and patent.
License is not formality. License is structural defense.
## The Attack Vector
I once assumed open-source licensing was about generosity. Slap MIT on it, share with world, feel warm inside. Then I watched a hedge fund's legal team send a cease-and-desist to an open-source author for code that author wrote. The fund had patented a specific algorithmic optimization they found in the project, repackaged it, and then had the audacity to claim prior art.
This is not urban legend. This is Tuesday in financial software.
The attack works like this:
1. Proprietary firm downloads QuanTAlib
2. They notice a specific incremental computation pattern: say, the O(1) streaming Savitzky-Golay implementation using circular buffers
3. Legal department files a patent on that specific methodology
4. They send cease-and-desist to original author
5. Author discovers that "doing whatever you want" cuts both ways
6. Author's Friday evening is ruined. Possibly also Saturday
Permissive license without patent protection is an invitation printed on expensive cardstock.
## The MIT Temptation
MIT is 170 words. Beautiful in its brevity. Says users can do whatever they want, provided they keep copyright notice. I understand the appeal. I felt it myself. Two paragraphs, done, back to writing code.
The fatal flaw: no explicit patent grant. Zero protection against the scenario above. If someone patents a technique derived from your circular buffer implementation, MIT gives you the legal standing of a fortune cookie.
MIT works fine for frontend widgets and utility libraries where nobody is going to patent your string formatter. For a library that optimizes financial math with hardware-aware memory patterns and incremental algorithms? MIT is a t-shirt in a legal gunfight.
I have nothing against MIT. MIT did not hurt me. MIT simply does not solve this particular problem.
## The BSD Alternative
BSD 3-Clause shares MIT's structural weakness on patents. It adds a clause preventing users from plastering your name on their marketing materials. This prevents some shady exchange from stamping "Powered by QuanTAlib" on their homepage while their risk engine quietly produces incorrect signals because they modified a filter coefficient and told nobody.
That clause solves a marketing problem. It does not solve the intellectual property problem. Two different problems. Two different threat models.
## Why Apache 2.0 Specifically
Apache 2.0 provides structural protections that matter when your code will be consumed by entities with legal departments larger than your engineering team.
### Explicit Patent Grant (Section 3)
Every contributor explicitly grants a patent license to every user. This is not implied. Not assumed. Not "probably fine." Written in the actual text.
What this means in practice:
- Contributors cannot submit code and later claim patent rights over it
- Users receive a clear, irrevocable patent license for contributions they use
- Grant covers the specific contribution and its combination with existing work
- The word "irrevocable" is doing heavy lifting here, and it knows it
### Patent Retaliation (Section 3, Final Paragraph)
This is the nuclear deterrent. Worth quoting the operational logic:
If any entity uses QuanTAlib and then sues any contributor for patent infringement related to the Work, their license to use QuanTAlib **terminates immediately**. On the date litigation is filed. Not when judgment is rendered. Not after appeals. The moment the lawsuit hits the docket.
Mutually assured destruction. Bad actors can use library for commercial gain (which drives adoption, which is good), but they cannot weaponize legal system against creators. The moment they file patent suit, they lose right to use the software they built their system on.
I have watched a general counsel's face when this clause was explained to them. The expression was educational.
### Change Tracking (Section 4b)
Modified files must carry prominent notices stating they were changed. This creates audit trail. When derivative work surfaces in the wild producing subtly wrong RSI values because someone "optimized" the circular buffer logic, it is clear what was modified and by whom.
For a library where numerical correctness is the product: where a wrong coefficient in a filter produces trading signals that look plausible but bleed money slowly enough that you do not notice until Q3 reporting: traceability has value beyond legal compliance.
## The Honest Tradeoff
Apache 2.0 is roughly 4,300 words. MIT is 170. That is a 25x complexity increase. Apache requires attribution, license inclusion, change notices. This adds compliance friction for downstream users.
I will not pretend otherwise. The friction is real.
But consider the consumer. QuanTAlib will be consumed by hedge funds, proprietary trading desks, and platform vendors. These are entities that employ compliance officers whose entire job is reading license files. They have already read Apache 2.0. They have templates for it. The "friction" for these consumers is approximately zero.
The friction for a weekend hobbyist who just wants to calculate a moving average? Also approximately zero, because hobbyists do not file patents.
The friction exists in a narrow band of consumers who want to do something unusual with the code. For that narrow band, 4,300 words of clarity is better than 170 words of ambiguity.
## Side-by-Side
| Protection | MIT | BSD 3-Clause | Apache 2.0 |
|:---|:---:|:---:|:---:|
| Copyright protection | ✅ | ✅ | ✅ |
| Disclaimer of warranty | ✅ | ✅ | ✅ |
| Explicit patent grant | ❌ | ❌ | ✅ |
| Patent retaliation clause | ❌ | ❌ | ✅ |
| Name use restriction | ❌ | ✅ | ✅ |
| Change tracking requirement | ❌ | ❌ | ✅ |
| Contribution license terms | ❌ | ❌ | ✅ |
Six of seven protections versus two. The math is not subtle.
## What This Means for You
**Using QuanTAlib commercially?** Go ahead. Apache 2.0 explicitly permits commercial use, modification, distribution, and sublicensing. No phone call required. No royalties. No awkward conversations.
**Modifying QuanTAlib?** Note your changes in modified files. That is it. You are not required to open-source your modifications. You are not required to share your proprietary trading strategy that uses a custom indicator chain. Keep your secrets. Just mark what you changed.
**Building a product on QuanTAlib?** Include the license file and attribution. Your compliance officer already knows how to do this. If you do not have a compliance officer, the LICENSE file in repository root contains everything you need.
**Thinking about patenting something derived from QuanTAlib?** Read Section 3 carefully. Then read it again. Then perhaps reconsider.
## Bottom Line
Finance code is a target. Always has been. The firms that consume open-source math libraries are the same firms that maintain patent portfolios as competitive weapons.
Apache 2.0 does not restrict freedom. Anyone can use, modify, distribute, sell, and build empires on QuanTAlib. It restricts one specific behavior: using the legal system to attack the people who wrote the code you are profiting from.
That seems reasonable. Even to a curmudgeon.
Full license text: [LICENSE](../LICENSE)
+350
View File
@@ -0,0 +1,350 @@
# PineScript Guide: QuanTAlib Indicators for TradingView
> "You do not need to understand the math. But the math does not care whether you understand it."
## Welcome, Brave Copy-Paster
You are here because you want an indicator on your TradingView chart. Maybe someone on Crypto Twitter posted a screenshot with colored lines and you thought "I need that." Maybe you googled "best RSI Pine Script" at 2 AM. Maybe you clicked a link by accident. All valid paths to enlightenment.
Here is the good news: QuanTAlib provides **Pine Script v6 source code** for every one of its 393 indicators. Each script is self-contained, tested against the C# reference implementation, and ready to paste into TradingView's Pine Editor. No dependencies. No imports. No subscription to someone's Discord.
Here is the less-good news: the scripts contain actual mathematics. You do not have to read it. But it is there, silently judging.
## How to Use a Pine Script (The Short Version)
1. Find the indicator you want (see table below)
2. Click the link to open the `.pine` file
3. Copy the entire file contents
4. Open TradingView. Click "Pine Editor" at the bottom
5. Delete whatever is in there. Paste the code
6. Click "Add to chart"
7. Adjust the inputs in the indicator settings panel
That is it. You have deployed a mathematically rigorous indicator without writing a single line of code. Your ancestors would be proud.
## Finding Your Indicator
Every indicator has a `.pine` file sitting next to its C# implementation and documentation. The folder structure is predictable:
``` shell
lib/
trends_IIR/
ema/
Ema.cs ← the actual engine (C#, you can ignore this)
Ema.md ← documentation (math, history, validation)
ema.pine ← THIS IS WHAT YOU WANT
```
### By category
Pick your category. Find your indicator. Click the `.pine` link. Copy. Paste. Done.
#### Core (price transforms)
These turn OHLC data into derived price series. If you do not know what "typical price" means, you probably want `close` instead.
| Indicator | What It Does | Pine Script |
| :--- | :--- | :--- |
| AVGPRICE | Average of OHLC | [avgprice.pine](../lib/core/avgprice/avgprice.pine) |
| HA | Heikin-Ashi candles | [ha.pine](../lib/core/ha/ha.pine) |
| MEDPRICE | Median of high and low | [medprice.pine](../lib/core/medprice/medprice.pine) |
| MIDPOINT | Midpoint of highest and lowest | [midpoint.pine](../lib/core/midpoint/midpoint.pine) |
| MIDPRICE | Midpoint of high and low | [midprice.pine](../lib/core/midprice/midprice.pine) |
| TYPPRICE | Typical price: (H+L+C)/3 | [typprice.pine](../lib/core/typprice/typprice.pine) |
| WCLPRICE | Weighted close: (H+L+2C)/4 | [wclprice.pine](../lib/core/wclprice/wclprice.pine) |
#### Moving Averages: FIR (the "simple" ones)
FIR stands for Finite Impulse Response. It means the average uses a fixed window of bars. SMA is an FIR filter. You have been using FIR filters this whole time.
| Indicator | What It Does | Pine Script |
| :--- | :--- | :--- |
| SMA | Simple Moving Average (the one everyone knows) | [sma.pine](../lib/trends_FIR/sma/sma.pine) |
| WMA | Weighted Moving Average | [wma.pine](../lib/trends_FIR/wma/wma.pine) |
| HMA | Hull Moving Average (fast, smooth) | [hma.pine](../lib/trends_FIR/hma/hma.pine) |
| ALMA | Arnaud Legoux MA (offset + sigma tuning) | [alma.pine](../lib/trends_FIR/alma/alma.pine) |
| TRIMA | Triangular Moving Average | [trima.pine](../lib/trends_FIR/trima/trima.pine) |
| LSMA | Least Squares (linear regression line) | [lsma.pine](../lib/trends_FIR/lsma/lsma.pine) |
| FWMA | Fibonacci Weighted MA | [fwma.pine](../lib/trends_FIR/fwma/fwma.pine) |
| GWMA | Gaussian Weighted MA | [gwma.pine](../lib/trends_FIR/gwma/gwma.pine) |
| SWMA | Symmetric Weighted MA | [swma.pine](../lib/trends_FIR/swma/swma.pine) |
| DWMA | Double Weighted MA | [dwma.pine](../lib/trends_FIR/dwma/dwma.pine) |
| SINEMA | Sine-Weighted MA | [sinema.pine](../lib/trends_FIR/sinema/sinema.pine) |
| HANMA | Hann-Weighted MA | [hanma.pine](../lib/trends_FIR/hanma/hanma.pine) |
| PARZEN | Parzen-Weighted MA | [parzen.pine](../lib/trends_FIR/parzen/parzen.pine) |
| SGMA | Savitzky-Golay MA | [sgma.pine](../lib/trends_FIR/sgma/sgma.pine) |
| TSF | Time Series Forecast | [tsf.pine](../lib/trends_FIR/tsf/tsf.pine) |
| BLMA | Blackman MA | [blma.pine](../lib/trends_FIR/blma/blma.pine) |
| PWMA | Pascal Weighted MA | [pwma.pine](../lib/trends_FIR/pwma/pwma.pine) |
| NLMA | Non-Linear MA | [nlma.pine](../lib/trends_FIR/nlma/nlma.pine) |
| ILRS | Integral of Linear Regression Slope | [ilrs.pine](../lib/trends_FIR/ilrs/ilrs.pine) |
| HAMMA | Hamming MA | [hamma.pine](../lib/trends_FIR/hamma/hamma.pine) |
| KAISER | Kaiser-Windowed MA | [kaiser.pine](../lib/trends_FIR/kaiser/kaiser.pine) |
| LANCZOS | Lanczos MA | [lanczos.pine](../lib/trends_FIR/lanczos/lanczos.pine) |
| PMA | Polynomial MA | [pma.pine](../lib/trends_FIR/pma/pma.pine) |
| NYQMA | Nyquist MA | [nyqma.pine](../lib/trends_FIR/nyqma/nyqma.pine) |
| QRMA | QR Decomposition MA | [qrma.pine](../lib/trends_FIR/qrma/qrma.pine) |
| RWMA | Range Weighted MA | [rwma.pine](../lib/trends_FIR/rwma/rwma.pine) |
| HEND | Henderson MA | [hend.pine](../lib/trends_FIR/hend/hend.pine) |
| CONV | Convolution (custom kernel) | [conv.pine](../lib/trends_FIR/conv/conv.pine) |
| BWMA | Butterworth-Weighted MA | [bwma.pine](../lib/trends_FIR/bwma/bwma.pine) |
| CRMA | Crowley MA | [crma.pine](../lib/trends_FIR/crma/crma.pine) |
| SP15 | SP-15 MA | [sp15.pine](../lib/trends_FIR/sp15/sp15.pine) |
| TUKEY_W | Tukey-Windowed MA | [tukey_w.pine](../lib/trends_FIR/tukey_w/tukey_w.pine) |
| RAIN | RAIN MA | [rain.pine](../lib/trends_FIR/rain/rain.pine) |
#### Moving Averages: IIR (the "smart" ones)
IIR stands for Infinite Impulse Response. These use recursive feedback: the previous output affects the current output. Generally smoother, lower lag, and harder to understand. The indicator descriptions link to documentation if curiosity ever strikes.
| Indicator | What It Does | Pine Script |
| :--- | :--- | :--- |
| EMA | Exponential MA (the popular one) | [ema.pine](../lib/trends_IIR/ema/ema.pine) |
| DEMA | Double EMA (less lag) | [dema.pine](../lib/trends_IIR/dema/dema.pine) |
| TEMA | Triple EMA (even less lag) | [tema.pine](../lib/trends_IIR/tema/tema.pine) |
| T3 | Tillson T3 (six-stage cascade) | [t3.pine](../lib/trends_IIR/t3/t3.pine) |
| JMA | Jurik MA (adaptive, low noise) | [jma.pine](../lib/trends_IIR/jma/jma.pine) |
| KAMA | Kaufman Adaptive MA | [kama.pine](../lib/trends_IIR/kama/kama.pine) |
| VIDYA | Variable Index Dynamic Average | [vidya.pine](../lib/trends_IIR/vidya/vidya.pine) |
| FRAMA | Fractal Adaptive MA | [frama.pine](../lib/trends_IIR/frama/frama.pine) |
| MAMA | MESA Adaptive MA (Ehlers) | [mama.pine](../lib/trends_IIR/mama/mama.pine) |
| HOLT | Holt Exponential Smoothing | [holt.pine](../lib/trends_IIR/holt/holt.pine) |
| HWMA | Holt-Winters MA | [hwma.pine](../lib/trends_IIR/hwma/hwma.pine) |
| RMA | Wilder's MA (used inside RSI) | [rma.pine](../lib/trends_IIR/rma/rma.pine) |
| ZLEMA | Zero-Lag EMA | [zlema.pine](../lib/trends_IIR/zlema/zlema.pine) |
| ZLDEMA | Zero-Lag Double EMA | [zldema.pine](../lib/trends_IIR/zldema/zldema.pine) |
| ZLTEMA | Zero-Lag Triple EMA | [zltema.pine](../lib/trends_IIR/zltema/zltema.pine) |
| MGDI | McGinley Dynamic | [mgdi.pine](../lib/trends_IIR/mgdi/mgdi.pine) |
| LEMA | Leader EMA | [lema.pine](../lib/trends_IIR/lema/lema.pine) |
| HEMA | Hull EMA | [hema.pine](../lib/trends_IIR/hema/hema.pine) |
| GDEMA | Generalized Double EMA | [gdema.pine](../lib/trends_IIR/gdema/gdema.pine) |
| DSMA | Deviation-Scaled MA | [dsma.pine](../lib/trends_IIR/dsma/dsma.pine) |
| CORAL | Coral Trend Filter | [coral.pine](../lib/trends_IIR/coral/coral.pine) |
| AHRENS | Ahrens MA | [ahrens.pine](../lib/trends_IIR/ahrens/ahrens.pine) |
| DECYCLER | Ehlers Decycler | [decycler.pine](../lib/trends_IIR/decycler/decycler.pine) |
| MCNMA | McNicholl EMA | [mcnma.pine](../lib/trends_IIR/mcnma/mcnma.pine) |
| MMA | Modified MA | [mma.pine](../lib/trends_IIR/mma/mma.pine) |
| NMA | Natural MA | [nma.pine](../lib/trends_IIR/nma/nma.pine) |
| QEMA | Quad EMA | [qema.pine](../lib/trends_IIR/qema/qema.pine) |
| REMA | Regularized EMA | [rema.pine](../lib/trends_IIR/rema/rema.pine) |
| RGMA | Recursive Gaussian MA | [rgma.pine](../lib/trends_IIR/rgma/rgma.pine) |
| TRAMA | Trend Regularity Adaptive MA | [trama.pine](../lib/trends_IIR/trama/trama.pine) |
| LTMA | Linear Trend MA | [ltma.pine](../lib/trends_IIR/ltma/ltma.pine) |
| HTIT | Hilbert Transform Instantaneous Trend | [htit.pine](../lib/trends_IIR/htit/htit.pine) |
| ADXVMA | ADX Variable MA | [adxvma.pine](../lib/trends_IIR/adxvma/adxvma.pine) |
| VAMA | Volatility Adjusted MA | [vama.pine](../lib/trends_IIR/vama/vama.pine) |
| YZVAMA | Yang-Zhang Volatility Adjusted MA | [yzvama.pine](../lib/trends_IIR/yzvama/yzvama.pine) |
| MAVP | Moving Average Variable Period | [mavp.pine](../lib/trends_IIR/mavp/mavp.pine) |
#### Oscillators
Numbers that bounce between limits. Overbought, oversold, divergence. You know the drill.
| Indicator | What It Does | Pine Script |
| :--- | :--- | :--- |
| RSI | Relative Strength Index (the king) | [rsi.pine](../lib/momentum/rsi/rsi.pine) |
| STOCH | Stochastic Oscillator | [stoch.pine](../lib/oscillators/stoch/stoch.pine) |
| STOCHF | Fast Stochastic | [stochf.pine](../lib/oscillators/stochf/stochf.pine) |
| STOCHRSI | Stochastic RSI | [stochrsi.pine](../lib/oscillators/stochrsi/stochrsi.pine) |
| CCI | Commodity Channel Index | [cci.pine](../lib/momentum/cci/cci.pine) |
| WILLR | Williams %R | [willr.pine](../lib/oscillators/willr/willr.pine) |
| FISHER | Fisher Transform (Ehlers) | [fisher.pine](../lib/oscillators/fisher/Fisher.pine) |
| QQE | Qualitative Quantitative Estimation | [qqe.pine](../lib/oscillators/qqe/qqe.pine) |
| TRIX | Triple EMA Rate of Change | [trix.pine](../lib/oscillators/trix/trix.pine) |
| KDJ | KDJ Indicator | [See oscillators](../lib/oscillators/) |
| CTI | Correlation Trend Indicator | [cti.pine](../lib/oscillators/cti/cti.pine) |
| LRSI | Laguerre RSI | [lrsi.pine](../lib/oscillators/lrsi/lrsi.pine) |
| REFLEX | Ehlers Reflex | [reflex.pine](../lib/oscillators/reflex/reflex.pine) |
| SMI | Stochastic Momentum Index | [smi.pine](../lib/oscillators/smi/smi.pine) |
| STC | Schaff Trend Cycle | [stc.pine](../lib/oscillators/stc/stc.pine) |
#### Momentum
How fast price is moving. Direction matters here.
| Indicator | What It Does | Pine Script |
| :--- | :--- | :--- |
| MACD | Moving Average Convergence Divergence | [macd.pine](../lib/momentum/macd/macd.pine) |
| ROC | Rate of Change | [roc.pine](../lib/momentum/roc/roc.pine) |
| MOM | Momentum (price change over N bars) | [mom.pine](../lib/momentum/mom/mom.pine) |
| TSI | True Strength Index | [tsi.pine](../lib/momentum/tsi/tsi.pine) |
| CMO | Chande Momentum Oscillator | [cmo.pine](../lib/momentum/cmo/cmo.pine) |
| PPO | Percentage Price Oscillator | [ppo.pine](../lib/momentum/ppo/ppo.pine) |
| VEL | Velocity | [vel.pine](../lib/momentum/vel/vel.pine) |
| BOP | Balance of Power | [bop.pine](../lib/momentum/bop/bop.pine) |
| CFB | Composite Force Balance | [cfb.pine](../lib/momentum/cfb/cfb.pine) |
#### Dynamics (trend strength)
Is there a trend? How strong? These indicators answer that.
| Indicator | What It Does | Pine Script |
| :--- | :--- | :--- |
| ADX | Average Directional Index | [adx.pine](../lib/dynamics/adx/adx.pine) |
| AROON | Aroon Up/Down | [aroon.pine](../lib/dynamics/aroon/aroon.pine) |
| SUPERTREND | SuperTrend (ATR-based stops) | [super.pine](../lib/dynamics/super/super.pine) |
| ICHIMOKU | Ichimoku Cloud | [ichimoku.pine](../lib/dynamics/ichimoku/ichimoku.pine) |
| VORTEX | Vortex Indicator | [vortex.pine](../lib/dynamics/vortex/vortex.pine) |
| CHOP | Choppiness Index | [chop.pine](../lib/dynamics/chop/chop.pine) |
| ALLIGATOR | Williams Alligator | [alligator.pine](../lib/dynamics/alligator/alligator.pine) |
| PSAR | Parabolic SAR | [psar.pine](../lib/reversals/psar/psar.pine) |
#### Volatility
How much price moves. Not direction. Just magnitude.
| Indicator | What It Does | Pine Script |
| :--- | :--- | :--- |
| ATR | Average True Range | [atr.pine](../lib/volatility/atr/atr.pine) |
| TR | True Range | [tr.pine](../lib/volatility/tr/tr.pine) |
| BBW | Bollinger Band Width | [bbw.pine](../lib/volatility/bbw/bbw.pine) |
| HV | Historical Volatility | [hv.pine](../lib/volatility/hv/hv.pine) |
| NATR | Normalized ATR | [natr.pine](../lib/volatility/natr/natr.pine) |
#### Channels (bands around price)
Upper band, lower band, sometimes a middle. Price bounces between them. In theory.
| Indicator | What It Does | Pine Script |
| :--- | :--- | :--- |
| BBANDS | Bollinger Bands | [bbands.pine](../lib/channels/bbands/bbands.pine) |
| KCHANNEL | Keltner Channels | [kchannel.pine](../lib/channels/kchannel/kchannel.pine) |
| DCHANNEL | Donchian Channels | [dchannel.pine](../lib/channels/dchannel/dchannel.pine) |
| PCHANNEL | Price Channels | [pchannel.pine](../lib/channels/pchannel/pchannel.pine) |
| ACCBANDS | Acceleration Bands | [accbands.pine](../lib/channels/accbands/accbands.pine) |
#### Volume
What the crowd is doing with their money.
| Indicator | What It Does | Pine Script |
| :--- | :--- | :--- |
| OBV | On-Balance Volume | [obv.pine](../lib/volume/obv/obv.pine) |
| VWAP | Volume Weighted Average Price | [vwap.pine](../lib/volume/vwap/vwap.pine) |
| MFI | Money Flow Index (volume RSI) | [mfi.pine](../lib/volume/mfi/mfi.pine) |
| CMF | Chaikin Money Flow | [cmf.pine](../lib/volume/cmf/cmf.pine) |
| ADL | Accumulation/Distribution Line | [adl.pine](../lib/volume/adl/adl.pine) |
| VWMA | Volume Weighted MA | [vwma.pine](../lib/volume/vwma/vwma.pine) |
| KVO | Klinger Volume Oscillator | [kvo.pine](../lib/volume/kvo/kvo.pine) |
#### Filters (signal processing)
These are the heavy artillery. Kalman filters, Butterworth filters, wavelets. If you do not know what a transfer function is, start with the moving averages above and come back when you are ready. No judgment. (Some judgment.)
| Indicator | What It Does | Pine Script |
| :--- | :--- | :--- |
| KALMAN | Kalman Filter | [kalman.pine](../lib/filters/kalman/kalman.pine) |
| SGF | Savitzky-Golay Filter | [sgf.pine](../lib/filters/sgf/sgf.pine) |
| SSF2 | Ehlers Super Smoother (2-pole) | [ssf2.pine](../lib/filters/ssf2/ssf2.pine) |
| SSF3 | Ehlers Super Smoother (3-pole) | [ssf3.pine](../lib/filters/ssf3/ssf3.pine) |
| GAUSS | Gaussian Filter | [gauss.pine](../lib/filters/gauss/gauss.pine) |
| BUTTER2 | Butterworth (2nd order) | [butter2.pine](../lib/filters/butter2/butter2.pine) |
| BUTTER3 | Butterworth (3rd order) | [butter3.pine](../lib/filters/butter3/butter3.pine) |
| HPF | High-Pass Filter | [hpf.pine](../lib/filters/hpf/hpf.pine) |
| LAGUERRE | Laguerre Filter | [laguerre.pine](../lib/filters/laguerre/laguerre.pine) |
| ROOFING | Ehlers Roofing Filter | [roofing.pine](../lib/filters/roofing/roofing.pine) |
| VOSS | Voss Predictor | [voss.pine](../lib/filters/voss/voss.pine) |
**Not every indicator is listed here.** Browse the [full catalog of 393 indicators](../lib/_index.md) for the complete collection, including cycles, statistics, error metrics, reversals, numerics, and forecasts.
## Anatomy of a QuanTAlib Pine Script
Every script follows the same structure. Understanding this structure is optional but occasionally useful when things do not look right on your chart.
```pine
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Exponential Moving Average (EMA)", "EMA", overlay=true)
// ---- The function definition ----
// This is where the math lives. You do not need to touch this.
ema(series float source, simple int period=0, simple float alpha=0) =>
// ... math happens here ...
result
// ---- Inputs ----
// These create the settings panel on TradingView
i_period = input.int(10, "Period", minval=1)
i_source = input.source(close, "Source")
// ---- Calculation ----
ema_value = ema(i_source, period=i_period)
// ---- Plot ----
plot(ema_value, "EMA", color=color.yellow, linewidth=2)
```
### The parts that matter to you
- **Inputs section**: Change `10` to whatever period you want as the default. Or just use the TradingView settings panel after adding the indicator.
- **Plot section**: Change `color.yellow` to whatever color you prefer. Options include `color.red`, `color.green`, `color.blue`, `color.white`, `color.orange`, `color.purple`.
- **The function**: Do not touch this unless you know what you are doing. It was validated against the C# reference implementation. Your modifications will not be validated against anything.
## Common Questions
### "The indicator shows values from bar 1 — shouldn't there be a warmup gap?"
That is correct behavior. QuanTAlib generates output from the very first bar. A 14-period RSI with only 5 bars uses the best estimate possible given available data. The values become fully converged once enough bars have accumulated, but you never see NaN or blank bars. Other libraries leave gaps. QuanTAlib fills them with mathematically defensible approximations that improve as data accumulates.
### "Can I use this in a strategy?"
Yes. Replace `indicator(...)` with `strategy(...)` and add your entry/exit logic. The indicator function itself does not change.
### "The values differ from TradingView's built-in version"
Possible reasons, in order of likelihood:
1. **Different default parameters.** Check the period, source, and any multipliers.
2. **Different warmup handling.** QuanTAlib uses exponential compensation from bar 1. TradingView's built-in indicators sometimes use different warmup methods.
3. **You are looking at the wrong indicator.** DEMA is not "double EMA period." It is a specific algorithm by Patrick Mulloy.
### "I modified the math and now it is broken"
Put the original code back. The math was correct before you edited it.
### "Which moving average should I use?"
If you are asking this question, use EMA. It is the Honda Civic of moving averages: reliable, understood by everyone, and good enough for most situations. Come back and explore JMA, KAMA, or T3 when you have a specific problem that EMA does not solve.
### "Can I combine multiple indicators?"
Yes. Add multiple scripts to your chart or combine functions within a single script:
```pine
//@version=6
indicator("EMA + RSI Combo", overlay=false)
// Calculate both
ema_val = ta.ema(close, 20) // TradingView built-in
rsi_val = ta.rsi(close, 14) // TradingView built-in
// Or use QuanTAlib versions by pasting the function definitions
// from their respective .pine files and calling them
plot(rsi_val, "RSI", color=color.purple)
hline(70, "Overbought", color=color.red)
hline(30, "Oversold", color=color.green)
```
## Going Deeper (If You Dare)
Each indicator has a `.md` documentation file next to its `.pine` file. These contain:
- Mathematical formulas (yes, with actual math notation)
- Historical context (who invented it and why)
- Performance characteristics
- Validation against other libraries
- Common pitfalls
For example: [EMA documentation](../lib/trends_IIR/ema/Ema.md) explains the exponential warmup compensator, why it matters, and why most other implementations get the first few values wrong.
You do not have to read any of this. But if you ever wonder why your backtest results differ from someone else's, the answer is probably in there.
## The Full Catalog
**[All 393 indicators with descriptions →](../lib/_index.md)**
Every indicator in that list has a `.pine` file. Every `.pine` file works on TradingView. Every implementation matches the C# reference engine.
Copy responsibly.
+273
View File
@@ -0,0 +1,273 @@
# Python Guide: quantalib for Data Scientists
> "You chose Python because life is short. QuanTAlib talks raw machine code directly to the CPU because nanoseconds are shorter."
## What This Is (and What It Is Not)
`quantalib` is not a Python library. It is a pre-compiled native binary wearing a Python trench coat. The math runs as raw machine code — the kind that talks directly to CPU vector registers and crunches 8 numbers simultaneously per clock tick. Think NumPy speed, except the entire indicator algorithm is fused into one native call with zero Python loops. Half a million bars of SMA in 328 microseconds. That is faster than your monitor can refresh a single frame.
The `pip install` delivers a ready-to-run native binary for your platform. No compilation step, no build tools, no waiting. Just import and go.
This means:
- **Batch only.** You pass in an array, you get back an array. No streaming, no bar-by-bar updates. (The .NET version does streaming at 0.4 μs per update. Python's function-call overhead would eat that alive.)
- **Same numbers.** The results are identical to the C# core library, bit for bit. Cross-validated against TA-Lib, Tulip, Skender, and half a dozen other implementations nobody remembers.
- **393 indicators.** Not 12. Not "the popular ones." All of them. From SMA to Yang-Zhang Volatility Adaptive Moving Average.
## Installation
```bash
pip install quantalib
```
Pre-built wheels ship for all 6 combinations of Windows, Linux, and macOS on both x64 and ARM64. If your platform is missing, you are doing something creative.
### Optional backends
```bash
pip install quantalib[pandas] # pd.Series round-trip
pip install quantalib[polars] # pl.Series round-trip
pip install quantalib[pyarrow] # pa.Array round-trip
pip install quantalib[all] # all three, because indecision is valid
```
Without any optional backend, plain NumPy arrays work. Always have. Always will.
## Finding Your Indicator
Every indicator lives in one of 15 category modules. You do not need to know which module contains what: the top-level `quantalib` namespace re-exports everything.
```python
import quantalib as qtl
# These are identical:
result = qtl.sma(prices, period=20)
result = qtl.trends_fir.sma(prices, period=20)
```
If you know the indicator name, call it. If you do not, here is the map:
| Category | Module | What It Measures | Examples |
| :--- | :--- | :--- | :--- |
| **Core** | `core` | Price transforms, building blocks | `avgprice`, `medprice`, `typprice`, `ha` |
| **Trends (FIR)** | `trends_fir` | Finite impulse response averages | `sma`, `wma`, `hma`, `alma`, `trima` |
| **Trends (IIR)** | `trends_iir` | Infinite impulse response averages | `ema`, `dema`, `tema`, `kama`, `jma` |
| **Filters** | `filters` | Signal processing, noise reduction | `kalman`, `sgf`, `butter2`, `gauss` |
| **Oscillators** | `oscillators` | Bounded/centered oscillators | `stoch`, `rsi`, `cci`, `fisher`, `willr` |
| **Dynamics** | `dynamics` | Trend strength and direction | `adx`, `aroon`, `supertrend`, `ichimoku` |
| **Momentum** | `momentum` | Speed of price changes | `roc`, `mom`, `macd`, `tsi`, `vel` |
| **Volatility** | `volatility` | Price variability | `atr`, `bbw`, `stddev`, `hv`, `tr` |
| **Volume** | `volume` | Trading activity | `obv`, `vwma`, `mfi`, `cmf`, `adl` |
| **Statistics** | `statistics` | Statistical measures | `zscore`, `correlation`, `entropy` |
| **Channels** | `channels` | Price boundaries | `bbands`, `kchannel`, `dchannel` |
| **Cycles** | `cycles` | Cycle analysis | `ht_dcperiod`, `ht_sine`, `cg`, `dsp` |
| **Reversals** | `reversals` | Pattern detection | `psar`, `pivot`, `fractals`, `swings` |
| **Errors** | `errors` | Error metrics, loss functions | `rmse`, `mae`, `mape`, `smape` |
| **Numerics** | `numerics` | Mathematical transforms | `fft`, `normalize`, `sigmoid`, `slope` |
**Full indicator catalog with descriptions: [393 indicators](../lib/_index.md)**
## Calling Convention
Simple indicators follow this pattern:
```python
result = qtl.indicator_name(source_data, period=N, offset=0)
```
Many indicators have their own parameters — multiple periods, multipliers, smoothing factors, phase controls. Use your IDE's autocomplete or `help()` to see what each function accepts. A few representative examples:
```python
# Simple: one period
sma = qtl.sma(close, period=20)
# Multiple periods
macd_line, signal, hist = qtl.macd(close, fastPeriod=12, slowPeriod=26)
# Period + multiplier
upper, mid, lower = qtl.bbands(close, bbPeriod=20, bbMult=2.0)
# Complex: many named parameters
gator_upper, gator_lower = qtl.gator(close, jawPeriod=13, jawShift=8,
teethPeriod=8, teethShift=5, lipsPeriod=5, lipsShift=3)
# Filter with float controls
filtered = qtl.kalman(close, q=0.01, r=0.1)
```
### Common parameters
- **`source_data`**: NumPy array, pandas Series, polars Series, or PyArrow Array. The library detects the type and returns the same type.
- **`period`**: The primary lookback window (where applicable). This is the canonical name. If you are coming from `pandas-ta`, `length=` works too — it is silently aliased.
- **`offset`**: Shift the output by N bars. Default 0. Positive values shift right.
- **`**kwargs`**: Every function accepts `**kwargs` for forward compatibility and aliasing.
### Input patterns
Most indicators take a single source series. Some need OHLCV data:
```python
# Single source (Pattern A): most indicators
sma = qtl.sma(close, period=20)
# High-Low-Close (Pattern E): ATR, channels
atr = qtl.atr(high, low, close, period=14)
# OHLCV (Pattern B): volume indicators, dynamics
adx = qtl.adx(open, high, low, close, volume, period=14)
# Dual source (Pattern F): error metrics
rmse = qtl.rmse(actual, predicted, period=20)
# Source + Volume (Pattern G): volume-weighted indicators
vwma = qtl.vwma(close, volume, period=20)
```
### Return types
| Input type | Single output | Multi output |
| :--- | :--- | :--- |
| `np.ndarray` | `np.ndarray` | `tuple[np.ndarray, ...]` |
| `pd.Series` | `pd.Series` (preserves index) | `pd.DataFrame` |
| `pl.Series` | `pl.Series` | `pl.DataFrame` |
| `pa.Array` | `pa.Array` | `dict[str, pa.Array]` |
Multi-output indicators (Bollinger Bands, Stochastic, MACD, Ichimoku) return multiple arrays. Unpack them:
```python
upper, mid, lower = qtl.bbands(close, period=20, std=2.0)
k, d = qtl.stoch(high, low, close, kLength=14, dPeriod=3)
```
## Working with DataFrames
### pandas
```python
import pandas as pd
import quantalib as qtl
df = pd.read_csv("ohlcv.csv", parse_dates=["date"], index_col="date")
df["sma_20"] = qtl.sma(df["close"], period=20)
df["rsi_14"] = qtl.rsi(df["close"], period=14)
df["atr_14"] = qtl.atr(df["high"], df["low"], df["close"], period=14)
# Multi-output unpacks into separate columns
df["bb_upper"], df["bb_mid"], df["bb_lower"] = qtl.bbands(
df["close"], period=20, std=2.0
)
```
The pandas index survives the round-trip. The output Series inherits the input's index, gets a name like `"SMA_20"`, and stores the category in `.attrs["category"]`.
### polars
```python
import polars as pl
import quantalib as qtl
df = pl.read_csv("ohlcv.csv")
df = df.with_columns(
qtl.sma(df["close"], period=20).alias("sma_20"),
qtl.rsi(df["close"], period=14).alias("rsi_14"),
)
```
### pyarrow
```python
import pyarrow as pa
import pyarrow.parquet as pq
import quantalib as qtl
table = pq.read_table("ohlcv.parquet")
close = table.column("close").combine_chunks()
rsi = qtl.rsi(close, period=14) # pa.Array
```
## Warmup Behavior
QuanTAlib produces output from bar 1. There are no NaN gaps at the beginning. A 20-period SMA with only 5 bars returns the average of those 5 bars — not the 20-period average (that would require prescience), but a mathematically defensible estimate that improves as data accumulates.
```python
sma = qtl.sma(prices, period=20)
# sma[0] has a value — the best estimate given 1 bar
# sma[19] is the first fully-converged 20-period SMA
# All 20 values are finite numbers, not NaN
```
Early values are usable approximations, not garbage. This is a deliberate design decision. Other libraries leave NaN gaps. QuanTAlib fills them.
If your input data itself contains NaN (e.g., missing bars), those propagate through as the last valid value — QuanTAlib substitutes rather than spreading the disease.
## pandas-ta Migration
Coming from `pandas-ta`? Two things changed:
1. **Parameter name**: `period` is canonical. `length` still works as an alias.
2. **Function names**: Most are identical. For ambiguous cases, use the compatibility layer:
```python
from quantalib._compat import get_compat
# Resolve a pandas-ta name to a quantalib function
fn = get_compat("bbands")
if fn:
result = fn(close, period=20)
```
## Performance Reality Check
The native engine bypasses Python entirely for the math. No interpreter loop, no garbage collector pauses, no GIL contention. Your data goes in as a memory pointer, the CPU grinds through it at hardware speed, and the result comes back as a NumPy array. The Python overhead (marshaling the pointer, wrapping the result) adds about 10 μs — roughly the time it takes to blink, divided by 10,000.
To put the numbers in perspective:
| Indicator | quantalib (500K bars) | pandas-ta (500K bars) | How much faster |
| :--- | ---: | ---: | :--- |
| SMA | 328 μs (⅓ of a millisecond) | ~50 ms | **150×** faster |
| EMA | 421 μs | ~45 ms | **107×** faster |
| RSI | 517 μs | ~80 ms | **155×** faster |
What does 150× mean in practice? If your pandas-ta backtest over 2,000 symbols takes **8 hours**, quantalib finishes the same work in **3 minutes**. That is the difference between "run it overnight and hope" and "run it while the coffee brews."
## Error Handling
The C ABI returns status codes. The Python bridge converts them to exceptions:
```python
from quantalib._bridge import QtlInvalidLengthError, QtlInvalidParamError
try:
qtl.sma(prices, period=0) # period must be > 0
except QtlInvalidLengthError:
print("Period must be positive")
try:
qtl.sma(np.array([])) # empty array
except QtlInvalidLengthError:
print("Array too short")
```
## Platform Support
| Platform | Architecture | Status |
| :--- | :--- | :--- |
| Windows | x64 | ✅ Pre-built wheel |
| Windows | ARM64 | ✅ Pre-built wheel |
| Linux | x64 | ✅ Pre-built wheel |
| Linux | ARM64 (aarch64) | ✅ Pre-built wheel |
| macOS | x64 | ✅ Pre-built wheel |
| macOS | ARM64 (Apple Silicon) | ✅ Pre-built wheel |
The native shared library (`quantalib.dll` / `libquantalib.so` / `libquantalib.dylib`) is bundled inside the wheel. No separate installation, no system dependencies, no `cmake` rituals.
## Going Deeper
- **[Full indicator catalog](../lib/_index.md)**: Every indicator with mathematical descriptions
- **[Architecture](architecture.md)**: How the SIMD engine works
- **[Benchmarks](benchmarks.md)**: Performance numbers with methodology
- **[Validation](validation.md)**: Cross-library verification matrices
- **[API Reference](api.md)**: The .NET API (for when Python is not enough)