mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-19 02:58:05 +00:00
Refactor T3 Moving Average Implementation and Remove Unused Tests
- Deleted DebugTulip.Tests.cs as it was no longer needed. - Refactored T3.cs to encapsulate parameters in a struct for better organization and readability. - Updated methods in T3.cs to use the new Parameters struct, improving clarity and reducing redundancy. - Enhanced T3.md documentation to provide clearer explanations of the T3 moving average and its parameters. - Removed Wma.Coverage.Tests.cs as it was obsolete. - Added new tests in IndicatorExtensions.Tests.cs to validate logic methods and ensure correct calculations. - Updated IndicatorExtensions.cs to improve method organization and add new functionality for handling chart coordinates. - Refactored mocks in TradingPlatformMocks.cs to align with new chart interface definitions.
This commit is contained in:
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Tulip;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class DebugTulipTests
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public DebugTulipTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListTulipIndicators()
|
||||
{
|
||||
var type = typeof(Tulip.Indicators);
|
||||
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Static);
|
||||
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
_output.WriteLine("Tulip Indicators (Properties):");
|
||||
foreach (var p in properties)
|
||||
{
|
||||
_output.WriteLine(p.Name);
|
||||
}
|
||||
|
||||
_output.WriteLine("Tulip Indicators (Fields):");
|
||||
foreach (var f in fields)
|
||||
{
|
||||
_output.WriteLine(f.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
-25
@@ -34,8 +34,22 @@ public sealed class T3 : ITValuePublisher
|
||||
public static State New() => new() { IsInitialized = false };
|
||||
}
|
||||
|
||||
private readonly double _alpha;
|
||||
private readonly double _c1, _c2, _c3, _c4;
|
||||
private readonly struct Parameters
|
||||
{
|
||||
public readonly double Alpha;
|
||||
public readonly double C1, C2, C3, C4;
|
||||
|
||||
public Parameters(double alpha, double c1, double c2, double c3, double c4)
|
||||
{
|
||||
Alpha = alpha;
|
||||
C1 = c1;
|
||||
C2 = c2;
|
||||
C3 = c3;
|
||||
C4 = c4;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Parameters _params;
|
||||
private State _state = State.New();
|
||||
private State _p_state = State.New();
|
||||
private double _lastValidValue;
|
||||
@@ -57,17 +71,19 @@ public sealed class T3 : ITValuePublisher
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
_alpha = 2.0 / (period + 1);
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
|
||||
// Precompute coefficients
|
||||
double v = vfactor;
|
||||
double v2 = v * v;
|
||||
double v3 = v2 * v;
|
||||
|
||||
_c1 = -v3;
|
||||
_c2 = 3.0 * (v2 + v3);
|
||||
_c3 = -3.0 * (2.0 * v2 + v + v3);
|
||||
_c4 = 1.0 + 3.0 * v + 3.0 * v2 + v3;
|
||||
double c1 = -v3;
|
||||
double c2 = 3.0 * (v2 + v3);
|
||||
double c3 = -3.0 * (2.0 * v2 + v + v3);
|
||||
double c4 = 1.0 + 3.0 * v + 3.0 * v2 + v3;
|
||||
|
||||
_params = new Parameters(alpha, c1, c2, c3, c4);
|
||||
|
||||
Name = $"T3({period}, {vfactor:F2})";
|
||||
}
|
||||
@@ -118,7 +134,7 @@ public sealed class T3 : ITValuePublisher
|
||||
}
|
||||
|
||||
double val = GetValidValue(input.Value);
|
||||
val = Compute(val, _alpha, _c1, _c2, _c3, _c4, ref _state);
|
||||
val = Compute(val, _params, ref _state);
|
||||
Last = new TValue(input.Time, val);
|
||||
Pub?.Invoke(Last);
|
||||
return Last;
|
||||
@@ -142,21 +158,21 @@ public sealed class T3 : ITValuePublisher
|
||||
State state = _state;
|
||||
double lastValidValue = _lastValidValue;
|
||||
|
||||
CalculateCore(sourceValues, vSpan, _alpha, _c1, _c2, _c3, _c4, ref state, ref lastValidValue);
|
||||
CalculateCore(sourceValues, vSpan, _params, ref state, ref lastValidValue);
|
||||
|
||||
_state = state;
|
||||
_lastValidValue = lastValidValue;
|
||||
|
||||
sourceTimes.CopyTo(tSpan);
|
||||
|
||||
|
||||
_p_state = _state;
|
||||
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||
|
||||
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double Compute(double input, double alpha, double c1, double c2, double c3, double c4, ref State state)
|
||||
private static double Compute(double input, in Parameters p, ref State state)
|
||||
{
|
||||
if (!state.IsInitialized)
|
||||
{
|
||||
@@ -165,20 +181,19 @@ public sealed class T3 : ITValuePublisher
|
||||
}
|
||||
else
|
||||
{
|
||||
state.E1 += alpha * (input - state.E1);
|
||||
state.E2 += alpha * (state.E1 - state.E2);
|
||||
state.E3 += alpha * (state.E2 - state.E3);
|
||||
state.E4 += alpha * (state.E3 - state.E4);
|
||||
state.E5 += alpha * (state.E4 - state.E5);
|
||||
state.E6 += alpha * (state.E5 - state.E6);
|
||||
state.E1 += p.Alpha * (input - state.E1);
|
||||
state.E2 += p.Alpha * (state.E1 - state.E2);
|
||||
state.E3 += p.Alpha * (state.E2 - state.E3);
|
||||
state.E4 += p.Alpha * (state.E3 - state.E4);
|
||||
state.E5 += p.Alpha * (state.E4 - state.E5);
|
||||
state.E6 += p.Alpha * (state.E5 - state.E6);
|
||||
}
|
||||
|
||||
return c1 * state.E6 + c2 * state.E5 + c3 * state.E4 + c4 * state.E3;
|
||||
return p.C1 * state.E6 + p.C2 * state.E5 + p.C3 * state.E4 + p.C4 * state.E3;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void CalculateCore(ReadOnlySpan<double> source, Span<double> output, double alpha,
|
||||
double c1, double c2, double c3, double c4, ref State state, ref double lastValidValue)
|
||||
private static void CalculateCore(ReadOnlySpan<double> source, Span<double> output, in Parameters p, ref State state, ref double lastValidValue)
|
||||
{
|
||||
int len = source.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
@@ -189,7 +204,7 @@ public sealed class T3 : ITValuePublisher
|
||||
else
|
||||
val = lastValidValue;
|
||||
|
||||
output[i] = Compute(val, alpha, c1, c2, c3, c4, ref state);
|
||||
output[i] = Compute(val, p, ref state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +227,7 @@ public sealed class T3 : ITValuePublisher
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
double v = vfactor;
|
||||
double v2 = v * v;
|
||||
@@ -223,10 +238,11 @@ public sealed class T3 : ITValuePublisher
|
||||
double c3 = -3.0 * (2.0 * v2 + v + v3);
|
||||
double c4 = 1.0 + 3.0 * v + 3.0 * v2 + v3;
|
||||
|
||||
var p = new Parameters(alpha, c1, c2, c3, c4);
|
||||
State state = State.New();
|
||||
double lastValidValue = 0;
|
||||
|
||||
CalculateCore(source, output, alpha, c1, c2, c3, c4, ref state, ref lastValidValue);
|
||||
CalculateCore(source, output, p, ref state, ref lastValidValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
# T3: Tillson T3 Moving Average
|
||||
|
||||
## Overview and Purpose
|
||||
|
||||
The Tillson T3 Moving Average is an advanced technical indicator designed to provide superior smoothing with minimal lag. Developed by Tim Tillson and introduced in the January 1998 issue of Technical Analysis of Stocks & Commodities magazine, T3 implements a sophisticated six-stage EMA architecture with optimized coefficient distribution based on a volume factor parameter.
|
||||
|
||||
Unlike simpler moving averages or even triple-EMA approaches, T3 uses a unique mathematical framework that strategically combines multiple EMAs with precisely calculated coefficients. This approach creates a moving average that effectively reduces noise while preserving important trend information and minimizing lag.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
* **Multi-stage smoothing:** Uses a six-stage EMA cascade with optimized coefficient distribution to achieve superior noise reduction while minimizing lag
|
||||
* **Volume factor customization:** Provides a parameter that allows traders to fine-tune the balance between smoothness and responsiveness
|
||||
* **Strategic coefficient weighting:** Employs a sophisticated formula that prevents overshooting at turning points while maintaining responsiveness
|
||||
|
||||
## Calculation and Mathematical Foundation
|
||||
|
||||
T3 works by running price data through a series of six EMAs, then combining the outputs of these EMAs using carefully calculated weights. These weights are determined by a "volume factor" parameter ($v$) that controls how much the indicator prioritizes smoothness versus responsiveness.
|
||||
|
||||
### Formula
|
||||
|
||||
$$ T3 = c_1 \cdot EMA_6 + c_2 \cdot EMA_5 + c_3 \cdot EMA_4 + c_4 \cdot EMA_3 $$
|
||||
|
||||
Where:
|
||||
|
||||
* $EMA_1$ through $EMA_6$ are exponential moving averages applied in sequence:
|
||||
* $EMA_1(x) = EMA(x)$
|
||||
* $EMA_n(x) = EMA(EMA_{n-1}(x))$
|
||||
@@ -28,6 +33,7 @@ Where:
|
||||
* Default volume factor $v = 0.7$
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Default | Range | Description |
|
||||
|-----------|---------|-------|-------------|
|
||||
| Period | 10 | > 0 | The smoothing period for the internal EMAs |
|
||||
@@ -36,6 +42,7 @@ Where:
|
||||
## C# Usage
|
||||
|
||||
### Standard TSeries Usage
|
||||
|
||||
```csharp
|
||||
// Calculate T3 with period 10 and default volume factor 0.7
|
||||
var t3 = T3.Calculate(sourceSeries, 10);
|
||||
@@ -47,6 +54,7 @@ Console.WriteLine($"T3 Value: {t3.Last.Value}");
|
||||
```
|
||||
|
||||
### Eventing and Reactive Support
|
||||
|
||||
The `T3` class implements `ITValuePublisher`, allowing for event-driven updates.
|
||||
|
||||
```csharp
|
||||
|
||||
Reference in New Issue
Block a user