mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-22 12:38:06 +00:00
Refactor tests and project files for improved consistency and clarity
This commit is contained in:
@@ -35,9 +35,9 @@ public class AoIndicatorTests
|
||||
var indicator = new AoIndicator { FastPeriod = 10, SlowPeriod = 40 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("AO", indicator.ShortName);
|
||||
Assert.Contains("10", indicator.ShortName);
|
||||
Assert.Contains("40", indicator.ShortName);
|
||||
Assert.Contains("AO", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("10", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("40", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -45,8 +45,8 @@ public class AoIndicatorTests
|
||||
{
|
||||
var indicator = new AoIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Ao.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Ao.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -69,10 +69,10 @@ public sealed class AoValidationTests : IDisposable
|
||||
|
||||
var tulipIndicator = Tulip.Indicators.ao;
|
||||
double[][] inputs = { high, low };
|
||||
double[] options = { };
|
||||
double[] options = Array.Empty<double>();
|
||||
|
||||
int lookback = 33;
|
||||
double[][] outputs = { new double[_data.Bars.Count - lookback] };
|
||||
double[][] outputs = [new double[_data.Bars.Count - lookback]];
|
||||
|
||||
tulipIndicator.Run(inputs, options, outputs);
|
||||
var tulipResults = outputs[0];
|
||||
|
||||
@@ -35,9 +35,9 @@ public class ApoIndicatorTests
|
||||
var indicator = new ApoIndicator { FastPeriod = 10, SlowPeriod = 40 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("APO", indicator.ShortName);
|
||||
Assert.Contains("10", indicator.ShortName);
|
||||
Assert.Contains("40", indicator.ShortName);
|
||||
Assert.Contains("APO", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("10", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("40", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -45,8 +45,8 @@ public class ApoIndicatorTests
|
||||
{
|
||||
var indicator = new ApoIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Apo.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Apo.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class AroonIndicatorTests
|
||||
var indicator = new AroonIndicator { Period = 20 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("Aroon", indicator.ShortName);
|
||||
Assert.Contains("20", indicator.ShortName);
|
||||
Assert.Contains("Aroon", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class AroonIndicatorTests
|
||||
{
|
||||
var indicator = new AroonIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Aroon.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Aroon.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class AroonOscIndicatorTests
|
||||
var indicator = new AroonOscIndicator { Period = 20 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("AroonOsc", indicator.ShortName);
|
||||
Assert.Contains("20", indicator.ShortName);
|
||||
Assert.Contains("AroonOsc", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class AroonOscIndicatorTests
|
||||
{
|
||||
var indicator = new AroonOscIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("AroonOsc.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("AroonOsc.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -97,41 +97,4 @@ public sealed class AroonOscValidationTests : IDisposable
|
||||
ValidationHelper.VerifyData(results, tulipOsc, lookback: 14);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Ooples implementation deviates significantly from standard (TA-Lib, Tulip, Skender, QuanTAlib)")]
|
||||
public void MatchesOoples()
|
||||
{
|
||||
// Note: OoplesFinance implementation of Aroon Oscillator is an outlier.
|
||||
// It deviates from the consensus of TA-Lib, Tulip, Skender, and QuanTAlib.
|
||||
// The deviation is not a simple offset; it involves inconsistent steps and reversals,
|
||||
// likely due to differences in how the high/low window indices are tracked.
|
||||
|
||||
var aroon = new AroonOsc(14);
|
||||
var results = new List<double>();
|
||||
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
var res = aroon.Update(_data.Bars[i]);
|
||||
results.Add(res.Value);
|
||||
}
|
||||
|
||||
var ooplesData = _data.SkenderQuotes.Select(q => new TickerData
|
||||
{
|
||||
Date = q.Date,
|
||||
Open = (double)q.Open,
|
||||
High = (double)q.High,
|
||||
Low = (double)q.Low,
|
||||
Close = (double)q.Close,
|
||||
Volume = (double)q.Volume
|
||||
}).ToList();
|
||||
|
||||
var stockData = new StockData(ooplesData);
|
||||
|
||||
// Ooples only provides CalculateAroonOscillator
|
||||
var aroonOscResults = stockData.CalculateAroonOscillator(14);
|
||||
var ooplesOsc = aroonOscResults.OutputValues["Aroon"]
|
||||
.ToArray();
|
||||
|
||||
// Verify Oscillator
|
||||
ValidationHelper.VerifyData(results, ooplesOsc, lookback: 14, tolerance: ValidationHelper.OoplesTolerance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ public class BopIndicatorTests
|
||||
{
|
||||
var indicator = new BopIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Bop.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Bop.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -62,7 +62,7 @@ public sealed class BopValidationTests : IDisposable
|
||||
var close = _data.Bars.Close.Values.ToArray();
|
||||
|
||||
double[][] inputs = { open, high, low, close };
|
||||
double[] options = { }; // No options for BOP
|
||||
double[] options = Array.Empty<double>(); // No options for BOP
|
||||
|
||||
var bopInd = Tulip.Indicators.bop;
|
||||
double[][] outputs = { new double[open.Length - bopInd.Start(options)] };
|
||||
|
||||
@@ -38,9 +38,9 @@ public class CfbIndicatorTests
|
||||
// Initialize to update SourceName
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("CFB", indicator.ShortName);
|
||||
Assert.Contains("5-20", indicator.ShortName);
|
||||
Assert.Contains("Close", indicator.ShortName);
|
||||
Assert.Contains("CFB", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("5-20", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("Close", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -48,8 +48,8 @@ public class CfbIndicatorTests
|
||||
{
|
||||
var indicator = new CfbIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Cfb.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Cfb.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -35,8 +35,8 @@ public class DmxIndicatorTests
|
||||
// Initialize to update SourceName (though DMX doesn't use SourceName)
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("DMX", indicator.ShortName);
|
||||
Assert.Contains("20", indicator.ShortName);
|
||||
Assert.Contains("DMX", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -44,8 +44,8 @@ public class DmxIndicatorTests
|
||||
{
|
||||
var indicator = new DmxIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Dmx.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Dmx.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -49,8 +49,8 @@ public class MacdIndicatorTests
|
||||
{
|
||||
var indicator = new MacdIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Macd.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Macd.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -39,7 +39,7 @@ public class RsiIndicatorTests
|
||||
};
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("RSI(20)", indicator.ShortName);
|
||||
Assert.Contains("RSI(20)", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -47,8 +47,8 @@ public class RsiIndicatorTests
|
||||
{
|
||||
var indicator = new RsiIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Rsi.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Rsi.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+394
-50
@@ -1,63 +1,261 @@
|
||||
using Xunit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
namespace QuanTAlib;
|
||||
|
||||
public class RsiTests
|
||||
{
|
||||
[Fact]
|
||||
public void BasicCalculation()
|
||||
public void Constructor_InvalidParameters_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new Rsi(0));
|
||||
Assert.Throws<ArgumentException>(() => new Rsi(-1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BasicCalculation_DoesNotCrash()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
// RSI requires a period of data to be valid
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
Assert.True(double.IsFinite(rsi.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Properties_Accessible()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
Assert.Equal("Rsi(14)", rsi.Name);
|
||||
Assert.False(rsi.IsHot);
|
||||
Assert.Equal(0, rsi.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchMatchesStreaming()
|
||||
public void IsHot_BecomesTrueAfterWarmup()
|
||||
{
|
||||
var rsi = new Rsi(5);
|
||||
var series = new TSeries();
|
||||
// Generate some data
|
||||
for (int i = 0; i < 20; i++)
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
Assert.False(rsi.IsHot);
|
||||
|
||||
// RSI wraps two RMA indicators (gain and loss)
|
||||
// RMA wraps EMA with alpha = 1/period
|
||||
// EMA becomes hot when E <= 0.05, which occurs after ~-ln(0.05)/ln(1-alpha) values
|
||||
// For period=14: alpha=1/14, needs ~40 values to become hot
|
||||
// Both RMAs must be hot for RSI to be hot
|
||||
for (int i = 0; i < 45; i++)
|
||||
{
|
||||
series.Add(new TValue(DateTime.UtcNow.AddMinutes(i), 100 + Math.Sin(i) * 10));
|
||||
rsi.Update(series.Close[i]);
|
||||
if (i < 40)
|
||||
{
|
||||
Assert.False(rsi.IsHot, $"Should not be hot at index {i}");
|
||||
}
|
||||
}
|
||||
|
||||
var batchResult = rsi.Update(series);
|
||||
|
||||
// After sufficient warmup, IsHot should be true
|
||||
Assert.True(rsi.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNew_True_AdvancesState()
|
||||
{
|
||||
var rsi = new Rsi(5);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i], isNew: true);
|
||||
}
|
||||
|
||||
var val1 = rsi.Update(series.Close[9], isNew: true);
|
||||
var nextTime = series.Close[9].Time + TimeSpan.FromMinutes(1).Ticks;
|
||||
var val2 = rsi.Update(new TValue(nextTime, series.Close[9].Value + 1), isNew: true);
|
||||
|
||||
Assert.NotEqual(val1.Value, val2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNew_False_UpdatesCurrentBar()
|
||||
{
|
||||
var rsi = new Rsi(5);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
var val1 = rsi.Update(series.Close[9], isNew: true);
|
||||
var modifiedValue = new TValue(series.Close[9].Time, series.Close[9].Value + 5);
|
||||
var val2 = rsi.Update(modifiedValue, isNew: false);
|
||||
|
||||
// Should update the same bar
|
||||
Assert.Equal(val1.Time, val2.Time);
|
||||
Assert.NotEqual(val1.Value, val2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNew_Consistency()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Feed first 99
|
||||
for (int i = 0; i < 99; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
// Update with 100th point (isNew=true)
|
||||
rsi.Update(series.Close[99], true);
|
||||
|
||||
// Update with modified 100th point (isNew=false)
|
||||
var modifiedValue = new TValue(series.Close[99].Time, series.Close[99].Value + 2.0);
|
||||
var val2 = rsi.Update(modifiedValue, false);
|
||||
|
||||
// Create new instance and feed up to modified
|
||||
var rsi2 = new Rsi(14);
|
||||
for (int i = 0; i < 99; i++)
|
||||
{
|
||||
rsi2.Update(series.Close[i]);
|
||||
}
|
||||
var val3 = rsi2.Update(modifiedValue, true);
|
||||
|
||||
Assert.Equal(val3.Value, val2.Value, 1e-9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IterativeCorrections_RestoreToOriginalState()
|
||||
{
|
||||
var rsi = new Rsi(10);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Feed N values
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
var originalValue = rsi.Last;
|
||||
|
||||
// Make M updates with isNew=false
|
||||
for (int m = 0; m < 5; m++)
|
||||
{
|
||||
var modifiedValue = new TValue(series.Close[29].Time, series.Close[29].Value + m);
|
||||
rsi.Update(modifiedValue, isNew: false);
|
||||
}
|
||||
|
||||
// Restore with original 30th value
|
||||
var restoredValue = rsi.Update(series.Close[29], isNew: false);
|
||||
|
||||
Assert.Equal(originalValue.Value, restoredValue.Value, 1e-9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_ClearsState()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
Assert.True(rsi.IsHot);
|
||||
var valueBefore = rsi.Last.Value;
|
||||
|
||||
rsi.Reset();
|
||||
var streamResults = new System.Collections.Generic.List<double>();
|
||||
foreach (var item in series)
|
||||
|
||||
Assert.False(rsi.IsHot);
|
||||
Assert.Equal(0, rsi.Last.Value);
|
||||
|
||||
// Feed again
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamResults.Add(rsi.Update(item).Value);
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
Assert.Equal(batchResult[i].Value, streamResults[i], 8);
|
||||
}
|
||||
Assert.True(rsi.IsHot);
|
||||
Assert.Equal(valueBefore, rsi.Last.Value, 1e-9);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void SpanMatchesBatch()
|
||||
public void NaN_Input_DoesNotCrash()
|
||||
{
|
||||
var rsi = new Rsi(5);
|
||||
var series = new TSeries();
|
||||
// Generate some data
|
||||
var rsi = new Rsi(10);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
// Feed NaN
|
||||
var nanValue = new TValue(DateTime.UtcNow, double.NaN);
|
||||
var result = rsi.Update(nanValue);
|
||||
|
||||
// Should not crash and should return finite value
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Infinity_Input_DoesNotCrash()
|
||||
{
|
||||
var rsi = new Rsi(10);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
var infValue = new TValue(DateTime.UtcNow, double.PositiveInfinity);
|
||||
var result = rsi.Update(infValue);
|
||||
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleNaN_ContinuesCorrectly()
|
||||
{
|
||||
var rsi = new Rsi(10);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(30, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
series.Add(new TValue(DateTime.UtcNow.AddMinutes(i), 100 + Math.Sin(i) * 10));
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
var batchResult = rsi.Update(series);
|
||||
|
||||
var output = new double[series.Count];
|
||||
Rsi.Calculate(series.Values, output, 5);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
|
||||
// Feed multiple NaN
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Assert.Equal(batchResult[i].Value, output[i], 8);
|
||||
var nanValue = new TValue(DateTime.UtcNow.AddMinutes(i), double.NaN);
|
||||
var result = rsi.Update(nanValue);
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
|
||||
// Continue with valid data
|
||||
for (int i = 20; i < 30; i++)
|
||||
{
|
||||
var result = rsi.Update(series.Close[i]);
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,24 +270,170 @@ public class RsiTests
|
||||
}
|
||||
|
||||
var result = rsi.Update(series);
|
||||
// Flat line means no gains or losses, RSI should be 50 (or 0/100 depending on implementation details, but typically 50 or 0 if no moves)
|
||||
// Actually, if AvgGain=0 and AvgLoss=0, RSI is typically defined as 50 or 0.
|
||||
// Our implementation: RS = 0/0 -> NaN?
|
||||
// Let's check implementation.
|
||||
// If AvgLoss is 0, RSI is 100.
|
||||
// If AvgGain is 0, RSI is 0.
|
||||
// If both are 0?
|
||||
// In Rma: if all inputs are 0, Rma is 0.
|
||||
// So AvgGain=0, AvgLoss=0.
|
||||
// RS = 0/0 = NaN.
|
||||
// RSI = 100 - 100/(1+NaN) = NaN.
|
||||
// Let's see what happens.
|
||||
|
||||
// Actually, standard behavior for flat line is often 50 or 0.
|
||||
// Let's verify what our implementation does.
|
||||
// If we look at Rsi.cs:
|
||||
// if (avgLoss == 0) return avgGain == 0 ? 50 : 100;
|
||||
|
||||
// Flat line: no gains or losses, RSI = 50
|
||||
Assert.Equal(50, result.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TSeries_Update_Matches_Streaming()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var streamingResults = new List<double>();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamingResults.Add(rsi.Update(series.Close[i]).Value);
|
||||
}
|
||||
|
||||
var rsi2 = new Rsi(14);
|
||||
var seriesResults = rsi2.Update(series.Close);
|
||||
|
||||
Assert.Equal(streamingResults.Count, seriesResults.Count);
|
||||
for (int i = 0; i < seriesResults.Count; i++)
|
||||
{
|
||||
Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticBatch_Matches_Streaming()
|
||||
{
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var rsi = new Rsi(14);
|
||||
var streamingResults = new List<double>();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamingResults.Add(rsi.Update(series.Close[i]).Value);
|
||||
}
|
||||
|
||||
var batchResults = Rsi.Batch(series.Close, 14);
|
||||
|
||||
Assert.Equal(streamingResults.Count, batchResults.Count);
|
||||
for (int i = 0; i < batchResults.Count; i++)
|
||||
{
|
||||
Assert.Equal(streamingResults[i], batchResults.Values[i], 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalc_ValidatesInput()
|
||||
{
|
||||
var source = new double[10];
|
||||
var output = new double[5];
|
||||
|
||||
Assert.Throws<ArgumentException>(() => Rsi.Calculate(source, output, 14));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalc_InvalidPeriod_Throws()
|
||||
{
|
||||
var source = new double[10];
|
||||
var output = new double[10];
|
||||
|
||||
Assert.Throws<ArgumentException>(() => Rsi.Calculate(source, output, 0));
|
||||
Assert.Throws<ArgumentException>(() => Rsi.Calculate(source, output, -1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalc_MatchesTSeriesCalc()
|
||||
{
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var batchResults = Rsi.Batch(series.Close, 14);
|
||||
|
||||
var output = new double[series.Count];
|
||||
Rsi.Calculate(series.Close.Values, output, 14);
|
||||
|
||||
for (int i = 0; i < output.Length; i++)
|
||||
{
|
||||
Assert.Equal(batchResults.Values[i], output[i], 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalc_HandlesNaN()
|
||||
{
|
||||
var source = new double[20];
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
source[i] = series.Close[i].Value;
|
||||
}
|
||||
|
||||
source[10] = double.NaN;
|
||||
source[15] = double.NaN;
|
||||
|
||||
var output = new double[20];
|
||||
Rsi.Calculate(source, output, 10);
|
||||
|
||||
// Should not crash and produce finite results
|
||||
for (int i = 0; i < output.Length; i++)
|
||||
{
|
||||
Assert.True(double.IsFinite(output[i]) || output[i] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllModes_ProduceSameResult()
|
||||
{
|
||||
int period = 14;
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// 1. Batch Mode
|
||||
var batchSeries = Rsi.Batch(series.Close, period);
|
||||
double expected = batchSeries.Last.Value;
|
||||
|
||||
// 2. Span Mode
|
||||
var spanOutput = new double[series.Count];
|
||||
Rsi.Calculate(series.Close.Values, spanOutput, period);
|
||||
double spanResult = spanOutput[^1];
|
||||
|
||||
// 3. Streaming Mode
|
||||
var streamingRsi = new Rsi(period);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamingRsi.Update(series.Close[i]);
|
||||
}
|
||||
double streamingResult = streamingRsi.Last.Value;
|
||||
|
||||
// 4. Eventing Mode
|
||||
var pubSource = new TSeries();
|
||||
var eventingRsi = new Rsi(pubSource, period);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
pubSource.Add(series.Close[i]);
|
||||
}
|
||||
double eventingResult = eventingRsi.Last.Value;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, spanResult, 9);
|
||||
Assert.Equal(expected, streamingResult, 9);
|
||||
Assert.Equal(expected, eventingResult, 9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Chainability_Works()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Test TSeries chain
|
||||
var result = rsi.Update(series.Close);
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<TSeries>(result);
|
||||
|
||||
// Test TValue chain (returns TValue)
|
||||
var result2 = rsi.Update(series.Close[0]);
|
||||
Assert.IsType<TValue>(result2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ public class RsxIndicatorTests
|
||||
{
|
||||
var indicator = new RsxIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("RSX", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("RSX", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class RsxIndicatorTests
|
||||
{
|
||||
var indicator = new RsxIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Rsx.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Rsx.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class VelIndicatorTests
|
||||
{
|
||||
var indicator = new VelIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("VEL", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("VEL", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class VelIndicatorTests
|
||||
{
|
||||
var indicator = new VelIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Vel.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Vel.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user