mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 21:18:04 +00:00
feat: Enhance volume indicators with ADOSC and SSF implementation and validation
This commit is contained in:
+34
-157
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user