mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-24 05:28:05 +00:00
feat: Enhance volume indicators with ADOSC and SSF implementation and validation
This commit is contained in:
@@ -170,4 +170,58 @@ public class T3Tests
|
||||
Assert.Throws<ArgumentException>(() => new T3(0));
|
||||
Assert.Throws<ArgumentException>(() => new T3(-1));
|
||||
}
|
||||
|
||||
private class TestPublisher : ITValuePublisher
|
||||
{
|
||||
public event Action<TValue>? Pub;
|
||||
public int SubscriberCount => Pub?.GetInvocationList().Length ?? 0;
|
||||
|
||||
public void Publish(TValue item)
|
||||
{
|
||||
Pub?.Invoke(item);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SubscribesToSource()
|
||||
{
|
||||
var source = new TestPublisher();
|
||||
var t3 = new T3(source, 5);
|
||||
|
||||
Assert.Equal(1, source.SubscriberCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_UnsubscribesFromSource()
|
||||
{
|
||||
var source = new TestPublisher();
|
||||
var t3 = new T3(source, 5);
|
||||
|
||||
Assert.Equal(1, source.SubscriberCount);
|
||||
|
||||
t3.Dispose();
|
||||
|
||||
Assert.Equal(0, source.SubscriberCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_CanBeCalledMultipleTimes()
|
||||
{
|
||||
var source = new TestPublisher();
|
||||
var t3 = new T3(source, 5);
|
||||
|
||||
t3.Dispose();
|
||||
t3.Dispose();
|
||||
|
||||
Assert.Equal(0, source.SubscriberCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_DoesNothing_WhenNoSource()
|
||||
{
|
||||
var t3 = new T3(5);
|
||||
|
||||
// Should not throw
|
||||
t3.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
+20
-3
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -24,7 +25,7 @@ namespace QuanTAlib;
|
||||
/// alpha = 2 / (period + 1)
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class T3 : AbstractBase
|
||||
public sealed class T3 : AbstractBase, IDisposable
|
||||
{
|
||||
private record struct State(double E1, double E2, double E3, double E4, double E5, double E6, bool IsInitialized)
|
||||
{
|
||||
@@ -38,6 +39,8 @@ public sealed class T3 : AbstractBase
|
||||
private State _p_state = State.New();
|
||||
private double _lastValidValue;
|
||||
private double _p_lastValidValue;
|
||||
private ITValuePublisher? _publisher;
|
||||
private Action<TValue>? _handler;
|
||||
|
||||
/// <summary>
|
||||
/// Creates T3 with specified period and volume factor.
|
||||
@@ -76,7 +79,9 @@ public sealed class T3 : AbstractBase
|
||||
/// <param name="vfactor">Volume Factor (default 0.7)</param>
|
||||
public T3(ITValuePublisher source, int period, double vfactor = 0.7) : this(period, vfactor)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
_publisher = source;
|
||||
_handler = (item) => Update(item);
|
||||
_publisher.Pub += _handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,12 +92,14 @@ public sealed class T3 : AbstractBase
|
||||
/// <param name="vfactor">Volume Factor (default 0.7)</param>
|
||||
public T3(TSeries source, int period, double vfactor = 0.7) : this(period, vfactor)
|
||||
{
|
||||
_publisher = source;
|
||||
Prime(source.Values);
|
||||
if (source.Count > 0)
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
source.Pub += (item) => Update(item);
|
||||
_handler = (item) => Update(item);
|
||||
_publisher.Pub += _handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -308,4 +315,14 @@ public sealed class T3 : AbstractBase
|
||||
_p_lastValidValue = 0;
|
||||
Last = default;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_publisher != null && _handler != null)
|
||||
{
|
||||
_publisher.Pub -= _handler;
|
||||
_publisher = null;
|
||||
_handler = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+39
-104
@@ -1,133 +1,68 @@
|
||||
# T3: Tillson T3 Moving Average
|
||||
|
||||
## What It Does
|
||||
> "If one EMA is good, six must be better. Tim Tillson's logic is impeccable, provided you hate noise more than you love latency."
|
||||
|
||||
The T3 Moving Average is a hyper-smooth, low-lag indicator developed by Tim Tillson. It uses a unique "volume factor" to control how aggressively the moving average tracks the price. Unlike standard moving averages that simply smooth data, T3 applies multiple layers of smoothing (specifically, a generalized DEMA) to create a curve that is exceptionally smooth yet responsive to significant price moves.
|
||||
The T3 Moving Average is a hyper-smooth, low-lag filter that cascades six Exponential Moving Averages (EMAs). Unlike standard cascading (which increases lag), T3 uses a "Volume Factor" ($v$) to weight the EMAs in a way that partially cancels out the lag, resulting in a curve that is smoother than an EMA but more responsive than an SMA.
|
||||
|
||||
## Historical Context
|
||||
|
||||
Tim Tillson introduced the T3 in his article "Smoothing Techniques for More Accurate Signals" in *Technical Analysis of Stocks & Commodities* (January 1998). His goal was to improve upon the lag characteristics of traditional moving averages and the overshoot problems of DEMA (Double Exponential Moving Average).
|
||||
Introduced by Tim Tillson in *Technical Analysis of Stocks & Commodities* (Jan 1998), "Smoothing Techniques for More Accurate Signals." Tillson sought to improve upon the DEMA (Double EMA) and TEMA (Triple EMA) concepts by generalizing the lag-reduction mathematics.
|
||||
|
||||
## How It Works
|
||||
## Architecture & Physics
|
||||
|
||||
### The Core Idea
|
||||
T3 is essentially a filter of filters. It passes data through a chain of 6 EMAs:
|
||||
$Input \to EMA_1 \to EMA_2 \to EMA_3 \to EMA_4 \to EMA_5 \to EMA_6$
|
||||
|
||||
T3 is essentially a "moving average of a moving average of a moving average..." but using a generalized DEMA (GD) instead of a simple EMA.
|
||||
It then combines these outputs using coefficients derived from the Volume Factor ($v$).
|
||||
|
||||
- **GD (Generalized DEMA):** A mix of EMA and DEMA controlled by a volume factor $v$.
|
||||
- **T3:** Applying the GD filter six times in sequence ($GD(GD(GD(GD(GD(GD(Price))))))$).
|
||||
### The Volume Factor ($v$)
|
||||
|
||||
The "Volume Factor" ($v$) determines how much "DEMA" (fast, overshooting) vs "EMA" (slow, lagging) is mixed in.
|
||||
* **$v = 0$**: T3 becomes a standard EMA (actually, a triple EMA of EMAs).
|
||||
* **$v = 1$**: T3 behaves like DEMA/TEMA with aggressive lag reduction (and potential overshoot).
|
||||
* **$v = 0.7$**: The default. A "Goldilocks" zone of smoothness and responsiveness.
|
||||
|
||||
- $v=0$: T3 behaves like a triple EMA (very smooth, some lag).
|
||||
- $v=1$: T3 behaves like a DEMA (very fast, prone to overshoot).
|
||||
- $v=0.7$: The standard default, offering a balance.
|
||||
## Mathematical Foundation
|
||||
|
||||
### Mathematical Foundation
|
||||
### 1. Coefficients
|
||||
|
||||
1. **Generalized DEMA (GD):**
|
||||
$$ GD(x, v) = EMA(x) \times (1 + v) - EMA(EMA(x)) \times v $$
|
||||
Given $v$ (default 0.7):
|
||||
|
||||
2. **T3 Sequence:**
|
||||
$$ e1 = GD(Price) $$
|
||||
$$ e2 = GD(e1) $$
|
||||
$$ e3 = GD(e2) $$
|
||||
$$ ... $$
|
||||
$$ T3 = e6 $$
|
||||
$$ c_1 = -v^3 $$
|
||||
$$ c_2 = 3v^2 + 3v^3 $$
|
||||
$$ c_3 = -6v^2 - 3v - 3v^3 $$
|
||||
$$ c_4 = 1 + 3v + 3v^2 + v^3 $$
|
||||
|
||||
### Implementation Details
|
||||
### 2. The Formula
|
||||
|
||||
Our implementation uses the recursive GD formula for O(1) updates.
|
||||
(Note: There are multiple variations of T3. QuanTAlib uses the standard Tillson formula).
|
||||
|
||||
- **Complexity:** O(1) per update (6 GD calculations).
|
||||
- **Stability:** Requires a warmup period to stabilize all 6 internal layers.
|
||||
$$ T3 = c_1 e_6 + c_2 e_5 + c_3 e_4 + c_4 e_3 $$
|
||||
|
||||
## Configuration
|
||||
|
||||
| Parameter | Default | Purpose | Adjustment Guidelines |
|
||||
|-----------|---------|---------|----------------------|
|
||||
| Period | 14 | Smoothing period | Standard lookback. |
|
||||
| Volume Factor (v) | 0.7 | Responsiveness | 0.7 is standard. Lower (0.1-0.5) = smoother/slower. Higher (0.8-1.0) = faster/responsive. |
|
||||
Where $e_n$ is the output of the $n$-th EMA in the cascade.
|
||||
|
||||
## Performance Profile
|
||||
|
||||
| Operation | Complexity | Description |
|
||||
|-----------|------------|-------------------|
|
||||
| Streaming update | O(1) | 6 layers of GD calculation |
|
||||
| Bar correction | O(1) | Efficient state rollback |
|
||||
| Batch processing | O(N) | Single pass through data |
|
||||
| Memory footprint | O(1) | Stores state for 6 internal layers |
|
||||
Despite the complexity, T3 is O(1).
|
||||
|
||||
## Interpretation
|
||||
### Zero-Allocation Design
|
||||
|
||||
### Trading Signals
|
||||
QuanTAlib implements T3 using a single `State` struct that holds the values of all 6 EMAs. This avoids creating 6 separate `Ema` objects and eliminates heap allocations.
|
||||
|
||||
#### Trend Identification
|
||||
| Metric | Score | Notes |
|
||||
| :--- | :--- | :--- |
|
||||
| **Throughput** | Moderate | 6 EMAs |
|
||||
| **Complexity** | O(1) | Constant time update |
|
||||
| **Accuracy** | 8/10 | Very smooth, organic curve |
|
||||
| **Timeliness** | 7/10 | Lag depends heavily on 'v' factor |
|
||||
| **Overshoot** | 6/10 | Can overshoot if v > 0.7 |
|
||||
| **Smoothness** | 10/10 | One of the smoothest filters available |
|
||||
|
||||
- **Smoothness:** T3 is famous for filtering out "noise" better than almost any other MA. If T3 is rising, the trend is likely real, not just a blip.
|
||||
- **Crossovers:** Price crossing T3 is a significant event due to the indicator's smoothness.
|
||||
## Validation
|
||||
|
||||
### When It Works Best
|
||||
Validated against TA-Lib and Skender.Stock.Indicators.
|
||||
|
||||
- **Noisy Markets:** T3 shines in markets with lots of wicks and erratic movement, where standard EMAs would get chopped up.
|
||||
### Common Pitfalls
|
||||
|
||||
### When It Struggles
|
||||
|
||||
- **Lag:** Despite its clever math, applying a filter 6 times introduces lag. It will turn after the market turns, not with it.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
This implementation makes specific trade-offs:
|
||||
|
||||
### Choice: 6 Layers
|
||||
|
||||
- **Implementation:** We implement the standard "T3" which implies 6 layers of smoothing.
|
||||
- **Rationale:** While "T2" or "T4" are possible, "T3" (6 layers) is the industry standard definition.
|
||||
|
||||
## References
|
||||
|
||||
- Tillson, Tim. "Smoothing Techniques for More Accurate Signals." *Technical Analysis of Stocks & Commodities*, V. 16:1 (33-37), 1998.
|
||||
|
||||
## C# Usage
|
||||
|
||||
### Streaming Updates (Single Instance)
|
||||
|
||||
```csharp
|
||||
using QuanTAlib;
|
||||
|
||||
var t3 = new T3(period: 14, vFactor: 0.7);
|
||||
|
||||
// Process each new bar
|
||||
TValue result = t3.Update(new TValue(timestamp, closePrice));
|
||||
Console.WriteLine($"T3: {result.Value:F2}");
|
||||
|
||||
// Check if buffer is full
|
||||
if (t3.IsHot)
|
||||
{
|
||||
// Indicator is fully initialized
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Processing (Historical Data)
|
||||
|
||||
```csharp
|
||||
// TSeries API
|
||||
TSeries prices = ...;
|
||||
TSeries t3Values = T3.Batch(prices, period: 14, vFactor: 0.7);
|
||||
|
||||
// Span API (High Performance)
|
||||
double[] prices = new double[1000];
|
||||
double[] output = new double[1000];
|
||||
T3.Calculate(prices.AsSpan(), output.AsSpan(), period: 14, vFactor: 0.7);
|
||||
```
|
||||
|
||||
### Bar Correction (isNew Parameter)
|
||||
|
||||
```csharp
|
||||
var t3 = new T3(14);
|
||||
|
||||
// New bar
|
||||
t3.Update(new TValue(time, 100), isNew: true);
|
||||
|
||||
// Intra-bar update
|
||||
t3.Update(new TValue(time, 101), isNew: false); // Replaces 100 with 101
|
||||
1. **Warmup**: Because it cascades 6 EMAs, T3 takes significantly longer to stabilize than a standard EMA. A T3(10) might need 60+ bars to converge.
|
||||
2. **Overshoot**: With high $v$ values ($>1$), T3 can overshoot price turns, creating false breakout signals.
|
||||
3. **Complexity**: It is computationally heavier than SMA or EMA (approx 6x ops), though still negligible on modern CPUs.
|
||||
|
||||
Reference in New Issue
Block a user