mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-16 01:28:05 +00:00
first iteration
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class CsvFeedTests
|
||||
{
|
||||
private const string TestCsvPath = "daily_IBM.csv";
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ValidFile_LoadsData()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
Assert.NotNull(feed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NonExistentFile_ThrowsFileNotFoundException()
|
||||
{
|
||||
Assert.Throws<FileNotFoundException>(() => new CsvFeed("nonexistent.csv"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullPath_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new CsvFeed(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_EmptyPath_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new CsvFeed(""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Next_StreamsDataChronologically()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
// Get first bar
|
||||
var bar1 = feed.Next(isNew: true);
|
||||
Assert.True(bar1.Time > 0);
|
||||
|
||||
// Get second bar - should be later in time
|
||||
var bar2 = feed.Next(isNew: true);
|
||||
Assert.True(bar2.Time > bar1.Time);
|
||||
|
||||
// Get third bar
|
||||
var bar3 = feed.Next(isNew: true);
|
||||
Assert.True(bar3.Time > bar2.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Next_WithRefParameter_StreamsCorrectly()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
bool isNew = true;
|
||||
var bar1 = feed.Next(ref isNew);
|
||||
Assert.True(isNew); // Should still be true
|
||||
Assert.True(bar1.Time > 0);
|
||||
|
||||
isNew = true;
|
||||
var bar2 = feed.Next(ref isNew);
|
||||
Assert.True(isNew);
|
||||
Assert.True(bar2.Time > bar1.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Next_UpdateCurrentBar_ReturnsSameBar()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
// Get first bar
|
||||
var bar1 = feed.Next(isNew: true);
|
||||
|
||||
// Update current bar (should return same bar)
|
||||
var bar2 = feed.Next(isNew: false);
|
||||
Assert.Equal(bar1.Time, bar2.Time);
|
||||
Assert.Equal(bar1.Close, bar2.Close);
|
||||
|
||||
// Get next bar
|
||||
var bar3 = feed.Next(isNew: true);
|
||||
Assert.True(bar3.Time > bar1.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Next_EndOfData_SignalsNoMoreData()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
// Stream through all data
|
||||
TBar lastBar = default;
|
||||
bool isNew = true;
|
||||
int count = 0;
|
||||
|
||||
while (isNew && count < 200) // Safety limit
|
||||
{
|
||||
lastBar = feed.Next(ref isNew);
|
||||
count++;
|
||||
}
|
||||
|
||||
// Should have reached end and isNew should be false
|
||||
Assert.False(isNew);
|
||||
Assert.True(lastBar.Time > 0);
|
||||
|
||||
// Calling again should return same bar with isNew=false
|
||||
isNew = true;
|
||||
var finalBar = feed.Next(ref isNew);
|
||||
Assert.False(isNew);
|
||||
Assert.Equal(lastBar.Time, finalBar.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fetch_ReturnsCorrectNumberOfBars()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
var startTime = new DateTime(2025, 7, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
|
||||
var interval = TimeSpan.FromDays(1);
|
||||
|
||||
var series = feed.Fetch(10, startTime, interval);
|
||||
|
||||
Assert.True(series.Count > 0);
|
||||
Assert.True(series.Count <= 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fetch_InvalidCount_ThrowsArgumentException()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
var startTime = DateTime.UtcNow.Ticks;
|
||||
var interval = TimeSpan.FromDays(1);
|
||||
|
||||
Assert.Throws<ArgumentException>(() => feed.Fetch(0, startTime, interval));
|
||||
Assert.Throws<ArgumentException>(() => feed.Fetch(-1, startTime, interval));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fetch_ResetsStreamingPosition()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
// Stream a few bars
|
||||
feed.Next(isNew: true);
|
||||
feed.Next(isNew: true);
|
||||
feed.Next(isNew: true);
|
||||
|
||||
// Fetch from start
|
||||
var startTime = new DateTime(2025, 7, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
|
||||
var series = feed.Fetch(5, startTime, TimeSpan.FromDays(1));
|
||||
|
||||
// Next should now stream from fetched position
|
||||
var bar = feed.Next(isNew: true);
|
||||
Assert.True(bar.Time >= startTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadFromCsv_ParsesValuesCorrectly()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
// Get first bar (oldest in chronological order)
|
||||
var bar = feed.Next(isNew: true);
|
||||
|
||||
// Verify it has valid OHLCV data
|
||||
Assert.True(bar.Open > 0);
|
||||
Assert.True(bar.High >= bar.Open);
|
||||
Assert.True(bar.High >= bar.Close);
|
||||
Assert.True(bar.Low <= bar.Open);
|
||||
Assert.True(bar.Low <= bar.Close);
|
||||
Assert.True(bar.Close > 0);
|
||||
Assert.True(bar.Volume > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadFromCsv_DataInChronologicalOrder()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
var bars = new List<TBar>();
|
||||
bool isNew = true;
|
||||
|
||||
// Collect first 10 bars
|
||||
for (int i = 0; i < 10 && isNew; i++)
|
||||
{
|
||||
bars.Add(feed.Next(ref isNew));
|
||||
}
|
||||
|
||||
// Verify chronological order (each bar later than previous)
|
||||
for (int i = 1; i < bars.Count; i++)
|
||||
{
|
||||
Assert.True(bars[i].Time > bars[i - 1].Time,
|
||||
$"Bar {i} time ({bars[i].AsDateTime}) should be after bar {i-1} time ({bars[i-1].AsDateTime})");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CsvFeed_WorksWithIFeedInterface()
|
||||
{
|
||||
IFeed feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
var bar1 = feed.Next(isNew: true);
|
||||
Assert.True(bar1.Time > 0);
|
||||
|
||||
var bar2 = feed.Next(isNew: true);
|
||||
Assert.True(bar2.Time > bar1.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Next_MixedNewAndUpdate_WorksCorrectly()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
var bar1 = feed.Next(isNew: true);
|
||||
var bar1Update = feed.Next(isNew: false);
|
||||
Assert.Equal(bar1.Time, bar1Update.Time);
|
||||
|
||||
var bar2 = feed.Next(isNew: true);
|
||||
Assert.True(bar2.Time > bar1.Time);
|
||||
|
||||
var bar2Update = feed.Next(isNew: false);
|
||||
Assert.Equal(bar2.Time, bar2Update.Time);
|
||||
|
||||
var bar3 = feed.Next(isNew: true);
|
||||
Assert.True(bar3.Time > bar2.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fetch_WithEarlyStartTime_ReturnsData()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
// Start from very early date (before any data)
|
||||
var startTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
|
||||
var series = feed.Fetch(5, startTime, TimeSpan.FromDays(1));
|
||||
|
||||
// Should return data starting from first available bar
|
||||
Assert.True(series.Count > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fetch_WithFutureStartTime_ReturnsEmpty()
|
||||
{
|
||||
var feed = new CsvFeed(TestCsvPath);
|
||||
|
||||
// Start from future date (after all data)
|
||||
var startTime = new DateTime(2030, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
|
||||
var series = feed.Fetch(5, startTime, TimeSpan.FromDays(1));
|
||||
|
||||
// Should return empty or minimal data
|
||||
Assert.True(series.Count == 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class GBMTests
|
||||
{
|
||||
[Fact]
|
||||
public void Next_DefaultParameter_GeneratesNewBar()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
|
||||
var bar1 = gbm.Next();
|
||||
var bar2 = gbm.Next();
|
||||
|
||||
Assert.NotEqual(bar1.Time, bar2.Time);
|
||||
Assert.True(bar2.Time > bar1.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Next_IsNewTrue_AdvancesToNewBar()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
|
||||
var bar1 = gbm.Next(isNew: true);
|
||||
var bar2 = gbm.Next(isNew: true);
|
||||
|
||||
Assert.NotEqual(bar1.Time, bar2.Time);
|
||||
Assert.True(bar2.Time > bar1.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Next_IsNewFalse_UpdatesCurrentBar()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
|
||||
var bar1 = gbm.Next(isNew: true);
|
||||
long initialTime = bar1.Time;
|
||||
|
||||
var bar2 = gbm.Next(isNew: false);
|
||||
|
||||
Assert.Equal(initialTime, bar2.Time);
|
||||
// Price likely changed (GBM random walk)
|
||||
Assert.NotEqual(bar1.Close, bar2.Close);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Next_RefBool_HonorsRequest()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
|
||||
// GBM always honors isNew - parameter should remain unchanged
|
||||
bool isNew1 = true;
|
||||
var bar1 = gbm.Next(ref isNew1);
|
||||
Assert.True(isNew1, "GBM should honor isNew=true request");
|
||||
|
||||
bool isNew2 = false;
|
||||
long time1 = bar1.Time;
|
||||
var bar2 = gbm.Next(ref isNew2);
|
||||
Assert.False(isNew2, "GBM should honor isNew=false request");
|
||||
Assert.Equal(time1, bar2.Time);
|
||||
|
||||
bool isNew3 = true;
|
||||
var bar3 = gbm.Next(ref isNew3);
|
||||
Assert.True(isNew3, "GBM should honor isNew=true request");
|
||||
Assert.NotEqual(time1, bar3.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fetch_GeneratesCorrectCount()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
int count = 10;
|
||||
long startTime = DateTime.UtcNow.Ticks;
|
||||
var interval = TimeSpan.FromMinutes(1);
|
||||
|
||||
var series = gbm.Fetch(count, startTime, interval);
|
||||
|
||||
Assert.Equal(count, series.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fetch_GeneratesSequentialBars()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
long startTime = DateTime.UtcNow.Ticks;
|
||||
var interval = TimeSpan.FromMinutes(1);
|
||||
|
||||
var series = gbm.Fetch(5, startTime, interval);
|
||||
|
||||
// Verify time sequence
|
||||
for (int i = 1; i < series.Count; i++)
|
||||
{
|
||||
Assert.True(series[i].Time > series[i - 1].Time);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fetch_RespectsInterval()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
var interval = TimeSpan.FromHours(1);
|
||||
long startTime = DateTime.UtcNow.Ticks;
|
||||
|
||||
var series = gbm.Fetch(5, startTime, interval);
|
||||
|
||||
// Verify interval spacing
|
||||
for (int i = 1; i < series.Count; i++)
|
||||
{
|
||||
long expectedDiff = interval.Ticks;
|
||||
long actualDiff = series[i].Time - series[i - 1].Time;
|
||||
Assert.Equal(expectedDiff, actualDiff);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fetch_StartsAtSpecifiedTime()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
var startTime = new DateTime(2024, 1, 1, 9, 30, 0, DateTimeKind.Utc).Ticks;
|
||||
var interval = TimeSpan.FromMinutes(5);
|
||||
|
||||
var series = gbm.Fetch(3, startTime, interval);
|
||||
|
||||
Assert.Equal(startTime, series[0].Time);
|
||||
Assert.Equal(startTime + interval.Ticks, series[1].Time);
|
||||
Assert.Equal(startTime + 2 * interval.Ticks, series[2].Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fetch_WithDifferentIntervals_WorksCorrectly()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
long startTime = DateTime.UtcNow.Ticks;
|
||||
|
||||
// Test different intervals
|
||||
var intervals = new[] {
|
||||
TimeSpan.FromMinutes(1),
|
||||
TimeSpan.FromMinutes(5),
|
||||
TimeSpan.FromHours(1)
|
||||
};
|
||||
|
||||
foreach (var interval in intervals)
|
||||
{
|
||||
var series = gbm.Fetch(3, startTime, interval);
|
||||
|
||||
// Verify spacing
|
||||
for (int i = 1; i < series.Count; i++)
|
||||
{
|
||||
long expectedDiff = interval.Ticks;
|
||||
long actualDiff = series[i].Time - series[i - 1].Time;
|
||||
Assert.Equal(expectedDiff, actualDiff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GeneratesRealisticOHLCV()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
long startTime = DateTime.UtcNow.Ticks;
|
||||
var interval = TimeSpan.FromMinutes(1);
|
||||
var series = gbm.Fetch(10, startTime, interval);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
var bar = series[i];
|
||||
|
||||
// High should be >= max(Open, Close)
|
||||
Assert.True(bar.High >= Math.Max(bar.Open, bar.Close));
|
||||
|
||||
// Low should be <= min(Open, Close)
|
||||
Assert.True(bar.Low <= Math.Min(bar.Open, bar.Close));
|
||||
|
||||
// Volume should be positive
|
||||
Assert.True(bar.Volume > 0);
|
||||
|
||||
// All prices should be positive
|
||||
Assert.True(bar.Open > 0);
|
||||
Assert.True(bar.High > 0);
|
||||
Assert.True(bar.Low > 0);
|
||||
Assert.True(bar.Close > 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IntraBarUpdates_ModifyCurrentBar()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
|
||||
var bar1 = gbm.Next(isNew: true);
|
||||
long initialTime = bar1.Time;
|
||||
double initialClose = bar1.Close;
|
||||
|
||||
// Loop until price changes (random walk might stay same but unlikely)
|
||||
bool changed = false;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var bar = gbm.Next(isNew: false);
|
||||
Assert.Equal(initialTime, bar.Time);
|
||||
if (Math.Abs(bar.Close - initialClose) > double.Epsilon)
|
||||
{
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(changed, "Price should change during intra-bar updates");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MixedStreamingAndBatch_WorksCorrectly()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
|
||||
// Start with streaming
|
||||
var bar1 = gbm.Next();
|
||||
var bar2 = gbm.Next();
|
||||
|
||||
// Batch generation with explicit time
|
||||
long startTime = bar2.Time + TimeSpan.FromMinutes(1).Ticks;
|
||||
var interval = TimeSpan.FromMinutes(1);
|
||||
var series = gbm.Fetch(3, startTime, interval);
|
||||
|
||||
Assert.True(series[0].Time > bar2.Time);
|
||||
Assert.Equal(3, series.Count);
|
||||
|
||||
// Continue streaming after batch (uses internal state)
|
||||
var bar3 = gbm.Next();
|
||||
Assert.True(bar3.Time > series[2].Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftAndVolatility_AffectPriceMovement()
|
||||
{
|
||||
// High volatility should produce more price variation
|
||||
var gbmLowVol = new GBM(startPrice: 100.0, mu: 0.0, sigma: 0.01);
|
||||
var gbmHighVol = new GBM(startPrice: 100.0, mu: 0.0, sigma: 0.5);
|
||||
|
||||
long startTime = DateTime.UtcNow.Ticks;
|
||||
var interval = TimeSpan.FromMinutes(1);
|
||||
var seriesLow = gbmLowVol.Fetch(100, startTime, interval);
|
||||
var seriesHigh = gbmHighVol.Fetch(100, startTime, interval);
|
||||
|
||||
// Calculate price ranges
|
||||
double rangeLow = seriesLow[99].Close - seriesLow[0].Open;
|
||||
double rangeHigh = seriesHigh[99].Close - seriesHigh[0].Open;
|
||||
|
||||
// High volatility should generally produce larger absolute movements
|
||||
Assert.True(Math.Abs(rangeHigh) > Math.Abs(rangeLow) * 0.5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConsecutiveCalls_MaintainContinuity()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
|
||||
var bar1 = gbm.Next();
|
||||
var bar2 = gbm.Next();
|
||||
|
||||
// bar2.Open should equal bar1.Close (continuity)
|
||||
Assert.Equal(bar1.Close, bar2.Open);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stateless_NoHistoryStorage()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
|
||||
// Generate multiple bars
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
gbm.Next();
|
||||
}
|
||||
|
||||
// GBM should not expose any history storage
|
||||
var type = gbm.GetType();
|
||||
var barsProperty = type.GetProperty("Bars");
|
||||
|
||||
Assert.Null(barsProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0;net8.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<EnableMicrosoftTestingPlatform>false</EnableMicrosoftTestingPlatform>
|
||||
<TestingPlatformDotnetTestSupport>false</TestingPlatformDotnetTestSupport>
|
||||
<NoWarn>$(NoWarn);CS8892</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Testing.Platform.MSBuild" Version="1.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\lib\quantalib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="daily_IBM.csv">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class SimdExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void SumSIMD_EmptySpan_ReturnsZero()
|
||||
{
|
||||
var span = ReadOnlySpan<double>.Empty;
|
||||
Assert.Equal(0.0, span.SumSIMD());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SumSIMD_SingleElement_ReturnsElement()
|
||||
{
|
||||
double[] data = [42.5];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
Assert.Equal(42.5, span.SumSIMD());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SumSIMD_MultipleElements_ReturnsCorrectSum()
|
||||
{
|
||||
double[] data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
Assert.Equal(55.0, span.SumSIMD(), precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SumSIMD_LargeArray_ReturnsCorrectSum()
|
||||
{
|
||||
double[] data = new double[1000];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i] = i + 1.0;
|
||||
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
double expected = 1000.0 * 1001.0 / 2.0; // Sum of 1..1000
|
||||
Assert.Equal(expected, span.SumSIMD(), precision: 8);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinSIMD_EmptySpan_ReturnsNaN()
|
||||
{
|
||||
var span = ReadOnlySpan<double>.Empty;
|
||||
Assert.True(double.IsNaN(span.MinSIMD()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinSIMD_SingleElement_ReturnsElement()
|
||||
{
|
||||
double[] data = [42.5];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
Assert.Equal(42.5, span.MinSIMD());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinSIMD_MultipleElements_ReturnsMinimum()
|
||||
{
|
||||
double[] data = [5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0, 4.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
Assert.Equal(1.0, span.MinSIMD());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MaxSIMD_EmptySpan_ReturnsNaN()
|
||||
{
|
||||
var span = ReadOnlySpan<double>.Empty;
|
||||
Assert.True(double.IsNaN(span.MaxSIMD()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MaxSIMD_SingleElement_ReturnsElement()
|
||||
{
|
||||
double[] data = [42.5];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
Assert.Equal(42.5, span.MaxSIMD());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MaxSIMD_MultipleElements_ReturnsMaximum()
|
||||
{
|
||||
double[] data = [5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0, 4.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
Assert.Equal(9.0, span.MaxSIMD());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AverageSIMD_EmptySpan_ReturnsNaN()
|
||||
{
|
||||
var span = ReadOnlySpan<double>.Empty;
|
||||
Assert.True(double.IsNaN(span.AverageSIMD()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AverageSIMD_MultipleElements_ReturnsCorrectAverage()
|
||||
{
|
||||
double[] data = [1.0, 2.0, 3.0, 4.0, 5.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
Assert.Equal(3.0, span.AverageSIMD(), precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VarianceSIMD_LessThanTwoElements_ReturnsNaN()
|
||||
{
|
||||
double[] data = [42.5];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
Assert.True(double.IsNaN(span.VarianceSIMD()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VarianceSIMD_MultipleElements_ReturnsCorrectVariance()
|
||||
{
|
||||
double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
|
||||
// Expected variance: 4.571428... (sample variance)
|
||||
double variance = span.VarianceSIMD();
|
||||
Assert.True(Math.Abs(variance - 4.571428) < 0.0001);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StdDevSIMD_MultipleElements_ReturnsCorrectStdDev()
|
||||
{
|
||||
double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
|
||||
// Expected std dev: sqrt(4.571428) ≈ 2.138
|
||||
double stdDev = span.StdDevSIMD();
|
||||
Assert.True(Math.Abs(stdDev - 2.138) < 0.01);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinMaxSIMD_EmptySpan_ReturnsBothNaN()
|
||||
{
|
||||
var span = ReadOnlySpan<double>.Empty;
|
||||
var (min, max) = span.MinMaxSIMD();
|
||||
Assert.True(double.IsNaN(min));
|
||||
Assert.True(double.IsNaN(max));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinMaxSIMD_SingleElement_ReturnsSameValue()
|
||||
{
|
||||
double[] data = [42.5];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
var (min, max) = span.MinMaxSIMD();
|
||||
Assert.Equal(42.5, min);
|
||||
Assert.Equal(42.5, max);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinMaxSIMD_MultipleElements_ReturnsCorrectMinMax()
|
||||
{
|
||||
double[] data = [5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0, 4.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
var (min, max) = span.MinMaxSIMD();
|
||||
Assert.Equal(1.0, min);
|
||||
Assert.Equal(9.0, max);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SIMD_WorksWithTSeriesValues()
|
||||
{
|
||||
var series = new TSeries(100);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
series.Add(DateTime.UtcNow.Ticks + i, i + 1.0);
|
||||
}
|
||||
|
||||
var values = series.Values;
|
||||
|
||||
double sum = values.SumSIMD();
|
||||
double avg = values.AverageSIMD();
|
||||
double min = values.MinSIMD();
|
||||
double max = values.MaxSIMD();
|
||||
var (minAlt, maxAlt) = values.MinMaxSIMD();
|
||||
|
||||
Assert.Equal(5050.0, sum, precision: 8); // Sum of 1..100
|
||||
Assert.Equal(50.5, avg, precision: 8);
|
||||
Assert.Equal(1.0, min);
|
||||
Assert.Equal(100.0, max);
|
||||
Assert.Equal(min, minAlt);
|
||||
Assert.Equal(max, maxAlt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SIMD_WorksWithTBarSeriesClose()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
long startTime = DateTime.UtcNow.Ticks;
|
||||
var interval = TimeSpan.FromMinutes(1);
|
||||
var bars = gbm.Fetch(1000, startTime, interval);
|
||||
|
||||
var closeValues = bars.Close.Values;
|
||||
|
||||
double sum = closeValues.SumSIMD();
|
||||
double avg = closeValues.AverageSIMD();
|
||||
double min = closeValues.MinSIMD();
|
||||
double max = closeValues.MaxSIMD();
|
||||
|
||||
Assert.True(sum > 0);
|
||||
Assert.True(avg > 0);
|
||||
Assert.True(min > 0);
|
||||
Assert.True(max > min);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SIMD_PerformanceTest_LargeDataset()
|
||||
{
|
||||
// Generate large dataset
|
||||
var gbm = new GBM(startPrice: 100.0);
|
||||
long startTime = DateTime.UtcNow.Ticks;
|
||||
var interval = TimeSpan.FromMinutes(1);
|
||||
var bars = gbm.Fetch(10000, startTime, interval);
|
||||
var closeValues = bars.Close.Values;
|
||||
|
||||
// Warm up
|
||||
_ = closeValues.SumSIMD();
|
||||
|
||||
// Test SIMD operations
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
double sum = closeValues.SumSIMD();
|
||||
double avg = closeValues.AverageSIMD();
|
||||
double min = closeValues.MinSIMD();
|
||||
double max = closeValues.MaxSIMD();
|
||||
var (minAlt, maxAlt) = closeValues.MinMaxSIMD();
|
||||
double variance = closeValues.VarianceSIMD();
|
||||
double stdDev = closeValues.StdDevSIMD();
|
||||
|
||||
sw.Stop();
|
||||
|
||||
// Verify results are valid
|
||||
Assert.True(sum > 0);
|
||||
Assert.True(avg > 0);
|
||||
Assert.True(min > 0);
|
||||
Assert.True(max > min);
|
||||
Assert.True(variance > 0);
|
||||
Assert.True(stdDev > 0);
|
||||
|
||||
// Performance should be sub-millisecond for 10k elements
|
||||
Assert.True(sw.ElapsedMilliseconds < 10,
|
||||
$"SIMD operations took {sw.ElapsedMilliseconds}ms, expected < 10ms");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests
|
||||
{
|
||||
public class TBarSeriesTests
|
||||
{
|
||||
[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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests
|
||||
{
|
||||
public class TBarTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsPropertiesCorrectly()
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
double open = 100;
|
||||
double high = 110;
|
||||
double low = 90;
|
||||
double close = 105;
|
||||
double volume = 1000;
|
||||
|
||||
var bar = new TBar(time, open, high, low, close, volume);
|
||||
|
||||
Assert.Equal(time, bar.Time);
|
||||
Assert.Equal(open, bar.Open);
|
||||
Assert.Equal(high, bar.High);
|
||||
Assert.Equal(low, bar.Low);
|
||||
Assert.Equal(close, bar.Close);
|
||||
Assert.Equal(volume, bar.Volume);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HL2_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 105, 1000);
|
||||
Assert.Equal(100.0, bar.HL2); // (110 + 90) / 2
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OHL3_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 105, 1000);
|
||||
Assert.Equal(100.0, bar.OHL3); // (100 + 110 + 90) / 3
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HLC3_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 100, 1000);
|
||||
Assert.Equal(100.0, bar.HLC3); // (110 + 90 + 100) / 3
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OHLC4_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 100, 1000);
|
||||
Assert.Equal(100.0, bar.OHLC4); // (100 + 110 + 90 + 100) / 4
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HLCC4_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 100, 1000);
|
||||
Assert.Equal(100.0, bar.HLCC4); // (110 + 90 + 100 + 100) / 4
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests
|
||||
{
|
||||
public class TSeriesTests
|
||||
{
|
||||
[Fact]
|
||||
public void Add_NewValue_IncreasesCount()
|
||||
{
|
||||
var series = new TSeries();
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
|
||||
series.Add(time, 10.0, isNew: true);
|
||||
|
||||
Assert.Single(series);
|
||||
Assert.Equal(10.0, series.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_UpdateValue_DoesNotIncreaseCount()
|
||||
{
|
||||
var series = new TSeries();
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
|
||||
series.Add(time, 10.0, isNew: true);
|
||||
series.Add(time, 11.0, isNew: false);
|
||||
|
||||
Assert.Single(series);
|
||||
Assert.Equal(11.0, series.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_MultipleValues_MaintainsOrder()
|
||||
{
|
||||
var series = new TSeries();
|
||||
long t0 = DateTime.UtcNow.Ticks;
|
||||
long t1 = t0 + TimeSpan.TicksPerMinute;
|
||||
|
||||
series.Add(t0, 10.0, isNew: true);
|
||||
series.Add(t1, 20.0, isNew: true);
|
||||
|
||||
Assert.Equal(2, series.Count);
|
||||
Assert.Equal(10.0, series[0].Value);
|
||||
Assert.Equal(20.0, series[1].Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests
|
||||
{
|
||||
public class TValueTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsPropertiesCorrectly()
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
double value = 123.45;
|
||||
|
||||
var tValue = new TValue(time, value);
|
||||
|
||||
Assert.Equal(time, tValue.Time);
|
||||
Assert.Equal(value, tValue.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsDateTime_ReturnsCorrectDateTime()
|
||||
{
|
||||
DateTime dt = new DateTime(2023, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||
long ticks = dt.Ticks;
|
||||
var tValue = new TValue(ticks, 100.0);
|
||||
|
||||
Assert.Equal(dt, tValue.AsDateTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToString_FormatsCorrectly()
|
||||
{
|
||||
DateTime dt = new DateTime(2023, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||
var tValue = new TValue(dt.Ticks, 123.456);
|
||||
|
||||
string result = tValue.ToString();
|
||||
|
||||
Assert.Contains(dt.ToString("yyyy-MM-dd HH:mm:ss"), result);
|
||||
Assert.Contains("123.46", result); // Default formatting usually 2 decimals or similar
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversion_ToDouble()
|
||||
{
|
||||
var tValue = new TValue(DateTime.UtcNow.Ticks, 42.0);
|
||||
double val = tValue;
|
||||
Assert.Equal(42.0, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
timestamp,open,high,low,close,volume
|
||||
2025-11-25,304.1250,306.0000,297.0600,304.4800,2825322
|
||||
2025-11-24,299.1800,307.1800,297.5100,304.1200,6050640
|
||||
2025-11-21,293.4800,300.4800,291.8900,297.4400,5710903
|
||||
2025-11-20,294.6400,300.7100,290.1600,290.4000,5597028
|
||||
2025-11-19,290.5000,291.1099,288.0700,288.5300,3595912
|
||||
2025-11-18,297.0000,297.0000,289.9200,289.9500,4861928
|
||||
2025-11-17,305.5900,306.0000,296.5100,297.1700,3909741
|
||||
2025-11-14,300.0000,307.7200,297.5900,305.6900,3592455
|
||||
2025-11-13,312.2900,314.6000,303.6800,304.8600,5310150
|
||||
2025-11-12,319.8900,324.9000,314.5324,314.9800,6042686
|
||||
2025-11-11,309.0000,317.9100,308.4300,313.7200,4381913
|
||||
2025-11-10,306.8200,309.9400,304.2300,309.1300,2975188
|
||||
2025-11-07,309.6800,310.0000,302.6301,306.3800,5070773
|
||||
2025-11-06,306.7500,315.4400,301.0900,312.4200,6818521
|
||||
2025-11-05,301.3800,307.2000,299.7100,306.7700,4633195
|
||||
2025-11-04,300.0000,303.1700,296.0000,300.8500,5677330
|
||||
2025-11-03,308.0000,312.1411,304.2300,304.7300,4957958
|
||||
2025-10-31,312.0000,313.5000,301.6300,307.4100,7697499
|
||||
2025-10-30,306.6500,313.7500,305.0200,310.0600,4694275
|
||||
2025-10-29,312.7900,314.3300,307.5200,308.2100,4135948
|
||||
2025-10-28,312.6000,319.3500,311.4100,312.5700,6044770
|
||||
2025-10-27,307.8000,313.5000,302.8800,313.0900,9868151
|
||||
2025-10-24,283.7700,310.7500,282.2100,307.4600,16914243
|
||||
2025-10-23,264.9500,285.5791,263.5623,285.0000,16676394
|
||||
2025-10-22,281.9900,289.1700,281.3500,287.5100,10538480
|
||||
2025-10-21,283.3100,285.3100,281.6000,282.0500,4080981
|
||||
2025-10-20,281.2500,285.5000,280.9600,283.6500,3494336
|
||||
2025-10-17,276.1500,283.4000,275.3500,281.2800,5309565
|
||||
2025-10-16,281.1100,282.5600,275.6000,275.9700,2956923
|
||||
2025-10-15,278.3800,285.4500,277.0000,280.7500,3346753
|
||||
2025-10-14,275.5200,277.5300,272.5469,276.1500,3058149
|
||||
2025-10-13,279.7900,282.4399,274.6400,277.2200,4333836
|
||||
2025-10-10,288.9700,290.3850,277.5000,277.8200,4508506
|
||||
2025-10-09,289.8200,290.1300,283.3200,288.2300,4912375
|
||||
2025-10-08,294.1600,294.2000,286.4730,289.4600,5297030
|
||||
2025-10-07,295.5500,301.0425,293.2850,293.8700,7190126
|
||||
2025-10-06,288.6100,291.4500,287.8000,289.4200,2881947
|
||||
2025-10-03,287.5000,293.3200,287.3000,288.3700,4375082
|
||||
2025-10-02,285.7900,288.5400,282.7900,286.7200,3814232
|
||||
2025-10-01,280.2000,286.5900,280.1500,286.4900,4381338
|
||||
2025-09-30,280.8800,286.0250,280.5200,282.1600,5926924
|
||||
2025-09-29,286.0000,286.0000,279.6600,279.8000,6022125
|
||||
2025-09-26,280.5100,288.8500,280.1100,284.3100,9063938
|
||||
2025-09-25,272.9350,284.2300,271.1480,281.4400,11506192
|
||||
2025-09-24,272.6200,273.6499,267.3000,267.5300,3159924
|
||||
2025-09-23,272.7000,273.2962,269.2650,272.2400,5394121
|
||||
2025-09-22,266.6200,272.3100,266.0000,271.3700,5030540
|
||||
2025-09-19,266.0500,267.8700,263.6400,266.4000,9858112
|
||||
2025-09-18,258.8600,265.2300,256.8004,265.0000,4988421
|
||||
2025-09-17,257.4950,260.9644,257.0100,259.0800,3974785
|
||||
2025-09-16,256.2600,258.0000,254.4100,257.5200,2719918
|
||||
2025-09-15,254.0200,259.0500,254.0000,256.2400,4028365
|
||||
2025-09-12,256.9500,257.2500,252.4250,253.4400,3433300
|
||||
2025-09-11,257.5600,258.5450,255.6550,257.0100,3576048
|
||||
2025-09-10,259.6500,260.0800,254.5600,256.8800,5185420
|
||||
2025-09-09,256.1200,260.6600,254.8800,259.1100,4931105
|
||||
2025-09-08,248.6300,257.1500,247.0200,256.0900,6940270
|
||||
2025-09-05,248.2300,249.0300,245.4500,248.5300,3147478
|
||||
2025-09-04,245.4200,249.2800,242.8500,247.1800,4765087
|
||||
2025-09-03,240.0200,244.2500,239.4100,244.1000,3156289
|
||||
2025-09-02,240.9000,241.5500,238.2500,241.5000,3469501
|
||||
2025-08-29,245.2300,245.4599,241.7200,243.4900,2967558
|
||||
2025-08-28,245.4300,245.8800,243.3600,245.7300,2820817
|
||||
2025-08-27,242.8700,245.9600,242.0000,244.8400,3698372
|
||||
2025-08-26,241.0200,244.9800,240.3800,242.6300,5386582
|
||||
2025-08-25,242.5650,242.5650,239.4300,239.4300,3513327
|
||||
2025-08-22,240.7400,243.6800,240.2200,242.0900,3134882
|
||||
2025-08-21,242.2100,242.5000,238.6500,239.4000,2991902
|
||||
2025-08-20,242.1100,242.8800,240.3400,242.5500,3240064
|
||||
2025-08-19,240.0000,242.8300,239.4900,241.2800,3328305
|
||||
2025-08-18,239.5700,241.4200,239.1158,239.4500,3569594
|
||||
2025-08-15,237.6100,240.6200,236.7700,239.7200,4344322
|
||||
2025-08-14,238.2500,239.0000,235.6200,237.1100,4556725
|
||||
2025-08-13,236.2000,240.8411,236.2000,240.0700,5663562
|
||||
2025-08-12,236.5300,237.9600,233.3600,234.7700,8800597
|
||||
2025-08-11,242.2400,243.1500,234.7000,236.3000,9381960
|
||||
2025-08-08,248.8800,249.4800,241.6500,242.2700,6828390
|
||||
2025-08-07,252.8100,255.0000,248.8750,250.1600,6251285
|
||||
2025-08-06,251.5300,254.3200,249.2800,252.2800,3692105
|
||||
2025-08-05,252.0000,252.8000,248.9950,250.6700,5823016
|
||||
2025-08-04,251.0500,252.0800,248.1100,251.9800,5280588
|
||||
2025-08-01,251.4050,251.4791,245.6100,250.0500,9683404
|
||||
2025-07-31,259.5700,259.9900,252.2200,253.1500,6739092
|
||||
2025-07-30,261.6000,262.0000,258.9000,260.2600,3718290
|
||||
2025-07-29,264.3000,265.7999,261.0200,262.4100,4627265
|
||||
2025-07-28,260.3000,264.0000,259.6100,263.2100,5192516
|
||||
2025-07-25,260.0200,260.8000,256.3500,259.7200,7758653
|
||||
2025-07-24,261.2500,262.0486,252.7500,260.5100,22647720
|
||||
2025-07-23,284.3000,288.0800,281.4400,282.0100,8105906
|
||||
2025-07-22,284.7400,284.8800,281.2500,281.9600,4824219
|
||||
2025-07-21,286.2900,287.7300,284.3800,284.7100,3051791
|
||||
2025-07-18,283.3800,287.1600,282.2200,285.8700,4478165
|
||||
2025-07-17,281.5000,283.4566,280.9000,282.0000,3337168
|
||||
2025-07-16,282.7500,283.8700,279.8700,281.9200,2804831
|
||||
2025-07-15,283.7700,284.1550,280.7301,282.7000,2864106
|
||||
2025-07-14,282.8300,284.9250,281.7100,283.7900,2857401
|
||||
2025-07-11,285.0100,287.4300,282.9200,283.5900,3790679
|
||||
2025-07-10,288.9000,288.9000,282.2100,287.4300,3489068
|
||||
2025-07-09,291.3900,291.6000,288.6300,290.1400,2971309
|
||||
2025-07-08,293.1000,295.6100,289.4900,290.4200,2925329
|
||||
|
Reference in New Issue
Block a user