Files
Miha Kralj 060649192f docs: remove C# Implementation Considerations sections, clean up temp scripts, reorganize test files
- Remove 'C# Implementation Considerations' sections from 34 indicator .md files
- Delete 29 temp PowerShell scripts (_fix_mojibake.ps1, _hex_scan.ps1, etc.)
- Move test files into tests/ subdirectories for consistent project structure
- Add trader-focused bullet points to indicator documentation
2026-03-12 12:34:16 -07:00

1260 lines
37 KiB
C#

namespace QuanTAlib.Tests;
public class TBarSeriesTests
{
[Fact]
public void Constructor_Default_CreatesEmptySeries()
{
var series = new TBarSeries();
Assert.Empty(series);
}
[Fact]
public void Constructor_WithCapacity_CreatesEmptySeries()
{
var series = new TBarSeries(100);
Assert.Empty(series);
}
[Fact]
public void Name_DefaultValue_IsBar()
{
var series = new TBarSeries();
Assert.Equal("Bar", series.Name);
}
[Fact]
public void Name_CanBeSet()
{
var series = new TBarSeries { Name = "TestBars" };
Assert.Equal("TestBars", series.Name);
}
[Fact]
public void Add_NewBar_IncreasesCount()
{
var series = new TBarSeries();
var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000);
series.Add(bar, isNew: true);
Assert.Single(series);
Assert.Equal(105.0, series.Last.Close);
}
[Fact]
public void Add_UpdateBar_DoesNotIncreaseCount()
{
var series = new TBarSeries();
long time = DateTime.UtcNow.Ticks;
var bar1 = new TBar(time, 100, 110, 90, 105, 1000);
var bar2 = new TBar(time, 100, 112, 90, 108, 1200);
series.Add(bar1, isNew: true);
series.Add(bar2, isNew: false);
Assert.Single(series);
Assert.Equal(108.0, series.Last.Close);
Assert.Equal(112.0, series.Last.High);
}
[Fact]
public void Add_UpdateOnEmptySeries_AddsNewBar()
{
var series = new TBarSeries();
var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000);
series.Add(bar, isNew: false);
Assert.Single(series);
}
[Fact]
public void Add_WithLongTime_AddsBar()
{
var series = new TBarSeries();
long time = DateTime.UtcNow.Ticks;
series.Add(time, 100, 110, 90, 105, 1000, isNew: true);
Assert.Single(series);
Assert.Equal(time, series.Last.Time);
}
[Fact]
public void Add_WithDateTime_AddsBar()
{
var series = new TBarSeries();
var dt = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Utc);
series.Add(dt, 100, 110, 90, 105, 1000, isNew: true);
Assert.Single(series);
Assert.Equal(dt.Ticks, series.Last.Time);
}
[Fact]
public void Add_WithEnumerables_AddsMultipleBars()
{
var series = new TBarSeries();
long[] times = [100, 200, 300];
double[] opens = [10, 20, 30];
double[] highs = [15, 25, 35];
double[] lows = [5, 15, 25];
double[] closes = [12, 22, 32];
double[] volumes = [100, 200, 300];
series.Add(times, opens, highs, lows, closes, volumes);
Assert.Equal(3, series.Count);
Assert.Equal(10, series[0].Open);
Assert.Equal(32, series[2].Close);
}
[Fact]
public void SubSeries_AreUpdated()
{
var series = new TBarSeries();
var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000);
series.Add(bar, isNew: true);
Assert.Single(series.Open);
Assert.Single(series.High);
Assert.Single(series.Low);
Assert.Single(series.Close);
Assert.Single(series.Volume);
Assert.Equal(100.0, series.Open.Last.Value);
Assert.Equal(110.0, series.High.Last.Value);
Assert.Equal(90.0, series.Low.Last.Value);
Assert.Equal(105.0, series.Close.Last.Value);
Assert.Equal(1000.0, series.Volume.Last.Value);
}
[Fact]
public void SubSeries_Aliases_Work()
{
var series = new TBarSeries();
var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000);
series.Add(bar, isNew: true);
Assert.Same(series.Open, series.O);
Assert.Same(series.High, series.H);
Assert.Same(series.Low, series.L);
Assert.Same(series.Close, series.C);
Assert.Same(series.Volume, series.V);
}
[Fact]
public void SubSeries_HaveCorrectNames()
{
var series = new TBarSeries();
Assert.Equal("Open", series.Open.Name);
Assert.Equal("High", series.High.Name);
Assert.Equal("Low", series.Low.Name);
Assert.Equal("Close", series.Close.Name);
Assert.Equal("Volume", series.Volume.Name);
}
[Fact]
public void Last_EmptySeries_ReturnsDefault()
{
var series = new TBarSeries();
var last = series.Last;
Assert.Equal(0, last.Time);
Assert.Equal(0.0, last.Open);
Assert.Equal(0.0, last.Close);
}
[Fact]
public void Last_NonEmptySeries_ReturnsLastBar()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
var last = series.Last;
Assert.Equal(200, last.Time);
Assert.Equal(22.0, last.Close);
}
[Fact]
public void LastTime_EmptySeries_ReturnsZero()
{
var series = new TBarSeries();
Assert.Equal(0, series.LastTime);
}
[Fact]
public void LastTime_NonEmptySeries_ReturnsLastTime()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
Assert.Equal(200, series.LastTime);
}
[Fact]
public void LastOpen_EmptySeries_ReturnsNaN()
{
var series = new TBarSeries();
Assert.True(double.IsNaN(series.LastOpen));
}
[Fact]
public void LastOpen_NonEmptySeries_ReturnsLastOpen()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
Assert.Equal(20.0, series.LastOpen);
}
[Fact]
public void LastHigh_EmptySeries_ReturnsNaN()
{
var series = new TBarSeries();
Assert.True(double.IsNaN(series.LastHigh));
}
[Fact]
public void LastHigh_NonEmptySeries_ReturnsLastHigh()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
Assert.Equal(25.0, series.LastHigh);
}
[Fact]
public void LastLow_EmptySeries_ReturnsNaN()
{
var series = new TBarSeries();
Assert.True(double.IsNaN(series.LastLow));
}
[Fact]
public void LastLow_NonEmptySeries_ReturnsLastLow()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
Assert.Equal(15.0, series.LastLow);
}
[Fact]
public void LastClose_EmptySeries_ReturnsNaN()
{
var series = new TBarSeries();
Assert.True(double.IsNaN(series.LastClose));
}
[Fact]
public void LastClose_NonEmptySeries_ReturnsLastClose()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
Assert.Equal(22.0, series.LastClose);
}
[Fact]
public void LastVolume_EmptySeries_ReturnsNaN()
{
var series = new TBarSeries();
Assert.True(double.IsNaN(series.LastVolume));
}
[Fact]
public void LastVolume_NonEmptySeries_ReturnsLastVolume()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
Assert.Equal(200.0, series.LastVolume);
}
[Fact]
public void Indexer_ReturnsCorrectBar()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
series.Add(300, 30, 35, 25, 32, 300);
Assert.Equal(100, series[0].Time);
Assert.Equal(10.0, series[0].Open);
Assert.Equal(200, series[1].Time);
Assert.Equal(22.0, series[1].Close);
Assert.Equal(300, series[2].Time);
Assert.Equal(32.0, series[2].Close);
}
[Fact]
public void Count_ReturnsCorrectValue()
{
var series = new TBarSeries();
Assert.Empty(series);
series.Add(100, 10, 15, 5, 12, 100);
Assert.Single(series);
series.Add(200, 20, 25, 15, 22, 200);
Assert.Equal(2, series.Count);
}
[Fact]
public void GetEnumerator_IteratesAllBars()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
series.Add(300, 30, 35, 25, 32, 300);
var list = series.ToList();
Assert.Equal(3, list.Count);
Assert.Equal(10.0, list[0].Open);
Assert.Equal(22.0, list[1].Close);
Assert.Equal(32.0, list[2].Close);
}
[Fact]
public void GetEnumerator_NonGeneric_Works()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100, isNew: true);
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);
}
Assert.Equal(2, list.Count);
}
[Fact]
public void Pub_Event_IsRaisedOnAdd()
{
var series = new TBarSeries();
TBar? received = null;
series.Pub += (object? sender, in TBarEventArgs args) => received = args.Value;
var barToAdd = new TBar(100, 10, 15, 5, 12, 100);
series.Add(barToAdd, isNew: true);
Assert.NotNull(received);
Assert.Equal(100, received.Value.Time);
Assert.Equal(12.0, received.Value.Close);
}
[Fact]
public void Pub_Event_IsRaisedOnUpdate()
{
var series = new TBarSeries();
TBar? received = null;
series.Add(100, 10, 15, 5, 12, 100);
series.Pub += (object? sender, in TBarEventArgs args) => received = args.Value;
series.Add(100, 10, 18, 5, 15, 150, isNew: false);
Assert.NotNull(received);
Assert.Equal(15.0, received.Value.Close);
Assert.Equal(18.0, received.Value.High);
}
[Fact]
public void SubSeries_ShareSameTimeArray()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
Assert.Equal(series.Open.Times[0], series.Close.Times[0]);
Assert.Equal(series.High.Times[1], series.Volume.Times[1]);
}
[Fact]
public void Add_MultipleBars_MaintainsOrder()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
series.Add(300, 30, 35, 25, 32, 300);
Assert.Equal(3, series.Count);
Assert.Equal(100, series[0].Time);
Assert.Equal(200, series[1].Time);
Assert.Equal(300, series[2].Time);
}
[Fact]
public void Add_WithEnumerables_MismatchedLengths_ThrowsArgumentException()
{
var series = new TBarSeries();
long[] times = [100, 200, 300];
double[] opens = [10, 20]; // Mismatched length
double[] highs = [15, 25, 35];
double[] lows = [5, 15, 25];
double[] closes = [12, 22, 32];
double[] volumes = [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);
const 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);
}
[Fact]
public void GetEnumerator_ExplicitGenericInterface_Works()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
series.Add(300, 30, 35, 25, 32, 300);
// Explicitly call IEnumerable<TBar>.GetEnumerator() through interface cast
IEnumerable<TBar> genericEnumerable = series;
using var enumerator = genericEnumerable.GetEnumerator();
var closes = new List<double>();
while (enumerator.MoveNext())
{
closes.Add(enumerator.Current.Close);
}
Assert.Equal(3, closes.Count);
Assert.Equal(12.0, closes[0]);
Assert.Equal(22.0, closes[1]);
Assert.Equal(32.0, closes[2]);
}
[Fact]
public void GetEnumerator_ExplicitNonGenericInterface_Works()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
// Explicitly call IEnumerable.GetEnumerator() through interface cast
IEnumerable nonGenericEnumerable = series;
var enumerator = nonGenericEnumerable.GetEnumerator();
var closes = new List<double>();
while (enumerator.MoveNext())
{
var bar = (TBar)enumerator.Current;
closes.Add(bar.Close);
}
Assert.Equal(2, closes.Count);
Assert.Equal(12.0, closes[0]);
Assert.Equal(22.0, closes[1]);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: TryGetLast
// ────────────────────────────────────────────────────────────────────
[Fact]
public void TryGetLast_EmptySeries_ReturnsFalseAndDefault()
{
var series = new TBarSeries();
bool result = series.TryGetLast(out TBar bar);
Assert.False(result);
Assert.Equal(0, bar.Time);
Assert.Equal(0.0, bar.Close);
}
[Fact]
public void TryGetLast_NonEmptySeries_ReturnsTrueAndLastBar()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
bool result = series.TryGetLast(out TBar bar);
Assert.True(result);
Assert.Equal(200, bar.Time);
Assert.Equal(20.0, bar.Open);
Assert.Equal(25.0, bar.High);
Assert.Equal(15.0, bar.Low);
Assert.Equal(22.0, bar.Close);
Assert.Equal(200.0, bar.Volume);
}
[Fact]
public void TryGetLast_SingleBar_ReturnsOnlyBar()
{
var series = new TBarSeries();
series.Add(999, 50, 60, 40, 55, 500);
bool result = series.TryGetLast(out TBar bar);
Assert.True(result);
Assert.Equal(999, bar.Time);
Assert.Equal(55.0, bar.Close);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: Span Accessors (Times, OpenValues, HighValues, etc.)
// ────────────────────────────────────────────────────────────────────
[Fact]
public void Times_ReturnsCorrectSpan()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
series.Add(300, 30, 35, 25, 32, 300);
ReadOnlySpan<long> times = series.Times;
Assert.Equal(3, times.Length);
Assert.Equal(100, times[0]);
Assert.Equal(200, times[1]);
Assert.Equal(300, times[2]);
}
[Fact]
public void Times_EmptySeries_ReturnsEmptySpan()
{
var series = new TBarSeries();
ReadOnlySpan<long> times = series.Times;
Assert.Equal(0, times.Length);
}
[Fact]
public void OpenValues_ReturnsCorrectSpan()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
ReadOnlySpan<double> opens = series.OpenValues;
Assert.Equal(2, opens.Length);
Assert.Equal(10.0, opens[0]);
Assert.Equal(20.0, opens[1]);
}
[Fact]
public void HighValues_ReturnsCorrectSpan()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
ReadOnlySpan<double> highs = series.HighValues;
Assert.Equal(2, highs.Length);
Assert.Equal(15.0, highs[0]);
Assert.Equal(25.0, highs[1]);
}
[Fact]
public void LowValues_ReturnsCorrectSpan()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
ReadOnlySpan<double> lows = series.LowValues;
Assert.Equal(2, lows.Length);
Assert.Equal(5.0, lows[0]);
Assert.Equal(15.0, lows[1]);
}
[Fact]
public void CloseValues_ReturnsCorrectSpan()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
ReadOnlySpan<double> closes = series.CloseValues;
Assert.Equal(2, closes.Length);
Assert.Equal(12.0, closes[0]);
Assert.Equal(22.0, closes[1]);
}
[Fact]
public void VolumeValues_ReturnsCorrectSpan()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
ReadOnlySpan<double> vols = series.VolumeValues;
Assert.Equal(2, vols.Length);
Assert.Equal(100.0, vols[0]);
Assert.Equal(200.0, vols[1]);
}
[Fact]
public void SpanAccessors_EmptySeries_AllReturnEmptySpans()
{
var series = new TBarSeries();
Assert.Equal(0, series.OpenValues.Length);
Assert.Equal(0, series.HighValues.Length);
Assert.Equal(0, series.LowValues.Length);
Assert.Equal(0, series.CloseValues.Length);
Assert.Equal(0, series.VolumeValues.Length);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: AddRange with 6 span parameters
// ────────────────────────────────────────────────────────────────────
[Fact]
public void AddRange_WithSpans_AddsAllBars()
{
var series = new TBarSeries();
long[] times = [100, 200, 300];
double[] opens = [10, 20, 30];
double[] highs = [15, 25, 35];
double[] lows = [5, 15, 25];
double[] closes = [12, 22, 32];
double[] volumes = [1000, 2000, 3000];
series.AddRange(times, opens, highs, lows, closes, volumes);
Assert.Equal(3, series.Count);
Assert.Equal(100, series[0].Time);
Assert.Equal(10.0, series[0].Open);
Assert.Equal(15.0, series[0].High);
Assert.Equal(5.0, series[0].Low);
Assert.Equal(12.0, series[0].Close);
Assert.Equal(1000.0, series[0].Volume);
Assert.Equal(300, series[2].Time);
Assert.Equal(32.0, series[2].Close);
}
[Fact]
public void AddRange_WithSpans_EmptySpans_DoesNothing()
{
var series = new TBarSeries();
ReadOnlySpan<long> t = ReadOnlySpan<long>.Empty;
ReadOnlySpan<double> d = ReadOnlySpan<double>.Empty;
series.AddRange(t, d, d, d, d, d);
Assert.Empty(series);
}
[Fact]
public void AddRange_WithSpans_MismatchedLengths_ThrowsArgumentException()
{
var series = new TBarSeries();
long[] times = [100, 200, 300];
double[] opens = [10, 20]; // Mismatched
double[] highs = [15, 25, 35];
double[] lows = [5, 15, 25];
double[] closes = [12, 22, 32];
double[] volumes = [1000, 2000, 3000];
Assert.Throws<ArgumentException>(() =>
series.AddRange(times, opens, highs, lows, closes, volumes));
}
[Fact]
public void AddRange_WithSpans_DoesNotFirePubEvent()
{
var series = new TBarSeries();
int pubCount = 0;
series.Pub += (object? sender, in TBarEventArgs args) => pubCount++;
long[] times = [100, 200, 300];
double[] opens = [10, 20, 30];
double[] highs = [15, 25, 35];
double[] lows = [5, 15, 25];
double[] closes = [12, 22, 32];
double[] volumes = [1000, 2000, 3000];
series.AddRange(times, opens, highs, lows, closes, volumes);
Assert.Equal(0, pubCount);
Assert.Equal(3, series.Count);
}
[Fact]
public void AddRange_WithSpans_AppendsToExistingData()
{
var series = new TBarSeries();
series.Add(50, 5, 8, 3, 6, 500);
long[] times = [100, 200];
double[] opens = [10, 20];
double[] highs = [15, 25];
double[] lows = [5, 15];
double[] closes = [12, 22];
double[] volumes = [1000, 2000];
series.AddRange(times, opens, highs, lows, closes, volumes);
Assert.Equal(3, series.Count);
Assert.Equal(50, series[0].Time);
Assert.Equal(100, series[1].Time);
Assert.Equal(200, series[2].Time);
}
[Fact]
public void AddRange_WithSpans_SubSeriesReflectData()
{
var series = new TBarSeries();
long[] times = [100, 200];
double[] opens = [10, 20];
double[] highs = [15, 25];
double[] lows = [5, 15];
double[] closes = [12, 22];
double[] volumes = [1000, 2000];
series.AddRange(times, opens, highs, lows, closes, volumes);
// Sub-series share underlying storage, so they reflect AddRange data
Assert.Equal(2, series.Open.Count);
Assert.Equal(10.0, series.Open[0].Value);
Assert.Equal(20.0, series.Open[1].Value);
Assert.Equal(22.0, series.Close[1].Value);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: AddRange with ReadOnlySpan<TBar>
// ────────────────────────────────────────────────────────────────────
[Fact]
public void AddRange_WithTBarSpan_AddsAllBars()
{
var series = new TBarSeries();
TBar[] bars =
[
new TBar(100, 10, 15, 5, 12, 1000),
new TBar(200, 20, 25, 15, 22, 2000),
new TBar(300, 30, 35, 25, 32, 3000),
];
series.AddRange(bars);
Assert.Equal(3, series.Count);
Assert.Equal(100, series[0].Time);
Assert.Equal(12.0, series[0].Close);
Assert.Equal(300, series[2].Time);
Assert.Equal(32.0, series[2].Close);
Assert.Equal(3000.0, series[2].Volume);
}
[Fact]
public void AddRange_WithTBarSpan_EmptySpan_DoesNothing()
{
var series = new TBarSeries();
series.AddRange(ReadOnlySpan<TBar>.Empty);
Assert.Empty(series);
}
[Fact]
public void AddRange_WithTBarSpan_AppendsToExistingData()
{
var series = new TBarSeries();
series.Add(50, 5, 8, 3, 6, 500);
TBar[] bars =
[
new TBar(100, 10, 15, 5, 12, 1000),
new TBar(200, 20, 25, 15, 22, 2000),
];
series.AddRange(bars);
Assert.Equal(3, series.Count);
Assert.Equal(50, series[0].Time);
Assert.Equal(100, series[1].Time);
Assert.Equal(200, series[2].Time);
}
[Fact]
public void AddRange_WithTBarSpan_DoesNotFirePubEvent()
{
var series = new TBarSeries();
int pubCount = 0;
series.Pub += (object? sender, in TBarEventArgs args) => pubCount++;
TBar[] bars = [new TBar(100, 10, 15, 5, 12, 1000)];
series.AddRange(bars);
Assert.Equal(0, pubCount);
Assert.Single(series);
}
[Fact]
public void AddRange_WithTBarSpan_CorrectlySplitsToSoA()
{
var series = new TBarSeries();
TBar[] bars =
[
new TBar(100, 10, 15, 5, 12, 1000),
new TBar(200, 20, 25, 15, 22, 2000),
];
series.AddRange(bars);
// Verify SoA layout via span accessors
Assert.Equal(100, series.Times[0]);
Assert.Equal(200, series.Times[1]);
Assert.Equal(10.0, series.OpenValues[0]);
Assert.Equal(20.0, series.OpenValues[1]);
Assert.Equal(15.0, series.HighValues[0]);
Assert.Equal(25.0, series.HighValues[1]);
Assert.Equal(5.0, series.LowValues[0]);
Assert.Equal(15.0, series.LowValues[1]);
Assert.Equal(12.0, series.CloseValues[0]);
Assert.Equal(22.0, series.CloseValues[1]);
Assert.Equal(1000.0, series.VolumeValues[0]);
Assert.Equal(2000.0, series.VolumeValues[1]);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: TBarEventArgs struct
// ────────────────────────────────────────────────────────────────────
[Fact]
public void TBarEventArgs_Equals_SameValues_ReturnsTrue()
{
var bar = new TBar(100, 10, 15, 5, 12, 1000);
var a = new TBarEventArgs { Value = bar, IsNew = true };
var b = new TBarEventArgs { Value = bar, IsNew = true };
Assert.True(a.Equals(b));
Assert.True(a == b);
Assert.False(a != b);
}
[Fact]
public void TBarEventArgs_Equals_DifferentIsNew_ReturnsFalse()
{
var bar = new TBar(100, 10, 15, 5, 12, 1000);
var a = new TBarEventArgs { Value = bar, IsNew = true };
var b = new TBarEventArgs { Value = bar, IsNew = false };
Assert.False(a.Equals(b));
Assert.False(a == b);
Assert.True(a != b);
}
[Fact]
public void TBarEventArgs_Equals_DifferentValue_ReturnsFalse()
{
var bar1 = new TBar(100, 10, 15, 5, 12, 1000);
var bar2 = new TBar(200, 20, 25, 15, 22, 2000);
var a = new TBarEventArgs { Value = bar1, IsNew = true };
var b = new TBarEventArgs { Value = bar2, IsNew = true };
Assert.False(a.Equals(b));
}
[Fact]
public void TBarEventArgs_Equals_Object_SameValues_ReturnsTrue()
{
var bar = new TBar(100, 10, 15, 5, 12, 1000);
var a = new TBarEventArgs { Value = bar, IsNew = true };
object b = new TBarEventArgs { Value = bar, IsNew = true };
Assert.True(a.Equals(b));
}
[Fact]
public void TBarEventArgs_Equals_Object_DifferentType_ReturnsFalse()
{
var bar = new TBar(100, 10, 15, 5, 12, 1000);
var a = new TBarEventArgs { Value = bar, IsNew = true };
Assert.False(a.Equals("not a TBarEventArgs"));
Assert.False(a.Equals(null));
}
[Fact]
public void TBarEventArgs_GetHashCode_EqualObjects_SameHash()
{
var bar = new TBar(100, 10, 15, 5, 12, 1000);
var a = new TBarEventArgs { Value = bar, IsNew = true };
var b = new TBarEventArgs { Value = bar, IsNew = true };
Assert.Equal(a.GetHashCode(), b.GetHashCode());
}
[Fact]
public void TBarEventArgs_GetHashCode_DifferentObjects_LikelyDifferentHash()
{
var bar1 = new TBar(100, 10, 15, 5, 12, 1000);
var bar2 = new TBar(200, 20, 25, 15, 22, 2000);
var a = new TBarEventArgs { Value = bar1, IsNew = true };
var b = new TBarEventArgs { Value = bar2, IsNew = false };
// Not guaranteed but extremely likely for distinct values
Assert.NotEqual(a.GetHashCode(), b.GetHashCode());
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: TBarSeriesEnumerator struct
// ────────────────────────────────────────────────────────────────────
[Fact]
public void TBarSeriesEnumerator_Reset_AllowsReIteration()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
var enumerator = series.GetEnumerator();
// First pass
int count1 = 0;
while (enumerator.MoveNext()) { count1++; }
Assert.Equal(2, count1);
// Reset and re-iterate
enumerator.Reset();
int count2 = 0;
while (enumerator.MoveNext()) { count2++; }
Assert.Equal(2, count2);
}
[Fact]
public void TBarSeriesEnumerator_Dispose_DoesNotThrow()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
var enumerator = series.GetEnumerator();
enumerator.MoveNext();
enumerator.Dispose(); // Should be no-op
Assert.Equal(12.0, enumerator.Current.Close);
}
[Fact]
public void TBarSeriesEnumerator_Equals_SameState_ReturnsTrue()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
var a = series.GetEnumerator();
var b = series.GetEnumerator();
// Both at initial state (_index = -1)
Assert.True(a.Equals(b));
Assert.True(a == b);
Assert.False(a != b);
}
[Fact]
public void TBarSeriesEnumerator_Equals_DifferentState_ReturnsFalse()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
var a = series.GetEnumerator();
var b = series.GetEnumerator();
a.MoveNext(); // a is at index 0, b is at -1
Assert.False(a.Equals(b));
Assert.False(a == b);
Assert.True(a != b);
}
[Fact]
public void TBarSeriesEnumerator_Equals_Object_SameState_ReturnsTrue()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
var a = series.GetEnumerator();
object b = series.GetEnumerator();
Assert.True(a.Equals(b));
}
[Fact]
public void TBarSeriesEnumerator_Equals_Object_DifferentType_ReturnsFalse()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
var a = series.GetEnumerator();
Assert.False(a.Equals("not an enumerator"));
Assert.False(a.Equals(null));
}
[Fact]
public void TBarSeriesEnumerator_GetHashCode_SameState_SameHash()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
var a = series.GetEnumerator();
var b = series.GetEnumerator();
Assert.Equal(a.GetHashCode(), b.GetHashCode());
}
[Fact]
public void TBarSeriesEnumerator_GetHashCode_DifferentState_LikelyDifferentHash()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
series.Add(200, 20, 25, 15, 22, 200);
var a = series.GetEnumerator();
var b = series.GetEnumerator();
a.MoveNext();
Assert.NotEqual(a.GetHashCode(), b.GetHashCode());
}
[Fact]
public void TBarSeriesEnumerator_Current_ViaIEnumerator_ReturnsBoxedTBar()
{
var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100);
IEnumerator enumerator = series.GetEnumerator();
enumerator.MoveNext();
object current = enumerator.Current;
Assert.IsType<TBar>(current);
Assert.Equal(12.0, ((TBar)current).Close);
}
[Fact]
public void TBarSeriesEnumerator_DifferentSeries_NotEqual()
{
var series1 = new TBarSeries();
series1.Add(100, 10, 15, 5, 12, 100);
var series2 = new TBarSeries();
series2.Add(100, 10, 15, 5, 12, 100);
var a = series1.GetEnumerator();
var b = series2.GetEnumerator();
// Different underlying list references -> not equal
Assert.False(a.Equals(b));
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: AddRange large data set (capacity pre-alloc path)
// ────────────────────────────────────────────────────────────────────
[Fact]
public void AddRange_LargeDataSet_AllBarsAccessible()
{
var series = new TBarSeries();
const int N = 1000;
var times = new long[N];
var opens = new double[N];
var highs = new double[N];
var lows = new double[N];
var closes = new double[N];
var volumes = new double[N];
for (int i = 0; i < N; i++)
{
times[i] = i;
opens[i] = i * 10.0;
highs[i] = i * 10.0 + 5.0;
lows[i] = i * 10.0 - 5.0;
closes[i] = i * 10.0 + 2.0;
volumes[i] = i * 100.0;
}
series.AddRange(times, opens, highs, lows, closes, volumes);
Assert.Equal(N, series.Count);
Assert.Equal(0, series[0].Time);
Assert.Equal((N - 1) * 10.0 + 2.0, series[N - 1].Close);
Assert.Equal((N - 1) * 100.0, series[N - 1].Volume);
}
[Fact]
public void AddRange_TBarSpan_LargeDataSet_AllBarsAccessible()
{
var series = new TBarSeries();
const int N = 500;
var bars = new TBar[N];
for (int i = 0; i < N; i++)
{
bars[i] = new TBar(i, i * 10.0, i * 10.0 + 5.0, i * 10.0 - 5.0, i * 10.0 + 2.0, i * 100.0);
}
series.AddRange(bars);
Assert.Equal(N, series.Count);
Assert.Equal(0, series[0].Time);
Assert.Equal(2.0, series[0].Close);
Assert.Equal((N - 1) * 10.0 + 2.0, series[N - 1].Close);
}
}