mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-27 17:27:43 +00:00
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:
@@ -1,11 +1,11 @@
|
||||
[](https://app.codacy.com/gh/mihakralj/QuanTAlib/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
||||
[](https://codecov.io/gh/mihakralj/QuanTAlib)
|
||||
[](https://sonarcloud.io/summary/new_code?id=mihakralj_QuanTAlib)
|
||||
[](https://www.codefactor.io/repository/github/mihakralj/quantalib/overview/main)
|
||||
[](https://www.codefactor.io/repository/github/mihakralj/quantalib/overview/main)
|
||||
[](https://www.nuget.org/packages/QuanTAlib/)
|
||||

|
||||
[](https://www.nuget.org/packages/QuanTAlib/)
|
||||
[](https://dotnet.microsoft.com/en-us/download/dotnet)
|
||||
[](https://dotnet.microsoft.com/en-us/download/dotnet)
|
||||
|
||||
[](lib/_index.md)
|
||||
[](docs/ndepend.md)
|
||||
@@ -15,98 +15,144 @@
|
||||
[](docs/ndepend.md)
|
||||
[](docs/ndepend.md)
|
||||
|
||||
Static code analysis provided by [ndepend](https://www.ndepend.com/)
|
||||
# QuanTAlib
|
||||
|
||||
# QuanTAlib - Quantitative Technical Indicators Without Compromises
|
||||
**393 technical indicators. One library. Brutal architectural trade-offs for absolute speed.**
|
||||
|
||||
TA libraries face a fundamental choice: accept approximations for simplicity OR enforce math rigor. QuanTAlib chooses rigor.
|
||||
QuanTAlib grinds through half a million bars of SMA in 328 microseconds. Faster than an L1 cache miss. The same indicators run in C#, Python, and PineScript. Cross-validated against TA-Lib, Tulip, Skender, and every other implementation worth testing.
|
||||
|
||||
**Quan**titative **TA** **lib**rary (QuanTAlib) is a C# library built on the premise that you shouldn't have to choose. Modern CPUs process 4-8 FLOPS per cycle via SIMD. Modern .NET exposes memory layouts making hardware acceleration trivial. QuanTAlib exploits both. **Result:** mathematically rigorous indicators at speeds making real-time multi-symbol analysis practical on ordinary hardware.
|
||||
We achieve this by trading object allocation for contiguous memory spans and forcing SIMD vectorization. You want speed? We dictate the heap.
|
||||
|
||||
## Key Features
|
||||
Pick your weapon:
|
||||
|
||||
- **Zero Allocation**: Hot paths are allocation-free. No GC pauses during trading.
|
||||
- **SIMD Accelerated**: Uses AVX2/AVX-512 for 8x throughput on modern CPUs.
|
||||
- **O(1) Streaming**: Constant time updates regardless of lookback period.
|
||||
- **Platform Agnostic**: Runs on .NET 8/9/10, compatible with Quantower, NinjaTrader, QuantConnect.
|
||||
- **Mathematically Rigorous**: Validated against original research papers and established libraries.
|
||||
|
||||
## Indicators
|
||||
|
||||
| Category | Count | What It Measures | Representative Indicators |
|
||||
| -------- | :---: | ---------------- | ------------------------- |
|
||||
| [**Trends (FIR)**](lib/trends_FIR/_index.md) | 33 | Finite Impulse Response moving averages | SMA, WMA, HMA, ALMA, TRIMA, LSMA, TSF |
|
||||
| [**Trends (IIR)**](lib/trends_IIR/_index.md) | 36 | Infinite Impulse Response moving averages | EMA, DEMA, TEMA, T3, JMA, KAMA, VIDYA |
|
||||
| [**Filters**](lib/filters/_index.md) | 37 | Signal processing and noise reduction filters | Bessel, Butterworth, Gaussian, Savitzky-Golay, Ehlers Super Smoother |
|
||||
| [**Oscillators**](lib/oscillators/_index.md) | 47 | Indicators that fluctuate around a center line | RSI, MACD, Stochastic, AO, APO, CCI, Ultimate Oscillator |
|
||||
| [**Dynamics**](lib/dynamics/_index.md) | 21 | Trend strength and direction indicators | ADX, Aroon, SuperTrend, Vortex, Chop, Ichimoku, RAVI |
|
||||
| [**Momentum**](lib/momentum/_index.md) | 19 | Speed and magnitude of price changes | Momentum, ROC, Velocity, RSX, Qstick, KDJ |
|
||||
| [**Volatility**](lib/volatility/_index.md) | 26 | Size and variability of price movements | ATR, Bollinger Band Width, Historical Volatility, True Range |
|
||||
| [**Volume**](lib/volume/_index.md) | 27 | Trading activity and price-volume relationships | OBV, VWAP, MFI, ADL, CMF, TVI, Force Index |
|
||||
| [**Statistics**](lib/statistics/_index.md) | 33 | Statistical measures and tests | Correlation, Variance, StdDev, Skewness, Kurtosis, Z-Score |
|
||||
| [**Channels**](lib/channels/_index.md) | 23 | Price boundaries and range definitions | Bollinger Bands, Keltner Channels, Donchian Channels |
|
||||
| [**Cycles**](lib/cycles/_index.md) | 14 | Cycle analysis and signal processing | Hilbert Transform, Homodyne, Phasor, Ehlers Sine Wave |
|
||||
| [**Reversals**](lib/reversals/_index.md) | 12 | Pattern recognition and reversal detection | Pivot Points, Fractals, Swings, Pivot Components |
|
||||
| [**Forecasts**](lib/forecasts/_index.md) | 1 | Predictive indicators and projections | Time Series Forecast |
|
||||
| [**Errors**](lib/errors/_index.md) | 26 | Error metrics and loss functions | RMSE, MAE, MAPE, SMAPE, MASE, R-Squared |
|
||||
| [**Numerics**](lib/numerics/_index.md) | 27 | Mathematical transformations | Log, Exp, Sqrt, Tanh, ReLU, Sigmoid |
|
||||
| [**Core**](lib/core/_index.md) | 7 | Price transforms and fundamental building blocks | AVGPRICE, MEDPRICE, MIDPRICE, TYPPRICE, WCLPRICE |
|
||||
|
||||
**[Browse all 393 indicators →](lib/_index.md)**
|
||||
| Platform | Install | Guide |
|
||||
| :--- | :--- | :--- |
|
||||
| **C# / .NET 10** | `dotnet add package QuanTAlib` | [Architecture](docs/architecture.md) . [API Reference](docs/api.md) |
|
||||
| **Python** | `pip install quantalib` | [**Python Guide**](docs/python.md) |
|
||||
| **PineScript v6** | Copy-paste from `lib/` | [**PineScript Guide**](docs/pinescript.md) |
|
||||
|
||||
## Quick Start
|
||||
|
||||
Install from NuGet:
|
||||
|
||||
```bash
|
||||
dotnet add package QuanTAlib
|
||||
```
|
||||
|
||||
Calculate an SMA in real-time:
|
||||
### C# Streaming (Real-time incoming data)
|
||||
|
||||
```csharp
|
||||
using QuanTAlib;
|
||||
|
||||
// No allocations in the update loop. State is maintained internally.
|
||||
var sma = new Sma(period: 14);
|
||||
double price = 100.0;
|
||||
|
||||
// Update with new price
|
||||
var result = sma.Update(new TValue(DateTime.UtcNow, price));
|
||||
var result = sma.Update(110.4);
|
||||
|
||||
if (result.IsHot)
|
||||
{
|
||||
Console.WriteLine($"SMA: {result.Value}");
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Snapshot
|
||||
### C# — batch (500K bars in microseconds)
|
||||
|
||||
QuanTAlib is designed for speed. Here is how it compares calculating a 500,000 bar SMA against other libraries:
|
||||
```csharp
|
||||
// We evaluate code, not promises.
|
||||
// This processes as contiguous memory using AVX-512 vectorization.
|
||||
// Zero allocations. The Garbage Collector sleeps.
|
||||
double[] prices = LoadHistoricalData();
|
||||
double[] results = new double[prices.Length];
|
||||
|
||||
| Library | Mean Time | Allocations | Relative Speed |
|
||||
| ------- | --------- | ----------- | -------------- |
|
||||
| **QuanTAlib (Span)** | **318.3 μs** | **0 B** | **1.00x (baseline)** |
|
||||
| TA-Lib | 356.4 μs | 34 B | 1.12x slower |
|
||||
| Tulip Indicators | 359.3 μs | 0 B | 1.13x slower |
|
||||
| Skender Indicators | 71,277 μs | 50.8 MB | 224x slower |
|
||||
Sma.Batch(prices.AsSpan(), results.AsSpan(), period: 14);
|
||||
```
|
||||
|
||||
*See [Benchmarks](docs/benchmarks.md) for full details and methodology.*
|
||||
### Python
|
||||
|
||||
```python
|
||||
import quantalib as qtl
|
||||
import numpy as np
|
||||
|
||||
prices = np.random.default_rng(42).normal(100, 2, size=500_000)
|
||||
sma = qtl.sma(prices, period=14) # 393 indicators, similar syntax
|
||||
```
|
||||
|
||||
Works with NumPy, pandas, polars, and PyArrow. [Full Python guide →](docs/python.md)
|
||||
|
||||
### PineScript
|
||||
|
||||
Every indicator ships as a standalone .pine file. Open it. Copy it. Paste it into TradingView. No magic, just math. [Full PineScript guide →](docs/pinescript.md)
|
||||
|
||||
---
|
||||
|
||||
## 393 Indicators
|
||||
|
||||
| Category | Count | What It Measures | Examples |
|
||||
| :--- | :---: | :--- | :--- |
|
||||
| [**Core**](lib/core/_index.md) | 8 | Price transforms, building blocks | AVGPRICE, MEDPRICE, TYPPRICE, HA |
|
||||
| [**Trends (FIR)**](lib/trends_FIR/_index.md) | 33 | Finite impulse response averages | SMA, WMA, HMA, ALMA, TRIMA, LSMA |
|
||||
| [**Trends (IIR)**](lib/trends_IIR/_index.md) | 36 | Infinite impulse response averages | EMA, DEMA, TEMA, T3, JMA, KAMA, VIDYA |
|
||||
| [**Filters**](lib/filters/_index.md) | 37 | Signal processing, noise reduction | Kalman, Butterworth, Gaussian, Savitzky-Golay |
|
||||
| [**Oscillators**](lib/oscillators/_index.md) | 48 | Bounded/centered oscillators | RSI, MACD, Stochastic, CCI, Fisher, Williams %R |
|
||||
| [**Dynamics**](lib/dynamics/_index.md) | 21 | Trend strength and direction | ADX, Aroon, SuperTrend, Ichimoku, Vortex |
|
||||
| [**Momentum**](lib/momentum/_index.md) | 19 | Speed of price changes | ROC, Momentum, Velocity, TSI, Qstick |
|
||||
| [**Volatility**](lib/volatility/_index.md) | 26 | Price variability | ATR, Bollinger Width, Historical Vol, True Range |
|
||||
| [**Volume**](lib/volume/_index.md) | 27 | Trading activity | OBV, VWAP, MFI, CMF, ADL, Force Index |
|
||||
| [**Statistics**](lib/statistics/_index.md) | 35 | Statistical measures | Correlation, Variance, Skewness, Z-Score |
|
||||
| [**Channels**](lib/channels/_index.md) | 23 | Price boundaries | Bollinger Bands, Keltner, Donchian |
|
||||
| [**Cycles**](lib/cycles/_index.md) | 14 | Cycle analysis | Hilbert Transform, Homodyne, Ehlers Sine Wave |
|
||||
| [**Reversals**](lib/reversals/_index.md) | 12 | Pattern detection | Pivot Points, Fractals, Swings |
|
||||
| [**Forecasts**](lib/forecasts/_index.md) | 1 | Predictive indicators | Time Series Forecast |
|
||||
| [**Errors**](lib/errors/_index.md) | 26 | Error metrics, loss functions | RMSE, MAE, MAPE, SMAPE, R² |
|
||||
| [**Numerics**](lib/numerics/_index.md) | 27 | Mathematical transforms | Log, Exp, Sigmoid, Normalize, FFT |
|
||||
|
||||
**[Browse all 393 indicators →](lib/_index.md)**
|
||||
|
||||
---
|
||||
|
||||
## Performance
|
||||
|
||||
500,000 bars. Period 220. .NET 10.0, AVX-512. Zero allocations.
|
||||
|
||||
| Library | SMA Time | Allocations | vs QuanTAlib |
|
||||
| :--- | ---: | ---: | :--- |
|
||||
| **QuanTAlib** | **328 μs** | **0 B** | — |
|
||||
| TA-Lib | 365 μs | 32 B | 1.1× slower |
|
||||
| Tulip | 370 μs | 0 B | 1.1× slower |
|
||||
| Skender | 68,436 μs | 42 MB | **209× slower** |
|
||||
| Ooples | 347,453 μs | 151 MB | **1,060× slower** |
|
||||
|
||||
That is 0.66 nanoseconds per value — faster than a single L1 cache miss. [Full benchmarks →](docs/benchmarks.md)
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
### Core Concepts
|
||||
### Architecture & API
|
||||
|
||||
- [**Architecture**](docs/architecture.md): Learn about SoA layout, SIMD, and design philosophy.
|
||||
- [**API Reference**](docs/api.md): Deep dive into the Tri-Modal Architecture (Batch, Streaming, Priming).
|
||||
- [**Indicators**](docs/indicators.md): Full catalog of available indicators and their mathematical families.
|
||||
- [**Usage Guides**](docs/usage.md): Detailed patterns for Span, Streaming, Batch, and Eventing modes.
|
||||
- [**Integration**](docs/integration.md): Setup guides for Quantower, NinjaTrader, and QuantConnect.
|
||||
- [**Architecture**](docs/architecture.md) — SoA memory layout, SIMD vectorization, O(1) streaming, [design philosophy](docs/architecture.md#design-philosophy)
|
||||
- [**API Reference**](docs/api.md) — Batch, Streaming, and Priming modes
|
||||
- [**Usage Patterns**](docs/usage.md) — Span, Streaming, Batch, Eventing examples
|
||||
- [**Integration**](docs/integration.md) — Quantower, NinjaTrader, QuantConnect
|
||||
|
||||
### Analysis & Validation
|
||||
|
||||
- [**Benchmarks**](docs/benchmarks.md): Detailed performance evidence and test methodology.
|
||||
- [**Error Metrics**](docs/errors.md): Implementation details for 20+ error metrics and loss functions.
|
||||
- [**Trend Comparison**](docs/trendcomparison.md): Comparative analysis of lag, smoothness, and accuracy.
|
||||
- [**MA Qualities**](docs/ma-qualities.md): Theoretical framework for evaluating moving averages.
|
||||
- [**Validation**](docs/validation.md): Verification matrices against TA-Lib, Skender, and other libraries.
|
||||
- [**Glossary**](docs/glossary.md): Definitions of core QuanTAlib concepts, types, and terminology.
|
||||
- [**Benchmarks**](docs/benchmarks.md) — SMA, EMA, RSI, MACD, Bollinger, Chaikin results
|
||||
- [**Validation**](docs/validation.md) — Cross-library verification matrices
|
||||
- [**Error Metrics**](docs/errors.md) — 26 error and loss functions
|
||||
- [**Trend Comparison**](docs/trendcomparison.md) — Lag, smoothness, accuracy across MAs
|
||||
- [**MA Qualities**](docs/ma-qualities.md) — Theoretical framework for MA evaluation
|
||||
- [**Glossary**](docs/glossary.md) — Core concepts and terminology
|
||||
|
||||
### Code Quality
|
||||
|
||||
Static analysis: [NDepend](https://www.ndepend.com/) · [Codacy](https://app.codacy.com/gh/mihakralj/QuanTAlib/dashboard) · [SonarCloud](https://sonarcloud.io/summary/new_code?id=mihakralj_QuanTAlib) · [CodeFactor](https://www.codefactor.io/repository/github/mihakralj/quantalib/overview/main)
|
||||
|
||||
## ⚠️ Fair Warning
|
||||
|
||||
This library is **not yet 1.0.0**. There is exactly **one** grumpy engineer behind it, fueled by mass amounts of caffeine and an irrational belief that all technical indicators should be correct down to the 10th decimal place.
|
||||
|
||||
Implemented indicators are not yet complete. Things **will** break. APIs **will** change. Some indicators might produce values that make your quantitative models question the meaning of life. If you find something broken and don't [open an issue](https://github.com/mihakralj/QuanTAlib/issues), the grumpy dev will have absolutely no idea what needs fixing — and the backlog of things to fix, improve, and add is already longer than a Bollinger Band on a meme stock.
|
||||
|
||||
Your bug reports make this library better. Your silence makes the dev mass more coffee.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under [Apache 2.0](LICENSE). Not MIT. Not BSD. Deliberately.
|
||||
|
||||
Apache 2.0 includes an explicit patent grant and a retaliation clause — if someone patents a technique derived from this library and sues a contributor, their license terminates immediately. For a library optimizing financial math with SIMD-aligned memory patterns and incremental algorithms, patent protection is structural defense, not legal decoration.
|
||||
|
||||
Commercial use, modification, and distribution are all permitted. Keep the license file, note your changes, and don't weaponize the legal system against the people who wrote the code you're profiting from.
|
||||
|
||||
**[Full rationale →](docs/license.md)**
|
||||
|
||||
BIN
Binary file not shown.
@@ -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
@@ -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)
|
||||
@@ -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
@@ -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)
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.8.2
|
||||
0.8.3
|
||||
@@ -9,7 +9,7 @@
|
||||
| **Output range** | Varies (see docs) |
|
||||
| **Warmup** | 1 bar |
|
||||
|
||||
### TL;DR
|
||||
## TL;DR
|
||||
|
||||
- `TValue` is the fundamental atomic unit of data in QuanTAlib.
|
||||
- No configurable parameters; computation is stateless per bar.
|
||||
@@ -73,16 +73,23 @@ var t1 = new TValue(DateTime.UtcNow, 100.5);
|
||||
var t2 = new TValue(DateTime.UtcNow.Ticks, 100.5);
|
||||
```
|
||||
|
||||
### Implicit Conversions
|
||||
### Conversions
|
||||
|
||||
```csharp
|
||||
TValue tv = new TValue(DateTime.UtcNow, 42.0);
|
||||
|
||||
// Implicitly converts to double
|
||||
double val = tv; // 42.0
|
||||
// Explicitly converts to double (requires cast)
|
||||
double val = (double)tv; // 42.0
|
||||
|
||||
// Implicitly converts to DateTime
|
||||
DateTime dt = tv; // DateTime object
|
||||
|
||||
// Implicitly converts from double (uses DateTime.UtcNow)
|
||||
TValue fromDouble = 110.4; // same as new TValue(DateTime.UtcNow, 110.4)
|
||||
|
||||
// Enables ergonomic indicator APIs
|
||||
var sma = new Sma(14);
|
||||
var result = sma.Update(110.4); // double → TValue implicit conversion
|
||||
```
|
||||
|
||||
### String Representation
|
||||
|
||||
@@ -26,6 +26,13 @@ public readonly record struct TValue(long Time, double Value) : ISpanFormattable
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator DateTime(TValue tv) => new(tv.Time, DateTimeKind.Utc);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a raw double to a TValue with DateTime.UtcNow timestamp.
|
||||
/// Enables ergonomic APIs like <c>sma.Update(110.4)</c>.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator TValue(double value) => new(DateTime.UtcNow, value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
+59
-33
@@ -1,47 +1,74 @@
|
||||
# quantalib — Python NativeAOT Wrapper
|
||||
# quantalib
|
||||
|
||||
High-performance Python wrapper for [QuanTAlib](https://github.com/mihakralj/quantalib), a .NET NativeAOT technical analysis library.
|
||||
[](https://pypi.org/project/quantalib/)
|
||||
[](https://pypi.org/project/quantalib/)
|
||||
[](https://github.com/mihakralj/quantalib/blob/main/LICENSE)
|
||||
|
||||
## Features
|
||||
|
||||
- **~391 indicators** across 15 categories: channels, core, cycles, dynamics, errors, filters, momentum, numerics, oscillators, reversals, statistics, trends (FIR & IIR), volatility, volume
|
||||
- **Zero-copy FFI** — ctypes bridge to pre-compiled NativeAOT shared library
|
||||
- **NumPy native** — all inputs/outputs are `float64` arrays
|
||||
- **Optional pandas support** — pass `pd.Series` in, get `pd.Series` out with preserved index
|
||||
- **pandas-ta compatible** — `quantalib._compat` provides alias mapping for drop-in migration
|
||||
|
||||
## Installation
|
||||
393 technical analysis indicators compiled to native code via .NET NativeAOT, called from Python through `ctypes`. Same SIMD-accelerated engine as the [QuanTAlib](https://github.com/mihakralj/quantalib) .NET package. Zero Python math reimplementation.
|
||||
|
||||
```bash
|
||||
pip install quantalib
|
||||
```
|
||||
|
||||
> **Note:** The NativeAOT shared library (`quantalib_native.dll` / `.so` / `.dylib`) must be present in `quantalib/native/<platform>/`. Pre-built binaries are included in wheel distributions.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import quantalib as qtl
|
||||
|
||||
close = np.random.randn(200).cumsum() + 100
|
||||
close = np.random.default_rng(42).normal(100, 2, size=500)
|
||||
|
||||
# Simple Moving Average
|
||||
sma = qtl.sma(close, length=20)
|
||||
sma = qtl.sma(close, period=20)
|
||||
rsi = qtl.rsi(close, period=14)
|
||||
upper, mid, lower = qtl.bbands(close, period=20, std=2.0)
|
||||
```
|
||||
|
||||
# Bollinger Bands (multi-output → tuple or DataFrame)
|
||||
upper, mid, lower = qtl.bbands(close, length=20, std=2.0)
|
||||
Works with **pandas**, **polars**, and **pyarrow** — same-type-in, same-type-out:
|
||||
|
||||
# With pandas
|
||||
```python
|
||||
# pandas — preserves index
|
||||
import pandas as pd
|
||||
s = pd.Series(close, name="close")
|
||||
rsi = qtl.rsi(s, length=14) # returns pd.Series with preserved index
|
||||
rsi = qtl.rsi(s, period=14) # → pd.Series
|
||||
|
||||
# polars — zero-copy-friendly
|
||||
import polars as pl
|
||||
s = pl.Series("close", close)
|
||||
rsi = qtl.rsi(s, period=14) # → pl.Series
|
||||
bb = qtl.bbands(s, period=20) # → pl.DataFrame (upper, mid, lower)
|
||||
|
||||
# pyarrow — for Arrow-native pipelines
|
||||
import pyarrow as pa
|
||||
a = pa.array(close, type=pa.float64())
|
||||
rsi = qtl.rsi(a, period=14) # → pa.Array
|
||||
```
|
||||
|
||||
> **pandas-ta users:** `length=` is accepted everywhere as an alias for `period=`.
|
||||
|
||||
Install optional backends:
|
||||
|
||||
```bash
|
||||
pip install quantalib[pandas] # pandas / pd.Series support
|
||||
pip install quantalib[polars] # polars / pl.Series support
|
||||
pip install quantalib[pyarrow] # pyarrow / pa.Array support
|
||||
pip install quantalib[all] # all three
|
||||
```
|
||||
|
||||
## Performance (500,000 bars, AVX-512)
|
||||
|
||||
| Indicator | quantalib | pandas-ta | Ratio |
|
||||
| --------- | --------: | --------: | ----: |
|
||||
| SMA | 328 μs | ~50 ms | ~150× |
|
||||
| EMA | 421 μs | ~45 ms | ~107× |
|
||||
| WMA | 302 μs | ~60 ms | ~199× |
|
||||
| RSI | 517 μs | ~80 ms | ~155× |
|
||||
|
||||
The `ctypes` call adds 5-15 μs overhead. For arrays above a few hundred bars, NativeAOT wins by two orders of magnitude.
|
||||
|
||||
## Categories
|
||||
|
||||
| Category | Module | Examples |
|
||||
|----------|--------|----------|
|
||||
| -------- | ------ | -------- |
|
||||
| Channels | `channels` | bbands, kchannel, dchannel, aberr |
|
||||
| Core | `core` | ha, midpoint, avgprice, typprice |
|
||||
| Cycles | `cycles` | ht_dcperiod, ht_sine, cg, dsp |
|
||||
@@ -58,21 +85,20 @@ rsi = qtl.rsi(s, length=14) # returns pd.Series with preserved index
|
||||
| Volatility | `volatility` | atr, bbw, stddev, hv, tr |
|
||||
| Volume | `volume` | obv, vwma, mfi, cmf, adl |
|
||||
|
||||
## Local Development
|
||||
## Requirements
|
||||
|
||||
```bash
|
||||
cd python/
|
||||
python -m venv .venv && .venv/Scripts/activate # or source .venv/bin/activate
|
||||
pip install -e ".[dev]"
|
||||
pytest
|
||||
```
|
||||
- Python 3.10+
|
||||
- NumPy >= 1.24
|
||||
- Pre-built wheels: `win-x64`, `linux-x64`, `osx-x64`, `osx-arm64`
|
||||
|
||||
### Building the native library
|
||||
### Optional dependencies
|
||||
|
||||
```bash
|
||||
dotnet publish python.csproj -c Release
|
||||
```
|
||||
| Extra | Minimum version | Enables |
|
||||
| ----- | --------------- | ------- |
|
||||
| `pandas` | ≥ 1.5 | `pd.Series` / `pd.DataFrame` round-trip |
|
||||
| `polars` | ≥ 0.20 | `pl.Series` / `pl.DataFrame` round-trip |
|
||||
| `pyarrow` | ≥ 14.0 | `pa.Array` / `pa.ChunkedArray` round-trip |
|
||||
|
||||
## License
|
||||
|
||||
[MIT](../LICENSE)
|
||||
[MIT](https://github.com/mihakralj/quantalib/blob/main/LICENSE)
|
||||
|
||||
+27
-9
@@ -429,16 +429,22 @@ class QtlInternalError(QtlError): ... # status 4
|
||||
### 6.4 Indicator wrappers (`indicators.py`)
|
||||
|
||||
- Expose pandas-ta-compatible function signatures where practical.
|
||||
- Return types:
|
||||
- Return types adapt to the input container type (**"same-type-in, same-type-out"**):
|
||||
|
||||
| Input type | Output (single) | Output (multi) |
|
||||
|------------|-----------------|----------------|
|
||||
| `pd.Series` | `pd.Series` | `pd.DataFrame` |
|
||||
| `pd.DataFrame` | `pd.Series` | `pd.DataFrame` |
|
||||
| `np.ndarray` | `np.ndarray` | tuple of `np.ndarray` |
|
||||
| `pd.Series` | `pd.Series` | `pd.DataFrame` |
|
||||
| `pd.DataFrame` | `pd.Series` (1st col) | `pd.DataFrame` |
|
||||
| `pl.Series` | `pl.Series` | `pl.DataFrame` |
|
||||
| `pl.DataFrame` | `pl.Series` (1st col) | `pl.DataFrame` |
|
||||
| `pa.Array` | `pa.Array` (float64) | `dict[str, pa.Array]` |
|
||||
| `pa.ChunkedArray` | `pa.Array` (float64) | `dict[str, pa.Array]` |
|
||||
|
||||
- Series names follow pandas-ta conventions: `SMA_14`, `BBU_20_2.0`, etc.
|
||||
- DataFrame columns for multi-output: `BBU_20_2.0`, `BBM_20_2.0`, `BBL_20_2.0`.
|
||||
- Polars Series `.name` is set to the indicator name (e.g. `"SMA_14"`).
|
||||
- PyArrow arrays are always returned as `pa.float64()` type.
|
||||
|
||||
### 6.5 Concrete Python wrapper example
|
||||
|
||||
@@ -485,14 +491,26 @@ def sma(close, length=None, offset=None, **kwargs):
|
||||
- Known deltas from pandas-ta warmup lengths are documented in the
|
||||
compatibility table in `README.md`.
|
||||
|
||||
### 6.7 pandas fallback policy
|
||||
### 6.7 Optional dependency policy
|
||||
|
||||
When pandas is not installed:
|
||||
`numpy` is the only **hard** dependency. All DataFrame libraries are optional extras:
|
||||
|
||||
- Functions accept and return `np.ndarray` only.
|
||||
- `pd.Series` / `pd.DataFrame` input raises `ImportError` with message
|
||||
`"pandas required for Series/DataFrame input"`.
|
||||
- `numpy` is a hard dependency; `pandas` is an optional extra.
|
||||
| Extra | Install command | Enables |
|
||||
|-------|----------------|---------|
|
||||
| `pandas` | `pip install quantalib[pandas]` | `pd.Series` / `pd.DataFrame` I/O |
|
||||
| `polars` | `pip install quantalib[polars]` | `pl.Series` / `pl.DataFrame` I/O |
|
||||
| `pyarrow` | `pip install quantalib[pyarrow]` | `pa.Array` / `pa.ChunkedArray` I/O |
|
||||
| `all` | `pip install quantalib[all]` | All of the above |
|
||||
|
||||
When a library is **not** installed, its input types are silently unsupported:
|
||||
the `_arr()` helper falls through to `np.asarray()`, which may produce an
|
||||
error or unexpected result. Because detection uses `isinstance`, there is no
|
||||
import overhead when a library is absent.
|
||||
|
||||
**Origin token pattern:** `_arr()` returns an opaque `_Origin` token that
|
||||
`_wrap()` / `_wrap_multi()` use to reconstruct the original container type.
|
||||
Category modules never inspect this token — they pass it through unchanged.
|
||||
This keeps all 15 category modules free of any polars/pyarrow awareness.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -38,7 +38,10 @@ Issues = "https://github.com/mihakralj/quantalib/issues"
|
||||
|
||||
[project.optional-dependencies]
|
||||
pandas = ["pandas>=1.5"]
|
||||
dev = ["pytest>=8.0", "pandas>=1.5"]
|
||||
polars = ["polars>=0.20"]
|
||||
pyarrow = ["pyarrow>=14.0"]
|
||||
all = ["pandas>=1.5", "polars>=0.20", "pyarrow>=14.0"]
|
||||
dev = ["pytest>=8.0", "pandas>=1.5", "polars>=0.20", "pyarrow>=14.0"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
|
||||
+146
-38
@@ -9,35 +9,103 @@ from numpy.typing import NDArray
|
||||
|
||||
from ._bridge import _lib, _check, _dp, _ci, _cd
|
||||
|
||||
# Optional pandas support
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional dataframe library imports (all optional; numpy is the only hard dep)
|
||||
# ---------------------------------------------------------------------------
|
||||
try:
|
||||
import pandas as pd # type: ignore[import-untyped]
|
||||
except ImportError: # pragma: no cover
|
||||
pd = None # type: ignore[assignment]
|
||||
|
||||
try:
|
||||
import polars as pl # type: ignore[import-untyped]
|
||||
except ImportError: # pragma: no cover
|
||||
pl = None # type: ignore[assignment]
|
||||
|
||||
try:
|
||||
import pyarrow as pa # type: ignore[import-untyped]
|
||||
except ImportError: # pragma: no cover
|
||||
pa = None # type: ignore[assignment]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Origin token — opaque marker passed from _arr() → _wrap()/_wrap_multi()
|
||||
#
|
||||
# Callers in category modules simply do:
|
||||
# src, origin = _arr(close)
|
||||
# ...
|
||||
# return _wrap(dst, origin, name, category, offset)
|
||||
#
|
||||
# The token carries enough info to reconstruct the original container type.
|
||||
# ---------------------------------------------------------------------------
|
||||
_ORIGIN_NUMPY = "numpy"
|
||||
_ORIGIN_PANDAS = "pandas"
|
||||
_ORIGIN_POLARS = "polars"
|
||||
_ORIGIN_PYARROW = "pyarrow"
|
||||
|
||||
|
||||
class _Origin:
|
||||
"""Lightweight tag recording the input container type + metadata."""
|
||||
|
||||
__slots__ = ("kind", "meta")
|
||||
|
||||
def __init__(self, kind: str, meta: object = None) -> None:
|
||||
self.kind = kind
|
||||
self.meta = meta # pandas.Index | polars series name | None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
_F64 = np.float64
|
||||
|
||||
|
||||
def _arr(x: object) -> tuple[NDArray[np.float64], object]:
|
||||
"""Return (contiguous float64 array, original_index_or_None)."""
|
||||
def _arr(x: object) -> tuple[NDArray[np.float64], _Origin | None]:
|
||||
"""Return *(contiguous float64 array, origin_token)*.
|
||||
|
||||
Supported input types
|
||||
---------------------
|
||||
* ``numpy.ndarray``
|
||||
* ``pandas.Series`` / ``pandas.DataFrame`` (first column)
|
||||
* ``polars.Series`` / ``polars.DataFrame`` (first column)
|
||||
* ``pyarrow.Array`` / ``pyarrow.ChunkedArray``
|
||||
* Any sequence coercible via ``np.asarray``
|
||||
"""
|
||||
if x is None:
|
||||
raise ValueError("Input array must not be None")
|
||||
idx = None
|
||||
if pd is not None and isinstance(x, pd.Series):
|
||||
idx = x.index
|
||||
x = x.to_numpy(dtype=_F64, copy=False)
|
||||
elif pd is not None and isinstance(x, pd.DataFrame):
|
||||
idx = x.index
|
||||
x = x.iloc[:, 0].to_numpy(dtype=_F64, copy=False)
|
||||
|
||||
origin: _Origin | None = None
|
||||
|
||||
# ── pandas ──────────────────────────────────────────────────────────
|
||||
if pd is not None and isinstance(x, (pd.Series, pd.DataFrame)):
|
||||
if isinstance(x, pd.DataFrame):
|
||||
origin = _Origin(_ORIGIN_PANDAS, x.index)
|
||||
x = x.iloc[:, 0].to_numpy(dtype=_F64, copy=False)
|
||||
else:
|
||||
origin = _Origin(_ORIGIN_PANDAS, x.index)
|
||||
x = x.to_numpy(dtype=_F64, copy=False)
|
||||
|
||||
# ── polars ──────────────────────────────────────────────────────────
|
||||
elif pl is not None and isinstance(x, (pl.Series, pl.DataFrame)):
|
||||
if isinstance(x, pl.DataFrame):
|
||||
col = x.get_column(x.columns[0])
|
||||
origin = _Origin(_ORIGIN_POLARS, col.name)
|
||||
x = col.cast(pl.Float64).to_numpy(allow_copy=True)
|
||||
else:
|
||||
origin = _Origin(_ORIGIN_POLARS, x.name)
|
||||
x = x.cast(pl.Float64).to_numpy(allow_copy=True)
|
||||
|
||||
# ── pyarrow ─────────────────────────────────────────────────────────
|
||||
elif pa is not None and isinstance(x, (pa.Array, pa.ChunkedArray)):
|
||||
origin = _Origin(_ORIGIN_PYARROW)
|
||||
x = x.to_numpy(zero_copy_only=False).astype(_F64, copy=False)
|
||||
|
||||
# ── coerce to numpy ────────────────────────────────────────────────
|
||||
arr = np.asarray(x, dtype=_F64)
|
||||
if not arr.flags["C_CONTIGUOUS"]:
|
||||
arr = np.ascontiguousarray(arr)
|
||||
if arr.ndim == 0 or len(arr) == 0:
|
||||
raise ValueError("Input array must not be empty")
|
||||
return arr, idx
|
||||
return arr, origin
|
||||
|
||||
|
||||
def _ptr(a: NDArray[np.float64]): # noqa: ANN202
|
||||
@@ -63,33 +131,73 @@ def _offset(arr: NDArray[np.float64], off: int) -> NDArray[np.float64]:
|
||||
|
||||
def _wrap(
|
||||
arr: NDArray[np.float64],
|
||||
idx: object,
|
||||
origin: _Origin | None,
|
||||
name: str,
|
||||
category: str,
|
||||
offset: int = 0,
|
||||
):
|
||||
"""Wrap result: apply offset, optionally convert to pd.Series."""
|
||||
"""Wrap result: apply offset, reconstruct the original container type.
|
||||
|
||||
Return types by origin
|
||||
----------------------
|
||||
* numpy → ``np.ndarray``
|
||||
* pandas → ``pd.Series`` with original index
|
||||
* polars → ``pl.Series``
|
||||
* pyarrow → ``pa.Array`` (float64)
|
||||
"""
|
||||
arr = _offset(arr, offset)
|
||||
if idx is not None and pd is not None:
|
||||
s = pd.Series(arr, index=idx, name=name)
|
||||
|
||||
if origin is None:
|
||||
return arr
|
||||
|
||||
if origin.kind == _ORIGIN_PANDAS and pd is not None:
|
||||
s = pd.Series(arr, index=origin.meta, name=name)
|
||||
s.attrs["category"] = category
|
||||
return s
|
||||
|
||||
if origin.kind == _ORIGIN_POLARS and pl is not None:
|
||||
return pl.Series(name=name, values=arr)
|
||||
|
||||
if origin.kind == _ORIGIN_PYARROW and pa is not None:
|
||||
return pa.array(arr, type=pa.float64())
|
||||
|
||||
return arr
|
||||
|
||||
|
||||
def _wrap_multi(
|
||||
arrays: dict[str, NDArray[np.float64]],
|
||||
idx: object,
|
||||
origin: _Origin | None,
|
||||
category: str,
|
||||
offset: int = 0,
|
||||
):
|
||||
"""Wrap multi-output result into tuple or DataFrame."""
|
||||
"""Wrap multi-output result into the original container type.
|
||||
|
||||
Return types by origin
|
||||
----------------------
|
||||
* numpy → ``tuple[np.ndarray, ...]``
|
||||
* pandas → ``pd.DataFrame`` with original index
|
||||
* polars → ``pl.DataFrame``
|
||||
* pyarrow → ``dict[str, pa.Array]``
|
||||
"""
|
||||
for k in arrays:
|
||||
arrays[k] = _offset(arrays[k], offset)
|
||||
if idx is not None and pd is not None:
|
||||
df = pd.DataFrame(arrays, index=idx)
|
||||
|
||||
if origin is None:
|
||||
return tuple(arrays.values())
|
||||
|
||||
if origin.kind == _ORIGIN_PANDAS and pd is not None:
|
||||
df = pd.DataFrame(arrays, index=origin.meta)
|
||||
df.attrs["category"] = category
|
||||
return df
|
||||
|
||||
if origin.kind == _ORIGIN_POLARS and pl is not None:
|
||||
return pl.DataFrame(
|
||||
{k: pl.Series(name=k, values=v) for k, v in arrays.items()}
|
||||
)
|
||||
|
||||
if origin.kind == _ORIGIN_PYARROW and pa is not None:
|
||||
return {k: pa.array(v, type=pa.float64()) for k, v in arrays.items()}
|
||||
|
||||
return tuple(arrays.values())
|
||||
|
||||
|
||||
@@ -98,17 +206,17 @@ def _wrap_multi(
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def _pa(
|
||||
fn_name: str, close: object, length: int, offset: int,
|
||||
default_length: int, label: str, category: str,
|
||||
fn_name: str, close: object, period: int, offset: int,
|
||||
default_period: int, label: str, category: str,
|
||||
) -> object:
|
||||
"""Generic Pattern A wrapper: single-input + period."""
|
||||
length = int(length) if length is not None else default_length
|
||||
period = int(period) if period is not None else default_period
|
||||
offset = int(offset) if offset is not None else 0
|
||||
src, idx = _arr(close)
|
||||
n = len(src)
|
||||
dst = _out(n)
|
||||
_check(getattr(_lib, fn_name)(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"{label}_{length}", category, offset)
|
||||
_check(getattr(_lib, fn_name)(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"{label}_{period}", category, offset)
|
||||
|
||||
|
||||
def _pa3(
|
||||
@@ -126,18 +234,18 @@ def _pa3(
|
||||
|
||||
def _pf(
|
||||
fn_name: str, actual: object, predicted: object,
|
||||
length: int, offset: int, default_length: int,
|
||||
period: int, offset: int, default_period: int,
|
||||
label: str, category: str,
|
||||
) -> object:
|
||||
"""Generic Pattern F wrapper: actual+predicted+period."""
|
||||
length = int(length) if length is not None else default_length
|
||||
period = int(period) if period is not None else default_period
|
||||
offset = int(offset) if offset is not None else 0
|
||||
a, idx = _arr(actual)
|
||||
p, _ = _arr(predicted)
|
||||
n = len(a)
|
||||
dst = _out(n)
|
||||
_check(getattr(_lib, fn_name)(_ptr(a), _ptr(p), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"{label}_{length}", category, offset)
|
||||
_check(getattr(_lib, fn_name)(_ptr(a), _ptr(p), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"{label}_{period}", category, offset)
|
||||
|
||||
|
||||
def _pg(
|
||||
@@ -155,33 +263,33 @@ def _pg(
|
||||
|
||||
|
||||
def _pg2(
|
||||
fn_name: str, close: object, volume: object, length: int,
|
||||
offset: int, default_length: int, label: str, category: str,
|
||||
fn_name: str, close: object, volume: object, period: int,
|
||||
offset: int, default_period: int, label: str, category: str,
|
||||
) -> object:
|
||||
"""Pattern G2: source+volume+period."""
|
||||
length = int(length) if length is not None else default_length
|
||||
period = int(period) if period is not None else default_period
|
||||
offset = int(offset) if offset is not None else 0
|
||||
c, idx = _arr(close)
|
||||
v, _ = _arr(volume)
|
||||
n = len(c)
|
||||
dst = _out(n)
|
||||
_check(getattr(_lib, fn_name)(_ptr(c), _ptr(v), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"{label}_{length}", category, offset)
|
||||
_check(getattr(_lib, fn_name)(_ptr(c), _ptr(v), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"{label}_{period}", category, offset)
|
||||
|
||||
|
||||
def _ph(
|
||||
fn_name: str, x: object, y: object, length: int,
|
||||
offset: int, default_length: int, label: str, category: str,
|
||||
fn_name: str, x: object, y: object, period: int,
|
||||
offset: int, default_period: int, label: str, category: str,
|
||||
) -> object:
|
||||
"""Pattern H: X+Y+period."""
|
||||
length = int(length) if length is not None else default_length
|
||||
period = int(period) if period is not None else default_period
|
||||
offset = int(offset) if offset is not None else 0
|
||||
xarr, idx = _arr(x)
|
||||
yarr, _ = _arr(y)
|
||||
n = len(xarr)
|
||||
dst = _out(n)
|
||||
_check(getattr(_lib, fn_name)(_ptr(xarr), _ptr(yarr), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"{label}_{length}", category, offset)
|
||||
_check(getattr(_lib, fn_name)(_ptr(xarr), _ptr(yarr), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"{label}_{period}", category, offset)
|
||||
|
||||
|
||||
def _ohlcv_bars_period(
|
||||
|
||||
@@ -315,15 +315,15 @@ def vwapsd(price: object, volume: object, numDevs: float = 2.0, offset: int = 0,
|
||||
return _wrap_multi({"upper": upper, "lower": lower, "vwap": vwap, "stdDev": stdDev}, idx, "channels", offset)
|
||||
|
||||
|
||||
def bbands(close: object, length: int = 20, std: float = 2.0,
|
||||
def bbands(close: object, period: int = 20, std: float = 2.0,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Bollinger Bands -> (upper, mid, lower) or DataFrame."""
|
||||
length = int(length); std = float(std); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); std = float(std); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src)
|
||||
upper = _out(n); mid = _out(n); lower = _out(n)
|
||||
_check(_lib.qtl_bbands(_ptr(src), n, _ptr(upper), _ptr(mid), _ptr(lower), length, std))
|
||||
_check(_lib.qtl_bbands(_ptr(src), n, _ptr(upper), _ptr(mid), _ptr(lower), period, std))
|
||||
return _wrap_multi(
|
||||
{f"BBU_{length}_{std}": upper, f"BBM_{length}_{std}": mid, f"BBL_{length}_{std}": lower},
|
||||
{f"BBU_{period}_{std}": upper, f"BBM_{period}_{std}": mid, f"BBL_{period}_{std}": lower},
|
||||
idx, "channels", offset)
|
||||
|
||||
|
||||
@@ -341,12 +341,12 @@ def atrbands(high: object, low: object, close: object,
|
||||
idx, "channels", offset)
|
||||
|
||||
|
||||
def apchannel(high: object, low: object, length: int = 20,
|
||||
def apchannel(high: object, low: object, period: int = 20,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Average Price Channel -> (upper, lower) or DataFrame."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
h, idx = _arr(high); l, _ = _arr(low)
|
||||
n = len(h)
|
||||
upper = _out(n); lower = _out(n)
|
||||
_check(_lib.qtl_apchannel(_ptr(h), _ptr(l), n, _ptr(upper), _ptr(lower), float(length)))
|
||||
return _wrap_multi({f"APCU_{length}": upper, f"APCL_{length}": lower}, idx, "channels", offset)
|
||||
_check(_lib.qtl_apchannel(_ptr(h), _ptr(l), n, _ptr(upper), _ptr(lower), float(period)))
|
||||
return _wrap_multi({f"APCU_{period}": upper, f"APCL_{period}": lower}, idx, "channels", offset)
|
||||
|
||||
+12
-12
@@ -108,29 +108,29 @@ def ssfdsp(close: object, period: int = 14, offset: int = 0, **kwargs) -> object
|
||||
_check(_lib.qtl_ssfdsp(_ptr(src), _ptr(output), n, period))
|
||||
return _wrap(output, idx, f"SSFDSP_{period}", "cycles", offset)
|
||||
|
||||
def cg(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def cg(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Center of Gravity."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_cg(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"CG_{length}", "cycles", offset)
|
||||
_check(_lib.qtl_cg(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"CG_{period}", "cycles", offset)
|
||||
|
||||
|
||||
def dsp(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
def dsp(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
"""Dominant Cycle Period (DSP)."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_dsp(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"DSP_{length}", "cycles", offset)
|
||||
_check(_lib.qtl_dsp(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"DSP_{period}", "cycles", offset)
|
||||
|
||||
|
||||
def ccor(close: object, length: int = 20, alpha: float = 0.07,
|
||||
def ccor(close: object, period: int = 20, alpha: float = 0.07,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Circular Correlation."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_ccor(_ptr(src), n, _ptr(dst), length, float(alpha)))
|
||||
return _wrap(dst, idx, f"CCOR_{length}", "cycles", offset)
|
||||
_check(_lib.qtl_ccor(_ptr(src), n, _ptr(dst), period, float(alpha)))
|
||||
return _wrap(dst, idx, f"CCOR_{period}", "cycles", offset)
|
||||
|
||||
|
||||
def ebsw(close: object, hp_length: int = 40, ssf_length: int = 10,
|
||||
|
||||
+16
-16
@@ -282,41 +282,41 @@ def wrmse(actual: object, predicted: object, period: int = 14, offset: int = 0,
|
||||
_check(_lib.qtl_wrmse(_ptr(a), _ptr(p), _ptr(output), n, period))
|
||||
return _wrap(output, idx, f"WRMSE_{period}", "errors", offset)
|
||||
|
||||
def mse(actual: object, predicted: object, length: int = 20,
|
||||
def mse(actual: object, predicted: object, period: int = 20,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Mean Squared Error."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
a, idx = _arr(actual); p, _ = _arr(predicted)
|
||||
n = len(a); dst = _out(n)
|
||||
_check(_lib.qtl_mse(_ptr(a), _ptr(p), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"MSE_{length}", "errors", offset)
|
||||
_check(_lib.qtl_mse(_ptr(a), _ptr(p), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"MSE_{period}", "errors", offset)
|
||||
|
||||
|
||||
def rmse(actual: object, predicted: object, length: int = 20,
|
||||
def rmse(actual: object, predicted: object, period: int = 20,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Root Mean Squared Error."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
a, idx = _arr(actual); p, _ = _arr(predicted)
|
||||
n = len(a); dst = _out(n)
|
||||
_check(_lib.qtl_rmse(_ptr(a), _ptr(p), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"RMSE_{length}", "errors", offset)
|
||||
_check(_lib.qtl_rmse(_ptr(a), _ptr(p), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"RMSE_{period}", "errors", offset)
|
||||
|
||||
|
||||
def mae(actual: object, predicted: object, length: int = 20,
|
||||
def mae(actual: object, predicted: object, period: int = 20,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Mean Absolute Error."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
a, idx = _arr(actual); p, _ = _arr(predicted)
|
||||
n = len(a); dst = _out(n)
|
||||
_check(_lib.qtl_mae(_ptr(a), _ptr(p), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"MAE_{length}", "errors", offset)
|
||||
_check(_lib.qtl_mae(_ptr(a), _ptr(p), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"MAE_{period}", "errors", offset)
|
||||
|
||||
|
||||
def mape(actual: object, predicted: object, length: int = 20,
|
||||
def mape(actual: object, predicted: object, period: int = 20,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Mean Absolute Percentage Error."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
a, idx = _arr(actual); p, _ = _arr(predicted)
|
||||
n = len(a); dst = _out(n)
|
||||
_check(_lib.qtl_mape(_ptr(a), _ptr(p), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"MAPE_{length}", "errors", offset)
|
||||
_check(_lib.qtl_mape(_ptr(a), _ptr(p), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"MAPE_{period}", "errors", offset)
|
||||
|
||||
+56
-56
@@ -57,15 +57,15 @@ def gauss(close: object, sigma: float = 1.0, offset: int = 0, **kwargs) -> objec
|
||||
return _wrap(output, idx, "GAUSS", "filters", offset)
|
||||
|
||||
|
||||
def hann(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
def hann(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
"""Hann Filter."""
|
||||
length = int(length)
|
||||
period = int(kwargs.get("length", period))
|
||||
offset = int(offset)
|
||||
src, idx = _arr(close)
|
||||
n = len(src)
|
||||
output = _out(n)
|
||||
_check(_lib.qtl_hann(_ptr(src), _ptr(output), n, length))
|
||||
return _wrap(output, idx, f"HANN_{length}", "filters", offset)
|
||||
_check(_lib.qtl_hann(_ptr(src), _ptr(output), n, period))
|
||||
return _wrap(output, idx, f"HANN_{period}", "filters", offset)
|
||||
|
||||
|
||||
def hp(close: object, lam: float = 1600.0, offset: int = 0, **kwargs) -> object:
|
||||
@@ -79,15 +79,15 @@ def hp(close: object, lam: float = 1600.0, offset: int = 0, **kwargs) -> object:
|
||||
return _wrap(output, idx, "HP", "filters", offset)
|
||||
|
||||
|
||||
def hpf(close: object, length: int = 40, offset: int = 0, **kwargs) -> object:
|
||||
def hpf(close: object, period: int = 40, offset: int = 0, **kwargs) -> object:
|
||||
"""High-Pass Filter."""
|
||||
length = int(length)
|
||||
period = int(kwargs.get("length", period))
|
||||
offset = int(offset)
|
||||
src, idx = _arr(close)
|
||||
n = len(src)
|
||||
output = _out(n)
|
||||
_check(_lib.qtl_hpf(_ptr(src), _ptr(output), n, length))
|
||||
return _wrap(output, idx, f"HPF_{length}", "filters", offset)
|
||||
_check(_lib.qtl_hpf(_ptr(src), _ptr(output), n, period))
|
||||
return _wrap(output, idx, f"HPF_{period}", "filters", offset)
|
||||
|
||||
|
||||
def kalman(close: object, q: float = 0.01, r: float = 0.1, offset: int = 0, **kwargs) -> object:
|
||||
@@ -317,106 +317,106 @@ def wiener(close: object, period: int = 14, smoothPeriod: int = 10, offset: int
|
||||
_check(_lib.qtl_wiener(_ptr(src), _ptr(destination), n, period, smoothPeriod))
|
||||
return _wrap(destination, idx, f"WIENER_{period}", "filters", offset)
|
||||
|
||||
def bessel(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
def bessel(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
"""Bessel Filter."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_bessel(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"BESSEL_{length}", "filters", offset)
|
||||
_check(_lib.qtl_bessel(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"BESSEL_{period}", "filters", offset)
|
||||
|
||||
|
||||
def butter2(close: object, length: int = 14, gain: float = 1.0,
|
||||
def butter2(close: object, period: int = 14, gain: float = 1.0,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""2nd-order Butterworth."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_butter2(_ptr(src), n, _ptr(dst), length, float(gain)))
|
||||
return _wrap(dst, idx, f"BUTTER2_{length}", "filters", offset)
|
||||
_check(_lib.qtl_butter2(_ptr(src), n, _ptr(dst), period, float(gain)))
|
||||
return _wrap(dst, idx, f"BUTTER2_{period}", "filters", offset)
|
||||
|
||||
|
||||
def butter3(close: object, length: int = 14, gain: float = 1.0,
|
||||
def butter3(close: object, period: int = 14, gain: float = 1.0,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""3rd-order Butterworth."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_butter3(_ptr(src), n, _ptr(dst), length, float(gain)))
|
||||
return _wrap(dst, idx, f"BUTTER3_{length}", "filters", offset)
|
||||
_check(_lib.qtl_butter3(_ptr(src), n, _ptr(dst), period, float(gain)))
|
||||
return _wrap(dst, idx, f"BUTTER3_{period}", "filters", offset)
|
||||
|
||||
|
||||
def cheby1(close: object, length: int = 14, ripple: float = 0.5,
|
||||
def cheby1(close: object, period: int = 14, ripple: float = 0.5,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Chebyshev Type I."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_cheby1(_ptr(src), n, _ptr(dst), length, float(ripple)))
|
||||
return _wrap(dst, idx, f"CHEBY1_{length}", "filters", offset)
|
||||
_check(_lib.qtl_cheby1(_ptr(src), n, _ptr(dst), period, float(ripple)))
|
||||
return _wrap(dst, idx, f"CHEBY1_{period}", "filters", offset)
|
||||
|
||||
|
||||
def cheby2(close: object, length: int = 14, ripple: float = 0.5,
|
||||
def cheby2(close: object, period: int = 14, ripple: float = 0.5,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Chebyshev Type II."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_cheby2(_ptr(src), n, _ptr(dst), length, float(ripple)))
|
||||
return _wrap(dst, idx, f"CHEBY2_{length}", "filters", offset)
|
||||
_check(_lib.qtl_cheby2(_ptr(src), n, _ptr(dst), period, float(ripple)))
|
||||
return _wrap(dst, idx, f"CHEBY2_{period}", "filters", offset)
|
||||
|
||||
|
||||
def elliptic(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
def elliptic(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
"""Elliptic (Cauer) Filter."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_elliptic(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"ELLIPTIC_{length}", "filters", offset)
|
||||
_check(_lib.qtl_elliptic(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"ELLIPTIC_{period}", "filters", offset)
|
||||
|
||||
|
||||
def edcf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
def edcf(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
"""Ehlers Distance Coefficient Filter."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_edcf(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"EDCF_{length}", "filters", offset)
|
||||
_check(_lib.qtl_edcf(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"EDCF_{period}", "filters", offset)
|
||||
|
||||
|
||||
def bpf(close: object, length: int = 14, bandwidth: int = 5,
|
||||
def bpf(close: object, period: int = 14, bandwidth: int = 5,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Bandpass Filter."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_bpf(_ptr(src), n, _ptr(dst), length, int(bandwidth)))
|
||||
return _wrap(dst, idx, f"BPF_{length}", "filters", offset)
|
||||
_check(_lib.qtl_bpf(_ptr(src), n, _ptr(dst), period, int(bandwidth)))
|
||||
return _wrap(dst, idx, f"BPF_{period}", "filters", offset)
|
||||
|
||||
|
||||
def alaguerre(close: object, length: int = 20, order: int = 5,
|
||||
def alaguerre(close: object, period: int = 20, order: int = 5,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Adaptive Laguerre Filter."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_alaguerre(_ptr(src), n, _ptr(dst), length, int(order)))
|
||||
return _wrap(dst, idx, f"ALAGUERRE_{length}", "filters", offset)
|
||||
_check(_lib.qtl_alaguerre(_ptr(src), n, _ptr(dst), period, int(order)))
|
||||
return _wrap(dst, idx, f"ALAGUERRE_{period}", "filters", offset)
|
||||
|
||||
|
||||
def bilateral(close: object, length: int = 14, sigma_s: float = 0.5,
|
||||
def bilateral(close: object, period: int = 14, sigma_s: float = 0.5,
|
||||
sigma_r: float = 1.0, offset: int = 0, **kwargs) -> object:
|
||||
"""Bilateral Filter."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_bilateral(_ptr(src), n, _ptr(dst), length, float(sigma_s), float(sigma_r)))
|
||||
return _wrap(dst, idx, f"BILATERAL_{length}", "filters", offset)
|
||||
_check(_lib.qtl_bilateral(_ptr(src), n, _ptr(dst), period, float(sigma_s), float(sigma_r)))
|
||||
return _wrap(dst, idx, f"BILATERAL_{period}", "filters", offset)
|
||||
|
||||
|
||||
def baxterking(close: object, length: int = 12, min_period: int = 6,
|
||||
def baxterking(close: object, period: int = 12, min_period: int = 6,
|
||||
max_period: int = 32, offset: int = 0, **kwargs) -> object:
|
||||
"""Baxter-King Filter."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_baxterking(_ptr(src), n, _ptr(dst), length, int(min_period), int(max_period)))
|
||||
return _wrap(dst, idx, f"BAXTERKING_{length}", "filters", offset)
|
||||
_check(_lib.qtl_baxterking(_ptr(src), n, _ptr(dst), period, int(min_period), int(max_period)))
|
||||
return _wrap(dst, idx, f"BAXTERKING_{period}", "filters", offset)
|
||||
|
||||
|
||||
def cfitz(close: object, length: int = 6, bw_period: int = 32,
|
||||
def cfitz(close: object, period: int = 6, bw_period: int = 32,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Christiano-Fitzgerald Filter."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_cfitz(_ptr(src), n, _ptr(dst), length, int(bw_period)))
|
||||
return _wrap(dst, idx, f"CFITZ_{length}", "filters", offset)
|
||||
_check(_lib.qtl_cfitz(_ptr(src), n, _ptr(dst), period, int(bw_period)))
|
||||
return _wrap(dst, idx, f"CFITZ_{period}", "filters", offset)
|
||||
|
||||
@@ -145,36 +145,36 @@ def vel(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
_check(_lib.qtl_vel(_ptr(src), _ptr(output), n, period))
|
||||
return _wrap(output, idx, f"VEL_{period}", "momentum", offset)
|
||||
|
||||
def rsi(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
def rsi(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
"""Relative Strength Index."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_rsi(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"RSI_{length}", "momentum", offset)
|
||||
_check(_lib.qtl_rsi(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"RSI_{period}", "momentum", offset)
|
||||
|
||||
|
||||
def roc(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def roc(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Rate of Change."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_roc(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"ROC_{length}", "momentum", offset)
|
||||
_check(_lib.qtl_roc(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"ROC_{period}", "momentum", offset)
|
||||
|
||||
|
||||
def mom(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def mom(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Momentum."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_mom(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"MOM_{length}", "momentum", offset)
|
||||
_check(_lib.qtl_mom(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"MOM_{period}", "momentum", offset)
|
||||
|
||||
|
||||
def cmo(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
def cmo(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
"""Chande Momentum Oscillator."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_cmo(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"CMO_{length}", "momentum", offset)
|
||||
_check(_lib.qtl_cmo(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"CMO_{period}", "momentum", offset)
|
||||
|
||||
|
||||
def tsi(close: object, long_period: int = 25, short_period: int = 13,
|
||||
@@ -195,20 +195,20 @@ def apo(close: object, fast: int = 12, slow: int = 26,
|
||||
return _wrap(dst, idx, f"APO_{fast}_{slow}", "momentum", offset)
|
||||
|
||||
|
||||
def bias(close: object, length: int = 26, offset: int = 0, **kwargs) -> object:
|
||||
def bias(close: object, period: int = 26, offset: int = 0, **kwargs) -> object:
|
||||
"""Bias."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_bias(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"BIAS_{length}", "momentum", offset)
|
||||
_check(_lib.qtl_bias(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"BIAS_{period}", "momentum", offset)
|
||||
|
||||
|
||||
def cfo(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
def cfo(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
"""Chande Forecast Oscillator."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_cfo(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"CFO_{length}", "momentum", offset)
|
||||
_check(_lib.qtl_cfo(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"CFO_{period}", "momentum", offset)
|
||||
|
||||
|
||||
def cfb(close: object, lengths: list | None = None,
|
||||
|
||||
@@ -269,12 +269,12 @@ def weibulldist(close: object, k: float = 1.5, lam: float = 1.0, period: int = 1
|
||||
_check(_lib.qtl_weibulldist(_ptr(src), _ptr(output), n, k, lam, period))
|
||||
return _wrap(output, idx, f"WEIBULLDIST_{period}", "numerics", offset)
|
||||
|
||||
def change(close: object, length: int = 1, offset: int = 0, **kwargs) -> object:
|
||||
def change(close: object, period: int = 1, offset: int = 0, **kwargs) -> object:
|
||||
"""Price Change."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_change(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"CHANGE_{length}", "numerics", offset)
|
||||
_check(_lib.qtl_change(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"CHANGE_{period}", "numerics", offset)
|
||||
|
||||
|
||||
def exptrans(close: object, offset: int = 0, **kwargs) -> object:
|
||||
@@ -285,31 +285,31 @@ def exptrans(close: object, offset: int = 0, **kwargs) -> object:
|
||||
return _wrap(dst, idx, "EXPTRANS", "numerics", offset)
|
||||
|
||||
|
||||
def betadist(close: object, length: int = 50, alpha: float = 2.0,
|
||||
def betadist(close: object, period: int = 50, alpha: float = 2.0,
|
||||
beta: float = 2.0, offset: int = 0, **kwargs) -> object:
|
||||
"""Beta Distribution."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_betadist(_ptr(src), n, _ptr(dst), length, float(alpha), float(beta)))
|
||||
return _wrap(dst, idx, f"BETADIST_{length}", "numerics", offset)
|
||||
_check(_lib.qtl_betadist(_ptr(src), n, _ptr(dst), period, float(alpha), float(beta)))
|
||||
return _wrap(dst, idx, f"BETADIST_{period}", "numerics", offset)
|
||||
|
||||
|
||||
def expdist(close: object, length: int = 50, lam: float = 3.0,
|
||||
def expdist(close: object, period: int = 50, lam: float = 3.0,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Exponential Distribution."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_expdist(_ptr(src), n, _ptr(dst), length, float(lam)))
|
||||
return _wrap(dst, idx, f"EXPDIST_{length}", "numerics", offset)
|
||||
_check(_lib.qtl_expdist(_ptr(src), n, _ptr(dst), period, float(lam)))
|
||||
return _wrap(dst, idx, f"EXPDIST_{period}", "numerics", offset)
|
||||
|
||||
|
||||
def binomdist(close: object, length: int = 50, trials: int = 20,
|
||||
def binomdist(close: object, period: int = 50, trials: int = 20,
|
||||
threshold: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Binomial Distribution."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_binomdist(_ptr(src), n, _ptr(dst), length, int(trials), int(threshold)))
|
||||
return _wrap(dst, idx, f"BINOMDIST_{length}", "numerics", offset)
|
||||
_check(_lib.qtl_binomdist(_ptr(src), n, _ptr(dst), period, int(trials), int(threshold)))
|
||||
return _wrap(dst, idx, f"BINOMDIST_{period}", "numerics", offset)
|
||||
|
||||
|
||||
def cwt(close: object, scale: float = 10.0, omega: float = 6.0,
|
||||
@@ -321,10 +321,10 @@ def cwt(close: object, scale: float = 10.0, omega: float = 6.0,
|
||||
return _wrap(dst, idx, "CWT", "numerics", offset)
|
||||
|
||||
|
||||
def dwt(close: object, length: int = 4, levels: int = 0,
|
||||
def dwt(close: object, period: int = 4, levels: int = 0,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Discrete Wavelet Transform."""
|
||||
length = int(length); levels = int(levels); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); levels = int(levels); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_dwt(_ptr(src), n, _ptr(dst), length, levels))
|
||||
return _wrap(dst, idx, f"DWT_{length}", "numerics", offset)
|
||||
_check(_lib.qtl_dwt(_ptr(src), n, _ptr(dst), period, levels))
|
||||
return _wrap(dst, idx, f"DWT_{period}", "numerics", offset)
|
||||
|
||||
@@ -156,9 +156,9 @@ def imi(open: object, high: object, low: object, close: object, volume: object,
|
||||
return _wrap(dst, idx, f"IMI_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def kdj(high: object, low: object, close: object, length: int = 14, signal: int = 3, offset: int = 0, **kwargs) -> object:
|
||||
def kdj(high: object, low: object, close: object, period: int = 14, signal: int = 3, offset: int = 0, **kwargs) -> object:
|
||||
"""KDJ Indicator."""
|
||||
length = int(length)
|
||||
period = int(kwargs.get("length", period))
|
||||
signal = int(signal)
|
||||
offset = int(offset)
|
||||
h, idx = _arr(high); l, _ = _arr(low); c, _ = _arr(close)
|
||||
@@ -166,7 +166,7 @@ def kdj(high: object, low: object, close: object, length: int = 14, signal: int
|
||||
kOut = _out(n)
|
||||
dOut = _out(n)
|
||||
jOut = _out(n)
|
||||
_check(_lib.qtl_kdj(_ptr(h), _ptr(l), _ptr(c), _ptr(kOut), _ptr(dOut), _ptr(jOut), n, length, signal))
|
||||
_check(_lib.qtl_kdj(_ptr(h), _ptr(l), _ptr(c), _ptr(kOut), _ptr(dOut), _ptr(jOut), n, period, signal))
|
||||
return _wrap_multi({"kOut": kOut, "dOut": dOut, "jOut": jOut}, idx, "oscillators", offset)
|
||||
|
||||
|
||||
@@ -377,100 +377,100 @@ def willr(high: object, low: object, close: object, period: int = 14, offset: in
|
||||
_check(_lib.qtl_willr(_ptr(h), _ptr(l), _ptr(c), _ptr(output), n, period))
|
||||
return _wrap(output, idx, f"WILLR_{period}", "oscillators", offset)
|
||||
|
||||
def fisher(close: object, length: int = 9, offset: int = 0, **kwargs) -> object:
|
||||
def fisher(close: object, period: int = 9, offset: int = 0, **kwargs) -> object:
|
||||
"""Fisher Transform."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_fisher(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"FISHER_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_fisher(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"FISHER_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def fisher04(close: object, length: int = 9, offset: int = 0, **kwargs) -> object:
|
||||
def fisher04(close: object, period: int = 9, offset: int = 0, **kwargs) -> object:
|
||||
"""Fisher Transform (0.4 variant)."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_fisher04(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"FISHER04_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_fisher04(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"FISHER04_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def dpo(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
def dpo(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
"""Detrended Price Oscillator."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_dpo(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"DPO_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_dpo(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"DPO_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def trix(close: object, length: int = 18, offset: int = 0, **kwargs) -> object:
|
||||
def trix(close: object, period: int = 18, offset: int = 0, **kwargs) -> object:
|
||||
"""Triple EMA Rate of Change."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_trix(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"TRIX_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_trix(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"TRIX_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def inertia(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
def inertia(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
"""Inertia."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_inertia(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"INERTIA_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_inertia(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"INERTIA_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def rsx(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
def rsx(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
"""Relative Strength Xtra."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_rsx(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"RSX_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_rsx(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"RSX_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def er(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def er(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Efficiency Ratio."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_er(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"ER_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_er(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"ER_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def cti(close: object, length: int = 12, offset: int = 0, **kwargs) -> object:
|
||||
def cti(close: object, period: int = 12, offset: int = 0, **kwargs) -> object:
|
||||
"""Correlation Trend Indicator."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_cti(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"CTI_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_cti(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"CTI_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def reflex(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
def reflex(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
"""Reflex."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_reflex(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"REFLEX_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_reflex(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"REFLEX_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def trendflex(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
def trendflex(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
"""Trendflex."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_trendflex(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"TRENDFLEX_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_trendflex(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"TRENDFLEX_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def kri(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
def kri(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
"""Kairi Relative Index."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_kri(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"KRI_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_kri(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"KRI_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def psl(close: object, length: int = 12, offset: int = 0, **kwargs) -> object:
|
||||
def psl(close: object, period: int = 12, offset: int = 0, **kwargs) -> object:
|
||||
"""Psychological Line."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_psl(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"PSL_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_psl(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"PSL_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def deco(close: object, short_period: int = 30, long_period: int = 60,
|
||||
@@ -515,13 +515,13 @@ def crsi(close: object, rsi_period: int = 3, streak_period: int = 2,
|
||||
return _wrap(dst, idx, f"CRSI_{rsi_period}", "oscillators", offset)
|
||||
|
||||
|
||||
def bbb(close: object, length: int = 20, mult: float = 2.0,
|
||||
def bbb(close: object, period: int = 20, mult: float = 2.0,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Bollinger Band Bounce."""
|
||||
length = int(length); mult = float(mult); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); mult = float(mult); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_bbb(_ptr(src), n, _ptr(dst), length, mult))
|
||||
return _wrap(dst, idx, f"BBB_{length}", "oscillators", offset)
|
||||
_check(_lib.qtl_bbb(_ptr(src), n, _ptr(dst), period, mult))
|
||||
return _wrap(dst, idx, f"BBB_{period}", "oscillators", offset)
|
||||
|
||||
|
||||
def bbi(close: object, p1: int = 3, p2: int = 6, p3: int = 12, p4: int = 24,
|
||||
@@ -533,14 +533,14 @@ def bbi(close: object, p1: int = 3, p2: int = 6, p3: int = 12, p4: int = 24,
|
||||
return _wrap(dst, idx, "BBI", "oscillators", offset)
|
||||
|
||||
|
||||
def dem(high: object, low: object, length: int = 14,
|
||||
def dem(high: object, low: object, period: int = 14,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""DeMarker."""
|
||||
length = int(length)
|
||||
period = int(kwargs.get("length", period))
|
||||
h, idx = _arr(high); l, _ = _arr(low)
|
||||
n = len(h); dst = _out(n)
|
||||
_check(_lib.qtl_dem(_ptr(h), _ptr(l), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"DEM_{length}", "oscillators", int(offset))
|
||||
_check(_lib.qtl_dem(_ptr(h), _ptr(l), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"DEM_{period}", "oscillators", int(offset))
|
||||
|
||||
|
||||
def brar(open: object, high: object, low: object, close: object,
|
||||
|
||||
@@ -340,12 +340,12 @@ def ztest(close: object, period: int = 14, mu0: float = 0.0, offset: int = 0, **
|
||||
_check(_lib.qtl_ztest(_ptr(src), _ptr(output), n, period, mu0))
|
||||
return _wrap(output, idx, f"ZTEST_{period}", "statistics", offset)
|
||||
|
||||
def zscore(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
def zscore(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
"""Z-Score."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_zscore(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"ZSCORE_{length}", "statistics", offset)
|
||||
_check(_lib.qtl_zscore(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"ZSCORE_{period}", "statistics", offset)
|
||||
|
||||
|
||||
def cma(close: object, offset: int = 0, **kwargs) -> object:
|
||||
@@ -356,39 +356,39 @@ def cma(close: object, offset: int = 0, **kwargs) -> object:
|
||||
return _wrap(dst, idx, "CMA", "statistics", offset)
|
||||
|
||||
|
||||
def entropy(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def entropy(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Shannon Entropy."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_entropy(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"ENTROPY_{length}", "statistics", offset)
|
||||
_check(_lib.qtl_entropy(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"ENTROPY_{period}", "statistics", offset)
|
||||
|
||||
|
||||
def correlation(x: object, y: object, length: int = 20,
|
||||
def correlation(x: object, y: object, period: int = 20,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Pearson Correlation."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
xarr, idx = _arr(x); yarr, _ = _arr(y)
|
||||
n = len(xarr); dst = _out(n)
|
||||
_check(_lib.qtl_correlation(_ptr(xarr), _ptr(yarr), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"CORR_{length}", "statistics", offset)
|
||||
_check(_lib.qtl_correlation(_ptr(xarr), _ptr(yarr), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"CORR_{period}", "statistics", offset)
|
||||
|
||||
|
||||
def covariance(x: object, y: object, length: int = 20,
|
||||
def covariance(x: object, y: object, period: int = 20,
|
||||
is_sample: bool = True, offset: int = 0, **kwargs) -> object:
|
||||
"""Covariance."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
xarr, idx = _arr(x); yarr, _ = _arr(y)
|
||||
n = len(xarr); dst = _out(n)
|
||||
_check(_lib.qtl_covariance(_ptr(xarr), _ptr(yarr), n, _ptr(dst), length, int(is_sample)))
|
||||
return _wrap(dst, idx, f"COV_{length}", "statistics", offset)
|
||||
_check(_lib.qtl_covariance(_ptr(xarr), _ptr(yarr), n, _ptr(dst), period, int(is_sample)))
|
||||
return _wrap(dst, idx, f"COV_{period}", "statistics", offset)
|
||||
|
||||
|
||||
def cointegration(x: object, y: object, length: int = 20,
|
||||
def cointegration(x: object, y: object, period: int = 20,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Cointegration."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
xarr, idx = _arr(x); yarr, _ = _arr(y)
|
||||
n = len(xarr); dst = _out(n)
|
||||
_check(_lib.qtl_cointegration(_ptr(xarr), _ptr(yarr), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"COINT_{length}", "statistics", offset)
|
||||
_check(_lib.qtl_cointegration(_ptr(xarr), _ptr(yarr), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"COINT_{period}", "statistics", offset)
|
||||
|
||||
@@ -195,117 +195,117 @@ def rwma(high: object, low: object, close: object, period: int = 14, offset: int
|
||||
_check(_lib.qtl_rwma(_ptr(c), _ptr(h), _ptr(l), _ptr(output), n, period))
|
||||
return _wrap(output, idx, f"RWMA_{period}", "trends_fir", offset)
|
||||
|
||||
def sma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def sma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Simple Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_sma(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"SMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_sma(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"SMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def wma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def wma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Weighted Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_wma(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"WMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_wma(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"WMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def hma(close: object, length: int = 9, offset: int = 0, **kwargs) -> object:
|
||||
def hma(close: object, period: int = 9, offset: int = 0, **kwargs) -> object:
|
||||
"""Hull Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_hma(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"HMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_hma(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"HMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def trima(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def trima(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Triangular Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_trima(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"TRIMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_trima(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"TRIMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def swma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def swma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Symmetric Weighted Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_swma(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"SWMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_swma(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"SWMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def dwma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def dwma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Double Weighted Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_dwma(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"DWMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_dwma(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"DWMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def blma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def blma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Blackman Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_blma(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"BLMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_blma(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"BLMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def alma(close: object, length: int = 10, alma_offset: float = 0.85,
|
||||
def alma(close: object, period: int = 10, alma_offset: float = 0.85,
|
||||
sigma: float = 6.0, offset: int = 0, **kwargs) -> object:
|
||||
"""Arnaud Legoux Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_alma(_ptr(src), n, _ptr(dst), length, float(alma_offset), float(sigma)))
|
||||
return _wrap(dst, idx, f"ALMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_alma(_ptr(src), n, _ptr(dst), period, float(alma_offset), float(sigma)))
|
||||
return _wrap(dst, idx, f"ALMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def lsma(close: object, length: int = 25, offset: int = 0, **kwargs) -> object:
|
||||
def lsma(close: object, period: int = 25, offset: int = 0, **kwargs) -> object:
|
||||
"""Least Squares Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_lsma(_ptr(src), n, _ptr(dst), length, 0, 1.0))
|
||||
return _wrap(dst, idx, f"LSMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_lsma(_ptr(src), n, _ptr(dst), period, 0, 1.0))
|
||||
return _wrap(dst, idx, f"LSMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def sgma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def sgma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Savitzky-Golay Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_sgma(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"SGMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_sgma(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"SGMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def sinema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def sinema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Sine-weighted Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_sinema(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"SINEMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_sinema(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"SINEMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def hanma(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def hanma(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Hann-weighted Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_hanma(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"HANMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_hanma(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"HANMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def parzen(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def parzen(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Parzen-weighted Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_parzen(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"PARZEN_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_parzen(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"PARZEN_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def tsf(close: object, length: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
def tsf(close: object, period: int = 14, offset: int = 0, **kwargs) -> object:
|
||||
"""Time Series Forecast."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_tsf(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"TSF_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_tsf(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"TSF_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def conv(close: object, kernel: list | None = None,
|
||||
@@ -320,52 +320,52 @@ def conv(close: object, kernel: list | None = None,
|
||||
return _wrap(dst, idx, "CONV", "trends_fir", offset)
|
||||
|
||||
|
||||
def bwma(close: object, length: int = 10, order: int = 0,
|
||||
def bwma(close: object, period: int = 10, order: int = 0,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Butterworth-weighted Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_bwma(_ptr(src), n, _ptr(dst), length, int(order)))
|
||||
return _wrap(dst, idx, f"BWMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_bwma(_ptr(src), n, _ptr(dst), period, int(order)))
|
||||
return _wrap(dst, idx, f"BWMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def crma(close: object, length: int = 10, volume_factor: float = 1.0,
|
||||
def crma(close: object, period: int = 10, volume_factor: float = 1.0,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Cosine-Ramp Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_crma(_ptr(src), n, _ptr(dst), length, float(volume_factor)))
|
||||
return _wrap(dst, idx, f"CRMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_crma(_ptr(src), n, _ptr(dst), period, float(volume_factor)))
|
||||
return _wrap(dst, idx, f"CRMA_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def sp15(close: object, length: int = 15, offset: int = 0, **kwargs) -> object:
|
||||
def sp15(close: object, period: int = 15, offset: int = 0, **kwargs) -> object:
|
||||
"""SP-15 Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_sp15(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"SP15_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_sp15(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"SP15_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def tukey_w(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def tukey_w(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Tukey-windowed Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_tukey_w(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"TUKEY_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_tukey_w(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"TUKEY_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def rain(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def rain(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""RAIN Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_rain(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"RAIN_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_rain(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"RAIN_{period}", "trends_fir", offset)
|
||||
|
||||
|
||||
def afirma(close: object, length: int = 10, window_type: int = 0,
|
||||
def afirma(close: object, period: int = 10, window_type: int = 0,
|
||||
use_simd: bool = False, offset: int = 0, **kwargs) -> object:
|
||||
"""Adaptive FIR Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_afirma(_ptr(src), n, _ptr(dst), length, int(window_type), int(use_simd)))
|
||||
return _wrap(dst, idx, f"AFIRMA_{length}", "trends_fir", offset)
|
||||
_check(_lib.qtl_afirma(_ptr(src), n, _ptr(dst), period, int(window_type), int(use_simd)))
|
||||
return _wrap(dst, idx, f"AFIRMA_{period}", "trends_fir", offset)
|
||||
|
||||
@@ -357,12 +357,12 @@ def zltema(close: object, period: int = 14, offset: int = 0, **kwargs) -> object
|
||||
_check(_lib.qtl_zltema(_ptr(src), _ptr(output), n, period))
|
||||
return _wrap(output, idx, f"ZLTEMA_{period}", "trends_iir", offset)
|
||||
|
||||
def ema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def ema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Exponential Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_ema(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"EMA_{length}", "trends_iir", offset)
|
||||
_check(_lib.qtl_ema(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"EMA_{period}", "trends_iir", offset)
|
||||
|
||||
|
||||
def ema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object:
|
||||
@@ -373,12 +373,12 @@ def ema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> o
|
||||
return _wrap(dst, idx, f"EMA_a{alpha:.4f}", "trends_iir", offset)
|
||||
|
||||
|
||||
def dema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def dema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Double Exponential Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_dema(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"DEMA_{length}", "trends_iir", offset)
|
||||
_check(_lib.qtl_dema(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"DEMA_{period}", "trends_iir", offset)
|
||||
|
||||
|
||||
def dema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object:
|
||||
@@ -389,71 +389,71 @@ def dema_alpha(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) ->
|
||||
return _wrap(dst, idx, f"DEMA_a{alpha:.4f}", "trends_iir", offset)
|
||||
|
||||
|
||||
def tema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def tema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Triple Exponential Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_tema(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"TEMA_{length}", "trends_iir", offset)
|
||||
_check(_lib.qtl_tema(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"TEMA_{period}", "trends_iir", offset)
|
||||
|
||||
|
||||
def lema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def lema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Laguerre-based EMA."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_lema(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"LEMA_{length}", "trends_iir", offset)
|
||||
_check(_lib.qtl_lema(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"LEMA_{period}", "trends_iir", offset)
|
||||
|
||||
|
||||
def hema(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def hema(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Henderson EMA."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_hema(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"HEMA_{length}", "trends_iir", offset)
|
||||
_check(_lib.qtl_hema(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"HEMA_{period}", "trends_iir", offset)
|
||||
|
||||
|
||||
def ahrens(close: object, length: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
def ahrens(close: object, period: int = 10, offset: int = 0, **kwargs) -> object:
|
||||
"""Ahrens Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_ahrens(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"AHRENS_{length}", "trends_iir", offset)
|
||||
_check(_lib.qtl_ahrens(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"AHRENS_{period}", "trends_iir", offset)
|
||||
|
||||
|
||||
def decycler(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
def decycler(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
"""Simple Decycler."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_decycler(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"DECYCLER_{length}", "trends_iir", offset)
|
||||
_check(_lib.qtl_decycler(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"DECYCLER_{period}", "trends_iir", offset)
|
||||
|
||||
|
||||
def dsma(close: object, length: int = 10, factor: float = 0.5,
|
||||
def dsma(close: object, period: int = 10, factor: float = 0.5,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Deviation-Scaled Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_dsma(_ptr(src), n, _ptr(dst), length, float(factor)))
|
||||
return _wrap(dst, idx, f"DSMA_{length}", "trends_iir", offset)
|
||||
_check(_lib.qtl_dsma(_ptr(src), n, _ptr(dst), period, float(factor)))
|
||||
return _wrap(dst, idx, f"DSMA_{period}", "trends_iir", offset)
|
||||
|
||||
|
||||
def gdema(close: object, length: int = 10, vfactor: float = 1.0,
|
||||
def gdema(close: object, period: int = 10, vfactor: float = 1.0,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Generalized DEMA."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_gdema(_ptr(src), n, _ptr(dst), length, float(vfactor)))
|
||||
return _wrap(dst, idx, f"GDEMA_{length}", "trends_iir", offset)
|
||||
_check(_lib.qtl_gdema(_ptr(src), n, _ptr(dst), period, float(vfactor)))
|
||||
return _wrap(dst, idx, f"GDEMA_{period}", "trends_iir", offset)
|
||||
|
||||
|
||||
def coral(close: object, length: int = 10, friction: float = 0.4,
|
||||
def coral(close: object, period: int = 10, friction: float = 0.4,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""CORAL Trend."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_coral(_ptr(src), n, _ptr(dst), length, float(friction)))
|
||||
return _wrap(dst, idx, f"CORAL_{length}", "trends_iir", offset)
|
||||
_check(_lib.qtl_coral(_ptr(src), n, _ptr(dst), period, float(friction)))
|
||||
return _wrap(dst, idx, f"CORAL_{period}", "trends_iir", offset)
|
||||
|
||||
|
||||
def agc(close: object, alpha: float = 0.1, offset: int = 0, **kwargs) -> object:
|
||||
|
||||
@@ -252,57 +252,57 @@ def tr(high: object, low: object, close: object, offset: int = 0, **kwargs) -> o
|
||||
return _wrap(dst, idx, "TR", "volatility", int(offset))
|
||||
|
||||
|
||||
def bbw(close: object, length: int = 20, mult: float = 2.0,
|
||||
def bbw(close: object, period: int = 20, mult: float = 2.0,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Bollinger Band Width."""
|
||||
length = int(length); mult = float(mult); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); mult = float(mult); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_bbw(_ptr(src), n, _ptr(dst), length, mult))
|
||||
return _wrap(dst, idx, f"BBW_{length}", "volatility", offset)
|
||||
_check(_lib.qtl_bbw(_ptr(src), n, _ptr(dst), period, mult))
|
||||
return _wrap(dst, idx, f"BBW_{period}", "volatility", offset)
|
||||
|
||||
|
||||
def bbwn(close: object, length: int = 20, mult: float = 2.0,
|
||||
def bbwn(close: object, period: int = 20, mult: float = 2.0,
|
||||
lookback: int = 252, offset: int = 0, **kwargs) -> object:
|
||||
"""Bollinger Band Width Normalized."""
|
||||
length = int(length); mult = float(mult); lookback = int(lookback); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); mult = float(mult); lookback = int(lookback); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_bbwn(_ptr(src), n, _ptr(dst), length, mult, lookback))
|
||||
return _wrap(dst, idx, f"BBWN_{length}", "volatility", offset)
|
||||
_check(_lib.qtl_bbwn(_ptr(src), n, _ptr(dst), period, mult, lookback))
|
||||
return _wrap(dst, idx, f"BBWN_{period}", "volatility", offset)
|
||||
|
||||
|
||||
def bbwp(close: object, length: int = 20, mult: float = 2.0,
|
||||
def bbwp(close: object, period: int = 20, mult: float = 2.0,
|
||||
lookback: int = 252, offset: int = 0, **kwargs) -> object:
|
||||
"""Bollinger Band Width Percentile."""
|
||||
length = int(length); mult = float(mult); lookback = int(lookback); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); mult = float(mult); lookback = int(lookback); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_bbwp(_ptr(src), n, _ptr(dst), length, mult, lookback))
|
||||
return _wrap(dst, idx, f"BBWP_{length}", "volatility", offset)
|
||||
_check(_lib.qtl_bbwp(_ptr(src), n, _ptr(dst), period, mult, lookback))
|
||||
return _wrap(dst, idx, f"BBWP_{period}", "volatility", offset)
|
||||
|
||||
|
||||
def stddev(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
def stddev(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
"""Standard Deviation."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_stddev(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"STDDEV_{length}", "volatility", offset)
|
||||
_check(_lib.qtl_stddev(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"STDDEV_{period}", "volatility", offset)
|
||||
|
||||
|
||||
def variance(close: object, length: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
def variance(close: object, period: int = 20, offset: int = 0, **kwargs) -> object:
|
||||
"""Variance."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_variance(_ptr(src), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"VAR_{length}", "volatility", offset)
|
||||
_check(_lib.qtl_variance(_ptr(src), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"VAR_{period}", "volatility", offset)
|
||||
|
||||
|
||||
def etherm(high: object, low: object, length: int = 14,
|
||||
def etherm(high: object, low: object, period: int = 14,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Elder Thermometer."""
|
||||
length = int(length)
|
||||
period = int(kwargs.get("length", period))
|
||||
h, idx = _arr(high); l, _ = _arr(low)
|
||||
n = len(h); dst = _out(n)
|
||||
_check(_lib.qtl_etherm(_ptr(h), _ptr(l), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"ETHERM_{length}", "volatility", int(offset))
|
||||
_check(_lib.qtl_etherm(_ptr(h), _ptr(l), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"ETHERM_{period}", "volatility", int(offset))
|
||||
|
||||
|
||||
def ccv(close: object, short_period: int = 20, long_period: int = 1,
|
||||
@@ -314,13 +314,13 @@ def ccv(close: object, short_period: int = 20, long_period: int = 1,
|
||||
return _wrap(dst, idx, f"CCV_{short_period}", "volatility", offset)
|
||||
|
||||
|
||||
def cv(close: object, length: int = 20, min_vol: float = 0.2,
|
||||
def cv(close: object, period: int = 20, min_vol: float = 0.2,
|
||||
max_vol: float = 0.7, offset: int = 0, **kwargs) -> object:
|
||||
"""Coefficient of Variation."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_cv(_ptr(src), n, _ptr(dst), length, float(min_vol), float(max_vol)))
|
||||
return _wrap(dst, idx, f"CV_{length}", "volatility", offset)
|
||||
_check(_lib.qtl_cv(_ptr(src), n, _ptr(dst), period, float(min_vol), float(max_vol)))
|
||||
return _wrap(dst, idx, f"CV_{period}", "volatility", offset)
|
||||
|
||||
|
||||
def cvi(close: object, ema_period: int = 10, roc_period: int = 10,
|
||||
@@ -332,10 +332,10 @@ def cvi(close: object, ema_period: int = 10, roc_period: int = 10,
|
||||
return _wrap(dst, idx, f"CVI_{ema_period}", "volatility", offset)
|
||||
|
||||
|
||||
def ewma(close: object, length: int = 20, is_pop: int = 1,
|
||||
def ewma(close: object, period: int = 20, is_pop: int = 1,
|
||||
ann_factor: int = 252, offset: int = 0, **kwargs) -> object:
|
||||
"""Exponentially Weighted Moving Average (volatility)."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
src, idx = _arr(close); n = len(src); dst = _out(n)
|
||||
_check(_lib.qtl_ewma(_ptr(src), n, _ptr(dst), length, int(is_pop), int(ann_factor)))
|
||||
return _wrap(dst, idx, f"EWMA_{length}", "volatility", offset)
|
||||
_check(_lib.qtl_ewma(_ptr(src), n, _ptr(dst), period, int(is_pop), int(ann_factor)))
|
||||
return _wrap(dst, idx, f"EWMA_{period}", "volatility", offset)
|
||||
|
||||
+20
-20
@@ -216,54 +216,54 @@ def pvi(close: object, volume: object, offset: int = 0, **kwargs) -> object:
|
||||
return _wrap(dst, idx, "PVI", "volume", offset)
|
||||
|
||||
|
||||
def tvi(close: object, volume: object, length: int = 14,
|
||||
def tvi(close: object, volume: object, period: int = 14,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Trade Volume Index."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
c, idx = _arr(close); v, _ = _arr(volume)
|
||||
n = len(c); dst = _out(n)
|
||||
_check(_lib.qtl_tvi(_ptr(c), _ptr(v), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"TVI_{length}", "volume", offset)
|
||||
_check(_lib.qtl_tvi(_ptr(c), _ptr(v), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"TVI_{period}", "volume", offset)
|
||||
|
||||
|
||||
def pvd(close: object, volume: object, length: int = 14,
|
||||
def pvd(close: object, volume: object, period: int = 14,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Price Volume Divergence."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
c, idx = _arr(close); v, _ = _arr(volume)
|
||||
n = len(c); dst = _out(n)
|
||||
_check(_lib.qtl_pvd(_ptr(c), _ptr(v), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"PVD_{length}", "volume", offset)
|
||||
_check(_lib.qtl_pvd(_ptr(c), _ptr(v), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"PVD_{period}", "volume", offset)
|
||||
|
||||
|
||||
def vwma(close: object, volume: object, length: int = 20,
|
||||
def vwma(close: object, volume: object, period: int = 20,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Volume Weighted Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
c, idx = _arr(close); v, _ = _arr(volume)
|
||||
n = len(c); dst = _out(n)
|
||||
_check(_lib.qtl_vwma(_ptr(c), _ptr(v), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"VWMA_{length}", "volume", offset)
|
||||
_check(_lib.qtl_vwma(_ptr(c), _ptr(v), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"VWMA_{period}", "volume", offset)
|
||||
|
||||
|
||||
def evwma(close: object, volume: object, length: int = 20,
|
||||
def evwma(close: object, volume: object, period: int = 20,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Elastic Volume Weighted Moving Average."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
c, idx = _arr(close); v, _ = _arr(volume)
|
||||
n = len(c); dst = _out(n)
|
||||
_check(_lib.qtl_evwma(_ptr(c), _ptr(v), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"EVWMA_{length}", "volume", offset)
|
||||
_check(_lib.qtl_evwma(_ptr(c), _ptr(v), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"EVWMA_{period}", "volume", offset)
|
||||
|
||||
|
||||
def efi(close: object, volume: object, length: int = 13,
|
||||
def efi(close: object, volume: object, period: int = 13,
|
||||
offset: int = 0, **kwargs) -> object:
|
||||
"""Elder Force Index."""
|
||||
length = int(length); offset = int(offset)
|
||||
period = int(kwargs.get("length", period)); offset = int(offset)
|
||||
c, idx = _arr(close); v, _ = _arr(volume)
|
||||
n = len(c); dst = _out(n)
|
||||
_check(_lib.qtl_efi(_ptr(c), _ptr(v), n, _ptr(dst), length))
|
||||
return _wrap(dst, idx, f"EFI_{length}", "volume", offset)
|
||||
_check(_lib.qtl_efi(_ptr(c), _ptr(v), n, _ptr(dst), period))
|
||||
return _wrap(dst, idx, f"EFI_{period}", "volume", offset)
|
||||
|
||||
|
||||
def aobv(close: object, volume: object, offset: int = 0, **kwargs) -> object:
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
"""Round-trip tests for PyArrow Array / ChunkedArray input → output.
|
||||
|
||||
Requires: ``pip install quantalib[pyarrow]``
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
pa = pytest.importorskip("pyarrow", minversion="14.0")
|
||||
|
||||
import quantalib as qtl # noqa: E402
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
# ---------------------------------------------------------------------------
|
||||
@pytest.fixture()
|
||||
def close_array() -> pa.Array:
|
||||
"""100-bar random close prices as a PyArrow float64 Array."""
|
||||
rng = np.random.default_rng(42)
|
||||
return pa.array(rng.random(100) * 100 + 50, type=pa.float64())
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def close_chunked() -> pa.ChunkedArray:
|
||||
"""100-bar random close prices as a PyArrow ChunkedArray (2 chunks)."""
|
||||
rng = np.random.default_rng(42)
|
||||
data = rng.random(100) * 100 + 50
|
||||
chunk1 = pa.array(data[:50], type=pa.float64())
|
||||
chunk2 = pa.array(data[50:], type=pa.float64())
|
||||
return pa.chunked_array([chunk1, chunk2])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Single-output: pa.Array in → pa.Array out
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestSingleOutput:
|
||||
def test_sma_returns_arrow_array(self, close_array: pa.Array) -> None:
|
||||
result = qtl.sma(close_array, length=14)
|
||||
assert isinstance(result, pa.Array)
|
||||
assert len(result) == len(close_array)
|
||||
assert result.type == pa.float64()
|
||||
|
||||
def test_ema_returns_arrow_array(self, close_array: pa.Array) -> None:
|
||||
result = qtl.ema(close_array, length=14)
|
||||
assert isinstance(result, pa.Array)
|
||||
assert len(result) == len(close_array)
|
||||
|
||||
def test_rsi_returns_arrow_array(self, close_array: pa.Array) -> None:
|
||||
result = qtl.rsi(close_array, length=14)
|
||||
assert isinstance(result, pa.Array)
|
||||
assert len(result) == len(close_array)
|
||||
|
||||
def test_stddev_returns_arrow_array(self, close_array: pa.Array) -> None:
|
||||
result = qtl.stddev(close_array, length=14)
|
||||
assert isinstance(result, pa.Array)
|
||||
assert len(result) == len(close_array)
|
||||
|
||||
def test_mom_returns_arrow_array(self, close_array: pa.Array) -> None:
|
||||
result = qtl.mom(close_array, length=10)
|
||||
assert isinstance(result, pa.Array)
|
||||
assert len(result) == len(close_array)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ChunkedArray input
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestChunkedArray:
|
||||
def test_chunked_array_accepted(self, close_chunked: pa.ChunkedArray) -> None:
|
||||
result = qtl.sma(close_chunked, length=14)
|
||||
assert isinstance(result, pa.Array)
|
||||
assert len(result) == len(close_chunked)
|
||||
|
||||
def test_chunked_values_match_flat(self, close_chunked: pa.ChunkedArray) -> None:
|
||||
flat = close_chunked.combine_chunks()
|
||||
result_chunked = qtl.sma(close_chunked, length=14)
|
||||
result_flat = qtl.sma(flat, length=14)
|
||||
np.testing.assert_allclose(
|
||||
result_chunked.to_numpy(zero_copy_only=False),
|
||||
result_flat.to_numpy(zero_copy_only=False),
|
||||
rtol=1e-12,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Multi-output: pa.Array in → dict[str, pa.Array] out
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestMultiOutput:
|
||||
def test_bbands_returns_dict_of_arrays(self, close_array: pa.Array) -> None:
|
||||
result = qtl.bbands(close_array, length=20, std=2.0)
|
||||
assert isinstance(result, dict)
|
||||
assert all(isinstance(v, pa.Array) for v in result.values())
|
||||
assert len(result) == 3 # upper, mid, lower
|
||||
for v in result.values():
|
||||
assert len(v) == len(close_array)
|
||||
assert v.type == pa.float64()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Numerical equivalence: Arrow vs numpy should produce identical values
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestNumericalEquivalence:
|
||||
def test_sma_values_match_numpy(self, close_array: pa.Array) -> None:
|
||||
np_arr = close_array.to_numpy(zero_copy_only=False)
|
||||
result_pa = qtl.sma(close_array, length=14)
|
||||
result_np = qtl.sma(np_arr, length=14)
|
||||
np.testing.assert_allclose(
|
||||
result_pa.to_numpy(zero_copy_only=False),
|
||||
result_np,
|
||||
rtol=1e-12,
|
||||
)
|
||||
|
||||
def test_rsi_values_match_numpy(self, close_array: pa.Array) -> None:
|
||||
np_arr = close_array.to_numpy(zero_copy_only=False)
|
||||
result_pa = qtl.rsi(close_array, length=14)
|
||||
result_np = qtl.rsi(np_arr, length=14)
|
||||
np.testing.assert_allclose(
|
||||
result_pa.to_numpy(zero_copy_only=False),
|
||||
result_np,
|
||||
rtol=1e-12,
|
||||
equal_nan=True,
|
||||
)
|
||||
|
||||
def test_ema_values_match_numpy(self, close_array: pa.Array) -> None:
|
||||
np_arr = close_array.to_numpy(zero_copy_only=False)
|
||||
result_pa = qtl.ema(close_array, length=14)
|
||||
result_np = qtl.ema(np_arr, length=14)
|
||||
np.testing.assert_allclose(
|
||||
result_pa.to_numpy(zero_copy_only=False),
|
||||
result_np,
|
||||
rtol=1e-12,
|
||||
equal_nan=True,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Type coercion
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestTypeCoercion:
|
||||
def test_int32_array_coerced(self) -> None:
|
||||
arr = pa.array(list(range(1, 101)), type=pa.int32())
|
||||
result = qtl.sma(arr, length=5)
|
||||
assert isinstance(result, pa.Array)
|
||||
assert result.type == pa.float64()
|
||||
assert len(result) == 100
|
||||
|
||||
def test_float32_array_coerced(self) -> None:
|
||||
rng = np.random.default_rng(42)
|
||||
arr = pa.array(rng.random(100).astype(np.float32), type=pa.float32())
|
||||
result = qtl.sma(arr, length=5)
|
||||
assert isinstance(result, pa.Array)
|
||||
assert result.type == pa.float64()
|
||||
assert len(result) == 100
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Edge cases
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestEdgeCases:
|
||||
def test_empty_array_raises(self) -> None:
|
||||
empty = pa.array([], type=pa.float64())
|
||||
with pytest.raises(ValueError, match="must not be empty"):
|
||||
qtl.sma(empty, length=14)
|
||||
@@ -0,0 +1,142 @@
|
||||
"""Round-trip tests for Polars Series / DataFrame input → output.
|
||||
|
||||
Requires: ``pip install quantalib[polars]``
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
pl = pytest.importorskip("polars", minversion="0.20")
|
||||
|
||||
import quantalib as qtl # noqa: E402
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
# ---------------------------------------------------------------------------
|
||||
@pytest.fixture()
|
||||
def close_series() -> pl.Series:
|
||||
"""100-bar random close prices as a Polars Series."""
|
||||
rng = np.random.default_rng(42)
|
||||
return pl.Series(name="close", values=rng.random(100) * 100 + 50)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def ohlcv_df() -> pl.DataFrame:
|
||||
"""100-bar OHLCV DataFrame."""
|
||||
rng = np.random.default_rng(42)
|
||||
c = rng.random(100) * 100 + 50
|
||||
return pl.DataFrame({
|
||||
"open": c + rng.uniform(-2, 2, 100),
|
||||
"high": c + rng.uniform(0, 5, 100),
|
||||
"low": c - rng.uniform(0, 5, 100),
|
||||
"close": c,
|
||||
"volume": rng.uniform(1e4, 1e6, 100),
|
||||
})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Single-output: Polars Series in → Polars Series out
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestSingleOutput:
|
||||
def test_sma_returns_polars_series(self, close_series: pl.Series) -> None:
|
||||
result = qtl.sma(close_series, length=14)
|
||||
assert isinstance(result, pl.Series)
|
||||
assert len(result) == len(close_series)
|
||||
|
||||
def test_ema_returns_polars_series(self, close_series: pl.Series) -> None:
|
||||
result = qtl.ema(close_series, length=14)
|
||||
assert isinstance(result, pl.Series)
|
||||
assert len(result) == len(close_series)
|
||||
|
||||
def test_rsi_returns_polars_series(self, close_series: pl.Series) -> None:
|
||||
result = qtl.rsi(close_series, length=14)
|
||||
assert isinstance(result, pl.Series)
|
||||
assert len(result) == len(close_series)
|
||||
|
||||
def test_series_name_follows_convention(self, close_series: pl.Series) -> None:
|
||||
result = qtl.sma(close_series, length=20)
|
||||
assert isinstance(result, pl.Series)
|
||||
assert result.name == "SMA_20"
|
||||
|
||||
def test_stddev_returns_polars_series(self, close_series: pl.Series) -> None:
|
||||
result = qtl.stddev(close_series, length=14)
|
||||
assert isinstance(result, pl.Series)
|
||||
assert len(result) == len(close_series)
|
||||
|
||||
def test_mom_returns_polars_series(self, close_series: pl.Series) -> None:
|
||||
result = qtl.mom(close_series, length=10)
|
||||
assert isinstance(result, pl.Series)
|
||||
assert len(result) == len(close_series)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DataFrame input: first column extracted
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestDataFrameInput:
|
||||
def test_dataframe_first_col_used(self, ohlcv_df: pl.DataFrame) -> None:
|
||||
close_col = ohlcv_df.select("close")
|
||||
result = qtl.sma(close_col, length=14)
|
||||
assert isinstance(result, pl.Series)
|
||||
assert len(result) == len(ohlcv_df)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Multi-output: Polars Series in → Polars DataFrame out
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestMultiOutput:
|
||||
def test_bbands_returns_polars_dataframe(self, close_series: pl.Series) -> None:
|
||||
result = qtl.bbands(close_series, length=20, std=2.0)
|
||||
assert isinstance(result, pl.DataFrame)
|
||||
assert result.shape[0] == len(close_series)
|
||||
assert result.shape[1] == 3 # upper, mid, lower
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Numerical equivalence: Polars vs numpy should produce identical values
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestNumericalEquivalence:
|
||||
def test_sma_values_match_numpy(self, close_series: pl.Series) -> None:
|
||||
np_arr = close_series.to_numpy()
|
||||
result_pl = qtl.sma(close_series, length=14)
|
||||
result_np = qtl.sma(np_arr, length=14)
|
||||
np.testing.assert_allclose(
|
||||
result_pl.to_numpy(), result_np, rtol=1e-12
|
||||
)
|
||||
|
||||
def test_rsi_values_match_numpy(self, close_series: pl.Series) -> None:
|
||||
np_arr = close_series.to_numpy()
|
||||
result_pl = qtl.rsi(close_series, length=14)
|
||||
result_np = qtl.rsi(np_arr, length=14)
|
||||
np.testing.assert_allclose(
|
||||
result_pl.to_numpy(), result_np, rtol=1e-12, equal_nan=True
|
||||
)
|
||||
|
||||
def test_ema_values_match_numpy(self, close_series: pl.Series) -> None:
|
||||
np_arr = close_series.to_numpy()
|
||||
result_pl = qtl.ema(close_series, length=14)
|
||||
result_np = qtl.ema(np_arr, length=14)
|
||||
np.testing.assert_allclose(
|
||||
result_pl.to_numpy(), result_np, rtol=1e-12, equal_nan=True
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Edge cases
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestEdgeCases:
|
||||
def test_none_input_raises(self) -> None:
|
||||
with pytest.raises(ValueError, match="must not be None"):
|
||||
qtl.sma(None, length=14)
|
||||
|
||||
def test_empty_series_raises(self) -> None:
|
||||
empty = pl.Series(name="empty", values=[], dtype=pl.Float64)
|
||||
with pytest.raises(ValueError, match="must not be empty"):
|
||||
qtl.sma(empty, length=14)
|
||||
|
||||
def test_int_series_coerced_to_float(self) -> None:
|
||||
int_series = pl.Series(name="ints", values=list(range(1, 101)))
|
||||
result = qtl.sma(int_series, length=5)
|
||||
assert isinstance(result, pl.Series)
|
||||
assert len(result) == 100
|
||||
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Transform new-style functions from `length` → `period` (primary) with `length` as kwargs alias.
|
||||
|
||||
Target: all category module .py files in python/quantalib/ that have functions
|
||||
using bare `length` as a parameter name.
|
||||
|
||||
Rules:
|
||||
1. `def foo(close, length: int = X, ...)` → `def foo(close, period: int = X, ...)`
|
||||
2. `length = int(length)` (standalone assignment) → `period = int(kwargs.get("length", period))`
|
||||
3. Any remaining standalone `length` in the function body → `period`
|
||||
4. Compound names like hpLength, ssLength, minLength, etc. are NOT touched.
|
||||
5. `lengths` (plural) is NOT touched.
|
||||
6. `default_length` in _helpers.py pattern helpers is NOT touched (separate ticket).
|
||||
|
||||
Also fixes _helpers.py pattern helpers (_pa, _pf, _pg2, _ph) the same way.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
QUANTALIB = Path(__file__).resolve().parent.parent / "quantalib"
|
||||
|
||||
# Files to process (category modules + _helpers.py)
|
||||
TARGET_FILES = [
|
||||
"channels.py",
|
||||
"core.py",
|
||||
"cycles.py",
|
||||
"dynamics.py",
|
||||
"errors.py",
|
||||
"filters.py",
|
||||
"momentum.py",
|
||||
"numerics.py",
|
||||
"oscillators.py",
|
||||
"reversals.py",
|
||||
"statistics.py",
|
||||
"trends_fir.py",
|
||||
"trends_iir.py",
|
||||
"volatility.py",
|
||||
"volume.py",
|
||||
"_helpers.py",
|
||||
]
|
||||
|
||||
# Pattern: standalone `length` as a word — NOT preceded or followed by
|
||||
# alphanumeric or underscore (i.e., not part of hpLength, minLength, etc.)
|
||||
# Also NOT `lengths` (plural).
|
||||
STANDALONE_LENGTH = re.compile(r'(?<![a-zA-Z0-9_])length(?![a-zA-Z0-9_])')
|
||||
|
||||
# Pattern for the signature line: `length: int = <default>`
|
||||
SIG_PATTERN = re.compile(r'(?<![a-zA-Z0-9_])length(\s*:\s*int\s*=\s*\d+)')
|
||||
|
||||
# Pattern for the assignment line: `length = int(length)` possibly with `;`
|
||||
ASSIGN_PATTERN = re.compile(
|
||||
r'^(\s*)length\s*=\s*int\(length\)\s*;?\s*'
|
||||
)
|
||||
|
||||
# Pattern for assignment in _helpers.py: `length = int(length) if length is not None else default_length`
|
||||
HELPERS_ASSIGN = re.compile(
|
||||
r'^(\s*)length\s*=\s*int\(length\)\s+if\s+length\s+is\s+not\s+None\s+else\s+default_length'
|
||||
)
|
||||
|
||||
|
||||
def has_standalone_length_param(line: str) -> bool:
|
||||
"""Check if a def line has standalone `length` as a parameter."""
|
||||
if not line.lstrip().startswith("def "):
|
||||
return False
|
||||
# Must have `length` as standalone word (not part of compound)
|
||||
return bool(STANDALONE_LENGTH.search(line))
|
||||
|
||||
|
||||
def transform_file(filepath: Path) -> tuple[int, int]:
|
||||
"""Transform a single file. Returns (functions_changed, lines_changed)."""
|
||||
text = filepath.read_text(encoding="utf-8")
|
||||
lines = text.split("\n")
|
||||
new_lines: list[str] = []
|
||||
in_target_func = False
|
||||
func_indent = ""
|
||||
funcs_changed = 0
|
||||
lines_changed = 0
|
||||
assignment_done = False
|
||||
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
stripped = line.lstrip()
|
||||
|
||||
# Detect start of a function with `length` parameter
|
||||
if stripped.startswith("def ") and has_standalone_length_param(line):
|
||||
in_target_func = True
|
||||
func_indent = line[: len(line) - len(stripped)]
|
||||
assignment_done = False
|
||||
funcs_changed += 1
|
||||
|
||||
# Handle multi-line def (continuation lines)
|
||||
full_def = line
|
||||
while i < len(lines) - 1 and line.rstrip().endswith(","):
|
||||
# Replace standalone length in this line
|
||||
new_line = STANDALONE_LENGTH.sub("period", line)
|
||||
if new_line != line:
|
||||
lines_changed += 1
|
||||
new_lines.append(new_line)
|
||||
i += 1
|
||||
line = lines[i]
|
||||
|
||||
# Last line of def (or single-line def)
|
||||
new_line = STANDALONE_LENGTH.sub("period", line)
|
||||
if new_line != line:
|
||||
lines_changed += 1
|
||||
new_lines.append(new_line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Inside a target function?
|
||||
if in_target_func:
|
||||
# Detect end of function: non-empty line at or less than func indent,
|
||||
# or a new def/class
|
||||
if stripped and not line.startswith(func_indent + " ") and not line.startswith(func_indent + "\t"):
|
||||
if not stripped.startswith('"""') and not stripped.startswith("'"):
|
||||
# Could be the docstring continuation; check differently
|
||||
if stripped.startswith("def ") or stripped.startswith("class ") or (len(line) - len(stripped) <= len(func_indent) and stripped and not stripped.startswith('#')):
|
||||
in_target_func = False
|
||||
|
||||
if in_target_func:
|
||||
# Check for _helpers.py style assignment:
|
||||
# `length = int(length) if length is not None else default_length`
|
||||
m_helpers = HELPERS_ASSIGN.match(line)
|
||||
if m_helpers and not assignment_done:
|
||||
indent = m_helpers.group(1)
|
||||
new_line = f"{indent}period = int(kwargs.get(\"length\", period)) if period is not None else default_length"
|
||||
new_lines.append(new_line)
|
||||
lines_changed += 1
|
||||
assignment_done = True
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check for standard assignment: `length = int(length);`
|
||||
m_assign = ASSIGN_PATTERN.match(line)
|
||||
if m_assign and not assignment_done:
|
||||
indent = m_assign.group(1)
|
||||
# Preserve anything after the semicolon on the same line
|
||||
rest_of_line = ASSIGN_PATTERN.sub("", line)
|
||||
# Check if there's more after (e.g., "; offset = int(offset)")
|
||||
remaining = line[m_assign.end():]
|
||||
new_assignment = f'{indent}period = int(kwargs.get("length", period))'
|
||||
if remaining.strip():
|
||||
new_assignment += "; " + remaining.strip()
|
||||
new_lines.append(new_assignment)
|
||||
lines_changed += 1
|
||||
assignment_done = True
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Replace any remaining standalone `length` references
|
||||
new_line = STANDALONE_LENGTH.sub("period", line)
|
||||
if new_line != line:
|
||||
lines_changed += 1
|
||||
new_lines.append(new_line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
new_lines.append(line)
|
||||
i += 1
|
||||
|
||||
new_text = "\n".join(new_lines)
|
||||
if new_text != text:
|
||||
filepath.write_text(new_text, encoding="utf-8")
|
||||
|
||||
return funcs_changed, lines_changed
|
||||
|
||||
|
||||
def main() -> None:
|
||||
total_funcs = 0
|
||||
total_lines = 0
|
||||
|
||||
for fname in TARGET_FILES:
|
||||
fpath = QUANTALIB / fname
|
||||
if not fpath.exists():
|
||||
print(f" SKIP {fname} (not found)")
|
||||
continue
|
||||
|
||||
funcs, lines = transform_file(fpath)
|
||||
if funcs > 0:
|
||||
print(f" {fname}: {funcs} functions, {lines} lines changed")
|
||||
total_funcs += funcs
|
||||
total_lines += lines
|
||||
else:
|
||||
print(f" {fname}: no changes")
|
||||
|
||||
print(f"\nTotal: {total_funcs} functions, {total_lines} lines changed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user