mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-26 06:18:05 +00:00
Refactor and optimize various components of QuanTAlib
- Removed WmaVector class to streamline weighted moving average calculations. - Simplified RingBuffer implementation by removing unnecessary comments and improving clarity. - Enhanced SIMD extensions for better performance and readability. - Updated TBar and TBarSeries classes to improve property calculations and reduce overhead. - Cleaned up TValue struct by removing redundant comments. - Added comprehensive unit tests for IndicatorExtensions and TrimaIndicator to ensure functionality and correctness.
This commit is contained in:
+5
-63
@@ -25,30 +25,18 @@ namespace QuanTAlib;
|
||||
/// </remarks>
|
||||
public class Ema
|
||||
{
|
||||
private struct State : IEquatable<State>
|
||||
private struct State
|
||||
{
|
||||
public double Ema;
|
||||
public double E; // Compensator: decays from 1.0 to 1e-10 for bias correction
|
||||
public bool IsHot; // True when 95% coverage reached (E <= 0.05)
|
||||
public bool IsCompensated; // True when compensator fully decayed (E <= 1e-10)
|
||||
public double E;
|
||||
public bool IsHot;
|
||||
public bool IsCompensated;
|
||||
|
||||
public static State New() => new() { Ema = 0, E = 1.0, IsHot = false, IsCompensated = false };
|
||||
|
||||
public readonly bool Equals(State other) =>
|
||||
Ema == other.Ema && E == other.E && IsHot == other.IsHot && IsCompensated == other.IsCompensated;
|
||||
|
||||
public override readonly bool Equals(object? obj) =>
|
||||
obj is State other && Equals(other);
|
||||
|
||||
public override readonly int GetHashCode() =>
|
||||
HashCode.Combine(Ema, E, IsHot, IsCompensated);
|
||||
|
||||
public static bool operator ==(State left, State right) => left.Equals(right);
|
||||
public static bool operator !=(State left, State right) => !left.Equals(right);
|
||||
}
|
||||
|
||||
private readonly double _alpha;
|
||||
private readonly double _decay; // Pre-calculated (1.0 - alpha) to avoid subtraction per tick
|
||||
private readonly double _decay;
|
||||
private State _state = State.New();
|
||||
private State _p_state = State.New();
|
||||
private double _lastValidValue;
|
||||
@@ -97,9 +85,6 @@ public class Ema
|
||||
/// </summary>
|
||||
public bool IsHot => _state.IsHot;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a valid input value, using last-value substitution for non-finite inputs.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double GetValidValue(double input)
|
||||
{
|
||||
@@ -111,17 +96,9 @@ public class Ema
|
||||
return _lastValidValue;
|
||||
}
|
||||
|
||||
// 95% coverage threshold: E = 1 - 0.95 = 0.05
|
||||
private const double COVERAGE_THRESHOLD = 0.05;
|
||||
// Compensator decay threshold for bias correction
|
||||
private const double COMPENSATOR_THRESHOLD = 1e-10;
|
||||
|
||||
/// <summary>
|
||||
/// Core EMA calculation kernel.
|
||||
/// Assumes input has already been validated via GetValidValue().
|
||||
/// IsHot becomes true at 95% coverage (E <= 0.05).
|
||||
/// Bias correction continues until compensator decays to 1e-10.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double Compute(double input, double alpha, double decay, ref State state)
|
||||
{
|
||||
@@ -132,11 +109,9 @@ public class Ema
|
||||
{
|
||||
state.E *= decay;
|
||||
|
||||
// IsHot triggers at 95% coverage
|
||||
if (!state.IsHot && state.E <= COVERAGE_THRESHOLD)
|
||||
state.IsHot = true;
|
||||
|
||||
// Continue bias correction until compensator fully decays
|
||||
if (state.E <= COMPENSATOR_THRESHOLD)
|
||||
{
|
||||
state.IsCompensated = true;
|
||||
@@ -155,9 +130,6 @@ public class Ema
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core calculation kernel that handles both batch and streaming-continuation.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void CalculateCore(ReadOnlySpan<double> source, Span<double> output, double alpha, ref State state, ref double lastValidValue)
|
||||
{
|
||||
@@ -165,8 +137,6 @@ public class Ema
|
||||
double decay = 1.0 - alpha;
|
||||
int i = 0;
|
||||
|
||||
// Phase 1: Warmup with bias correction
|
||||
// If state is already compensated, this loop is skipped
|
||||
if (!state.IsCompensated)
|
||||
{
|
||||
for (; i < len && state.E > COMPENSATOR_THRESHOLD; i++)
|
||||
@@ -189,7 +159,6 @@ public class Ema
|
||||
state.IsCompensated = true;
|
||||
}
|
||||
|
||||
// Phase 2: Hot loop
|
||||
for (; i < len; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
@@ -203,12 +172,6 @@ public class Ema
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates EMA with the given value.
|
||||
/// </summary>
|
||||
/// <param name="input">Input value</param>
|
||||
/// <param name="isNew">True for new bar, false for update to current bar (default: true)</param>
|
||||
/// <returns>Compensated EMA value</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
@@ -221,19 +184,12 @@ public class Ema
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
// Last-value substitution: replace non-finite inputs with last valid value
|
||||
double val = GetValidValue(input.Value);
|
||||
val = Compute(val, _alpha, _decay, ref _state);
|
||||
Value = new TValue(input.Time, val);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates EMA with the entire series.
|
||||
/// Uses split-loop optimization: warmup phase with bias correction, then branchless hot loop.
|
||||
/// </summary>
|
||||
/// <param name="source">Input series</param>
|
||||
/// <returns>EMA series</returns>
|
||||
public TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return new TSeries(new List<long>(), new List<double>());
|
||||
@@ -249,9 +205,6 @@ public class Ema
|
||||
var sourceValues = source.Values;
|
||||
var sourceTimes = source.Times;
|
||||
|
||||
// 1. Fast Batch Calculation
|
||||
// Uses the unified CalculateCore to handle both new and continuing states
|
||||
// Optimization: Copy state to locals to allow JIT register allocation
|
||||
State state = _state;
|
||||
double lastValidValue = _lastValidValue;
|
||||
|
||||
@@ -260,7 +213,6 @@ public class Ema
|
||||
_state = state;
|
||||
_lastValidValue = lastValidValue;
|
||||
|
||||
// Copy Times
|
||||
sourceTimes.CopyTo(tSpan);
|
||||
|
||||
_p_state = _state;
|
||||
@@ -299,15 +251,6 @@ public class Ema
|
||||
Calculate(source, output, alpha);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates EMA in-place using alpha, writing results to pre-allocated output span.
|
||||
/// Zero-allocation method for maximum performance.
|
||||
/// Bias correction continues until compensator decays to 1e-10.
|
||||
/// Uses split-loop optimization: warmup phase with bias correction, then branchless hot loop.
|
||||
/// </summary>
|
||||
/// <param name="source">Input values</param>
|
||||
/// <param name="output">Output span (must be same length as source)</param>
|
||||
/// <param name="alpha">Smoothing factor (0 < alpha <= 1)</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, double alpha)
|
||||
{
|
||||
@@ -318,7 +261,6 @@ public class Ema
|
||||
|
||||
if (source.Length == 0) return;
|
||||
|
||||
// Initialize default state for static calculation
|
||||
State state = State.New();
|
||||
double lastValid = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user