mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-18 10:38:05 +00:00
Adl, Adosc, Aobv, Cmf
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class CoreTests
|
||||
{
|
||||
#region CircularBuffer Tests
|
||||
|
||||
[Fact]
|
||||
public void CircularBuffer_BasicOperations()
|
||||
{
|
||||
var buffer = new CircularBuffer(5);
|
||||
|
||||
// Test initial state
|
||||
Assert.Equal(5, buffer.Capacity);
|
||||
Assert.Equal(0, buffer.Count);
|
||||
|
||||
// Test adding items
|
||||
buffer.Add(1.0);
|
||||
buffer.Add(2.0);
|
||||
Assert.Equal(2, buffer.Count);
|
||||
Assert.Equal(1.0, buffer[0]);
|
||||
Assert.Equal(2.0, buffer[^1]);
|
||||
|
||||
// Test overflow behavior
|
||||
buffer.Add(3.0);
|
||||
buffer.Add(4.0);
|
||||
buffer.Add(5.0);
|
||||
buffer.Add(6.0); // Should remove oldest item (1.0)
|
||||
Assert.Equal(5, buffer.Count);
|
||||
Assert.Equal(2.0, buffer[0]);
|
||||
Assert.Equal(6.0, buffer[^1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CircularBuffer_UpdateBehavior()
|
||||
{
|
||||
var buffer = new CircularBuffer(3);
|
||||
|
||||
// Add new values
|
||||
buffer.Add(1.0, isNew: true);
|
||||
buffer.Add(2.0, isNew: true);
|
||||
Assert.Equal(2, buffer.Count);
|
||||
|
||||
// Update last value
|
||||
buffer.Add(2.5, isNew: false);
|
||||
Assert.Equal(2, buffer.Count);
|
||||
Assert.Equal(2.5, buffer[^1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CircularBuffer_MinMaxSumAverage()
|
||||
{
|
||||
var buffer = new CircularBuffer(5);
|
||||
|
||||
buffer.Add(1.0);
|
||||
buffer.Add(2.0);
|
||||
buffer.Add(3.0);
|
||||
buffer.Add(4.0);
|
||||
buffer.Add(5.0);
|
||||
|
||||
Assert.Equal(1.0, buffer.Min());
|
||||
Assert.Equal(5.0, buffer.Max());
|
||||
Assert.Equal(15.0, buffer.Sum());
|
||||
Assert.Equal(3.0, buffer.Average());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CircularBuffer_Enumeration()
|
||||
{
|
||||
var buffer = new CircularBuffer(3);
|
||||
|
||||
buffer.Add(1.0);
|
||||
buffer.Add(2.0);
|
||||
buffer.Add(3.0);
|
||||
|
||||
var list = buffer.ToList();
|
||||
Assert.Equal(3, list.Count);
|
||||
Assert.Equal(1.0, list[0]);
|
||||
Assert.Equal(3.0, list[2]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TBar Tests
|
||||
|
||||
[Fact]
|
||||
public void TBar_Construction()
|
||||
{
|
||||
// Default constructor
|
||||
var bar1 = new TBar();
|
||||
Assert.Equal(0, bar1.Open);
|
||||
Assert.True(bar1.IsNew);
|
||||
|
||||
// Value constructor
|
||||
var bar2 = new TBar(10.0);
|
||||
Assert.Equal(10.0, bar2.Open);
|
||||
Assert.Equal(10.0, bar2.High);
|
||||
Assert.Equal(10.0, bar2.Low);
|
||||
Assert.Equal(10.0, bar2.Close);
|
||||
|
||||
// Full constructor
|
||||
var time = DateTime.Now;
|
||||
var bar3 = new TBar(time, 10.0, 12.0, 9.0, 11.0, 1000.0, false);
|
||||
Assert.Equal(time, bar3.Time);
|
||||
Assert.Equal(10.0, bar3.Open);
|
||||
Assert.Equal(12.0, bar3.High);
|
||||
Assert.Equal(9.0, bar3.Low);
|
||||
Assert.Equal(11.0, bar3.Close);
|
||||
Assert.Equal(1000.0, bar3.Volume);
|
||||
Assert.False(bar3.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TBar_DerivedValues()
|
||||
{
|
||||
var bar = new TBar(DateTime.Now, 10.0, 20.0, 5.0, 15.0, 1000.0);
|
||||
|
||||
Assert.Equal(12.5, bar.HL2); // (20 + 5) / 2
|
||||
Assert.Equal(12.5, bar.OC2); // (10 + 15) / 2
|
||||
Assert.Equal(11.67, bar.OHL3, 2); // (10 + 20 + 5) / 3
|
||||
Assert.Equal(13.33, bar.HLC3, 2); // (20 + 5 + 15) / 3
|
||||
Assert.Equal(12.5, bar.OHLC4); // (10 + 20 + 5 + 15) / 4
|
||||
Assert.Equal(13.75, bar.HLCC4); // (20 + 5 + 15 + 15) / 4
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TBarSeries_Operations()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var time = DateTime.Now;
|
||||
var bar1 = new TBar(time, 10.0, 12.0, 9.0, 11.0, 1000.0);
|
||||
var bar2 = new TBar(time.AddMinutes(1), 11.0, 13.0, 10.0, 12.0, 1100.0);
|
||||
|
||||
// Test adding bars
|
||||
series.Add(bar1);
|
||||
series.Add(bar2);
|
||||
Assert.Equal(2, series.Count);
|
||||
|
||||
// Test updating last bar
|
||||
var bar2Update = new TBar(bar2.Time, 11.0, 13.5, 9.5, 12.5, 1200.0, false);
|
||||
series.Add(bar2Update);
|
||||
Assert.Equal(2, series.Count);
|
||||
Assert.Equal(12.5, series.Last.Close);
|
||||
|
||||
// Test derived series
|
||||
Assert.Equal(11.0, series.Open.Last.Value);
|
||||
Assert.Equal(13.5, series.High.Last.Value);
|
||||
Assert.Equal(9.5, series.Low.Last.Value);
|
||||
Assert.Equal(12.5, series.Close.Last.Value);
|
||||
Assert.Equal(1200.0, series.Volume.Last.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TValue Tests
|
||||
|
||||
[Fact]
|
||||
public void TValue_Construction()
|
||||
{
|
||||
// Default constructor
|
||||
var value1 = new TValue();
|
||||
Assert.Equal(0, value1.Value);
|
||||
Assert.True(value1.IsNew);
|
||||
Assert.True(value1.IsHot);
|
||||
|
||||
// Value constructor
|
||||
var value2 = new TValue(10.0);
|
||||
Assert.Equal(10.0, value2.Value);
|
||||
|
||||
// Full constructor
|
||||
var time = DateTime.Now;
|
||||
var value3 = new TValue(time, 10.0, false, false);
|
||||
Assert.Equal(time, value3.Time);
|
||||
Assert.Equal(10.0, value3.Value);
|
||||
Assert.False(value3.IsNew);
|
||||
Assert.False(value3.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TValue_Conversions()
|
||||
{
|
||||
var value = new TValue(10.0);
|
||||
|
||||
// Test implicit conversions
|
||||
double d = value;
|
||||
Assert.Equal(10.0, d);
|
||||
|
||||
DateTime time = value;
|
||||
Assert.Equal(value.Time, time);
|
||||
|
||||
// Test implicit conversion from double
|
||||
TValue newValue = 20.0;
|
||||
Assert.Equal(20.0, newValue.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TSeries_Operations()
|
||||
{
|
||||
var series = new TSeries();
|
||||
var time = DateTime.Now;
|
||||
|
||||
// Test adding values
|
||||
series.Add(time, 10.0);
|
||||
series.Add(time.AddMinutes(1), 20.0);
|
||||
Assert.Equal(2, series.Count);
|
||||
|
||||
// Test updating last value
|
||||
series.Add(new TValue(time.AddMinutes(1), 25.0, false));
|
||||
Assert.Equal(2, series.Count);
|
||||
Assert.Equal(25.0, series.Last.Value);
|
||||
|
||||
// Test adding range of values
|
||||
var values = new[] { 30.0, 40.0, 50.0 };
|
||||
foreach (var value in values)
|
||||
{
|
||||
series.Add(time.AddMinutes(series.Count + 1), value);
|
||||
}
|
||||
Assert.Equal(5, series.Count);
|
||||
|
||||
// Test conversions
|
||||
var doubleList = (List<double>)series;
|
||||
Assert.Equal(5, doubleList.Count);
|
||||
Assert.Equal(50.0, doubleList[^1]);
|
||||
|
||||
var doubleArray = (double[])series;
|
||||
Assert.Equal(5, doubleArray.Length);
|
||||
Assert.Equal(50.0, doubleArray[^1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TSeries_EventHandling()
|
||||
{
|
||||
var series = new TSeries();
|
||||
var receivedValues = new List<double>();
|
||||
var time = DateTime.Now;
|
||||
|
||||
series.Pub += (object sender, in ValueEventArgs args) => receivedValues.Add(args.Tick.Value);
|
||||
|
||||
series.Add(time, 10.0);
|
||||
series.Add(time.AddMinutes(1), 20.0);
|
||||
series.Add(time.AddMinutes(2), 30.0);
|
||||
|
||||
Assert.Equal(3, receivedValues.Count);
|
||||
Assert.Equal(10.0, receivedValues[0]);
|
||||
Assert.Equal(20.0, receivedValues[1]);
|
||||
Assert.Equal(30.0, receivedValues[2]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+53
-16
@@ -13,12 +13,13 @@ public class EventingTests
|
||||
// Create a cryptographically secure random number generator
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
|
||||
// Create an input series to hold our random values
|
||||
// Create input series to hold our random values
|
||||
var input = new TSeries();
|
||||
var barInput = new TBarSeries();
|
||||
int p = 10;
|
||||
|
||||
// Create a list of indicator pairs (direct calculation and event-based) with names
|
||||
var indicators = new List<(string Name, AbstractBase Direct, AbstractBase EventBased)>
|
||||
// Create a list of value-based indicator pairs
|
||||
var valueIndicators = new List<(string Name, AbstractBase Direct, AbstractBase EventBased)>
|
||||
{
|
||||
("Afirma", new Afirma(p,p,Afirma.WindowType.BlackmanHarris), new Afirma(input, p,p,Afirma.WindowType.BlackmanHarris)),
|
||||
("Alma", new Alma(p), new Alma(input, p)),
|
||||
@@ -51,19 +52,15 @@ public class EventingTests
|
||||
("Tema", new Tema(p), new Tema(input, p)),
|
||||
("Kama", new Kama(2, 30, 6), new Kama(input, 2, 30, 6)),
|
||||
("Zlema", new Zlema(p), new Zlema(input, p)),
|
||||
// Added missing averages
|
||||
("Sinema", new Sinema(p), new Sinema(input, p)),
|
||||
("Smma", new Smma(p), new Smma(input, p)),
|
||||
("T3", new T3(p), new T3(input, p)),
|
||||
("Trima", new Trima(p), new Trima(input, p)),
|
||||
("Vidya", new Vidya(p), new Vidya(input, p)),
|
||||
// momentum indicators
|
||||
("Apo", new Apo(12, 26), new Apo(input, 12, 26)),
|
||||
// oscillators
|
||||
("Rsi", new Rsi(p), new Rsi(input, p)),
|
||||
("Rsx", new Rsx(p), new Rsx(input, p)),
|
||||
("Cmo", new Cmo(p), new Cmo(input, p)),
|
||||
// statistics
|
||||
("Curvature", new Curvature(p), new Curvature(input, p)),
|
||||
("Entropy", new Entropy(p), new Entropy(input, p)),
|
||||
("Kurtosis", new Kurtosis(p), new Kurtosis(input, p)),
|
||||
@@ -77,12 +74,12 @@ public class EventingTests
|
||||
("Stddev", new Stddev(p), new Stddev(input, p)),
|
||||
("Variance", new Variance(p), new Variance(input, p)),
|
||||
("Zscore", new Zscore(p), new Zscore(input, p)),
|
||||
// volatility
|
||||
// Volatility indicators (value-based)
|
||||
("Hv", new Hv(p), new Hv(input, p)),
|
||||
("Jvolty", new Jvolty(p), new Jvolty(input, p)),
|
||||
("Rv", new Rv(p), new Rv(input, p)),
|
||||
("Rvi", new Rvi(p), new Rvi(input, p)),
|
||||
// error classes
|
||||
// Error classes
|
||||
("Mae", new Mae(p), new Mae(input, p)),
|
||||
("Mapd", new Mapd(p), new Mapd(input, p)),
|
||||
("Mape", new Mape(p), new Mape(input, p)),
|
||||
@@ -101,26 +98,66 @@ public class EventingTests
|
||||
("Huber", new Huber(p), new Huber(input, p))
|
||||
};
|
||||
|
||||
// Generate 200 random values and feed them to both direct and event-based indicators
|
||||
// Create a list of bar-based indicator pairs
|
||||
var barIndicators = new List<(string Name, AbstractBase Direct, AbstractBase EventBased)>
|
||||
{
|
||||
// Volume indicators
|
||||
("Adl", new Adl(), new Adl(barInput)),
|
||||
("Adosc", new Adosc(3, 10), new Adosc(barInput, 3, 10)),
|
||||
("Aobv", new Aobv(), new Aobv(barInput)),
|
||||
("Cmf", new Cmf(20), new Cmf(barInput, 20)),
|
||||
("Eom", new Eom(14), new Eom(barInput, 14)),
|
||||
("Kvo", new Kvo(34, 55), new Kvo(barInput, 34, 55)),
|
||||
// Volatility indicators (bar-based)
|
||||
("Atr", new Atr(14), new Atr(barInput, 14))
|
||||
};
|
||||
|
||||
// Generate 200 random values and feed them to indicators
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
// Generate random value for value-based indicators
|
||||
double randomValue = GetRandomDouble(rng) * 100;
|
||||
input.Add(randomValue);
|
||||
|
||||
// Calculate direct indicators
|
||||
foreach (var (_, direct, _) in indicators)
|
||||
// Calculate value-based indicators
|
||||
foreach (var (_, direct, _) in valueIndicators)
|
||||
{
|
||||
direct.Calc(randomValue);
|
||||
}
|
||||
|
||||
// Generate random bar for bar-based indicators
|
||||
var bar = new TBar(
|
||||
DateTime.Now,
|
||||
randomValue,
|
||||
randomValue + Math.Abs(GetRandomDouble(rng) * 10),
|
||||
randomValue - Math.Abs(GetRandomDouble(rng) * 10),
|
||||
randomValue + GetRandomDouble(rng) * 5,
|
||||
Math.Abs(GetRandomDouble(rng) * 1000),
|
||||
true
|
||||
);
|
||||
barInput.Add(bar);
|
||||
|
||||
// Calculate bar-based indicators
|
||||
foreach (var (_, direct, _) in barIndicators)
|
||||
{
|
||||
direct.Calc(bar);
|
||||
}
|
||||
}
|
||||
|
||||
// Compare the results of direct and event-based calculations
|
||||
for (int i = 0; i < indicators.Count; i++)
|
||||
// Compare the results for value-based indicators
|
||||
foreach (var (name, direct, eventBased) in valueIndicators)
|
||||
{
|
||||
var (name, direct, eventBased) = indicators[i];
|
||||
bool areEqual = (double.IsNaN(direct.Value) && double.IsNaN(eventBased.Value)) ||
|
||||
Math.Abs(direct.Value - eventBased.Value) < 1e-9;
|
||||
Assert.True(areEqual, $"Indicator {name} failed: Expected {direct.Value}, Actual {eventBased.Value}");
|
||||
Assert.True(areEqual, $"Value indicator {name} failed: Expected {direct.Value}, Actual {eventBased.Value}");
|
||||
}
|
||||
|
||||
// Compare the results for bar-based indicators
|
||||
foreach (var (name, direct, eventBased) in barIndicators)
|
||||
{
|
||||
bool areEqual = (double.IsNaN(direct.Value) && double.IsNaN(eventBased.Value)) ||
|
||||
Math.Abs(direct.Value - eventBased.Value) < 1e-9;
|
||||
Assert.True(areEqual, $"Bar indicator {name} failed: Expected {direct.Value}, Actual {eventBased.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
using Xunit;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class VolumeUpdateTests
|
||||
{
|
||||
private readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();
|
||||
private const int RandomUpdates = 100;
|
||||
private const int precision = 8;
|
||||
|
||||
private double GetRandomDouble()
|
||||
{
|
||||
byte[] bytes = new byte[8];
|
||||
rng.GetBytes(bytes);
|
||||
return (double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue * 200 - 100; // Range: -100 to 100
|
||||
}
|
||||
|
||||
private TBar GetRandomBar(bool IsNew)
|
||||
{
|
||||
double open = GetRandomDouble();
|
||||
double high = open + Math.Abs(GetRandomDouble());
|
||||
double low = open - Math.Abs(GetRandomDouble());
|
||||
double close = low + (high - low) * GetRandomDouble();
|
||||
double volume = Math.Abs(GetRandomDouble()) * 1000; // Random positive volume
|
||||
return new TBar(DateTime.Now, open, high, low, close, volume, IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Adl_Update()
|
||||
{
|
||||
var indicator = new Adl();
|
||||
TBar r = GetRandomBar(true);
|
||||
|
||||
// First calculation with IsNew: true
|
||||
double value1 = indicator.Calc(r);
|
||||
|
||||
// Multiple recalculations with IsNew: false should not change the value
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
}
|
||||
|
||||
// Final calculation with IsNew: false should match initial value
|
||||
double value2 = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
Assert.Equal(value1, value2, precision);
|
||||
|
||||
// New calculation with IsNew: true should update the value
|
||||
double value3 = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: true));
|
||||
Assert.NotEqual(value1, value3, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Adosc_Update()
|
||||
{
|
||||
var indicator = new Adosc(shortPeriod: 3, longPeriod: 10);
|
||||
TBar r = GetRandomBar(true);
|
||||
double initialValue = indicator.Calc(r);
|
||||
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(GetRandomBar(IsNew: false));
|
||||
}
|
||||
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
|
||||
Assert.Equal(initialValue, finalValue, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Aobv_Update()
|
||||
{
|
||||
var indicator = new Aobv();
|
||||
TBar r = GetRandomBar(true);
|
||||
|
||||
// First calculation with IsNew: true
|
||||
double value1 = indicator.Calc(r);
|
||||
|
||||
// Multiple recalculations with IsNew: false should not change the value
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
}
|
||||
|
||||
// Final calculation with IsNew: false should match initial value
|
||||
double value2 = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
Assert.Equal(value1, value2, precision);
|
||||
|
||||
// New calculation with IsNew: true should update the value
|
||||
double value3 = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: true));
|
||||
Assert.NotEqual(value1, value3, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cmf_Update()
|
||||
{
|
||||
var indicator = new Cmf(period: 20);
|
||||
TBar r = GetRandomBar(true);
|
||||
|
||||
// Generate a sequence of bars for warmup
|
||||
var warmupBars = new List<TBar>();
|
||||
for (int i = 0; i < indicator.WarmupPeriod; i++)
|
||||
{
|
||||
var bar = GetRandomBar(IsNew: true);
|
||||
warmupBars.Add(bar);
|
||||
indicator.Calc(bar);
|
||||
}
|
||||
|
||||
// Calculate initial value after warmup
|
||||
double initialValue = indicator.Calc(r);
|
||||
|
||||
// Apply random updates
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(GetRandomBar(IsNew: false));
|
||||
}
|
||||
|
||||
// Reset and replay the same sequence
|
||||
indicator.Init();
|
||||
foreach (var bar in warmupBars)
|
||||
{
|
||||
indicator.Calc(bar);
|
||||
}
|
||||
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
|
||||
Assert.Equal(initialValue, finalValue, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Eom_Update()
|
||||
{
|
||||
var indicator = new Eom(period: 14);
|
||||
TBar r = GetRandomBar(true);
|
||||
double initialValue = indicator.Calc(r);
|
||||
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(GetRandomBar(IsNew: false));
|
||||
}
|
||||
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
|
||||
Assert.Equal(initialValue, finalValue, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Kvo_Update()
|
||||
{
|
||||
var indicator = new Kvo(shortPeriod: 34, longPeriod: 55);
|
||||
TBar r = GetRandomBar(true);
|
||||
double initialValue = indicator.Calc(r);
|
||||
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(GetRandomBar(IsNew: false));
|
||||
}
|
||||
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
|
||||
Assert.Equal(initialValue, finalValue, precision);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user