Files
QuanTAlib/lib/momentum/rsx/Rsx.Validation.Tests.cs
T
86fe32a682 SIMD Refactor: Merge simd-dev into dev (#55)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: aider (openrouter/anthropic/claude-sonnet-4) <aider@aider.chat>
Co-authored-by: Warp <agent@warp.dev>
2026-01-18 19:02:03 -08:00

126 lines
3.8 KiB
C#

using QuanTAlib.Tests;
namespace QuanTAlib;
public class RsxValidationTests
{
private readonly GBM _gbm;
public RsxValidationTests()
{
_gbm = new GBM();
}
[Fact]
public void Validate_Against_Reference_Implementation()
{
// Generate data
const int count = 1000;
int period = 14;
var bars = _gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var prices = bars.Close.Values;
// QuanTAlib implementation
var rsx = new Rsx(period);
var quantalibResults = new double[count];
for (int i = 0; i < count; i++)
{
quantalibResults[i] = rsx.Update(new TValue(DateTime.UtcNow, prices[i])).Value;
}
// Reference implementation (from user prompt)
var refRsx = new ReferenceRsx(period);
var refResults = new double[count];
for (int i = 0; i < count; i++)
{
refResults[i] = refRsx.Add(prices[i]);
}
// Compare
for (int i = 0; i < count; i++)
{
// Allow small difference due to floating point arithmetic order
Assert.Equal(refResults[i], quantalibResults[i], ValidationHelper.DefaultTolerance);
}
}
// Reference implementation provided in the task description
private class ReferenceRsx
{
private readonly double alpha, ialpha;
// Internal state variables for filter registers:
private double f28, f30, f38, f40, f48, f50;
private double f58, f60, f68, f70, f78, f80;
// Added state for f10 logic
private double lastF8;
private bool initialized;
public double Current { get; private set; }
public ReferenceRsx(int length)
{
// Initialize constants:
this.alpha = 3.0 / (length + 2.0);
this.ialpha = 1.0 - this.alpha;
// Initialize filters to 0:
f28 = f30 = f38 = f40 = f48 = f50 = 0.0;
f58 = f60 = f68 = f70 = f78 = f80 = 0.0;
this.Current = 50.0; // neutral start
this.initialized = false;
}
public double Add(double price)
{
// Core RSX calculations (assuming price input as closing price):
double f8 = 100 * price;
if (!initialized)
{
lastF8 = f8;
initialized = true;
}
double v8 = f8 - lastF8;
lastF8 = f8;
// First smoothing stage:
f28 = ialpha * f28 + alpha * v8;
f30 = alpha * f28 + ialpha * f30;
double vC = 1.5 * f28 - 0.5 * f30;
// Second smoothing stage:
f38 = ialpha * f38 + alpha * vC;
f40 = alpha * f38 + ialpha * f40;
double v10 = 1.5 * f38 - 0.5 * f40;
// Third smoothing stage:
f48 = ialpha * f48 + alpha * v10;
f50 = alpha * f48 + ialpha * f50;
double v14 = 1.5 * f48 - 0.5 * f50;
// Repeat stages for absolute value (momentum magnitude):
f58 = ialpha * f58 + alpha * Math.Abs(v8);
f60 = alpha * f58 + ialpha * f60;
double v18 = 1.5 * f58 - 0.5 * f60;
f68 = ialpha * f68 + alpha * v18;
f70 = alpha * f68 + ialpha * f70;
double v1C = 1.5 * f68 - 0.5 * f70;
f78 = ialpha * f78 + alpha * v1C;
f80 = alpha * f78 + ialpha * f80;
double v20 = 1.5 * f78 - 0.5 * f80;
// Final RSX value:
double rsx;
if (v20 > 1e-10) // Avoid division by zero
{
double v4 = (v14 / v20 + 1.0) * 50.0;
rsx = Math.Clamp(v4, 0.0, 100.0);
}
else
{
rsx = 50.0;
}
this.Current = rsx;
return rsx;
}
}
}