mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-22 04:28:04 +00:00
feat(tests): enhance tests with GBM for noise generation and improve tolerance for MAMA validation
feat(trends): implement IDisposable in Bessel and Conv classes to manage event subscriptions fix(trends): add validation for period and parameters in Kama and MGDI calculations fix(trends): clamp logarithmic calculations in JMA to avoid -Infinity
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class JmaZeroDivTests
|
||||
{
|
||||
[Fact]
|
||||
public void Period1_DoesNotProduceInfinityOrNaN()
|
||||
{
|
||||
// Arrange
|
||||
var jma = new Jma(period: 1);
|
||||
double[] values = { 100, 101, 102, 101, 100 };
|
||||
|
||||
// Act & Assert
|
||||
foreach (var v in values)
|
||||
{
|
||||
var result = jma.Update(new TValue(DateTime.UtcNow, v));
|
||||
Assert.False(double.IsNaN(result.Value), $"JMA(1) produced NaN for input {v}");
|
||||
Assert.False(double.IsInfinity(result.Value), $"JMA(1) produced Infinity for input {v}");
|
||||
// For period 1, JMA should ideally track price very closely
|
||||
Assert.Equal(v, result.Value, precision: 1);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Period1_LogValuesAreFinite()
|
||||
{
|
||||
// This test inspects private fields via reflection or just checks behavior
|
||||
// Since we can't easily access private fields, we'll rely on the calculation logic check
|
||||
// If the fix is applied, we shouldn't see -Infinity in internal calculations if we could see them.
|
||||
// But we can check if the output is exactly the input, which implies adapt=0 (if logic holds).
|
||||
|
||||
var jma = new Jma(period: 1);
|
||||
var result = jma.Update(new TValue(DateTime.UtcNow, 100));
|
||||
Assert.Equal(100, result.Value);
|
||||
|
||||
result = jma.Update(new TValue(DateTime.UtcNow, 200));
|
||||
// If adapt is 0 (due to -Infinity log), bands snap to price.
|
||||
// If JMA(1) is identity, result should be 200.
|
||||
// With clamping, adapt is slightly non-zero (approx 1e-12), so result is very close to 200.
|
||||
Assert.Equal(200, result.Value, precision: 8);
|
||||
}
|
||||
}
|
||||
+12
-7
@@ -87,8 +87,9 @@ public sealed class Jma : AbstractBase
|
||||
double sqrtDivider = sqrtParam / (sqrtParam + 1.0);
|
||||
|
||||
// Precompute logs for Math.Exp optimization
|
||||
_logLengthDivider = Math.Log(_lengthDivider);
|
||||
_logSqrtDivider = Math.Log(sqrtDivider);
|
||||
// Clamp to avoid -Infinity when period=1 (dividers can be zero)
|
||||
_logLengthDivider = Math.Log(Math.Max(_lengthDivider, 1e-12));
|
||||
_logSqrtDivider = Math.Log(Math.Max(sqrtDivider, 1e-12));
|
||||
|
||||
// same warmup heuristic used in the AFL port (SetBarsRequired)
|
||||
WarmupPeriod = (int)Math.Ceiling(20.0 + 80.0 * Math.Pow(period, 0.36));
|
||||
@@ -130,10 +131,14 @@ public sealed class Jma : AbstractBase
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
_devBuffer.Snapshot();
|
||||
_volBuffer.Snapshot();
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
_devBuffer.Restore();
|
||||
_volBuffer.Restore();
|
||||
}
|
||||
|
||||
// --- Handle NaN/inf: reuse last finite price ---
|
||||
@@ -169,11 +174,11 @@ public sealed class Jma : AbstractBase
|
||||
double deviation = absValue + 1e-10;
|
||||
|
||||
// 2. 10-bar SMA of local deviation -> "volatility"
|
||||
_devBuffer.Add(deviation, isNew);
|
||||
_devBuffer.Add(deviation);
|
||||
double volatility = _devBuffer.Average;
|
||||
|
||||
// 3. 128-bar volatility history + middle-65 trimmed mean
|
||||
_volBuffer.Add(volatility, isNew);
|
||||
_volBuffer.Add(volatility);
|
||||
double refVolatility = CalculateTrimmedMean(volatility);
|
||||
|
||||
if (refVolatility <= 0.0)
|
||||
@@ -263,8 +268,6 @@ public sealed class Jma : AbstractBase
|
||||
vSpan[i] = j;
|
||||
}
|
||||
|
||||
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||
|
||||
// Restore state by replaying history
|
||||
// JMA needs a lot of history (128 bars for volatility).
|
||||
Reset();
|
||||
@@ -272,9 +275,11 @@ public sealed class Jma : AbstractBase
|
||||
int startIndex = Math.Max(0, len - lookback);
|
||||
for (int i = startIndex; i < len; i++)
|
||||
{
|
||||
Update(new TValue(source.Times[i], source.Values[i]));
|
||||
Step(source.Values[i], true);
|
||||
}
|
||||
|
||||
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user