mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-22 04:28:04 +00:00
style patterns
This commit is contained in:
@@ -100,7 +100,9 @@ public sealed class Bessel : AbstractBase
|
||||
public Bessel(int length)
|
||||
{
|
||||
if (length < 2)
|
||||
{
|
||||
throw new ArgumentException("Length must be at least 2 for 2nd-order Bessel filter", nameof(length));
|
||||
}
|
||||
|
||||
double a = Math.Exp(-Math.PI / length);
|
||||
double b = 2.0 * a * Math.Cos(1.738 * Math.PI / length);
|
||||
@@ -171,7 +173,9 @@ public sealed class Bessel : AbstractBase
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
||||
@@ -209,9 +213,13 @@ public sealed class Bessel : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
_state.LastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = _state.LastValidValue;
|
||||
}
|
||||
|
||||
_state.F2 = _state.F1;
|
||||
_state.F1 = val;
|
||||
@@ -223,9 +231,13 @@ public sealed class Bessel : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
_state.LastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = _state.LastValidValue;
|
||||
}
|
||||
|
||||
double filt = Math.FusedMultiplyAdd(_c3, _state.F2,
|
||||
Math.FusedMultiplyAdd(_c2, _state.F1, _c1 * val));
|
||||
@@ -236,7 +248,9 @@ public sealed class Bessel : AbstractBase
|
||||
}
|
||||
|
||||
if (_state.Count >= WarmupPeriod)
|
||||
{
|
||||
_state.IsHot = true;
|
||||
}
|
||||
|
||||
Last = new TValue(DateTime.MinValue, _state.F1);
|
||||
|
||||
@@ -310,7 +324,9 @@ public sealed class Bessel : AbstractBase
|
||||
}
|
||||
|
||||
if (!_state.IsHot && _state.Count >= WarmupPeriod)
|
||||
{
|
||||
_state.IsHot = true;
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, filt);
|
||||
PubEvent(Last);
|
||||
@@ -330,7 +346,9 @@ public sealed class Bessel : AbstractBase
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -404,9 +422,13 @@ public sealed class Bessel : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
state.LastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = state.LastValidValue;
|
||||
}
|
||||
|
||||
state.F2 = state.F1;
|
||||
state.F1 = val;
|
||||
@@ -419,9 +441,13 @@ public sealed class Bessel : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
state.LastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = state.LastValidValue;
|
||||
}
|
||||
|
||||
double filt = Math.FusedMultiplyAdd(c3, state.F2,
|
||||
Math.FusedMultiplyAdd(c2, state.F1, c1 * val));
|
||||
@@ -433,7 +459,9 @@ public sealed class Bessel : AbstractBase
|
||||
}
|
||||
|
||||
if (!state.IsHot && state.Count >= warmupPeriod)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -479,13 +507,19 @@ public sealed class Bessel : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int length)
|
||||
{
|
||||
if (length < 2)
|
||||
{
|
||||
throw new ArgumentException("Length must be at least 2 for 2nd-order Bessel filter", nameof(length));
|
||||
}
|
||||
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double a = Math.Exp(-Math.PI / length);
|
||||
double b = 2.0 * a * Math.Cos(1.738 * Math.PI / length);
|
||||
|
||||
@@ -142,7 +142,10 @@ public sealed class BilateralValidationTests : IDisposable
|
||||
_history.RemoveAt(0);
|
||||
}
|
||||
|
||||
if (_history.Count == 0) return double.NaN;
|
||||
if (_history.Count == 0)
|
||||
{
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
double sigmaS = Math.Max(_length * _sigmaSRatio, 1e-10);
|
||||
|
||||
@@ -178,7 +181,10 @@ public sealed class BilateralValidationTests : IDisposable
|
||||
|
||||
private static double CalculateStDev(List<double> values)
|
||||
{
|
||||
if (values.Count < 2) return 0;
|
||||
if (values.Count < 2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
double avg = values.Average();
|
||||
double sumSqDiff = values.Sum(d => (d - avg) * (d - avg));
|
||||
|
||||
@@ -46,7 +46,9 @@ public sealed class Bilateral : AbstractBase
|
||||
public Bilateral(int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
_period = period;
|
||||
_sigmaSRatio = sigmaSRatio;
|
||||
@@ -74,7 +76,10 @@ public sealed class Bilateral : AbstractBase
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_buffer.Clear();
|
||||
_state = default;
|
||||
@@ -128,7 +133,10 @@ public sealed class Bilateral : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new System.Collections.Generic.List<long>(len);
|
||||
@@ -337,10 +345,14 @@ public sealed class Bilateral : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> destination, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
if (destination.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Destination must have length >= source length", nameof(destination));
|
||||
}
|
||||
|
||||
// Rent arrays for large periods to avoid heap allocations
|
||||
double[]? rentedSpatialWeights = null;
|
||||
@@ -424,7 +436,10 @@ public sealed class Bilateral : AbstractBase
|
||||
|
||||
int currentNewestIdx = windowIdx;
|
||||
windowIdx = (windowIdx + 1) % period;
|
||||
if (count < period) count++;
|
||||
if (count < period)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
// Calculate StDev
|
||||
double invCount = 1.0 / count;
|
||||
@@ -480,9 +495,14 @@ public sealed class Bilateral : AbstractBase
|
||||
finally
|
||||
{
|
||||
if (rentedSpatialWeights != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(rentedSpatialWeights);
|
||||
}
|
||||
|
||||
if (rentedWindow != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(rentedWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,4 +534,4 @@ public sealed class Bilateral : AbstractBase
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,4 +134,4 @@ public class BpfIndicatorTests
|
||||
Assert.Equal(60, indicator.LowerPeriod);
|
||||
Assert.Equal(20, indicator.UpperPeriod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,4 +116,4 @@ public class BpfTests
|
||||
// Last value should be close to 0
|
||||
Assert.True(Math.Abs(output[^1]) < 1e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ public class BpfValidationTests
|
||||
double[] sine15 = new double[T]; // Period 15 (Inside band, 10 < P < 40). Should pass.
|
||||
double[] sine100 = new double[T]; // Period 100 (Too slow, should be blocked by HP(40)). HP(40) passes P < 40.
|
||||
|
||||
for (int i = 0; i < T; i++) {
|
||||
for (int i = 0; i < T; i++)
|
||||
{
|
||||
sine5[i] = Math.Sin(2 * Math.PI * i / 5.0);
|
||||
sine15[i] = Math.Sin(2 * Math.PI * i / 15.0);
|
||||
sine100[i] = Math.Sin(2 * Math.PI * i / 100.0);
|
||||
|
||||
+16
-3
@@ -53,9 +53,14 @@ public sealed class Bpf : AbstractBase
|
||||
public Bpf(int lowerPeriod, int upperPeriod)
|
||||
{
|
||||
if (lowerPeriod < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(lowerPeriod), "Lower period must be >= 1");
|
||||
}
|
||||
|
||||
if (upperPeriod < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(upperPeriod), "Upper period must be >= 1");
|
||||
}
|
||||
|
||||
LowerPeriod = lowerPeriod;
|
||||
UpperPeriod = upperPeriod;
|
||||
@@ -95,7 +100,10 @@ public sealed class Bpf : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
double[] values = source.Values.ToArray();
|
||||
double[] results = new double[values.Length];
|
||||
@@ -186,7 +194,9 @@ public sealed class Bpf : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int lowerPeriod, int upperPeriod)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output spans must be of the same length.", nameof(output));
|
||||
}
|
||||
|
||||
// Coefficients
|
||||
double sqrt2Pi = Math.Sqrt(2.0) * Math.PI;
|
||||
@@ -213,7 +223,10 @@ public sealed class Bpf : AbstractBase
|
||||
if (source.Length > 0)
|
||||
{
|
||||
lastValid = source[0];
|
||||
if (!double.IsFinite(lastValid)) lastValid = 0;
|
||||
if (!double.IsFinite(lastValid))
|
||||
{
|
||||
lastValid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
@@ -260,4 +273,4 @@ public sealed class Bpf : AbstractBase
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,4 +111,4 @@ public class Cheby1Tests
|
||||
Assert.Equal(iterativeResults[i], spanResults[i], 1e-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ public class Cheby1ValidationTests
|
||||
|
||||
var filter = new Cheby1(20, 1.0); // Wn = 1/20
|
||||
var input = new TSeries();
|
||||
for (int i = 0; i < 100; i++) input.Add(new TValue(DateTime.UtcNow, 100)); // Impulse/Step
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
input.Add(new TValue(DateTime.UtcNow, 100)); // Impulse/Step
|
||||
}
|
||||
|
||||
var output = filter.Update(input);
|
||||
|
||||
|
||||
@@ -46,9 +46,14 @@ public sealed class Cheby1 : AbstractBase
|
||||
public Cheby1(int period, double ripple = 1.0)
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 2");
|
||||
}
|
||||
|
||||
if (ripple <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(ripple), "Ripple must be > 0");
|
||||
}
|
||||
|
||||
Period = period;
|
||||
Ripple = ripple;
|
||||
@@ -105,7 +110,10 @@ public sealed class Cheby1 : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
double[] values = source.Values.ToArray();
|
||||
double[] results = new double[values.Length];
|
||||
@@ -178,7 +186,9 @@ public sealed class Cheby1 : AbstractBase
|
||||
_state.Filt1 = filt;
|
||||
|
||||
if (_state.Count < 2)
|
||||
{
|
||||
_state.Count++;
|
||||
}
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, filt);
|
||||
@@ -197,12 +207,19 @@ public sealed class Cheby1 : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, double ripple = 1.0)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output spans must be of the same length.", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 2");
|
||||
}
|
||||
|
||||
if (ripple <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(ripple), "Ripple must be > 0");
|
||||
}
|
||||
|
||||
// Coefficients
|
||||
double safeRipple = Math.Max(ripple, 0.01);
|
||||
@@ -239,7 +256,10 @@ public sealed class Cheby1 : AbstractBase
|
||||
if (source.Length > 0)
|
||||
{
|
||||
lastValid = source[0];
|
||||
if (!double.IsFinite(lastValid)) lastValid = 0;
|
||||
if (!double.IsFinite(lastValid))
|
||||
{
|
||||
lastValid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
@@ -285,4 +305,4 @@ public sealed class Cheby1 : AbstractBase
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,4 +109,4 @@ public class Cheby2Tests
|
||||
Assert.Equal(iterativeResults[i], spanResults[i], 1e-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,11 @@ public class Cheby2ValidationTests
|
||||
Assert.True(Math.Abs(result1) > 0);
|
||||
|
||||
// Verify stability (should decay towards zero for lowpass IIR)
|
||||
for(int i=0; i<50; i++)
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
filter.Update(new TValue(DateTime.UtcNow, 0.0));
|
||||
}
|
||||
|
||||
Assert.True(Math.Abs(filter.Last.Value) < 1e-5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,14 @@ public sealed class Cheby2 : AbstractBase
|
||||
public Cheby2(int period, double attenuation = 5.0)
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 2");
|
||||
}
|
||||
|
||||
if (attenuation <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(attenuation), "Attenuation must be > 0");
|
||||
}
|
||||
|
||||
Period = period;
|
||||
Attenuation = attenuation;
|
||||
@@ -123,7 +128,10 @@ public sealed class Cheby2 : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
double[] values = source.Values.ToArray();
|
||||
double[] results = new double[values.Length];
|
||||
@@ -192,7 +200,9 @@ public sealed class Cheby2 : AbstractBase
|
||||
_state.Filt1 = filt;
|
||||
|
||||
if (_state.Count < 2)
|
||||
{
|
||||
_state.Count++;
|
||||
}
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, filt);
|
||||
@@ -211,12 +221,19 @@ public sealed class Cheby2 : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, double attenuation = 5.0)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output spans must be of the same length.", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 2");
|
||||
}
|
||||
|
||||
if (attenuation <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(attenuation), "Attenuation must be > 0");
|
||||
}
|
||||
|
||||
// Coefficients
|
||||
double safeAtten = Math.Max(attenuation, 0.1);
|
||||
@@ -268,7 +285,10 @@ public sealed class Cheby2 : AbstractBase
|
||||
if (source.Length > 0)
|
||||
{
|
||||
lastValid = source[0];
|
||||
if (!double.IsFinite(lastValid)) lastValid = 0;
|
||||
if (!double.IsFinite(lastValid))
|
||||
{
|
||||
lastValid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
|
||||
@@ -109,4 +109,4 @@ public class EllipticIndicatorTests
|
||||
indicator.Period = 50;
|
||||
Assert.Equal(50, indicator.Period);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,4 +101,4 @@ public class EllipticTests
|
||||
Assert.False(filter.IsHot);
|
||||
Assert.Equal(0, filter.Last.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +49,13 @@ public sealed class EllipticValidationTests : IDisposable
|
||||
|
||||
private static double StdDev(List<double> values)
|
||||
{
|
||||
if (values.Count < 2) return 0;
|
||||
if (values.Count < 2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
double avg = values.Average();
|
||||
double sumSq = values.Sum(v => Math.Pow(v - avg, 2));
|
||||
return Math.Sqrt(sumSq / (values.Count - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ public sealed class Elliptic : AbstractBase
|
||||
public Elliptic(int period)
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 2");
|
||||
}
|
||||
|
||||
Period = period;
|
||||
Name = $"Elliptic({period})";
|
||||
@@ -54,14 +56,20 @@ public sealed class Elliptic : AbstractBase
|
||||
|
||||
// Precompute coefficients based on hardcoded Rp=1dB, Rs=40dB
|
||||
double Wc = Math.Tan(Math.PI / period);
|
||||
if (Wc < 1e-9) Wc = 1e-9;
|
||||
if (Wc < 1e-9)
|
||||
{
|
||||
Wc = 1e-9;
|
||||
}
|
||||
|
||||
double omega_z_scaled = C_wz * Wc;
|
||||
double sigma_scaled = C_sigma * Wc;
|
||||
double Kp_scaled = C_Kp_norm * Wc * Wc;
|
||||
|
||||
double a0_denom = 1.0 - 2.0 * sigma_scaled + Kp_scaled;
|
||||
if (Math.Abs(a0_denom) < 1e-9) a0_denom = 1e-9;
|
||||
if (Math.Abs(a0_denom) < 1e-9)
|
||||
{
|
||||
a0_denom = 1e-9;
|
||||
}
|
||||
|
||||
const double norm_factor = C_Kp_norm / (C_k * C_wz * C_wz);
|
||||
|
||||
@@ -111,7 +119,10 @@ public sealed class Elliptic : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
double[] values = source.Values.ToArray();
|
||||
double[] results = new double[values.Length];
|
||||
@@ -206,21 +217,31 @@ public sealed class Elliptic : AbstractBase
|
||||
endState = default;
|
||||
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output spans must be of the same length.", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 2");
|
||||
}
|
||||
|
||||
// Precompute coefficients based on hardcoded Rp=1dB, Rs=40dB
|
||||
double Wc = Math.Tan(Math.PI / period);
|
||||
if (Wc < 1e-9) Wc = 1e-9;
|
||||
if (Wc < 1e-9)
|
||||
{
|
||||
Wc = 1e-9;
|
||||
}
|
||||
|
||||
double omega_z_scaled = C_wz * Wc;
|
||||
double sigma_scaled = C_sigma * Wc;
|
||||
double Kp_scaled = C_Kp_norm * Wc * Wc;
|
||||
|
||||
double a0_denom = 1.0 - 2.0 * sigma_scaled + Kp_scaled;
|
||||
if (Math.Abs(a0_denom) < 1e-9) a0_denom = 1e-9;
|
||||
if (Math.Abs(a0_denom) < 1e-9)
|
||||
{
|
||||
a0_denom = 1e-9;
|
||||
}
|
||||
|
||||
const double norm_factor = C_Kp_norm / (C_k * C_wz * C_wz);
|
||||
|
||||
@@ -249,7 +270,10 @@ public sealed class Elliptic : AbstractBase
|
||||
if (source.Length > 0)
|
||||
{
|
||||
lastValid = source[0];
|
||||
if (!double.IsFinite(lastValid)) lastValid = 0;
|
||||
if (!double.IsFinite(lastValid))
|
||||
{
|
||||
lastValid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
|
||||
@@ -158,4 +158,4 @@ public class GaussIndicatorTests
|
||||
indicator.Sigma = 2.0;
|
||||
Assert.Equal(2.0, indicator.Sigma);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +85,10 @@ public class GaussTests
|
||||
Assert.False(double.IsNaN(result.Value));
|
||||
|
||||
// Fill full buffer with NaNs
|
||||
for (int i=0; i<20; i++)
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
gauss.Update(new TValue(DateTime.MinValue, double.NaN));
|
||||
}
|
||||
|
||||
// Should evaluate to NaN if all are NaN
|
||||
Assert.True(double.IsNaN(gauss.Last.Value));
|
||||
@@ -100,11 +102,13 @@ public class GaussTests
|
||||
Assert.False(gauss.IsHot);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
gauss.Update(new TValue(DateTime.MinValue, i));
|
||||
}
|
||||
|
||||
Assert.False(gauss.IsHot);
|
||||
|
||||
gauss.Update(new TValue(DateTime.MinValue, 6));
|
||||
Assert.True(gauss.IsHot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,11 @@ public class GaussValidationTests : IDisposable
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_testData?.Dispose();
|
||||
@@ -96,9 +100,13 @@ public class GaussValidationTests : IDisposable
|
||||
}
|
||||
|
||||
if (wSum > 0)
|
||||
{
|
||||
result[i] = val / wSum;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[i] = source[i]; // Fallback if no weights applied (shouldn't happen with valid sigma)
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -163,4 +171,4 @@ public class GaussValidationTests : IDisposable
|
||||
}
|
||||
_output.WriteLine("Span mode successfully validated against reference implementation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,9 +148,13 @@ public sealed class Gauss : AbstractBase
|
||||
}
|
||||
|
||||
if (wSum > 0)
|
||||
{
|
||||
result /= wSum;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = input.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -187,7 +191,10 @@ public sealed class Gauss : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// Calculate using static method for performance
|
||||
var resultValues = new double[source.Count];
|
||||
@@ -294,9 +301,13 @@ public sealed class Gauss : AbstractBase
|
||||
}
|
||||
|
||||
if (wSum > 0)
|
||||
{
|
||||
output[i] = result / wSum;
|
||||
}
|
||||
else
|
||||
{
|
||||
output[i] = double.NaN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,4 +156,4 @@ public class HannIndicatorTests
|
||||
indicator.Length = 10;
|
||||
Assert.Equal(10, indicator.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,11 +141,15 @@ public class HannTests
|
||||
Assert.False(hann.IsHot);
|
||||
|
||||
// Fill it
|
||||
for(int i=0; i<5; i++) hann.Update(new TValue(DateTime.UtcNow, 100));
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
hann.Update(new TValue(DateTime.UtcNow, 100));
|
||||
}
|
||||
|
||||
Assert.True(hann.IsHot);
|
||||
|
||||
hann.Reset();
|
||||
Assert.False(hann.IsHot);
|
||||
Assert.Equal(0, hann.Last.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,11 @@ public class HannValidationTests : IDisposable
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_testData?.Dispose();
|
||||
@@ -71,9 +75,13 @@ public class HannValidationTests : IDisposable
|
||||
}
|
||||
|
||||
if (wSum > double.Epsilon)
|
||||
{
|
||||
result[i] = acc / wSum;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[i] = source[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -150,7 +150,10 @@ public sealed class Hann : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var resultValues = new double[source.Count];
|
||||
Calculate(source.Values, resultValues, Length);
|
||||
|
||||
@@ -165,4 +165,4 @@ public class HpIndicatorTests
|
||||
indicator.Lambda = 500;
|
||||
Assert.Equal(500, indicator.Lambda);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,4 +88,4 @@ public class HpValidationTests : IDisposable
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-4
@@ -149,7 +149,7 @@ public sealed class Hp : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
_state.Trend = currentTrend;
|
||||
_state.Trend = currentTrend;
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, currentTrend);
|
||||
@@ -159,7 +159,10 @@ public sealed class Hp : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var resultValues = new double[source.Count];
|
||||
Calculate(source.Values, resultValues, Lambda);
|
||||
@@ -203,7 +206,10 @@ public sealed class Hp : AbstractBase
|
||||
throw new ArgumentException("Source and output spans must be of equal length.", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double s = Math.Sqrt(lambda);
|
||||
double alpha = (s * 0.5 - 1.0) / (s * 0.5 + 1.0);
|
||||
@@ -242,4 +248,4 @@ public sealed class Hp : AbstractBase
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,4 +152,4 @@ public class HpfIndicatorTests
|
||||
indicator.Length = 50;
|
||||
Assert.Equal(50, indicator.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-6
@@ -36,7 +36,10 @@ public sealed class Hpf : AbstractBase
|
||||
|
||||
public Hpf(int length = 40)
|
||||
{
|
||||
if (length < 2) throw new ArgumentOutOfRangeException(nameof(length), "Length must be at least 2.");
|
||||
if (length < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(length), "Length must be at least 2.");
|
||||
}
|
||||
|
||||
Length = length;
|
||||
|
||||
@@ -45,7 +48,9 @@ public sealed class Hpf : AbstractBase
|
||||
double sinW = Math.Sin(omega);
|
||||
|
||||
if (Math.Abs(cosW) < 1e-15)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(length), "Length produces an unstable coefficient set (cos(ω)≈0).");
|
||||
}
|
||||
|
||||
double a = (cosW + sinW - 1.0) / cosW;
|
||||
double oneMinusA = 1.0 - a;
|
||||
@@ -86,14 +91,22 @@ public sealed class Hpf : AbstractBase
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
foreach (double v in source)
|
||||
{
|
||||
Update(new TValue(DateTime.MinValue, v), isNew: true);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew) _pState = _state;
|
||||
else _state = _pState;
|
||||
if (isNew)
|
||||
{
|
||||
_pState = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _pState;
|
||||
}
|
||||
|
||||
double price = input.Value;
|
||||
|
||||
@@ -140,8 +153,8 @@ public sealed class Hpf : AbstractBase
|
||||
|
||||
double src1 = _state.Src1;
|
||||
double src2 = _state.Src2;
|
||||
double hp1 = _state.Hp1;
|
||||
double hp2 = _state.Hp2;
|
||||
double hp1 = _state.Hp1;
|
||||
double hp2 = _state.Hp2;
|
||||
|
||||
double d2 = Math.FusedMultiplyAdd(-2.0, src1, price + src2);
|
||||
|
||||
@@ -161,7 +174,10 @@ public sealed class Hpf : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var output = new double[source.Count];
|
||||
|
||||
@@ -183,7 +199,9 @@ public sealed class Hpf : AbstractBase
|
||||
Last = new TValue(times[^1], output[^1]);
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
result.Add(new TValue(times[i], output[i]));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -205,7 +223,9 @@ public sealed class Hpf : AbstractBase
|
||||
out (double Hp1, double Hp2, double Src1, double Src2, int Samples, bool HasSrc) state)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output spans must be of equal length.", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0)
|
||||
{
|
||||
@@ -219,7 +239,9 @@ public sealed class Hpf : AbstractBase
|
||||
double sinW = Math.Sin(omega);
|
||||
|
||||
if (Math.Abs(cosW) < 1e-15)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(length), "Length produces an unstable coefficient set (cos(ω)≈0).");
|
||||
}
|
||||
|
||||
double a = (cosW + sinW - 1.0) / cosW;
|
||||
double oneMinusA = 1.0 - a;
|
||||
|
||||
@@ -38,4 +38,4 @@ public class KalmanIndicatorTests
|
||||
Assert.Equal(1, indicator.LinesSeries[0].Count);
|
||||
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ public class KalmanTests
|
||||
// 3. Streaming Update
|
||||
filter.Reset();
|
||||
var streamingResults = new List<double>();
|
||||
foreach(var item in series)
|
||||
foreach (var item in series)
|
||||
{
|
||||
streamingResults.Add(filter.Update(item).Value);
|
||||
}
|
||||
@@ -136,4 +136,4 @@ public class KalmanTests
|
||||
Assert.Equal(tseriesResult[i].Value, streamingResults[i], 1e-9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,4 +85,4 @@ public class KalmanValidationTests : IDisposable
|
||||
Assert.Equal(expected[i], actual[i], 1e-9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,15 @@ public sealed class Kalman : AbstractBase
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when q or r are not positive.</exception>
|
||||
public Kalman(double q = 0.01, double r = 0.1)
|
||||
{
|
||||
if (q <= 0) throw new ArgumentOutOfRangeException(nameof(q), "q must be positive.");
|
||||
if (r <= 0) throw new ArgumentOutOfRangeException(nameof(r), "r must be positive.");
|
||||
if (q <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(q), "q must be positive.");
|
||||
}
|
||||
|
||||
if (r <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(r), "r must be positive.");
|
||||
}
|
||||
|
||||
ProcessNoise = q;
|
||||
MeasurementNoise = r;
|
||||
@@ -112,8 +119,14 @@ public sealed class Kalman : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
// "rewind" behavior for isNew=false
|
||||
if (isNew) _pState = _state;
|
||||
else _state = _pState;
|
||||
if (isNew)
|
||||
{
|
||||
_pState = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _pState;
|
||||
}
|
||||
|
||||
double z = input.Value;
|
||||
|
||||
@@ -164,7 +177,10 @@ public sealed class Kalman : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var output = new double[source.Count];
|
||||
|
||||
@@ -174,7 +190,9 @@ public sealed class Kalman : AbstractBase
|
||||
var result = new TSeries();
|
||||
var times = source.Times;
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
result.Add(new TValue(times[i], output[i]));
|
||||
}
|
||||
|
||||
_state = new State { X = endX, P = endP, Samples = endSamples };
|
||||
_pState = _state;
|
||||
@@ -186,7 +204,9 @@ public sealed class Kalman : AbstractBase
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
foreach (double v in source)
|
||||
{
|
||||
Update(new TValue(DateTime.MinValue, v), isNew: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -203,7 +223,9 @@ public sealed class Kalman : AbstractBase
|
||||
out int endSamples)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must be same length.", nameof(output));
|
||||
}
|
||||
|
||||
double x = 0.0;
|
||||
double p = 0.0;
|
||||
|
||||
@@ -61,7 +61,7 @@ public class LoessValidationTests : IDisposable
|
||||
// Check after warmup
|
||||
for (int i = 10; i < 50; i++)
|
||||
{
|
||||
Assert.Equal(expected[i], actual[i], 1e-6);
|
||||
Assert.Equal(expected[i], actual[i], 1e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
-12
@@ -156,7 +156,10 @@ public sealed class Loess : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// Use static Calculate for performance on the whole series
|
||||
var resultValues = new double[source.Count];
|
||||
@@ -175,13 +178,18 @@ public sealed class Loess : AbstractBase
|
||||
// Restore Snap history if possible
|
||||
if (startup > 0)
|
||||
{
|
||||
double lastFinite = 0;
|
||||
bool found = false;
|
||||
for(int k=startup-1; k>=0; k--)
|
||||
{
|
||||
if (double.IsFinite(source.Values[k])) { lastFinite = source.Values[k]; found=true; break; }
|
||||
}
|
||||
if(found) { _snap.LastFiniteInput = lastFinite; _snap.HasFiniteInput = true; _pSnap = _snap; }
|
||||
double lastFinite = 0;
|
||||
bool found = false;
|
||||
for (int k = startup - 1; k >= 0; k--)
|
||||
{
|
||||
if (double.IsFinite(source.Values[k]))
|
||||
{
|
||||
lastFinite = source.Values[k];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) { _snap.LastFiniteInput = lastFinite; _snap.HasFiniteInput = true; _pSnap = _snap; }
|
||||
}
|
||||
|
||||
for (int i = startup; i < source.Count; i++)
|
||||
@@ -213,7 +221,10 @@ public sealed class Loess : AbstractBase
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
double dist = Math.Abs(i - halfWindow) / bandwidth;
|
||||
if (dist >= 1.0) dist = 0.9999;
|
||||
if (dist >= 1.0)
|
||||
{
|
||||
dist = 0.9999;
|
||||
}
|
||||
|
||||
double t = 1.0 - dist * dist * dist;
|
||||
double w = t * t * t;
|
||||
@@ -226,14 +237,21 @@ public sealed class Loess : AbstractBase
|
||||
}
|
||||
|
||||
double delta = weightSum * x2Sum - xSum * xSum;
|
||||
if (Math.Abs(delta) < double.Epsilon) delta = 1.0;
|
||||
if (Math.Abs(delta) < double.Epsilon)
|
||||
{
|
||||
delta = 1.0;
|
||||
}
|
||||
|
||||
double targetX = -halfWindow;
|
||||
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
double dist = Math.Abs(i - halfWindow) / bandwidth;
|
||||
if (dist >= 1.0) dist = 0.9999;
|
||||
if (dist >= 1.0)
|
||||
{
|
||||
dist = 0.9999;
|
||||
}
|
||||
|
||||
double t = 1.0 - dist * dist * dist;
|
||||
double w = t * t * t;
|
||||
double xi = i - halfWindow;
|
||||
@@ -252,7 +270,10 @@ public sealed class Loess : AbstractBase
|
||||
{
|
||||
// a and b expected to be same length (slice called in Update ensures this)
|
||||
int length = a.Length;
|
||||
if (length == 0) return 0;
|
||||
if (length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
double sum = 0;
|
||||
@@ -296,10 +317,14 @@ public sealed class Loess : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output spans must be of equal length.", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 3)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be at least 3.");
|
||||
}
|
||||
|
||||
int adjPeriod = (period & 1) == 0 ? period + 1 : period;
|
||||
|
||||
|
||||
@@ -291,4 +291,4 @@ public class NotchIndicatorTests
|
||||
$"Value at index {i} should be finite");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class NotchTests
|
||||
double output = 0;
|
||||
|
||||
// Warmup to stabilize (IIR transient)
|
||||
for(int i=0; i<100; i++)
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
output = notch.Update(new TValue(DateTime.UtcNow, input)).Value;
|
||||
}
|
||||
@@ -101,7 +101,7 @@ public class NotchTests
|
||||
|
||||
if (i > 50) // ignore transient
|
||||
{
|
||||
maxAmp = Math.Max(maxAmp, Math.Abs(outVal));
|
||||
maxAmp = Math.Max(maxAmp, Math.Abs(outVal));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,4 +109,4 @@ public class NotchTests
|
||||
// With Q=5, it should be very small.
|
||||
Assert.True(maxAmp < 0.1, $"Amplitude {maxAmp} should be attenuated ( < 0.1 )");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,10 @@ public class NotchValidationTests
|
||||
|
||||
double[] expected = { 0.5, 0.0, 0.0, 0.0, 0.0, 0.0 };
|
||||
|
||||
for(int i=0; i<input.Length; i++)
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
double val = notch.Update(input[i]).Value;
|
||||
Assert.Equal(expected[i], val, precision: 9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,4 +160,4 @@ public class SgfIndicatorTests
|
||||
Assert.Equal(10, indicator.Period);
|
||||
Assert.Equal(4, indicator.PolyOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,4 +93,4 @@ public class SgfTests
|
||||
|
||||
Assert.True(sgf.IsHot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,11 @@ public class SgfValidationTests : IDisposable
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_testData?.Dispose();
|
||||
@@ -104,11 +108,17 @@ public class SgfValidationTests : IDisposable
|
||||
}
|
||||
|
||||
if (wSum > double.Epsilon)
|
||||
{
|
||||
result[i] = val / wSum;
|
||||
}
|
||||
else if (wSum <= double.Epsilon && wSum > -double.Epsilon)
|
||||
{
|
||||
result[i] = double.NaN;
|
||||
}
|
||||
else // Negative or small sum fallback usually just NaN or raw
|
||||
{
|
||||
result[i] = ((i + 1) < adjPeriod) ? source[i] : double.NaN; // Match Sgf.cs partial window fallback logic roughly
|
||||
}
|
||||
|
||||
if (wSum <= double.Epsilon)
|
||||
{
|
||||
|
||||
@@ -129,9 +129,13 @@ public sealed class Sgf : AbstractBase
|
||||
}
|
||||
|
||||
if (wSum > double.Epsilon)
|
||||
{
|
||||
result /= wSum;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = input.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -166,7 +170,10 @@ public sealed class Sgf : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var resultValues = new double[source.Count];
|
||||
Calculate(source.Values, resultValues, _period, _polyOrder);
|
||||
|
||||
+37
-4
@@ -40,7 +40,9 @@ public sealed class Ssf : AbstractBase
|
||||
public Ssf(int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
// Use high precision constants
|
||||
// Note: Some implementations (like Ooples/PineScript) use 1.414 * 3.14159 which causes divergence
|
||||
@@ -88,7 +90,10 @@ public sealed class Ssf : AbstractBase
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
||||
@@ -126,9 +131,13 @@ public sealed class Ssf : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
_state.LastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = _state.LastValidValue;
|
||||
}
|
||||
|
||||
double ssf = (_state.Count < 4)
|
||||
? val
|
||||
@@ -142,7 +151,9 @@ public sealed class Ssf : AbstractBase
|
||||
}
|
||||
|
||||
if (_state.Count >= WarmupPeriod)
|
||||
{
|
||||
_state.IsHot = true;
|
||||
}
|
||||
|
||||
Last = new TValue(DateTime.MinValue, _state.Ssf1);
|
||||
|
||||
@@ -190,9 +201,15 @@ public sealed class Ssf : AbstractBase
|
||||
_state.Ssf1 = ssf;
|
||||
_state.PrevInput = val;
|
||||
|
||||
if (isNew) _state.Count++;
|
||||
if (isNew)
|
||||
{
|
||||
_state.Count++;
|
||||
}
|
||||
|
||||
if (!_state.IsHot && _state.Count >= WarmupPeriod)
|
||||
{
|
||||
_state.IsHot = true;
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, ssf);
|
||||
PubEvent(Last, isNew);
|
||||
@@ -201,7 +218,10 @@ public sealed class Ssf : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -269,9 +289,13 @@ public sealed class Ssf : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
state.LastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = state.LastValidValue;
|
||||
}
|
||||
|
||||
double ssf = (state.Count < 4)
|
||||
? val
|
||||
@@ -286,7 +310,9 @@ public sealed class Ssf : AbstractBase
|
||||
}
|
||||
|
||||
if (!state.IsHot && state.Count >= warmupPeriod)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static (TSeries Results, Ssf Indicator) Calculate(TSeries source, int period)
|
||||
@@ -300,7 +326,9 @@ public sealed class Ssf : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
double sqrt2_pi = Math.Sqrt(2) * Math.PI;
|
||||
double arg = sqrt2_pi / period;
|
||||
@@ -311,9 +339,14 @@ public sealed class Ssf : AbstractBase
|
||||
double c1 = 1.0 - c2 - c3;
|
||||
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var state = State.New();
|
||||
|
||||
|
||||
@@ -48,7 +48,9 @@ public sealed class UsfIndicator : Indicator, IWatchlistIndicator
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
|
||||
TValue result = _ma.Update(new TValue(item.TimeLeft.Ticks, _priceSelector(item)), args.IsNewBar());
|
||||
|
||||
@@ -413,7 +413,9 @@ public class UsfTests
|
||||
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42);
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
source[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
// Warm up
|
||||
Usf.Calculate(source.AsSpan(), output.AsSpan(), 100);
|
||||
@@ -509,7 +511,9 @@ public class UsfTests
|
||||
{
|
||||
var series = new TSeries();
|
||||
for (int i = 1; i <= 10; i++)
|
||||
{
|
||||
series.Add(DateTime.UtcNow, i * 10);
|
||||
}
|
||||
|
||||
var (results, indicator) = Usf.Calculate(series, 5);
|
||||
|
||||
|
||||
@@ -209,7 +209,11 @@ public sealed class UsfValidationTests : IDisposable
|
||||
|
||||
private static double CalculateVariance(List<double> values)
|
||||
{
|
||||
if (values.Count == 0) return 0;
|
||||
if (values.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
double mean = values.Average();
|
||||
return values.Sum(v => (v - mean) * (v - mean)) / values.Count;
|
||||
}
|
||||
|
||||
+37
-4
@@ -42,7 +42,9 @@ public sealed class Usf : AbstractBase
|
||||
public Usf(int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
double sqrt2_pi = Math.Sqrt(2) * Math.PI;
|
||||
double arg = sqrt2_pi / period;
|
||||
@@ -90,7 +92,10 @@ public sealed class Usf : AbstractBase
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
||||
@@ -117,9 +122,13 @@ public sealed class Usf : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
_state.LastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = _state.LastValidValue;
|
||||
}
|
||||
|
||||
double usf = (_state.Count < 4)
|
||||
? val
|
||||
@@ -136,7 +145,9 @@ public sealed class Usf : AbstractBase
|
||||
}
|
||||
|
||||
if (_state.Count >= WarmupPeriod)
|
||||
{
|
||||
_state.IsHot = true;
|
||||
}
|
||||
|
||||
Last = new TValue(DateTime.MinValue, _state.Usf1);
|
||||
|
||||
@@ -191,9 +202,15 @@ public sealed class Usf : AbstractBase
|
||||
_state.PrevInput2 = _state.PrevInput1;
|
||||
_state.PrevInput1 = val;
|
||||
|
||||
if (isNew && !initialized) _state.Count++;
|
||||
if (isNew && !initialized)
|
||||
{
|
||||
_state.Count++;
|
||||
}
|
||||
|
||||
if (!_state.IsHot && _state.Count >= WarmupPeriod)
|
||||
{
|
||||
_state.IsHot = true;
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, usf);
|
||||
PubEvent(Last, isNew);
|
||||
@@ -202,7 +219,10 @@ public sealed class Usf : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -266,9 +286,13 @@ public sealed class Usf : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
state.LastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = state.LastValidValue;
|
||||
}
|
||||
|
||||
double usf = (state.Count < 4)
|
||||
? val
|
||||
@@ -286,7 +310,9 @@ public sealed class Usf : AbstractBase
|
||||
}
|
||||
|
||||
if (!state.IsHot && state.Count >= warmupPeriod)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static (TSeries Results, Usf Indicator) Calculate(TSeries source, int period)
|
||||
@@ -300,7 +326,9 @@ public sealed class Usf : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
double sqrt2_pi = Math.Sqrt(2) * Math.PI;
|
||||
double arg = sqrt2_pi / period;
|
||||
@@ -311,9 +339,14 @@ public sealed class Usf : AbstractBase
|
||||
double c1 = (1.0 + c2 - c3) / 4.0;
|
||||
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var state = State.New();
|
||||
|
||||
|
||||
@@ -137,4 +137,4 @@ public class WienerIndicatorTests
|
||||
Assert.Equal(20, indicator.Period);
|
||||
Assert.Equal(5, indicator.SmoothPeriod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,11 @@ public class WienerValidationTests : IDisposable
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_testData?.Dispose();
|
||||
@@ -182,4 +186,4 @@ public class WienerValidationTests : IDisposable
|
||||
}
|
||||
_output.WriteLine("Span mode successfully validated against reference implementation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user