mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-04 04:07:42 +00:00
060649192f
- 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
695 lines
19 KiB
C#
695 lines
19 KiB
C#
// RVI Unit Tests
|
|
|
|
using Xunit;
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
public class RviTests
|
|
{
|
|
private readonly GBM _gbm;
|
|
private const int DefaultStdevLength = 10;
|
|
private const int DefaultRmaLength = 14;
|
|
private const double Tolerance = 1e-10;
|
|
|
|
public RviTests()
|
|
{
|
|
_gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.2, seed: 42);
|
|
}
|
|
|
|
private TBarSeries GenerateBars(int count)
|
|
{
|
|
_gbm.Reset(DateTime.UtcNow.Ticks);
|
|
return _gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
}
|
|
|
|
private static TSeries GeneratePriceSeries(int count, int seed = 42)
|
|
{
|
|
var gbm = new GBM(seed: seed);
|
|
var bars = gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
var t = new List<long>(count);
|
|
var v = new List<double>(count);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
t.Add(bars[i].Time);
|
|
v.Add(bars[i].Close);
|
|
}
|
|
return new TSeries(t, v);
|
|
}
|
|
|
|
#region Constructor Tests
|
|
|
|
[Fact]
|
|
public void Constructor_DefaultParameters_SetsCorrectValues()
|
|
{
|
|
var rvi = new Rvi();
|
|
Assert.Equal(DefaultStdevLength, rvi.StdevLength);
|
|
Assert.Equal(DefaultRmaLength, rvi.RmaLength);
|
|
Assert.Equal($"Rvi({DefaultStdevLength},{DefaultRmaLength})", rvi.Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_CustomParameters_SetsCorrectValues()
|
|
{
|
|
var rvi = new Rvi(stdevLength: 20, rmaLength: 21);
|
|
Assert.Equal(20, rvi.StdevLength);
|
|
Assert.Equal(21, rvi.RmaLength);
|
|
Assert.Equal("Rvi(20,21)", rvi.Name);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(0)]
|
|
[InlineData(-5)]
|
|
public void Constructor_InvalidStdevLength_ThrowsArgumentException(int stdevLength)
|
|
{
|
|
var ex = Assert.Throws<ArgumentException>(() => new Rvi(stdevLength: stdevLength));
|
|
Assert.Equal("stdevLength", ex.ParamName);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(-1)]
|
|
public void Constructor_InvalidRmaLength_ThrowsArgumentException(int rmaLength)
|
|
{
|
|
var ex = Assert.Throws<ArgumentException>(() => new Rvi(stdevLength: 10, rmaLength: rmaLength));
|
|
Assert.Equal("rmaLength", ex.ParamName);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithSource_SubscribesToEvents()
|
|
{
|
|
var source = new TSeries();
|
|
var rvi = new Rvi(source, stdevLength: 10, rmaLength: 14);
|
|
source.Add(new TValue(DateTime.UtcNow, 100.0));
|
|
Assert.NotEqual(default, rvi.Last);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Basic Calculation Tests
|
|
|
|
[Fact]
|
|
public void Update_FirstValue_ReturnsNeutral()
|
|
{
|
|
var rvi = new Rvi();
|
|
var result = rvi.Update(new TValue(DateTime.UtcNow, 100.0));
|
|
Assert.Equal(50.0, result.Value, Tolerance);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_ReturnsValidTValue()
|
|
{
|
|
var rvi = new Rvi();
|
|
var time = DateTime.UtcNow;
|
|
rvi.Update(new TValue(time.AddSeconds(-1), 100.0));
|
|
var result = rvi.Update(new TValue(time, 101.0));
|
|
Assert.Equal(time.Ticks, result.Time);
|
|
Assert.True(double.IsFinite(result.Value));
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_WithTBar_UsesHighAndLow()
|
|
{
|
|
var rvi = new Rvi();
|
|
var bar = new TBar(DateTime.UtcNow, 98, 102, 97, 100, 1000);
|
|
var result = rvi.Update(bar);
|
|
Assert.Equal(50.0, result.Value, Tolerance); // First value is always neutral
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_WithTBar_RevisedDiffersFromOriginal()
|
|
{
|
|
// The revised RVI (high+low avg) should differ from original (close-only)
|
|
// Use oscillating close with asymmetric high/low
|
|
var rviBar = new Rvi(stdevLength: 5, rmaLength: 5);
|
|
var rviClose = new Rvi(stdevLength: 5, rmaLength: 5);
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
var time = DateTime.UtcNow.AddSeconds(i);
|
|
double close = 100.0 + (Math.Sin(i * 0.5) * 3.0); // oscillating
|
|
double high = close + 2.0 + (Math.Sin(i * 0.3) * 1.5); // asymmetric highs
|
|
double low = close - 1.0 - (Math.Cos(i * 0.7) * 0.8); // asymmetric lows
|
|
|
|
rviBar.Update(new TBar(time, close - 0.5, high, low, close, 1000));
|
|
rviClose.Update(new TValue(time, close));
|
|
}
|
|
|
|
// With asymmetric high/low, revised RVI should differ from close-only
|
|
Assert.NotEqual(rviBar.Last.Value, rviClose.Last.Value, 0.01);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_OutputRangeIsZeroToHundred()
|
|
{
|
|
var rvi = new Rvi(stdevLength: 5, rmaLength: 5);
|
|
var bars = GenerateBars(500);
|
|
|
|
for (int i = 0; i < 500; i++)
|
|
{
|
|
var result = rvi.Update(new TValue(bars[i].Time, bars[i].Close));
|
|
Assert.InRange(result.Value, 0.0, 100.0);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_ConsistentUpTrend_ProducesHighValues()
|
|
{
|
|
var rvi = new Rvi(stdevLength: 5, rmaLength: 10);
|
|
|
|
// Consistent up moves
|
|
double price = 100.0;
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
price += 1.0; // Always up
|
|
rvi.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
|
}
|
|
|
|
// Should be above 50 (bullish)
|
|
Assert.True(rvi.Last.Value > 50.0);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_ConsistentDownTrend_ProducesLowValues()
|
|
{
|
|
var rvi = new Rvi(stdevLength: 5, rmaLength: 10);
|
|
|
|
// Consistent down moves
|
|
double price = 200.0;
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
price -= 1.0; // Always down
|
|
rvi.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
|
}
|
|
|
|
// Should be below 50 (bearish)
|
|
Assert.True(rvi.Last.Value < 50.0);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_NoChange_StaysNeutral()
|
|
{
|
|
var rvi = new Rvi(stdevLength: 5, rmaLength: 10);
|
|
|
|
// Constant price - no direction
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
rvi.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0));
|
|
}
|
|
|
|
// Should approach neutral (50)
|
|
Assert.InRange(rvi.Last.Value, 40.0, 60.0);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsHot and WarmupPeriod Tests
|
|
|
|
[Fact]
|
|
public void IsHot_BeforeWarmup_ReturnsFalse()
|
|
{
|
|
var rvi = new Rvi(stdevLength: 10, rmaLength: 14);
|
|
|
|
for (int i = 0; i < 9; i++)
|
|
{
|
|
rvi.Update(new TValue(DateTime.UtcNow, 100.0 + i));
|
|
Assert.False(rvi.IsHot);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void IsHot_AfterWarmup_ReturnsTrue()
|
|
{
|
|
var rvi = new Rvi(stdevLength: 10, rmaLength: 14);
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
rvi.Update(new TValue(DateTime.UtcNow, 100.0 + i));
|
|
}
|
|
|
|
Assert.True(rvi.IsHot);
|
|
}
|
|
|
|
[Fact]
|
|
public void WarmupPeriod_EqualsStdevLengthPlusRmaLength()
|
|
{
|
|
var rvi = new Rvi(stdevLength: 10, rmaLength: 14);
|
|
Assert.Equal(24, rvi.WarmupPeriod);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region State and Bar Correction Tests
|
|
|
|
[Fact]
|
|
public void Update_IsNewTrue_AdvancesState()
|
|
{
|
|
var rvi = new Rvi();
|
|
var time = DateTime.UtcNow;
|
|
|
|
rvi.Update(new TValue(time.AddSeconds(-2), 100.0), isNew: true);
|
|
rvi.Update(new TValue(time.AddSeconds(-1), 101.0), isNew: true);
|
|
var val1 = rvi.Update(new TValue(time, 102.0), isNew: true);
|
|
|
|
rvi.Update(new TValue(time.AddSeconds(-1), 101.0), isNew: true);
|
|
var val2 = rvi.Update(new TValue(time.AddSeconds(1), 102.0), isNew: true);
|
|
|
|
// Different sequence should produce different result
|
|
Assert.NotEqual(val1.Value, val2.Value, Tolerance);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_IsNewFalse_RollsBackState()
|
|
{
|
|
var rvi = new Rvi();
|
|
var time = DateTime.UtcNow;
|
|
|
|
// Build up some history
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
rvi.Update(new TValue(time.AddSeconds(i), 100.0 + (i * 0.1)), isNew: true);
|
|
}
|
|
|
|
_ = rvi.Last; // Capture state before update
|
|
|
|
// New bar
|
|
var result1 = rvi.Update(new TValue(time.AddSeconds(20), 105.0), isNew: true);
|
|
|
|
// Update same bar with different value - should rollback
|
|
var result2 = rvi.Update(new TValue(time.AddSeconds(20), 106.0), isNew: false);
|
|
|
|
// Different input should produce different result
|
|
Assert.NotEqual(result1.Value, result2.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_IterativeCorrections_RestoreState()
|
|
{
|
|
var rvi = new Rvi(stdevLength: 5, rmaLength: 10);
|
|
var time = DateTime.UtcNow;
|
|
|
|
// Build history
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
rvi.Update(new TValue(time.AddSeconds(i), 100.0 + (i * 0.5)), isNew: true);
|
|
}
|
|
|
|
// Start a new bar
|
|
var newBarValue = rvi.Update(new TValue(time.AddSeconds(30), 120.0), isNew: true);
|
|
|
|
// Multiple corrections
|
|
_ = rvi.Update(new TValue(time.AddSeconds(30), 121.0), isNew: false);
|
|
_ = rvi.Update(new TValue(time.AddSeconds(30), 122.0), isNew: false);
|
|
var correction3 = rvi.Update(new TValue(time.AddSeconds(30), 120.0), isNew: false);
|
|
|
|
// Going back to original value should restore original result
|
|
Assert.Equal(newBarValue.Value, correction3.Value, Tolerance);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Reset Tests
|
|
|
|
[Fact]
|
|
public void Reset_ClearsState()
|
|
{
|
|
var rvi = new Rvi();
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
rvi.Update(new TValue(DateTime.UtcNow, 100.0 + i));
|
|
}
|
|
|
|
Assert.True(rvi.IsHot);
|
|
|
|
rvi.Reset();
|
|
|
|
Assert.False(rvi.IsHot);
|
|
Assert.Equal(default, rvi.Last);
|
|
}
|
|
|
|
[Fact]
|
|
public void Reset_AllowsReuseOfIndicator()
|
|
{
|
|
var rvi = new Rvi();
|
|
|
|
// First run
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
rvi.Update(new TValue(DateTime.UtcNow, 100.0 + i));
|
|
}
|
|
var firstResult = rvi.Last;
|
|
|
|
rvi.Reset();
|
|
|
|
// Second run with same data
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
rvi.Update(new TValue(DateTime.UtcNow, 100.0 + i));
|
|
}
|
|
var secondResult = rvi.Last;
|
|
|
|
Assert.Equal(firstResult.Value, secondResult.Value, Tolerance);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region NaN and Infinity Handling Tests
|
|
|
|
[Fact]
|
|
public void Update_NaNInput_UsesLastValidValue()
|
|
{
|
|
var rvi = new Rvi();
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
rvi.Update(new TValue(DateTime.UtcNow, 100.0 + i));
|
|
}
|
|
var validValue = rvi.Last;
|
|
|
|
var nanResult = rvi.Update(new TValue(DateTime.UtcNow, double.NaN));
|
|
|
|
Assert.Equal(validValue.Value, nanResult.Value, Tolerance);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_InfinityInput_UsesLastValidValue()
|
|
{
|
|
var rvi = new Rvi();
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
rvi.Update(new TValue(DateTime.UtcNow, 100.0 + i));
|
|
}
|
|
var validValue = rvi.Last;
|
|
|
|
var infResult = rvi.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity));
|
|
|
|
Assert.Equal(validValue.Value, infResult.Value, Tolerance);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_NegativeInfinityInput_UsesLastValidValue()
|
|
{
|
|
var rvi = new Rvi();
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
rvi.Update(new TValue(DateTime.UtcNow, 100.0 + i));
|
|
}
|
|
var validValue = rvi.Last;
|
|
|
|
var negInfResult = rvi.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity));
|
|
|
|
Assert.Equal(validValue.Value, negInfResult.Value, Tolerance);
|
|
}
|
|
|
|
[Fact]
|
|
public void Batch_WithNaN_ProducesSafeOutput()
|
|
{
|
|
double[] prices = [100.0, 101.0, double.NaN, 103.0, 104.0, 105.0, 106.0, 107.0, 108.0, 109.0, 110.0];
|
|
double[] output = new double[prices.Length];
|
|
|
|
Rvi.Batch(prices, output, stdevLength: 5, rmaLength: 5);
|
|
|
|
foreach (var val in output)
|
|
{
|
|
Assert.True(double.IsFinite(val));
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Mode Consistency Tests
|
|
|
|
[Fact]
|
|
public void AllModes_ProduceConsistentResults()
|
|
{
|
|
const int dataLen = 200;
|
|
var bars = GenerateBars(dataLen);
|
|
|
|
var prices = new double[dataLen];
|
|
var times = new long[dataLen];
|
|
|
|
for (int i = 0; i < dataLen; i++)
|
|
{
|
|
prices[i] = bars[i].Close;
|
|
times[i] = bars[i].Time;
|
|
}
|
|
|
|
// Mode 1: Streaming
|
|
var rvi1 = new Rvi(stdevLength: 10, rmaLength: 14);
|
|
for (int i = 0; i < dataLen; i++)
|
|
{
|
|
rvi1.Update(new TValue(times[i], prices[i]), isNew: true);
|
|
}
|
|
|
|
// Mode 2: Batch via TSeries
|
|
var tSeries = new TSeries(new List<long>(times), new List<double>(prices));
|
|
var batchResult = Rvi.Batch(tSeries, stdevLength: 10, rmaLength: 14);
|
|
|
|
// Mode 3: Span-based
|
|
double[] spanOutput = new double[dataLen];
|
|
Rvi.Batch(prices, spanOutput, stdevLength: 10, rmaLength: 14);
|
|
|
|
// Mode 4: Event-driven
|
|
var sourceSeries = new TSeries();
|
|
var rviEvent = new Rvi(sourceSeries, stdevLength: 10, rmaLength: 14);
|
|
for (int i = 0; i < dataLen; i++)
|
|
{
|
|
sourceSeries.Add(new TValue(times[i], prices[i]));
|
|
}
|
|
|
|
// Compare last 100 values
|
|
int compareStart = dataLen - 100;
|
|
for (int i = compareStart; i < dataLen; i++)
|
|
{
|
|
double batch = batchResult[i].Value;
|
|
double span = spanOutput[i];
|
|
|
|
// Batch and Span should match exactly
|
|
Assert.Equal(batch, span, Tolerance);
|
|
}
|
|
|
|
// Final values should match
|
|
Assert.Equal(rvi1.Last.Value, batchResult[dataLen - 1].Value, 1e-8);
|
|
Assert.Equal(rvi1.Last.Value, spanOutput[dataLen - 1], 1e-8);
|
|
Assert.Equal(rvi1.Last.Value, rviEvent.Last.Value, 1e-8);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Span API Tests
|
|
|
|
[Fact]
|
|
public void Batch_ValidatesOutputLength()
|
|
{
|
|
double[] prices = [100.0, 101.0, 102.0, 103.0, 104.0];
|
|
double[] output = new double[3]; // Too short
|
|
|
|
var ex = Assert.Throws<ArgumentException>(() => Rvi.Batch(prices, output));
|
|
Assert.Equal("output", ex.ParamName);
|
|
}
|
|
|
|
[Fact]
|
|
public void Batch_ValidatesStdevLength()
|
|
{
|
|
double[] prices = [100.0, 101.0, 102.0];
|
|
double[] output = new double[3];
|
|
|
|
var ex = Assert.Throws<ArgumentException>(() => Rvi.Batch(prices, output, stdevLength: 1));
|
|
Assert.Equal("stdevLength", ex.ParamName);
|
|
}
|
|
|
|
[Fact]
|
|
public void Batch_ValidatesRmaLength()
|
|
{
|
|
double[] prices = [100.0, 101.0, 102.0];
|
|
double[] output = new double[3];
|
|
|
|
var ex = Assert.Throws<ArgumentException>(() => Rvi.Batch(prices, output, stdevLength: 2, rmaLength: 0));
|
|
Assert.Equal("rmaLength", ex.ParamName);
|
|
}
|
|
|
|
[Fact]
|
|
public void Batch_EmptyInput_ProducesNoOutput()
|
|
{
|
|
double[] prices = [];
|
|
double[] output = [];
|
|
|
|
Rvi.Batch(prices, output);
|
|
// Should not throw, and output remains empty
|
|
Assert.Empty(output);
|
|
}
|
|
|
|
[Fact]
|
|
public void Batch_MatchesStreamingMode()
|
|
{
|
|
const int dataLen = 100;
|
|
var bars = GenerateBars(dataLen);
|
|
|
|
var prices = new double[dataLen];
|
|
for (int i = 0; i < dataLen; i++)
|
|
{
|
|
prices[i] = bars[i].Close;
|
|
}
|
|
|
|
// Streaming
|
|
var rvi = new Rvi(stdevLength: 10, rmaLength: 14);
|
|
for (int i = 0; i < dataLen; i++)
|
|
{
|
|
rvi.Update(new TValue(bars[i].Time, prices[i]));
|
|
}
|
|
|
|
// Batch
|
|
double[] batchOutput = new double[dataLen];
|
|
Rvi.Batch(prices, batchOutput, stdevLength: 10, rmaLength: 14);
|
|
|
|
// Compare final value
|
|
Assert.Equal(rvi.Last.Value, batchOutput[dataLen - 1], 1e-8);
|
|
}
|
|
|
|
[Fact]
|
|
public void Batch_LargeDataset_NoStackOverflow()
|
|
{
|
|
const int dataLen = 10000;
|
|
var bars = new GBM(seed: 42).Fetch(dataLen, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
double[] prices = bars.CloseValues.ToArray();
|
|
double[] output = new double[dataLen];
|
|
|
|
Rvi.Batch(prices, output, stdevLength: 10, rmaLength: 14);
|
|
|
|
// Verify all outputs are valid
|
|
for (int i = 0; i < dataLen; i++)
|
|
{
|
|
Assert.True(double.IsFinite(output[i]));
|
|
Assert.InRange(output[i], 0.0, 100.0);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Chainability Tests
|
|
|
|
[Fact]
|
|
public void Pub_FiresOnUpdate()
|
|
{
|
|
var rvi = new Rvi();
|
|
int eventCount = 0;
|
|
|
|
rvi.Pub += (object? sender, in TValueEventArgs args) => eventCount++;
|
|
|
|
rvi.Update(new TValue(DateTime.UtcNow, 100.0));
|
|
rvi.Update(new TValue(DateTime.UtcNow, 101.0));
|
|
rvi.Update(new TValue(DateTime.UtcNow, 102.0));
|
|
|
|
Assert.Equal(3, eventCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void EventChaining_Works()
|
|
{
|
|
var sourceSeries = new TSeries();
|
|
var rvi = new Rvi(sourceSeries, stdevLength: 5, rmaLength: 10);
|
|
|
|
var results = new List<double>();
|
|
rvi.Pub += (object? sender, in TValueEventArgs args) => results.Add(args.Value.Value);
|
|
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
sourceSeries.Add(new TValue(DateTime.UtcNow, 100.0 + i));
|
|
}
|
|
|
|
Assert.Equal(30, results.Count);
|
|
Assert.All(results.ToArray(), r => Assert.InRange(r, 0.0, 100.0));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TSeries and TBarSeries Tests
|
|
|
|
[Fact]
|
|
public void Update_TSeries_ReturnsCorrectLength()
|
|
{
|
|
var rvi = new Rvi();
|
|
var source = new TSeries();
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
source.Add(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + i));
|
|
}
|
|
|
|
var result = rvi.Update(source);
|
|
|
|
Assert.Equal(50, result.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_TBarSeries_ReturnsCorrectLength()
|
|
{
|
|
var rvi = new Rvi();
|
|
var source = new TBarSeries();
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
var time = DateTime.UtcNow.AddSeconds(i);
|
|
double price = 100.0 + i;
|
|
source.Add(new TBar(time, price - 1, price + 1, price - 2, price, 1000));
|
|
}
|
|
|
|
var result = rvi.Update(source);
|
|
|
|
Assert.Equal(50, result.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Calculate_Static_TSeries_Works()
|
|
{
|
|
var source = new TSeries();
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
source.Add(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + (i * 0.5)));
|
|
}
|
|
|
|
var result = Rvi.Batch(source, stdevLength: 10, rmaLength: 14);
|
|
|
|
Assert.Equal(50, result.Count);
|
|
// Allow small floating-point tolerance beyond [0,100]
|
|
Assert.All(result.Values.ToArray(), v => Assert.InRange(v, -1e-9, 100.0 + 1e-9));
|
|
}
|
|
|
|
[Fact]
|
|
public void Calculate_Static_TBarSeries_Works()
|
|
{
|
|
var source = new TBarSeries();
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
var time = DateTime.UtcNow.AddSeconds(i);
|
|
double price = 100.0 + (i * 0.5);
|
|
source.Add(new TBar(time, price - 1, price + 1, price - 2, price, 1000));
|
|
}
|
|
|
|
var result = Rvi.Batch(source, stdevLength: 10, rmaLength: 14);
|
|
|
|
Assert.Equal(50, result.Count);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Prime Tests
|
|
|
|
[Fact]
|
|
public void Prime_SetsInitialState()
|
|
{
|
|
var rvi = new Rvi(stdevLength: 5, rmaLength: 10);
|
|
double[] warmupData = [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114];
|
|
|
|
rvi.Prime(warmupData);
|
|
|
|
Assert.True(rvi.IsHot);
|
|
Assert.True(rvi.Last.Value > 0);
|
|
}
|
|
|
|
#endregion
|
|
}
|