mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-12 23:58:04 +00:00
Refactor documentation to remove "Zero-Allocation Design" sections across various trend indicators and implement a PowerShell script for automated cleanup
- Updated mathematical foundations and performance profiles where necessary to maintain clarity and coherence.
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
- **Trends**
|
||||
- [Overview](../lib/trends/_index.md)
|
||||
- [ALMA - Arnaud Legoux MA](../lib/trends/alma/Alma.md)
|
||||
- [BESSEL - Bessel Filter](../lib/trends/bessel/Bessel.md)
|
||||
- [CONV - Convolution](../lib/trends/conv/Conv.md)
|
||||
- [DEMA - Double Exponential MA](../lib/trends/dema/Dema.md)
|
||||
- [DWMA - Double Weighted MA](../lib/trends/dwma/Dwma.md)
|
||||
|
||||
@@ -8,15 +8,15 @@ Three design decisions define QuanTAlib's performance characteristics:
|
||||
|
||||
### 1. Structure of Arrays (SoA) Memory Layout
|
||||
|
||||
We store timestamps and values in separate contiguous arrays rather than interleaving them in objects. This seemingly minor change enables direct SIMD vectorization—CPU processes eight values in a single instruction instead of one at a time. The performance difference is measurable: averaging 10,000 values takes 2.4μs with SIMD versus 18.7μs with scalar operations. That's an 8x improvement just from rearranging memory.
|
||||
Timestamps and values are stored in separate contiguous arrays rather than interleaved in objects. This seemingly minor change enables direct SIMD vectorization—CPU processes eight values in a single instruction instead of one at a time. The performance difference is measurable: averaging 10,000 values takes 2.4μs with SIMD versus 18.7μs with scalar operations. That's an 8x improvement just from rearranging memory.
|
||||
|
||||
### 2. O(1) Streaming Algorithms
|
||||
|
||||
We maintain constant computational complexity per incoming data point regardless of lookback period. A 14-period RSI and a 200-period RSI both process new bars in 0.4μs. Traditional batch recalculation approaches scale linearly with period length, introducing variable latency that makes real-time processing unpredictable. QuanTAlib accepts higher memory overhead (40-60 bytes per indicator instance) to guarantee predictable timing when processing hundreds of symbols simultaneously.
|
||||
Constant computational complexity is maintained per incoming data point regardless of lookback period. A 14-period RSI and a 200-period RSI both process new bars in 0.4μs. Traditional batch recalculation approaches scale linearly with period length, introducing variable latency that makes real-time processing unpredictable. QuanTAlib accepts higher memory overhead (40-60 bytes per indicator instance) to guarantee predictable timing when processing hundreds of symbols simultaneously.
|
||||
|
||||
### 3. Explicit Initialization Handling
|
||||
|
||||
We return meaningful values from the first bar while exposing confidence through the `IsHot` property. A 14-period SMA calculates results starting at bar 1 using whatever data is available—the math to calculate averages works with limited history, just not at full precision for a period of 14. Other libraries either hide these early values (returning NaN or null) or output numbers without indicating their veracity. QuanTAlib returns usable values immediately and sets `IsHot = true` when the indicator has accumulated enough data to guarantee correctness of results.
|
||||
Meaningful values are returned from the first bar while confidence is exposed through the `IsHot` property. A 14-period SMA calculates results starting at bar 1 using whatever data is available—the math to calculate averages works with limited history, just not at full precision for a period of 14. Other libraries either hide these early values (returning NaN or null) or output numbers without indicating their veracity. QuanTAlib returns usable values immediately and sets `IsHot = true` when the indicator has accumulated enough data to guarantee correctness of results.
|
||||
|
||||
## Four Operating Modes
|
||||
|
||||
@@ -67,7 +67,7 @@ QuanTAlib leverages .NET's `System.Runtime.Intrinsics` to access hardware-specif
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
1. **Correctness First**: We validate against original research papers and established libraries.
|
||||
2. **Performance by Default**: We choose algorithms and data structures that are naturally fast.
|
||||
1. **Correctness First**: Validation is performed against original research papers and established libraries.
|
||||
2. **Performance by Default**: Algorithms and data structures that are naturally fast are chosen.
|
||||
3. **No Hidden Allocations**: Hot paths are allocation-free to prevent GC pauses.
|
||||
4. **Transparency**: We expose the internal state (like `IsHot`) so you know exactly what the indicator is doing.
|
||||
4. **Transparency**: The internal state (like `IsHot`) is exposed so you know exactly what the indicator is doing.
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
# Benchmarks
|
||||
|
||||
Performance claims require measurement. We benchmark QuanTAlib against established libraries: TA-Lib and Tulip (industry-standard C libraries accessed via P/Invoke), Skender.Stock.Indicators and Ooples.FinancialIndicators (popular .NET implementations).
|
||||
Performance claims require measurement. QuanTAlib is benchmarked against established libraries: TA-Lib and Tulip (industry-standard C libraries accessed via P/Invoke), Skender.Stock.Indicators and Ooples.FinancialIndicators (popular .NET implementations).
|
||||
|
||||
## Test Environment
|
||||
|
||||
@@ -15,7 +15,7 @@ These results represent what current-generation server CPUs achieve in productio
|
||||
|
||||
### Simple Moving Average (SMA)
|
||||
|
||||
QuanTAlib's Span mode calculates 500,000 SMA values in 318 microseconds with zero memory allocations. That's 0.64 nanoseconds per value. For context, a single L1 cache access takes approximately 1 nanosecond on modern CPUs — we're calculating moving averages faster than fetching data from the nearest cache level.
|
||||
QuanTAlib's Span mode calculates 500,000 SMA values in 318 microseconds with zero memory allocations. That's 0.64 nanoseconds per value. For context, a single L1 cache access takes approximately 1 nanosecond on modern CPUs, so moving averages are being calculated faster than data can be fetched from the nearest cache level.
|
||||
|
||||
| Library | Mean Time | Allocations | Relative Speed |
|
||||
| ------- | --------- | ----------- | -------------- |
|
||||
@@ -76,7 +76,7 @@ Even QuanTAlib's slowest mode (Eventing with complete event infrastructure and 1
|
||||
|
||||
## Methodology
|
||||
|
||||
We use [BenchmarkDotNet](https://benchmarkdotnet.org/) for all performance testing. This ensures:
|
||||
[BenchmarkDotNet](https://benchmarkdotnet.org/) is used for all performance testing. This ensures:
|
||||
|
||||
- Warmup iterations to stabilize JIT compilation
|
||||
- Statistical analysis of results (mean, standard deviation)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Glossary
|
||||
|
||||
Short reference for core QuanTAlib terminology and types.
|
||||
|
||||
| Term | Definition |
|
||||
| :--- | :--------- |
|
||||
| **Accuracy** | Measure of how well an indicator preserves the important structure of the original price series while still filtering out noise. It captures closeness to the original data: more *smoothness* removes zigzagging noise but will also reduce *accuracy* if it starts erasing meaningful swings and cycles. |
|
||||
| **AVX2** | **256-bit SIMD** extension for x86-64 (AMD64) CPUs from Intel and AMD. Used to accelerate vectorized span-based calculations. |
|
||||
| **AVX-512** | **512-bit SIMD** extension available on many x86-64 (AMD64) desktop and server CPUs released since around **2015**. Doubles vector width over AVX2 and adds masking and extra math operations. |
|
||||
| **Array of Structs (AoS)** | Memory layout where each element is a full record, for example `struct Bar { double Open, High, Low, Close; }` stored as `Bar[]`. Simple to model but cache-inefficient for single-field operations and harder to **SIMD-vectorize** than structure of arrays (SoA). Avoided in QuanTAlib. |
|
||||
| **Batch Mode** | Mode where indicators operate on `TSeries` objects instead of raw spans. Handles timestamps, resizing, and time alignment while still using span-based implementations internally. Best for historical analysis where you want time-aware series without managing arrays directly. |
|
||||
| **Eventing Mode** | **Reactive** usage pattern where indicators implement `ITValuePublisher` and raise events as values change or warmup completes (`IsHot`). Used to build chains of indicators and trading logic that react to state changes instead of polling for values. |
|
||||
| **FIR filter** | **Finite impulse response** filter. Output depends on a finite window of past inputs with no feedback from the past. Always stable. Typical examples in TA are a *simple moving average*, *weighted moving average* or *hull moving average*. |
|
||||
| **Hot path** | Code that executes for every incoming tick or bar during live trading. In QuanTAlib, hot paths (such as `Update` and span-based `Calculate` loops) must *avoid heap allocations*, run in *constant time* $O(1)$ where possible, and be *SIMD-optimized* when the algorithm allows. |
|
||||
| **IIR filter** | **Infinite impulse response** filter. Output depends on both current input and past outputs via feedback. More responsive for a given period but requires care for numerical stability. Typical examples in TA are an *exponential moving average*, *kaufman adaptive moving average* and *variable index moving average*. |
|
||||
| **isHot** | Boolean property on indicators that becomes **`true`** after sufficient data has been processed (for example once the internal period / warmup length is reached). Before `isHot` is `true`, output values are considered **not fully reliable**. |
|
||||
| **`isNew` flag** | Boolean parameter on `Update(TValue input, bool isNew = true)` that controls bar-correction behavior: `isNew = true` advances state to the next bar; `isNew = false` updates the most recent bar in-place (intra-bar correction) for streaming feeds where the latest bar can change before it closes. |
|
||||
| **NEON** | **128-bit SIMD** architecture for ARM CPUs (including many mobile devices and Apple Silicon). .NET exposes NEON via `System.Runtime.Intrinsics.Arm` so the same span-based indicator code can vectorize on ARM hardware. |
|
||||
| **O(1)** | **Constant-time** complexity. Work per update does not grow with the length of the time series or lookback window. Target complexity for streaming `Update` methods in QuanTAlib whenever mathematically possible. |
|
||||
| **O(n)** | **Linear-time** complexity in the number of input points $n$. Typical for batch calculations that walk the series once. Acceptable for one-off batch work, not for hot-path streaming updates. |
|
||||
| **Overshoot** | Degree to which an indicator *overreacts* around turning points, swinging past the underlying price or signal before settling. High overshoot produces dramatic but potentially misleading signals, especially near reversals. |
|
||||
| **Period** | Configuration parameter that describes how many bars or samples an indicator considers for its calculation. Relevant for FIR, not so much for IIR indicators. |
|
||||
| **RingBuffer** | **Fixed-size circular buffer** used for sliding-window calculations. New values overwrite the oldest entries once the buffer is full, keeping time and memory usage effectively constant regardless of history length. |
|
||||
| **SIMD** | *Single Instruction, Multiple Data*. Hardware feature allowing the CPU to apply one instruction to many values at once. QuanTAlib uses .NET SIMD support (for example **AVX2**, **AVX-512**, or **NEON** when available) to accelerate span-based calculations. |
|
||||
| **Smoothness** | Measure of how visually and numerically *calm* an indicator's line is. More *smoothness* filters random noise but usually increases lag; less smoothness responds faster but exposes more short-term fluctuation. |
|
||||
| **Span Mode** | Lowest-level, **zero-allocation** mode operating directly on `Span<double>` / `ReadOnlySpan<double>`. Designed for backtesting and research workloads that process large arrays with maximum SIMD acceleration and no object overhead. |
|
||||
| **Streaming Mode** | Real-time update mode using `Update(TValue input, bool isNew = true)`. Maintains internal state between calls and distinguishes between **new bars** and **intra-bar corrections** via the `isNew` flag. Intended for live feeds and tick-by-tick data. |
|
||||
| **Structure of Arrays (SoA)** | Memory layout where each field of a logical record is stored in its own contiguous buffer (for example, prices and timestamps in separate arrays). Improves cache locality and enables **vectorized operations** across large segments of a single field. |
|
||||
| **TBar** | Struct representing an **OHLCV bar**: time, open, high, low, close, and volume. Used when indicators need full bar context instead of a single price. |
|
||||
| **TSeries** | Primary time series container. Uses *structure-of-arrays* layout: timestamps and values stored in separate buffers and exposed as `ReadOnlySpan<T>` for **SIMD-friendly access**. Represents a sequence of scalar values over time. |
|
||||
| **Throughput** | Number of ticks or bars an indicator can **process per second** on a given machine. Driven by per-update complexity (target $O(1)$), SIMD utilization, and zero-allocation hot paths. |
|
||||
| **Timeliness** | Measures how much an indicator **lags** behind the underlying price series. Excessive lag pushes entries and exits late and can cut profits. For classic moving averages (SMA, WMA, EMA) more *smoothness* almost always means more lag; designs like DEMA, HMA or JMA aim to stay close to price while still filtering noise. |
|
||||
| **TValue** | Struct pairing a `DateTime` with a single `double` value. Standard input and output type for indicators in **streaming mode**. |
|
||||
| **Zero-allocation design** | Design rule that hot paths must **not allocate** on the managed heap. Achieved by using `Span<T>` or `ReadOnlySpan<T>` for batch APIs, preferring **`stackalloc`** for small temporaries, and reusing internal state instead of creating new objects on each update. |
|
||||
@@ -66,6 +66,7 @@ These measure the spread of data points around the mean.
|
||||
### Trends
|
||||
|
||||
- [**ALMA**](../lib/trends/alma/Alma.md) - Arnaud Legoux MA
|
||||
- [**BESSEL**](../lib/trends/bessel/Bessel.md) - Bessel Filter
|
||||
- [**CONV**](../lib/trends/conv/Conv.md) - Convolution MA
|
||||
- [**DEMA**](../lib/trends/dema/Dema.md) - Double Exponential MA
|
||||
- [**DWMA**](../lib/trends/dwma/Dwma.md) - Double Weighted MA
|
||||
|
||||
+11
-11
@@ -6,11 +6,11 @@ QuanTAlib is designed to be platform-agnostic. It can be integrated into any .NE
|
||||
|
||||
Quantower allows custom indicators via C#.
|
||||
|
||||
1. **Reference the DLL**:
|
||||
1. **Reference the DLL**:
|
||||
- Build QuanTAlib or download the NuGet package.
|
||||
- In your Quantower indicator project, add a reference to `QuanTAlib.dll`.
|
||||
|
||||
2. **Wrapper Class**:
|
||||
2. **Wrapper Class**:
|
||||
- Create a class that inherits from `Indicator`.
|
||||
- Instantiate the QuanTAlib indicator in `OnInit`.
|
||||
- Call `Update` in `OnUpdate`.
|
||||
@@ -38,7 +38,7 @@ public class MySmaIndicator : Indicator
|
||||
double price = ClosePrice;
|
||||
|
||||
// Update QuanTAlib
|
||||
// Note: Quantower handles bar updates, so we check if it's a new bar or update
|
||||
// Note: Quantower handles bar updates, so a check is performed to determine whether this is a new bar or an update
|
||||
bool isNew = args.Reason == UpdateReason.NewBar;
|
||||
var result = _sma.Update(new TValue(DateTime.UtcNow, price), isNew);
|
||||
|
||||
@@ -52,8 +52,8 @@ public class MySmaIndicator : Indicator
|
||||
|
||||
NinjaTrader 8 uses .NET Framework 4.8, but can interop with .NET Standard libraries.
|
||||
|
||||
1. **Copy DLL**: Place `QuanTAlib.dll` in `Documents\NinjaTrader 8\bin\Custom`.
|
||||
2. **Add Reference**: In NinjaScript Editor, right-click > References > Add `QuanTAlib.dll`.
|
||||
1. **Copy DLL**: Place `QuanTAlib.dll` in `Documents\NinjaTrader 8\bin\Custom`.
|
||||
2. **Add Reference**: In NinjaScript Editor, right-click > References > Add `QuanTAlib.dll`.
|
||||
|
||||
```csharp
|
||||
protected override void OnStateChange()
|
||||
@@ -85,8 +85,8 @@ protected override void OnBarUpdate()
|
||||
|
||||
LEAN supports custom libraries.
|
||||
|
||||
1. **NuGet**: Add `QuanTAlib` to your `config.json` or project file.
|
||||
2. **Usage**: Use inside `OnData`.
|
||||
1. **NuGet**: Add `QuanTAlib` to your `config.json` or project file.
|
||||
2. **Usage**: Use inside `OnData`.
|
||||
|
||||
```csharp
|
||||
public class MyAlgorithm : QCAlgorithm
|
||||
@@ -120,7 +120,7 @@ For proprietary trading engines, the **Streaming Mode** is usually the best fit.
|
||||
|
||||
### Key Considerations
|
||||
|
||||
1. **Time Handling**: QuanTAlib uses `DateTime.UtcNow`. Ensure your platform provides UTC timestamps or convert them.
|
||||
2. **Double Precision**: All calculations use `double`. If your platform uses `decimal`, cast to `double` for input and back to `decimal` for output.
|
||||
3. **State Management**: Persist the indicator instance for the lifetime of the symbol/strategy. Do not recreate the indicator on every tick.
|
||||
4. **Concurrency**: `Update` is not thread-safe for the same instance. If processing multiple symbols in parallel, use separate indicator instances for each symbol.
|
||||
1. **Time Handling**: QuanTAlib uses `DateTime.UtcNow`. Ensure your platform provides UTC timestamps or convert them.
|
||||
2. **Double Precision**: All calculations use `double`. If your platform uses `decimal`, cast to `double` for input and back to `decimal` for output.
|
||||
3. **State Management**: Persist the indicator instance for the lifetime of the symbol/strategy. Do not recreate the indicator on every tick.
|
||||
4. **Concurrency**: `Update` is not thread-safe for the same instance. If processing multiple symbols in parallel, use separate indicator instances for each symbol.
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
| **Average True Range Percent** | Atrp | - | - | - | - |
|
||||
| **Awesome Oscillator** | [Ao](../lib/momentum/ao/ao.md) | - | ✔️ | ✔️ | ✔️ |
|
||||
| **Balance of Power** | Bop | BOP | bop | Bop | BalanceOfPower |
|
||||
| **Bessel Filter** | Bessel | - | - | - | - |
|
||||
| **Bessel Filter** | [Bessel](../lib/trends/bessel/Bessel.md) | - | - | - | - |
|
||||
| **Bessel-Weighted MA** | Bwma | - | - | - | - |
|
||||
| **Beta Coefficient** | Beta | BETA | - | Beta | - |
|
||||
| **Bias** | Bias | - | - | - | - |
|
||||
|
||||
Reference in New Issue
Block a user