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:
Miha Kralj
2025-12-21 14:37:44 -08:00
parent 54c309e5cf
commit a7b7207801
65 changed files with 1766 additions and 482 deletions
+1
View File
@@ -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)
+6 -6
View File
@@ -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
View File
@@ -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)
+34
View File
@@ -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. |
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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 | - | - | - | - |