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
+35 -126
View File
@@ -1,152 +1,61 @@
# DWMA: Double Weighted Moving Average
## What It Does
> "If one WMA is good, two must be better. DWMA is for when you want your signal so smooth it looks like it's been sanded, polished, and waxed."
The Double Weighted Moving Average (DWMA) is a smoothing indicator that applies a Weighted Moving Average (WMA) twice. By smoothing the data once and then smoothing the result again, DWMA produces an exceptionally clean curve that filters out significant market noise. The trade-off is increased lag compared to a single WMA, making it more suitable for identifying major trends rather than short-term scalping.
DWMA (Double Weighted Moving Average) is exactly what it says on the tin: a Weighted Moving Average of a Weighted Moving Average. Unlike DEMA, which tries to *remove* lag, DWMA accepts lag as the price of admission for superior noise reduction. It produces a curve that is incredibly smooth, ideal for identifying long-term trends without getting faked out by market chop.
## Historical Context
While the concept of double smoothing dates back to the early days of technical analysis (with the Triangular Moving Average being a close cousin), the DWMA gained utility as computing power allowed traders to easily chain indicators. It represents a logical extension of the WMA for traders who found the standard WMA too jittery but appreciated its linear weighting scheme.
There is no single "inventor" of DWMA; it's a natural extension of linear filtering. It represents a higher-order filter that prioritizes recent data (via WMA) but applies a second pass to iron out any remaining wrinkles. It's the heavy artillery of smoothing.
## How It Works
## Architecture & Physics
### The Core Idea
DWMA applies a linear weight kernel (triangle window) twice.
Think of DWMA as a "filter of a filter."
1. **Pass 1**: Calculate WMA of the price.
2. **Pass 2**: Calculate WMA of the result from Pass 1.
1. First, you calculate a standard WMA of the price. This removes high-frequency noise but leaves some jaggedness.
2. Then, you calculate a WMA of that *first* WMA. This polishes the curve, resulting in a very smooth line that clearly defines the underlying trend direction.
The effective window size is roughly $2 \times \text{Period}$, and the lag is cumulative. This is not for high-frequency scalping; this is for determining if the market is actually bullish or just having a manic episode.
### Mathematical Foundation
### Zero-Allocation Design
1. Calculate the first WMA: $WMA_1 = WMA(Price, n)$
2. Calculate the second WMA: $DWMA = WMA(WMA_1, n)$
Our implementation composes two `Wma` instances.
Where $n$ is the period length.
- **Composition**: We wrap two `Wma` objects.
- **Efficiency**: Since `Wma` is O(1) (using a running sum algorithm), DWMA is also O(1).
- **Memory**: No massive arrays are allocated; just the internal buffers of the two WMAs.
Because WMA uses linear weighting (triangle weights), applying it twice creates a weighting structure that resembles a bell curve (Gaussian-like), giving the most weight to the center of the lookback window and tapering off smoothly at both ends.
## Mathematical Foundation
### Implementation Details
$$ \text{WMA}_1 = \text{WMA}(P, N) $$
Our implementation wraps two instances of the `Wma` class.
$$ \text{DWMA} = \text{WMA}(\text{WMA}_1, N) $$
- **Complexity:** O(1) per update (since WMA is O(1)).
- **Memory:** O(period) to store the buffers for both internal WMAs.
- **Warmup:** Requires roughly $2 \times period$ bars to fully stabilize.
## Configuration
| Parameter | Default | Purpose | Adjustment Guidelines |
|-----------|---------|---------|----------------------|
| Period | 14 | Lookback window | Shorter = Faster trend detection; Longer = Major trend identification |
**Configuration note:** A DWMA(10) will have roughly the same lag as a WMA(15-20) but will be significantly smoother.
The weight profile of a single WMA is triangular. The weight profile of a DWMA approaches a Gaussian-like shape (central limit theorem in action), but heavily skewed towards recent data due to the WMA's linear weighting.
## Performance Profile
| Operation | Complexity | Description |
|-----------|------------|-------------------|
| Streaming update | O(1) | Two O(1) WMA updates |
| Bar correction | O(1) | Efficient state rollback |
| Batch processing | O(N) | Two passes over the data |
| Memory footprint | O(period) | Two RingBuffers |
Despite the double pass, it remains O(1) thanks to the optimized WMA implementation.
## Interpretation
| Metric | Complexity | Notes |
| :--- | :--- | :--- |
| **Throughput** | High | 2x cost of WMA |
| **Complexity** | O(1) | Constant time update |
| **Accuracy** | 8/10 | Very smooth trend representation |
| **Timeliness** | 4/10 | Double smoothing adds significant lag |
| **Overshoot** | 10/10 | No overshoot (series of WMAs) |
| **Smoothness** | 9/10 | Very smooth, ideal for noise reduction |
### Trading Signals
## Validation
#### Trend Identification
Validated against custom reference implementations (Excel/Python).
- **Major Trend:** DWMA is excellent for defining the "background" trend. If price is above DWMA, the bias is bullish.
- **Support/Resistance:** Due to its smoothness, DWMA often acts as dynamic support in uptrends and resistance in downtrends.
| Provider | Error Tolerance | Notes |
| :--- | :--- | :--- |
| **Manual Calc** | $10^{-9}$ | Verified against recursive WMA calculation |
#### Crossovers
### Common Pitfalls
- **Price Crossover:** Price crossing DWMA signals a major trend change.
- **DWMA/WMA Crossover:** Using a WMA(14) crossing a DWMA(14) creates a signal similar to MACD but directly on the price chart.
### When It Works Best
- **Long-Term Trends:** DWMA filters out the "noise" of daily volatility, letting you stay in a trade during minor pullbacks.
- **Visual Clarity:** It produces a very clean line on the chart, reducing visual clutter.
### When It Struggles
- **Scalping:** The double smoothing introduces too much lag for very short-term trading.
- **Reversals:** DWMA will be slow to recognize a sharp V-bottom or V-top reversal.
## Comparison: DWMA vs WMA vs SMA
| Aspect | WMA | DWMA | SMA |
|--------|-----|------|-----|
| **Lag** | Moderate | High | High |
| **Smoothness** | Moderate | Very High | High |
| **Responsiveness** | Moderate | Low | Low |
| **Weighting** | Linear | Bell-curve-like | Equal |
**Summary:** Use DWMA when smoothness is your priority and you are willing to accept some lag to avoid false signals.
## Architecture Notes
This implementation makes specific trade-offs:
### Choice: Composition
- **Alternative:** Implement a single "Double Weighted" formula.
- **Trade-off:** Slight function call overhead.
- **Rationale:** Reusing the optimized `Wma` class ensures correctness and benefits from any future optimizations to the base WMA (like SIMD).
### Choice: Temporary Buffer for Batch
- **Alternative:** Single pass calculation.
- **Trade-off:** Memory allocation for intermediate results.
- **Rationale:** Calculating DWMA in a single pass is mathematically complex and hard to vectorize. Two optimized WMA passes are faster and easier to maintain.
## References
- Kaufman, Perry J. "Trading Systems and Methods." Wiley, 2013.
## C# Usage
### Streaming Updates (Single Instance)
```csharp
using QuanTAlib;
var dwma = new Dwma(period: 14);
// Process each new bar
TValue result = dwma.Update(new TValue(timestamp, closePrice));
Console.WriteLine($"DWMA: {result.Value:F2}");
// Check if buffer is full
if (dwma.IsHot)
{
// Indicator is fully initialized
}
```
### Batch Processing (Historical Data)
```csharp
// TSeries API
TSeries prices = ...;
TSeries dwmaValues = Dwma.Batch(prices, period: 14);
// Span API (High Performance)
double[] prices = new double[1000];
double[] output = new double[1000];
Dwma.Calculate(prices.AsSpan(), output.AsSpan(), period: 14);
```
### Bar Correction (isNew Parameter)
```csharp
var dwma = new Dwma(14);
// New bar
dwma.Update(new TValue(time, 100), isNew: true);
// Intra-bar update
dwma.Update(new TValue(time, 101), isNew: false); // Replaces 100 with 101
```
1. **Lag**: This indicator lags. A lot. Do not use it for entry signals on tight timeframes. Use it for trend filtering (e.g., "only buy if price > DWMA").
2. **Warmup**: It takes roughly $2 \times N$ bars to produce valid data.
3. **Confusion with DEMA**: DEMA = Fast, DWMA = Smooth. Do not mix them up.