feat: Enhance volume indicators with ADOSC and SSF implementation and validation

This commit is contained in:
Miha Kralj
2025-12-20 15:08:07 -08:00
parent 5549c7329a
commit d21fea3c18
85 changed files with 5144 additions and 3954 deletions
+34 -157
View File
@@ -1,185 +1,62 @@
# WMA: Weighted Moving Average
## What It Does
> "Because yesterday matters more than last Tuesday. WMA is the linear answer to the question: 'What have you done for me lately?'"
The Weighted Moving Average (WMA) addresses the lag issue inherent in Simple Moving Averages (SMA) by assigning linearly decreasing weights to historical prices. Recent data points carry significantly more influence than older ones, resulting in a trend indicator that reacts faster to price changes while maintaining better smoothness than exponential alternatives. It strikes a balance between responsiveness and noise reduction.
The Weighted Moving Average (WMA) assigns a linearly decreasing weight to data points. The most recent price gets weight $N$, the one before it $N-1$, down to 1. This makes it more responsive to recent price changes than an SMA, but without the infinite tail of an EMA.
## Historical Context
While moving averages have been a staple of financial analysis since the early 20th century, the Weighted Moving Average gained prominence as traders sought a middle ground between the significant lag of the SMA and the potential hypersensitivity of the EMA. It became a standard tool in technical analysis packages in the 1980s, offering a mathematically straightforward way to prioritize recent market action without the infinite memory tail of exponential smoothing.
WMA is the "finite impulse response" (FIR) counterpart to the EMA. It was developed to reduce the lag of the SMA while maintaining a finite window of influence.
## How It Works
## Architecture & Physics
### The Core Idea
A naive WMA implementation is $O(N)$, requiring a full loop over the history window for every update. QuanTAlib uses a dual running-sum algorithm to achieve $O(1)$ complexity.
Imagine a 5-day WMA. Today's price is the most important, so it gets a weight of 5. Yesterday's price gets a weight of 4, and so on, back to the oldest price in the window which gets a weight of 1. You sum up all these weighted prices and divide by the sum of the weights (1+2+3+4+5 = 15). As the window moves forward, the oldest price drops off completely, and every other price effectively "slides down" in importance, with the new price taking the top weight.
### The O(1) Algorithm
### Mathematical Foundation
We maintain two sums:
$$WMA = \frac{n \cdot P_n + (n-1) \cdot P_{n-1} + \ldots + 1 \cdot P_1}{\frac{n(n+1)}{2}}$$
1. `Sum`: The simple sum of values (like SMA).
2. `WSum`: The weighted sum.
Where:
$$ WSum_{new} = WSum_{old} - Sum_{old} + (N \times Price_{new}) $$
$$ Sum_{new} = Sum_{old} - Price_{oldest} + Price_{new} $$
- $n$ = period length
- $P_i$ = price at position $i$ (where $P_n$ is the most recent price)
- Denominator = $\frac{n(n+1)}{2}$ (the sum of weights from 1 to $n$, also known as the triangular number)
This allows calculating a WMA(1000) as fast as a WMA(10).
### Implementation Details: O(1) Streaming
### SIMD Optimization
A naive WMA implementation recalculates the entire weighted sum for each new bar, resulting in O(n) complexity. As the period grows, the calculation gets slower.
For batch processing, `Wma.Batch` uses advanced vectorization (AVX2/AVX-512/Neon). It computes prefix sums and weighted updates in parallel, achieving throughputs that scalar code cannot touch.
We use a dual running sum approach to achieve **O(1)** complexity:
## Mathematical Foundation
1. Maintain a simple unweighted sum of prices ($S$).
2. Maintain the weighted sum ($W$).
### 1. The Formula
When a new price ($P_{new}$) arrives and the oldest price ($P_{old}$) leaves the window:
$$W_{new} = W_{old} - S_{old} + (n \cdot P_{new})$$
$$S_{new} = S_{old} - P_{old} + P_{new}$$
$$ WMA = \frac{\sum_{i=0}^{N-1} (N-i) \times P_{t-i}}{\frac{N(N+1)}{2}} $$
This reduces the calculation to two subtractions, two additions, and one multiplication, regardless of the period length. To prevent floating-point drift from accumulating over millions of updates, we perform a full recalculation every 10,000 ticks.
## Configuration
| Parameter | Default | Purpose | Adjustment Guidelines |
|-----------|---------|---------|----------------------|
| Period | 14 | Lookback window | Shorter (5-10) = scalping/intraday; Longer (20-50) = swing/trend following |
| Source | Close | Price input | Typical usage is Close, but HL2 or HLC3 can provide smoother inputs |
The denominator is the sum of the weights (triangular number).
## Performance Profile
| Operation | Complexity | Description |
|-----------|------------|-------------------|
| Streaming update | O(1) | Constant time regardless of period length |
| Bar correction | O(1) | Efficient state rollback for real-time feeds |
| Batch processing | O(n) | SIMD-optimized (AVX2/AVX512/Neon) for high throughput |
| Memory footprint | O(period) | Uses a RingBuffer to store the lookback window |
### Zero-Allocation Design
**Note:** The batch implementation automatically selects the best available SIMD instruction set (AVX512, AVX2, or ARM Neon) for the running hardware, falling back to a scalar implementation if necessary.
WMA uses a pre-allocated `RingBuffer` and maintains dual running sums (`Sum` and `WSum`) in a struct. This design ensures that the hot path is entirely allocation-free.
## Interpretation
| Metric | Score | Notes |
| :--- | :--- | :--- |
| **Throughput** | High | O(1) algorithm |
| **Complexity** | O(1) | Constant time update |
| **Accuracy** | 6/10 | Linearly weighted to recent data |
| **Timeliness** | 6/10 | Reduced lag compared to SMA (Lag ≈ N/3) |
| **Overshoot** | 8/10 | Stable, minimal overshoot |
| **Smoothness** | 5/10 | Less smoothing than SMA |
### Trading Signals
## Validation
#### Trend Identification
Validated against TA-Lib (`TA_WMA`) and Skender.Stock.Indicators.
- **Uptrend:** Price is consistently above the WMA, and the WMA slope is positive.
- **Downtrend:** Price is consistently below the WMA, and the WMA slope is negative.
### Common Pitfalls
#### Crossovers
- **Price Crossover:** Price crossing above the WMA suggests a potential bullish reversal. Price crossing below suggests a bearish reversal.
- **Dual WMA:** Using two WMAs (e.g., 20 and 50). Fast crossing above Slow is a "Golden Cross" (bullish). Fast crossing below Slow is a "Death Cross" (bearish).
### When It Works Best
- **Trending Markets:** WMA excels in clearly defined trends where its reduced lag allows traders to enter and exit positions earlier than with an SMA.
- **Swing Trading:** The linear weighting aligns well with swing trading timeframes, capturing momentum shifts effectively.
### When It Struggles
- **Choppy/Sideways Markets:** Like all moving averages, WMA will generate false signals in range-bound markets.
- **Drop-off Effect:** Because the oldest price drops off the calculation entirely (weight goes from 1 to 0), a large price spike exiting the window can cause the WMA to move counter-intuitively, though less severely than an SMA.
## Architecture Notes
This implementation makes specific trade-offs:
### Choice: Dual Running Sums for O(1)
- **Alternative:** Recalculate weighted sum every bar (O(n)).
- **Trade-off:** Requires maintaining two state variables ($S$ and $W$) and a RingBuffer.
- **Rationale:** Critical for performance in real-time systems monitoring thousands of assets with long periods.
### Choice: Periodic Resync
- **Alternative:** Never resync.
- **Trade-off:** Small CPU cost every 10,000 ticks.
- **Rationale:** Floating-point errors accumulate in running sums. Periodic recalculation ensures long-running server stability.
#### Choice: SIMD for Batch
- **Alternative:** Scalar loop.
- **Trade-off:** Code complexity (multiple execution paths).
- **Rationale:** Batch processing is often the bottleneck in backtesting. SIMD provides 4-8x throughput improvement.
## References
- Colby, Robert W. "The Encyclopedia of Technical Market Indicators." McGraw-Hill, 2002.
- Murphy, John J. "Technical Analysis of the Financial Markets." New York Institute of Finance, 1999.
## C# Usage
### Streaming Updates (Single Instance)
```csharp
using QuanTAlib;
var wma = new Wma(period: 14);
// Process each new bar
TValue result = wma.Update(new TValue(timestamp, closePrice));
Console.WriteLine($"WMA: {result.Value:F2}");
// Check if buffer is full
if (wma.IsHot)
{
// Indicator is fully initialized
}
```
### Batch Processing (Historical Data)
```csharp
// TSeries API (object-oriented)
TSeries prices = ...;
TSeries wmaValues = Wma.Batch(prices, period: 14);
// High-performance Span API (zero allocation)
double[] prices = new double[10000];
double[] output = new double[10000];
Wma.Batch(prices.AsSpan(), output.AsSpan(), period: 14);
// The Span API utilizes SIMD (AVX2, AVX512, Neon) for maximum performance
// on supported hardware.
```
### Bar Correction (isNew Parameter)
```csharp
var wma = new Wma(14);
// New bar arrives
wma.Update(new TValue(time, 100.5), isNew: true);
// Intra-bar price updates (real-time tick data)
wma.Update(new TValue(time, 101.0), isNew: false); // Updates current bar
wma.Update(new TValue(time, 100.8), isNew: false); // Updates current bar
// Next bar
wma.Update(new TValue(time + 60, 101.2), isNew: true); // Advances state
```
### Event-Driven Architecture
```csharp
var source = new TSeries();
var wma = new Wma(source, period: 14);
// Subscribe to WMA output
wma.Pub += (value) => {
Console.WriteLine($"New WMA value: {value.Value}");
};
// Feeding source automatically triggers the chain
source.Add(new TValue(DateTime.Now, 105.2));
```
### Handling Invalid Data
```csharp
var wma = new Wma(14);
wma.Update(new TValue(time, 100));
wma.Update(new TValue(time, double.NaN)); // Uses last valid value (100)
wma.Update(new TValue(time, 110)); // Resumes normal calculation
1. **Drift**: Like SMA, the O(1) algorithm is susceptible to floating-point drift. QuanTAlib resets the sums every 10,000 ticks to guarantee accuracy.
2. **Aggressiveness**: WMA reacts faster than SMA but can be "twitchy." It is often used as a component in other indicators (e.g., HMA) rather than a standalone trend filter.
3. **Weights**: Users sometimes confuse WMA (linear weights) with EMA (exponential weights) or VWAP (volume weights).