From 8cec2fec8dd47b60642eeab6d4ad1c812e529c45 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Wed, 17 Dec 2025 07:27:45 -0800 Subject: [PATCH] feat: Refactor TBar and TValue to record structs; improve readability and performance fix: Update condition checks in Htit and Mgdi for better numerical stability refactor: Simplify parameter struct in T3 and enhance Vidya state management --- lib/core/tbar/tbar.cs | 43 +++------------------------------------ lib/core/tvalue/tvalue.cs | 25 +++-------------------- lib/trends/htit/Htit.cs | 4 ++-- lib/trends/mgdi/Mgdi.cs | 15 +++++++++----- lib/trends/t3/T3.cs | 30 +-------------------------- lib/trends/vidya/Vidya.cs | 3 +-- 6 files changed, 20 insertions(+), 100 deletions(-) diff --git a/lib/core/tbar/tbar.cs b/lib/core/tbar/tbar.cs index 37bd3781..92bd086f 100644 --- a/lib/core/tbar/tbar.cs +++ b/lib/core/tbar/tbar.cs @@ -7,15 +7,8 @@ namespace QuanTAlib; /// Pure data type: 48 bytes (long + 5 doubles). /// [SkipLocalsInit] -public readonly struct TBar : IEquatable +public readonly record struct TBar(long Time, double Open, double High, double Low, double Close, double Volume) { - public readonly long Time; - public readonly double Open; - public readonly double High; - public readonly double Low; - public readonly double Close; - public readonly double Volume; - public DateTime AsDateTime => new(Time, DateTimeKind.Utc); // TValue conversions (Zero-copy / lightweight creation) @@ -34,25 +27,9 @@ public readonly struct TBar : IEquatable public double HLCC4 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (High + Low + Close + Close) * 0.25; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TBar(long time, double open, double high, double low, double close, double volume) + public TBar(DateTime time, double open, double high, double low, double close, double volume) + : this(time.Ticks, open, high, low, close, volume) { - Time = time; - Open = open; - High = high; - Low = low; - Close = close; - Volume = volume; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TBar(DateTime time, double open, double high, double low, double close, double volume) - { - Time = time.Ticks; - Open = open; - High = high; - Low = low; - Close = close; - Volume = volume; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -66,18 +43,4 @@ public readonly struct TBar : IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() => $"[{AsDateTime:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(TBar other) => - Time == other.Time && - Open == other.Open && - High == other.High && - Low == other.Low && - Close == other.Close && - Volume == other.Volume; - - public override bool Equals(object? obj) => obj is TBar other && Equals(other); - public override int GetHashCode() => HashCode.Combine(Time, Open, High, Low, Close, Volume); - public static bool operator ==(TBar left, TBar right) => left.Equals(right); - public static bool operator !=(TBar left, TBar right) => !left.Equals(right); } diff --git a/lib/core/tvalue/tvalue.cs b/lib/core/tvalue/tvalue.cs index 9706ae64..c88e1af2 100644 --- a/lib/core/tvalue/tvalue.cs +++ b/lib/core/tvalue/tvalue.cs @@ -7,25 +7,14 @@ namespace QuanTAlib; /// Pure data type: 16 bytes (long + double). /// [SkipLocalsInit] -public readonly struct TValue : IEquatable +public readonly record struct TValue(long Time, double Value) { - public readonly long Time; - public readonly double Value; - public DateTime AsDateTime => new(Time, DateTimeKind.Utc); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TValue(long time, double value) + public TValue(DateTime time, double value) + : this(time.Kind == DateTimeKind.Utc ? time.Ticks : time.ToUniversalTime().Ticks, value) { - Time = time; - Value = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TValue(DateTime time, double value) - { - Time = time.Kind == DateTimeKind.Utc ? time.Ticks : time.ToUniversalTime().Ticks; - Value = value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -36,12 +25,4 @@ public readonly struct TValue : IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() => $"[{AsDateTime:yyyy-MM-dd HH:mm:ss}, {Value:F2}]"; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(TValue other) => Time == other.Time && Value == other.Value; - - public override bool Equals(object? obj) => obj is TValue other && Equals(other); - public override int GetHashCode() => HashCode.Combine(Time, Value); - public static bool operator ==(TValue left, TValue right) => left.Equals(right); - public static bool operator !=(TValue left, TValue right) => !left.Equals(right); } diff --git a/lib/trends/htit/Htit.cs b/lib/trends/htit/Htit.cs index f296210e..a705e298 100644 --- a/lib/trends/htit/Htit.cs +++ b/lib/trends/htit/Htit.cs @@ -220,7 +220,7 @@ public sealed class Htit : AbstractBase private double CalculatePeriod(double prevPeriod) { double period = 0; - if (_state.Im != 0 && _state.Re != 0) + if (Math.Abs(_state.Im) > 1e-9 && Math.Abs(_state.Re) > 1e-9) { period = 2 * Math.PI / Math.Atan(_state.Im / _state.Re); } @@ -372,7 +372,7 @@ public sealed class Htit : AbstractBase // 7. Calculate Period double period = 0; - if (im != 0 && re != 0) + if (Math.Abs(im) > 1e-9 && Math.Abs(re) > 1e-9) { period = 2 * Math.PI / Math.Atan(im / re); } diff --git a/lib/trends/mgdi/Mgdi.cs b/lib/trends/mgdi/Mgdi.cs index 7dcbc597..585e96d8 100644 --- a/lib/trends/mgdi/Mgdi.cs +++ b/lib/trends/mgdi/Mgdi.cs @@ -33,7 +33,7 @@ public sealed class Mgdi : AbstractBase public Mgdi(int period = 14, double k = 0.6) { - if (period < 1) throw new ArgumentOutOfRangeException(nameof(period)); + ArgumentOutOfRangeException.ThrowIfLessThan(period, 1); if (double.IsNaN(k) || double.IsInfinity(k) || k <= 0) throw new ArgumentOutOfRangeException(nameof(k), "k must be a finite value greater than 0"); _period = period; _k = k; @@ -57,10 +57,15 @@ public sealed class Mgdi : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public override TValue Update(TValue input, bool isNew = true) { - if (isNew) _p_state = _state; - else _state = _p_state; - - if (isNew) _state.Count++; + if (isNew) + { + _p_state = _state; + _state.Count++; + } + else + { + _state = _p_state; + } double price = input.Value; if (!double.IsFinite(price)) diff --git a/lib/trends/t3/T3.cs b/lib/trends/t3/T3.cs index db0a7276..dfcfb38d 100644 --- a/lib/trends/t3/T3.cs +++ b/lib/trends/t3/T3.cs @@ -31,35 +31,7 @@ public sealed class T3 : AbstractBase public static State New() => new() { IsInitialized = false }; } - private readonly struct Parameters : IEquatable - { - 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; - } - - public override bool Equals(object? obj) => obj is Parameters other && Equals(other); - -#pragma warning disable S1244 // Do not check floating point equality with exact values - public bool Equals(Parameters other) => - Alpha == other.Alpha && - C1 == other.C1 && C2 == other.C2 && - C3 == other.C3 && C4 == other.C4; -#pragma warning restore S1244 // Do not check floating point equality with exact values - - public override int GetHashCode() => HashCode.Combine(Alpha, C1, C2, C3, C4); - - public static bool operator ==(Parameters left, Parameters right) => left.Equals(right); - - public static bool operator !=(Parameters left, Parameters right) => !left.Equals(right); - } + private readonly record struct Parameters(double Alpha, double C1, double C2, double C3, double C4); private readonly Parameters _params; private State _state = State.New(); diff --git a/lib/trends/vidya/Vidya.cs b/lib/trends/vidya/Vidya.cs index 7023ab37..0dca6849 100644 --- a/lib/trends/vidya/Vidya.cs +++ b/lib/trends/vidya/Vidya.cs @@ -67,14 +67,13 @@ public sealed class Vidya : AbstractBase if (isNew) { _p_state = _state; + _state.BarCount++; } else { _state = _p_state; } - if (isNew) _state.BarCount++; - if (_state.IsInitialized) { _state.PrevClose = _state.CurrentClose;