mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 21:18:04 +00:00
Refactor tests and implementations for various indicators
- Updated RsiIndicatorTests to ensure proper initialization and state checks. - Added new tests for Rsx, Vel, and Adosc indicators to validate behavior under iterative corrections and edge cases (NaN, Infinity). - Enhanced Bessel indicator tests and implementation with consistent formatting. - Improved Ema and Pwma implementations by ensuring proper handling of values. - Introduced mock classes for charting to facilitate testing without dependencies. - Ensured all indicators produce consistent results across different modes of operation. - Cleaned up code formatting and added missing commas for better readability.
This commit is contained in:
@@ -344,7 +344,8 @@ public class TBarSeriesTests
|
||||
series.Add(200, 20, 25, 15, 22, 200, isNew: true);
|
||||
|
||||
var list = new List<object>();
|
||||
|
||||
|
||||
#pragma warning disable S4158
|
||||
foreach (var item in (IEnumerable)series)
|
||||
{
|
||||
list.Add(item);
|
||||
@@ -408,4 +409,146 @@ public class TBarSeriesTests
|
||||
Assert.Equal(200, series[1].Time);
|
||||
Assert.Equal(300, series[2].Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_WithEnumerables_MismatchedLengths_ThrowsArgumentException()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var times = new long[] { 100, 200, 300 };
|
||||
var opens = new double[] { 10, 20 }; // Mismatched length
|
||||
var highs = new double[] { 15, 25, 35 };
|
||||
var lows = new double[] { 5, 15, 25 };
|
||||
var closes = new double[] { 12, 22, 32 };
|
||||
var volumes = new double[] { 100, 200, 300 };
|
||||
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
series.Add(times, opens, highs, lows, closes, volumes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indexer_OutOfBounds_ThrowsException()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = series[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indexer_NegativeIndex_ThrowsException()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
|
||||
int invalidIndex = -1;
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = series[invalidIndex]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indexer_BeyondCount_ThrowsException()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = series[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_WithNaN_PreservesNaN()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var bar = new TBar(DateTime.UtcNow.Ticks, double.NaN, 110, 90, 105, 1000);
|
||||
|
||||
series.Add(bar, isNew: true);
|
||||
|
||||
Assert.True(double.IsNaN(series.Last.Open));
|
||||
Assert.True(double.IsNaN(series.Open.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_WithInfinity_PreservesInfinity()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var bar = new TBar(DateTime.UtcNow.Ticks, 100, double.PositiveInfinity, 90, 105, 1000);
|
||||
|
||||
series.Add(bar, isNew: true);
|
||||
|
||||
Assert.True(double.IsPositiveInfinity(series.Last.High));
|
||||
Assert.True(double.IsPositiveInfinity(series.High.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubSeries_EmptySeries_HaveZeroCount()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
|
||||
Assert.Empty(series.Open);
|
||||
Assert.Empty(series.High);
|
||||
Assert.Empty(series.Low);
|
||||
Assert.Empty(series.Close);
|
||||
Assert.Empty(series.Volume);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubSeries_ValuesSpan_ReturnsCorrectData()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
|
||||
ReadOnlySpan<double> closeValues = series.Close.Values;
|
||||
|
||||
Assert.Equal(2, closeValues.Length);
|
||||
Assert.Equal(12.0, closeValues[0]);
|
||||
Assert.Equal(22.0, closeValues[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubSeries_TimesSpan_ReturnsCorrectData()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
|
||||
ReadOnlySpan<long> times = series.Close.Times;
|
||||
|
||||
Assert.Equal(2, times.Length);
|
||||
Assert.Equal(100, times[0]);
|
||||
Assert.Equal(200, times[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_EventArgs_ContainsIsNewFlag()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
bool? receivedIsNew = null;
|
||||
series.Pub += (object? sender, in TBarEventArgs args) => receivedIsNew = args.IsNew;
|
||||
|
||||
series.Add(new TBar(100, 10, 15, 5, 12, 100), isNew: true);
|
||||
|
||||
Assert.True(receivedIsNew);
|
||||
|
||||
series.Add(new TBar(100, 10, 18, 5, 15, 150), isNew: false);
|
||||
|
||||
Assert.False(receivedIsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_WithEnumerables_EmptyArrays_AddsNothing()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var empty = Array.Empty<long>();
|
||||
var emptyD = Array.Empty<double>();
|
||||
|
||||
series.Add(empty, emptyD, emptyD, emptyD, emptyD, emptyD);
|
||||
|
||||
Assert.Empty(series);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithCapacity_DoesNotAffectCount()
|
||||
{
|
||||
var series = new TBarSeries(1000);
|
||||
|
||||
Assert.Empty(series);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -16,6 +17,74 @@ public readonly struct TBarEventArgs
|
||||
public bool IsNew { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// High-performance enumerator for TBarSeries.
|
||||
/// </summary>
|
||||
public struct TBarSeriesEnumerator : IEnumerator<TBar>, IEquatable<TBarSeriesEnumerator>
|
||||
{
|
||||
private readonly List<long> _t;
|
||||
private readonly List<double> _o;
|
||||
private readonly List<double> _h;
|
||||
private readonly List<double> _l;
|
||||
private readonly List<double> _c;
|
||||
private readonly List<double> _v;
|
||||
private readonly int _count;
|
||||
private int _index;
|
||||
private TBar _current;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal TBarSeriesEnumerator(List<long> t, List<double> o, List<double> h, List<double> l, List<double> c, List<double> v)
|
||||
{
|
||||
_t = t;
|
||||
_o = o;
|
||||
_h = h;
|
||||
_l = l;
|
||||
_c = c;
|
||||
_v = v;
|
||||
_count = c.Count;
|
||||
_index = -1;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index + 1 >= _count)
|
||||
return false;
|
||||
|
||||
_index++;
|
||||
_current = new TBar(_t[_index], _o[_index], _h[_index], _l[_index], _c[_index], _v[_index]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public readonly TBar Current => _current;
|
||||
readonly object IEnumerator.Current => Current;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
_index = -1;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public readonly void Dispose() { }
|
||||
|
||||
public readonly bool Equals(TBarSeriesEnumerator other) =>
|
||||
ReferenceEquals(_t, other._t) &&
|
||||
ReferenceEquals(_c, other._c) &&
|
||||
_count == other._count &&
|
||||
_index == other._index;
|
||||
|
||||
public override readonly bool Equals(object? obj) =>
|
||||
obj is TBarSeriesEnumerator other && Equals(other);
|
||||
|
||||
public override readonly int GetHashCode() =>
|
||||
HashCode.Combine(RuntimeHelpers.GetHashCode(_t), RuntimeHelpers.GetHashCode(_c), _count, _index);
|
||||
|
||||
public static bool operator ==(TBarSeriesEnumerator left, TBarSeriesEnumerator right) => left.Equals(right);
|
||||
public static bool operator !=(TBarSeriesEnumerator left, TBarSeriesEnumerator right) => !left.Equals(right);
|
||||
}
|
||||
|
||||
// Performance-focused event args struct; not derived from EventArgs by design.
|
||||
// We intentionally deviate from the standard EventArgs pattern here for perf.
|
||||
#pragma warning disable MA0046 // The second parameter must be of type 'System.EventArgs' or a derived type
|
||||
@@ -94,6 +163,60 @@ public class TBarSeries : IReadOnlyList<TBar>
|
||||
public double LastClose { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _c.Count > 0 ? _c[^1] : double.NaN; }
|
||||
public double LastVolume { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _v.Count > 0 ? _v[^1] : double.NaN; }
|
||||
|
||||
/// <summary>
|
||||
/// Direct access to the underlying Time array as a Span.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<long> Times
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => CollectionsMarshal.AsSpan(_t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direct access to the underlying Open array as a Span for SIMD operations.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<double> OpenValues
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => CollectionsMarshal.AsSpan(_o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direct access to the underlying High array as a Span for SIMD operations.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<double> HighValues
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => CollectionsMarshal.AsSpan(_h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direct access to the underlying Low array as a Span for SIMD operations.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<double> LowValues
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => CollectionsMarshal.AsSpan(_l);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direct access to the underlying Close array as a Span for SIMD operations.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<double> CloseValues
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => CollectionsMarshal.AsSpan(_c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direct access to the underlying Volume array as a Span for SIMD operations.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<double> VolumeValues
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => CollectionsMarshal.AsSpan(_v);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(TBar bar, bool isNew = true)
|
||||
{
|
||||
@@ -150,13 +273,10 @@ public class TBarSeries : IReadOnlyList<TBar>
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<TBar> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < _c.Count; i++)
|
||||
{
|
||||
yield return new TBar(_t[i], _o[i], _h[i], _l[i], _c[i], _v[i]);
|
||||
}
|
||||
}
|
||||
// IEnumerable implementation with struct enumerator for zero-allocation iteration
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TBarSeriesEnumerator GetEnumerator() => new(_t, _o, _h, _l, _c, _v);
|
||||
|
||||
IEnumerator<TBar> IEnumerable<TBar>.GetEnumerator() => GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user