fix: address code review issues in indicators and core components

This commit is contained in:
Miha Kralj
2025-12-09 16:00:04 -05:00
parent 5e6e3a070e
commit c802a9ea80
12 changed files with 54 additions and 26 deletions
+16
View File
@@ -82,6 +82,22 @@ public sealed class RingBuffer : IEnumerable<double>
get => _sum; get => _sum;
} }
/// <summary>
/// Recalculates the sum by iterating over all elements.
/// Useful for correcting floating-point drift after many updates.
/// </summary>
public double RecalculateSum()
{
double sum = 0;
var span = GetSpan();
for (int i = 0; i < span.Length; i++)
{
sum += span[i];
}
_sum = sum;
return sum;
}
/// <summary> /// <summary>
/// Average of all elements in the buffer. /// Average of all elements in the buffer.
/// Returns 0 if buffer is empty. /// Returns 0 if buffer is empty.
+4 -6
View File
@@ -129,12 +129,10 @@ public class TBarSeries : IReadOnlyList<TBar>
throw new ArgumentException("All arrays must have the same length"); throw new ArgumentException("All arrays must have the same length");
} }
_t.AddRange(tArr); for (int i = 0; i < tArr.Length; i++)
_o.AddRange(oArr); {
_h.AddRange(hArr); Add(tArr[i], oArr[i], hArr[i], lArr[i], cArr[i], vArr[i]);
_l.AddRange(lArr); }
_c.AddRange(cArr);
_v.AddRange(vArr);
} }
public IEnumerator<TBar> GetEnumerator() public IEnumerator<TBar> GetEnumerator()
+2 -2
View File
@@ -24,7 +24,7 @@ public readonly struct TValue : IEquatable<TValue>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue(DateTime time, double value) public TValue(DateTime time, double value)
{ {
Time = time.Ticks; Time = time.Kind == DateTimeKind.Utc ? time.Ticks : time.ToUniversalTime().Ticks;
Value = value; Value = value;
} }
@@ -38,7 +38,7 @@ public readonly struct TValue : IEquatable<TValue>
public override string ToString() => $"[{AsDateTime:yyyy-MM-dd HH:mm:ss}, {Value:F2}]"; public override string ToString() => $"[{AsDateTime:yyyy-MM-dd HH:mm:ss}, {Value:F2}]";
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(TValue other) => Time == other.Time && Math.Abs(Value - other.Value) < 1e-9; 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 bool Equals(object? obj) => obj is TValue other && Equals(other);
public override int GetHashCode() => HashCode.Combine(Time, Value); public override int GetHashCode() => HashCode.Combine(Time, Value);
+7 -3
View File
@@ -70,8 +70,9 @@ public class CsvFeed : IFeed
var line = dataLines[i]; var line = dataLines[i];
var parts = line.Split(','); var parts = line.Split(',');
int originalLineNumber = dataLines.Count - i + 1;
if (parts.Length != 6) if (parts.Length != 6)
throw new FormatException($"Invalid CSV format at line {i + 2}. Expected 6 columns, found {parts.Length}"); throw new FormatException($"Invalid CSV format at line {originalLineNumber}. Expected 6 columns, found {parts.Length}");
try try
{ {
@@ -89,7 +90,7 @@ public class CsvFeed : IFeed
} }
catch (Exception ex) when (ex is FormatException or OverflowException) catch (Exception ex) when (ex is FormatException or OverflowException)
{ {
throw new FormatException($"Failed to parse CSV line {i + 2}: {line}", ex); throw new FormatException($"Failed to parse CSV line {originalLineNumber}: {line}", ex);
} }
} }
@@ -153,7 +154,7 @@ public class CsvFeed : IFeed
var result = new TBarSeries(count); var result = new TBarSeries(count);
// Find starting index // Find starting index
int startIndex = 0; int startIndex = -1;
for (int i = 0; i < _data.Count; i++) for (int i = 0; i < _data.Count; i++)
{ {
if (_data[i].Time >= startTime) if (_data[i].Time >= startTime)
@@ -163,6 +164,9 @@ public class CsvFeed : IFeed
} }
} }
if (startIndex == -1)
return result;
// Collect bars matching interval // Collect bars matching interval
long expectedTime = startTime; long expectedTime = startTime;
int collected = 0; int collected = 0;
+5 -1
View File
@@ -26,6 +26,8 @@ namespace QuanTAlib;
public sealed class Alma : ITValuePublisher public sealed class Alma : ITValuePublisher
{ {
private readonly int _period; private readonly int _period;
private readonly double _offset;
private readonly double _sigma;
private readonly double[] _weights; private readonly double[] _weights;
private readonly double _weightSum; private readonly double _weightSum;
private readonly RingBuffer _buffer; private readonly RingBuffer _buffer;
@@ -62,6 +64,8 @@ public sealed class Alma : ITValuePublisher
throw new ArgumentException("Sigma must be greater than 0", nameof(sigma)); throw new ArgumentException("Sigma must be greater than 0", nameof(sigma));
_period = period; _period = period;
_offset = offset;
_sigma = sigma;
_buffer = new RingBuffer(period); _buffer = new RingBuffer(period);
_weights = new double[period]; _weights = new double[period];
Name = $"Alma({period}, {offset:F2}, {sigma:F2})"; Name = $"Alma({period}, {offset:F2}, {sigma:F2})";
@@ -129,7 +133,7 @@ public sealed class Alma : ITValuePublisher
var tSpan = CollectionsMarshal.AsSpan(t); var tSpan = CollectionsMarshal.AsSpan(t);
var vSpan = CollectionsMarshal.AsSpan(v); var vSpan = CollectionsMarshal.AsSpan(v);
Calculate(source.Values, vSpan, _period); Calculate(source.Values, vSpan, _period, _offset, _sigma);
source.Times.CopyTo(tSpan); source.Times.CopyTo(tSpan);
// Restore state // Restore state
+4
View File
@@ -57,6 +57,7 @@ public sealed class Dema : ITValuePublisher
private EmaState _p_state2 = EmaState.New(); private EmaState _p_state2 = EmaState.New();
private double _lastValidValue; private double _lastValidValue;
private double _p_lastValidValue;
public string Name { get; } public string Name { get; }
public TValue Last { get; private set; } public TValue Last { get; private set; }
@@ -93,11 +94,13 @@ public sealed class Dema : ITValuePublisher
{ {
_p_state1 = _state1; _p_state1 = _state1;
_p_state2 = _state2; _p_state2 = _state2;
_p_lastValidValue = _lastValidValue;
} }
else else
{ {
_state1 = _p_state1; _state1 = _p_state1;
_state2 = _p_state2; _state2 = _p_state2;
_lastValidValue = _p_lastValidValue;
} }
// EMA1 // EMA1
@@ -302,6 +305,7 @@ public sealed class Dema : ITValuePublisher
_p_state1 = EmaState.New(); _p_state1 = EmaState.New();
_p_state2 = EmaState.New(); _p_state2 = EmaState.New();
_lastValidValue = 0; _lastValidValue = 0;
_p_lastValidValue = 0;
Last = default; Last = default;
} }
} }
+4
View File
@@ -56,6 +56,7 @@ public sealed class Ema : ITValuePublisher
private State _state = State.New(); private State _state = State.New();
private State _p_state = State.New(); private State _p_state = State.New();
private double _lastValidValue; private double _lastValidValue;
private double _p_lastValidValue;
/// <summary> /// <summary>
/// Display name for the indicator. /// Display name for the indicator.
@@ -134,10 +135,12 @@ public sealed class Ema : ITValuePublisher
if (isNew) if (isNew)
{ {
_p_state = _state; _p_state = _state;
_p_lastValidValue = _lastValidValue;
} }
else else
{ {
_state = _p_state; _state = _p_state;
_lastValidValue = _p_lastValidValue;
} }
double val = GetValidValue(input.Value); double val = GetValidValue(input.Value);
@@ -305,6 +308,7 @@ public sealed class Ema : ITValuePublisher
_state = State.New(); _state = State.New();
_p_state = _state; _p_state = _state;
_lastValidValue = 0; _lastValidValue = 0;
_p_lastValidValue = 0;
Last = default; Last = default;
} }
} }
+1 -6
View File
@@ -177,12 +177,7 @@ public sealed class Kama : ITValuePublisher
// Use static Calculate for performance // Use static Calculate for performance
var outputSpan = new double[len]; var outputSpan = new double[len];
Calculate(source.Values, outputSpan, _period,
(int)(2.0 / _fastAlpha - 1), (int)(2.0 / _slowAlpha - 1)); // Reverse calc periods from alphas?
// Actually better to pass alphas or periods.
// The static method signature should match constructor params.
// Wait, I need to pass periods to static method.
// fastPeriod = 2/fastAlpha - 1. // fastPeriod = 2/fastAlpha - 1.
int fastPeriod = (int)Math.Round(2.0 / _fastAlpha - 1); int fastPeriod = (int)Math.Round(2.0 / _fastAlpha - 1);
int slowPeriod = (int)Math.Round(2.0 / _slowAlpha - 1); int slowPeriod = (int)Math.Round(2.0 / _slowAlpha - 1);
+2 -2
View File
@@ -252,6 +252,8 @@ public sealed class Lsma : ITValuePublisher
int windowSize = Math.Min(len, _period); int windowSize = Math.Min(len, _period);
int startIndex = len - windowSize; int startIndex = len - windowSize;
Reset();
// Initialize lastValidValue // Initialize lastValidValue
if (startIndex > 0) if (startIndex > 0)
{ {
@@ -269,8 +271,6 @@ public sealed class Lsma : ITValuePublisher
_lastValidValue = 0; _lastValidValue = 0;
} }
Reset();
for (int i = startIndex; i < len; i++) for (int i = startIndex; i < len; i++)
{ {
double val = GetValidValue(source.Values[i]); double val = GetValidValue(source.Values[i]);
+7 -4
View File
@@ -103,7 +103,7 @@ public sealed class Sma : ITValuePublisher
if (_buffer.IsFull && _tickCount >= ResyncInterval) if (_buffer.IsFull && _tickCount >= ResyncInterval)
{ {
_tickCount = 0; _tickCount = 0;
_sum = _buffer.Sum(); _sum = _buffer.RecalculateSum();
} }
} }
@@ -386,9 +386,12 @@ public sealed class Sma : ITValuePublisher
public void Reset() public void Reset()
{ {
_buffer.Clear(); _buffer.Clear();
var resetSum = 0; _sum = 0;
_sum = resetSum; _p_sum = 0;
Last = default; _p_lastInput = 0;
_lastValidValue = 0;
_p_lastValidValue = 0;
_tickCount = 0; _tickCount = 0;
Last = default;
} }
} }
+1 -1
View File
@@ -156,7 +156,7 @@ public sealed class Trima : ITValuePublisher
source.Times.CopyTo(tSpan); source.Times.CopyTo(tSpan);
// Restore state // Restore state
int lookback = _p1 + _p2; int lookback = _p1 + _p2 - 1;
int startIndex = Math.Max(0, len - lookback); int startIndex = Math.Max(0, len - lookback);
Reset(); Reset();
+1 -1
View File
@@ -188,7 +188,7 @@ public sealed class Wma : ITValuePublisher
_p_sum = _sum; _p_sum = _sum;
_p_wsum = _wsum; _p_wsum = _wsum;
_p_lastInput = source.Values[len - 1]; _p_lastInput = _lastValidValue;
_p_lastValidValue = _lastValidValue; _p_lastValidValue = _lastValidValue;
Last = new TValue(tSpan[len - 1], vSpan[len - 1]); Last = new TValue(tSpan[len - 1], vSpan[len - 1]);