mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-20 19:48:05 +00:00
- Implemented the Standardize class for calculating Z-Score normalization over a specified lookback period. - Updated NDepend badge SVG files to reflect new metrics. - Modified NDepend project files to reference the updated solution file name. - Removed outdated documentation files related to indicator proposals and channel documentation remediation. - Updated workspace configuration to point to the new solution file.
241 lines
6.7 KiB
C#
241 lines
6.7 KiB
C#
using Xunit;
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
public class RocrValidationTests
|
|
{
|
|
private const double Epsilon = 1e-10;
|
|
|
|
#region Mathematical Validation
|
|
|
|
[Fact]
|
|
public void Rocr_ManualCalculation_MatchesExpected()
|
|
{
|
|
// Manual test: ROCR = current / past
|
|
var rocr = new Rocr(3);
|
|
var time = DateTime.UtcNow;
|
|
|
|
var values = new double[] { 100, 105, 110, 115, 120, 125 };
|
|
|
|
for (int i = 0; i < values.Length; i++)
|
|
{
|
|
var result = rocr.Update(new TValue(time.AddSeconds(i), values[i]), true);
|
|
|
|
if (i >= 3)
|
|
{
|
|
// After warmup, should return ratio
|
|
double expected = values[i] / values[i - 3];
|
|
Assert.Equal(expected, result.Value, 10);
|
|
}
|
|
else
|
|
{
|
|
// During warmup, should return 1.0
|
|
Assert.Equal(1.0, result.Value, 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Rocr_TenPercentIncrease_Returns1Point1()
|
|
{
|
|
var rocr = new Rocr(1); // 1-period lookback
|
|
var time = DateTime.UtcNow;
|
|
|
|
rocr.Update(new TValue(time, 100.0), true);
|
|
var result = rocr.Update(new TValue(time.AddSeconds(1), 110.0), true);
|
|
|
|
// 110 / 100 = 1.10
|
|
Assert.Equal(1.10, result.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void Rocr_TenPercentDecrease_Returns0Point9()
|
|
{
|
|
var rocr = new Rocr(1);
|
|
var time = DateTime.UtcNow;
|
|
|
|
rocr.Update(new TValue(time, 100.0), true);
|
|
var result = rocr.Update(new TValue(time.AddSeconds(1), 90.0), true);
|
|
|
|
// 90 / 100 = 0.90
|
|
Assert.Equal(0.90, result.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void Rocr_ConversionToRocp_IsCorrect()
|
|
{
|
|
// ROCP = (ROCR - 1) * 100
|
|
var rocr = new Rocr(1);
|
|
var time = DateTime.UtcNow;
|
|
|
|
rocr.Update(new TValue(time, 100.0), true);
|
|
var result = rocr.Update(new TValue(time.AddSeconds(1), 115.0), true);
|
|
|
|
double rocp = (result.Value - 1.0) * 100.0;
|
|
// 115/100 = 1.15, ROCP = (1.15 - 1) * 100 = 15%
|
|
Assert.Equal(15.0, rocp, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void Rocr_ConversionFromChange_IsCorrect()
|
|
{
|
|
// CHANGE = (current - past) / past = ROCR - 1
|
|
var rocr = new Rocr(1);
|
|
var time = DateTime.UtcNow;
|
|
|
|
rocr.Update(new TValue(time, 100.0), true);
|
|
var result = rocr.Update(new TValue(time.AddSeconds(1), 125.0), true);
|
|
|
|
double change = result.Value - 1.0;
|
|
// 125/100 = 1.25, CHANGE = 0.25 = 25% increase
|
|
Assert.Equal(0.25, change, 10);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Relationship to ROC
|
|
|
|
[Fact]
|
|
public void Rocr_RelationshipToRoc_IsCorrect()
|
|
{
|
|
// ROC = current - past
|
|
// ROCR = current / past
|
|
// If we know ROC and past, we can verify: ROCR = (ROC + past) / past = 1 + ROC/past
|
|
|
|
var rocr = new Rocr(2);
|
|
var roc = new Roc(2);
|
|
var time = DateTime.UtcNow;
|
|
|
|
var values = new double[] { 100, 105, 110, 120, 115 };
|
|
|
|
for (int i = 0; i < values.Length; i++)
|
|
{
|
|
rocr.Update(new TValue(time.AddSeconds(i), values[i]), true);
|
|
roc.Update(new TValue(time.AddSeconds(i), values[i]), true);
|
|
}
|
|
|
|
// For last value: ROCR = current/past, ROC = current - past
|
|
// past = values[3] = 110, current = values[4] = 115
|
|
// ROCR = 115/110, ROC = 115 - 110 = 5
|
|
// Relationship: ROCR = (past + ROC) / past = 1 + ROC/past
|
|
double expectedRelationship = 1.0 + roc.Last.Value / values[2];
|
|
Assert.Equal(expectedRelationship, rocr.Last.Value, 10);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Compounding Property
|
|
|
|
[Fact]
|
|
public void Rocr_Compounding_MultiplyForTotalChange()
|
|
{
|
|
// ROCR values can be multiplied to get total change
|
|
var rocr = new Rocr(1);
|
|
var time = DateTime.UtcNow;
|
|
|
|
var values = new double[] { 100, 110, 121, 133.1 }; // ~10% increase each period
|
|
double compound = 1.0;
|
|
|
|
for (int i = 0; i < values.Length; i++)
|
|
{
|
|
var result = rocr.Update(new TValue(time.AddSeconds(i), values[i]), true);
|
|
if (i > 0)
|
|
{
|
|
compound *= result.Value;
|
|
}
|
|
}
|
|
|
|
// Total change from 100 to 133.1 = 1.331
|
|
double expectedTotal = values[^1] / values[0];
|
|
Assert.Equal(expectedTotal, compound, 5);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Edge Cases
|
|
|
|
[Fact]
|
|
public void Rocr_SmallValues_MaintainsPrecision()
|
|
{
|
|
var rocr = new Rocr(1);
|
|
var time = DateTime.UtcNow;
|
|
|
|
rocr.Update(new TValue(time, 0.0001), true);
|
|
var result = rocr.Update(new TValue(time.AddSeconds(1), 0.00015), true);
|
|
|
|
// 0.00015 / 0.0001 = 1.5
|
|
Assert.Equal(1.5, result.Value, 5);
|
|
}
|
|
|
|
[Fact]
|
|
public void Rocr_LargeValues_MaintainsPrecision()
|
|
{
|
|
var rocr = new Rocr(1);
|
|
var time = DateTime.UtcNow;
|
|
|
|
rocr.Update(new TValue(time, 1_000_000), true);
|
|
var result = rocr.Update(new TValue(time.AddSeconds(1), 1_100_000), true);
|
|
|
|
// 1_100_000 / 1_000_000 = 1.1
|
|
Assert.Equal(1.1, result.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void Rocr_NegativeValues_HandlesCorrectly()
|
|
{
|
|
// Negative values can occur in spreads, basis, etc.
|
|
var rocr = new Rocr(1);
|
|
var time = DateTime.UtcNow;
|
|
|
|
rocr.Update(new TValue(time, -100.0), true);
|
|
var result = rocr.Update(new TValue(time.AddSeconds(1), -50.0), true);
|
|
|
|
// -50 / -100 = 0.5
|
|
Assert.Equal(0.5, result.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void Rocr_MixedSigns_HandlesCorrectly()
|
|
{
|
|
var rocr = new Rocr(1);
|
|
var time = DateTime.UtcNow;
|
|
|
|
rocr.Update(new TValue(time, -100.0), true);
|
|
var result = rocr.Update(new TValue(time.AddSeconds(1), 100.0), true);
|
|
|
|
// 100 / -100 = -1.0
|
|
Assert.Equal(-1.0, result.Value, 10);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Batch vs Streaming Consistency
|
|
|
|
[Fact]
|
|
public void Batch_MatchesStreaming_IdenticalResults()
|
|
{
|
|
var gbm = new GBM(startPrice: 100, mu: 0.0, sigma: 0.5, seed: 42);
|
|
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
var source = bars.Close;
|
|
|
|
// Streaming
|
|
var streamingRocr = new Rocr(5);
|
|
var streamingResults = new List<double>();
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
var tv = streamingRocr.Update(new TValue(source[i].Time, source[i].Value), true);
|
|
streamingResults.Add(tv.Value);
|
|
}
|
|
|
|
// Batch
|
|
var batchResult = Rocr.Calculate(source, 5);
|
|
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
Assert.Equal(batchResult[i].Value, streamingResults[i], 10);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|