mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-20 11:38:05 +00:00
style patterns
This commit is contained in:
@@ -49,7 +49,10 @@ public sealed class Dema : AbstractBase
|
||||
|
||||
public Dema(int period)
|
||||
{
|
||||
if (period <= 0) throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
_alpha = 2.0 / (period + 1);
|
||||
_decay = 1.0 - _alpha;
|
||||
@@ -66,7 +69,10 @@ public sealed class Dema : AbstractBase
|
||||
|
||||
public Dema(double alpha)
|
||||
{
|
||||
if (alpha <= 0 || alpha > 1) throw new ArgumentException("Alpha must be between 0 and 1", nameof(alpha));
|
||||
if (alpha <= 0 || alpha > 1)
|
||||
{
|
||||
throw new ArgumentException("Alpha must be between 0 and 1", nameof(alpha));
|
||||
}
|
||||
|
||||
_alpha = alpha;
|
||||
_decay = 1.0 - alpha;
|
||||
@@ -94,9 +100,13 @@ public sealed class Dema : AbstractBase
|
||||
// EMA1
|
||||
double val = input.Value;
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
_lastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = _lastValidValue;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -118,7 +128,10 @@ public sealed class Dema : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
List<long> t = new(len);
|
||||
@@ -148,9 +161,13 @@ public sealed class Dema : AbstractBase
|
||||
{
|
||||
double val = sourceValues[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -197,7 +214,9 @@ public sealed class Dema : AbstractBase
|
||||
state.E *= decay;
|
||||
|
||||
if (!state.IsHot && state.E <= 0.05) // COVERAGE_THRESHOLD
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
if (state.E <= 1e-10) // COMPENSATOR_THRESHOLD
|
||||
{
|
||||
@@ -232,7 +251,9 @@ public sealed class Dema : 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 alpha = 2.0 / (period + 1);
|
||||
Calculate(source, output, alpha);
|
||||
@@ -241,11 +262,19 @@ public sealed class Dema : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, double alpha)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (alpha <= 0 || alpha > 1)
|
||||
throw new ArgumentException("Alpha must be between 0 and 1", nameof(alpha));
|
||||
}
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (alpha <= 0 || alpha > 1)
|
||||
{
|
||||
throw new ArgumentException("Alpha must be between 0 and 1", nameof(alpha));
|
||||
}
|
||||
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double decay = 1.0 - alpha;
|
||||
double lastValid = double.NaN;
|
||||
@@ -264,9 +293,13 @@ public sealed class Dema : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
|
||||
@@ -199,7 +199,7 @@ public class DsmaIndicatorTests
|
||||
|
||||
// Process first bar
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
|
||||
// Process second bar as new
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
||||
double afterNewBar = indicator.LinesSeries[0].GetValue(0);
|
||||
|
||||
@@ -232,7 +232,7 @@ public class DsmaTests
|
||||
|
||||
// Get the last bar again after loop
|
||||
lastBar = gbm.Next(isNew: false);
|
||||
|
||||
|
||||
// Inject NaN
|
||||
var nanResult = dsma.Update(new TValue(lastBar.Time, double.NaN));
|
||||
|
||||
@@ -257,7 +257,7 @@ public class DsmaTests
|
||||
|
||||
// Get the last bar again after loop
|
||||
lastBar = gbm.Next(isNew: false);
|
||||
|
||||
|
||||
// Inject Infinity
|
||||
var infResult = dsma.Update(new TValue(lastBar.Time, double.PositiveInfinity));
|
||||
var negInfResult = dsma.Update(new TValue(lastBar.Time, double.NegativeInfinity));
|
||||
|
||||
@@ -58,7 +58,7 @@ public class DsmaValidationTests
|
||||
}
|
||||
|
||||
// In higher volatility, absolute deviation should generally be larger
|
||||
Assert.True(highVolDeviation > lowVolDeviation * 2,
|
||||
Assert.True(highVolDeviation > lowVolDeviation * 2,
|
||||
$"High volatility deviation {highVolDeviation:F2} should be significantly larger than low volatility {lowVolDeviation:F2}");
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ public class DsmaValidationTests
|
||||
double avgHighLag = highScaleLag / count;
|
||||
|
||||
// Lower scale factor should have higher average lag (smoother, less responsive)
|
||||
Assert.True(avgLowLag > avgHighLag,
|
||||
Assert.True(avgLowLag > avgHighLag,
|
||||
$"Low scale lag {avgLowLag:F4} should be greater than high scale lag {avgHighLag:F4}");
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public class DsmaValidationTests
|
||||
double dsmaVariance = dsmaChanges.Average();
|
||||
|
||||
// DSMA should have lower variance than raw price
|
||||
Assert.True(dsmaVariance < priceVariance,
|
||||
Assert.True(dsmaVariance < priceVariance,
|
||||
$"DSMA variance {dsmaVariance:F4} should be less than price variance {priceVariance:F4}");
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ public class DsmaValidationTests
|
||||
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.01, sigma: 0.2, seed: 654);
|
||||
var series = new TSeries();
|
||||
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
var bar = gbm.Next(isNew: true);
|
||||
@@ -254,7 +254,7 @@ public class DsmaValidationTests
|
||||
double avgLongLag = longLag / count;
|
||||
|
||||
// Longer period should have higher average lag (more smoothing)
|
||||
Assert.True(avgLongLag > avgShortLag,
|
||||
Assert.True(avgLongLag > avgShortLag,
|
||||
$"Long period lag {avgLongLag:F4} should be greater than short period lag {avgShortLag:F4}");
|
||||
}
|
||||
|
||||
@@ -319,8 +319,15 @@ public class DsmaValidationTests
|
||||
|
||||
for (int i = 1; i < prices.Count; i++)
|
||||
{
|
||||
if (prices[i] > prices[i - 1]) priceUpCount++;
|
||||
if (dsmaValues[i] > dsmaValues[i - 1]) dsmaUpCount++;
|
||||
if (prices[i] > prices[i - 1])
|
||||
{
|
||||
priceUpCount++;
|
||||
}
|
||||
|
||||
if (dsmaValues[i] > dsmaValues[i - 1])
|
||||
{
|
||||
dsmaUpCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// DSMA should have similar directional trend but smoother
|
||||
|
||||
@@ -89,9 +89,14 @@ public sealed class Dsma : AbstractBase
|
||||
public Dsma(int period, double scaleFactor = 0.5)
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 2.");
|
||||
}
|
||||
|
||||
if (scaleFactor < 0.01 || scaleFactor > 0.9)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(scaleFactor), "Scale factor must be between 0.01 and 0.9.");
|
||||
}
|
||||
|
||||
WarmupPeriod = period;
|
||||
_periodRecip = 1.0 / period;
|
||||
@@ -146,12 +151,16 @@ public sealed class Dsma : AbstractBase
|
||||
HandleStateSnapshot(isNew);
|
||||
value = HandleInvalidInput(value);
|
||||
if (double.IsNaN(value))
|
||||
{
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
_state.Bars++;
|
||||
|
||||
if (_state.Bars == 1)
|
||||
{
|
||||
return InitializeFirstBar(value);
|
||||
}
|
||||
|
||||
return CalculateDsma(value);
|
||||
}
|
||||
@@ -256,7 +265,10 @@ public sealed class Dsma : AbstractBase
|
||||
/// <returns>Time series containing DSMA values</returns>
|
||||
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);
|
||||
@@ -325,7 +337,9 @@ public sealed class Dsma : AbstractBase
|
||||
double scaleFactor = 0.5)
|
||||
{
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output span is shorter than source span.", nameof(output));
|
||||
}
|
||||
|
||||
var dsma = new Dsma(period, scaleFactor);
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
|
||||
@@ -444,7 +444,9 @@ public class EmaTests
|
||||
|
||||
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
|
||||
Ema.Batch(source.AsSpan(), output.AsSpan(), 100);
|
||||
@@ -525,7 +527,10 @@ public class EmaTests
|
||||
// ...
|
||||
// We can verify against a fresh EMA fed with same data
|
||||
var verifyEma = new Ema(5);
|
||||
foreach (var val in history) verifyEma.Update(new TValue(DateTime.UtcNow, val));
|
||||
foreach (var val in history)
|
||||
{
|
||||
verifyEma.Update(new TValue(DateTime.UtcNow, val));
|
||||
}
|
||||
|
||||
Assert.Equal(verifyEma.Last.Value, ema.Last.Value, 1e-10);
|
||||
Assert.Equal(verifyEma.IsHot, ema.IsHot);
|
||||
@@ -545,7 +550,10 @@ public class EmaTests
|
||||
ema.Prime(history);
|
||||
|
||||
var verifyEma = new Ema(5);
|
||||
foreach (var val in history) verifyEma.Update(new TValue(DateTime.UtcNow, val));
|
||||
foreach (var val in history)
|
||||
{
|
||||
verifyEma.Update(new TValue(DateTime.UtcNow, val));
|
||||
}
|
||||
|
||||
Assert.Equal(verifyEma.Last.Value, ema.Last.Value, 1e-10);
|
||||
}
|
||||
@@ -565,7 +573,10 @@ public class EmaTests
|
||||
public void Calculate_ReturnsCorrectResultsAndHotIndicator()
|
||||
{
|
||||
var series = new TSeries();
|
||||
for (int i = 1; i <= 20; i++) series.Add(DateTime.UtcNow, i * 10);
|
||||
for (int i = 1; i <= 20; i++)
|
||||
{
|
||||
series.Add(DateTime.UtcNow, i * 10);
|
||||
}
|
||||
|
||||
// EMA(5)
|
||||
var (results, indicator) = Ema.Calculate(series, 5);
|
||||
|
||||
@@ -91,7 +91,9 @@ public sealed class Ema : AbstractBase
|
||||
public Ema(double alpha)
|
||||
{
|
||||
if (alpha <= 0 || alpha > 1)
|
||||
{
|
||||
throw new ArgumentException("Alpha must be greater than 0 and at most 1", nameof(alpha));
|
||||
}
|
||||
|
||||
_alpha = alpha;
|
||||
_decay = 1.0 - alpha;
|
||||
@@ -118,7 +120,10 @@ public sealed class Ema : AbstractBase
|
||||
/// <param name="source">Historical data</param>
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset state
|
||||
_state = State.New();
|
||||
@@ -170,7 +175,9 @@ public sealed class Ema : AbstractBase
|
||||
finally
|
||||
{
|
||||
if (rented != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(rented);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +222,10 @@ public sealed class Ema : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
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);
|
||||
@@ -262,7 +272,9 @@ public sealed class Ema : AbstractBase
|
||||
state.E *= decay;
|
||||
|
||||
if (!state.IsHot && state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
if (state.E <= COMPENSATOR_THRESHOLD)
|
||||
{
|
||||
@@ -300,21 +312,29 @@ public sealed class Ema : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValidValue;
|
||||
}
|
||||
|
||||
state.Ema = Math.FusedMultiplyAdd(state.Ema, decay, alpha * val);
|
||||
state.E *= decay;
|
||||
|
||||
if (!state.IsHot && state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
output[i] = state.Ema / (1.0 - state.E);
|
||||
state.TickCount++;
|
||||
}
|
||||
if (state.E <= COMPENSATOR_THRESHOLD)
|
||||
{
|
||||
state.IsCompensated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Post-compensation (hot path) - optimized with loop unrolling
|
||||
@@ -328,22 +348,54 @@ public sealed class Ema : AbstractBase
|
||||
for (; i < unrollEnd; i += 4)
|
||||
{
|
||||
double v0 = Unsafe.Add(ref srcRef, i);
|
||||
if (!double.IsFinite(v0)) v0 = lastValidValue; else lastValidValue = v0;
|
||||
if (!double.IsFinite(v0))
|
||||
{
|
||||
v0 = lastValidValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValidValue = v0;
|
||||
}
|
||||
|
||||
state.Ema = Math.FusedMultiplyAdd(state.Ema, decay, alpha * v0);
|
||||
Unsafe.Add(ref outRef, i) = state.Ema;
|
||||
|
||||
double v1 = Unsafe.Add(ref srcRef, i + 1);
|
||||
if (!double.IsFinite(v1)) v1 = lastValidValue; else lastValidValue = v1;
|
||||
if (!double.IsFinite(v1))
|
||||
{
|
||||
v1 = lastValidValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValidValue = v1;
|
||||
}
|
||||
|
||||
state.Ema = Math.FusedMultiplyAdd(state.Ema, decay, alpha * v1);
|
||||
Unsafe.Add(ref outRef, i + 1) = state.Ema;
|
||||
|
||||
double v2 = Unsafe.Add(ref srcRef, i + 2);
|
||||
if (!double.IsFinite(v2)) v2 = lastValidValue; else lastValidValue = v2;
|
||||
if (!double.IsFinite(v2))
|
||||
{
|
||||
v2 = lastValidValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValidValue = v2;
|
||||
}
|
||||
|
||||
state.Ema = Math.FusedMultiplyAdd(state.Ema, decay, alpha * v2);
|
||||
Unsafe.Add(ref outRef, i + 2) = state.Ema;
|
||||
|
||||
double v3 = Unsafe.Add(ref srcRef, i + 3);
|
||||
if (!double.IsFinite(v3)) v3 = lastValidValue; else lastValidValue = v3;
|
||||
if (!double.IsFinite(v3))
|
||||
{
|
||||
v3 = lastValidValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValidValue = v3;
|
||||
}
|
||||
|
||||
state.Ema = Math.FusedMultiplyAdd(state.Ema, decay, alpha * v3);
|
||||
Unsafe.Add(ref outRef, i + 3) = state.Ema;
|
||||
|
||||
@@ -364,7 +416,14 @@ public sealed class Ema : AbstractBase
|
||||
for (; i < len; i++)
|
||||
{
|
||||
double val = Unsafe.Add(ref srcRef, i);
|
||||
if (!double.IsFinite(val)) val = lastValidValue; else lastValidValue = val;
|
||||
if (!double.IsFinite(val))
|
||||
{
|
||||
val = lastValidValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValidValue = val;
|
||||
}
|
||||
|
||||
state.Ema = Math.FusedMultiplyAdd(state.Ema, decay, alpha * val);
|
||||
Unsafe.Add(ref outRef, i) = state.Ema;
|
||||
@@ -469,7 +528,9 @@ public sealed class Ema : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
Batch(source, output, alpha);
|
||||
@@ -486,11 +547,17 @@ public sealed class Ema : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, double alpha)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(alpha, 0.0);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(alpha, 1.0);
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// For large, clean datasets, use optimized path without NaN handling
|
||||
if (source.Length >= CleanPathThreshold && !source.ContainsNonFinite())
|
||||
@@ -535,4 +602,4 @@ public sealed class Ema : AbstractBase
|
||||
_p_lastValidValue = 0;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,18 +110,39 @@ public class FramaValidationTests
|
||||
lv = lastLow;
|
||||
}
|
||||
|
||||
if (hv > maxFull) maxFull = hv;
|
||||
if (lv < minFull) minFull = lv;
|
||||
if (hv > maxFull)
|
||||
{
|
||||
maxFull = hv;
|
||||
}
|
||||
|
||||
if (lv < minFull)
|
||||
{
|
||||
minFull = lv;
|
||||
}
|
||||
|
||||
if (j >= startRecent)
|
||||
{
|
||||
if (hv > maxRecent) maxRecent = hv;
|
||||
if (lv < minRecent) minRecent = lv;
|
||||
if (hv > maxRecent)
|
||||
{
|
||||
maxRecent = hv;
|
||||
}
|
||||
|
||||
if (lv < minRecent)
|
||||
{
|
||||
minRecent = lv;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hv > maxPrev) maxPrev = hv;
|
||||
if (lv < minPrev) minPrev = lv;
|
||||
if (hv > maxPrev)
|
||||
{
|
||||
maxPrev = hv;
|
||||
}
|
||||
|
||||
if (lv < minPrev)
|
||||
{
|
||||
minPrev = lv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +155,15 @@ public class FramaValidationTests
|
||||
{
|
||||
double dimen = (Math.Log(n1 + n2) - Math.Log(n3)) / 0.693147180559945309417232121458176568;
|
||||
alpha = Math.Exp(-4.6 * (dimen - 1.0));
|
||||
if (alpha < 0.01) alpha = 0.01;
|
||||
if (alpha > 1.0) alpha = 1.0;
|
||||
if (alpha < 0.01)
|
||||
{
|
||||
alpha = 0.01;
|
||||
}
|
||||
|
||||
if (alpha > 1.0)
|
||||
{
|
||||
alpha = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
double price = (highVal + lowVal) * 0.5;
|
||||
|
||||
@@ -147,8 +147,15 @@ public sealed class Frama : ITValuePublisher
|
||||
{
|
||||
double dimen = (Math.Log(n1 + n2) - Math.Log(n3)) / Log2;
|
||||
alpha = Math.Exp(-4.6 * (dimen - 1.0));
|
||||
if (alpha < AlphaFloor) alpha = AlphaFloor;
|
||||
if (alpha > AlphaCeil) alpha = AlphaCeil;
|
||||
if (alpha < AlphaFloor)
|
||||
{
|
||||
alpha = AlphaFloor;
|
||||
}
|
||||
|
||||
if (alpha > AlphaCeil)
|
||||
{
|
||||
alpha = AlphaCeil;
|
||||
}
|
||||
}
|
||||
|
||||
double prev = _state.HasValue && double.IsFinite(_state.Frama) ? _state.Frama : price;
|
||||
@@ -170,7 +177,10 @@ public sealed class Frama : ITValuePublisher
|
||||
|
||||
public TSeries Update(TBarSeries source)
|
||||
{
|
||||
if (source.Count == 0) return new TSeries([], []);
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return new TSeries([], []);
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var v = new double[len];
|
||||
@@ -195,7 +205,10 @@ public sealed class Frama : ITValuePublisher
|
||||
|
||||
public TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -224,7 +237,9 @@ public sealed class Frama : ITValuePublisher
|
||||
public static void Calculate(ReadOnlySpan<double> high, ReadOnlySpan<double> low, int period, Span<double> output)
|
||||
{
|
||||
if (high.Length != low.Length || high.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Input spans must have the same length.", nameof(output));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 2);
|
||||
|
||||
@@ -240,7 +255,9 @@ public sealed class Frama : ITValuePublisher
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length.", nameof(output));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 2);
|
||||
|
||||
@@ -254,7 +271,10 @@ public sealed class Frama : ITValuePublisher
|
||||
|
||||
public static TSeries Batch(TBarSeries source, int period)
|
||||
{
|
||||
if (source.Count == 0) return new TSeries([], []);
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return new TSeries([], []);
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var v = new double[len];
|
||||
@@ -275,7 +295,9 @@ public sealed class Frama : ITValuePublisher
|
||||
{
|
||||
int count = buffer.Count;
|
||||
if (count == 0 || length <= 0)
|
||||
{
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
int capacity = buffer.Capacity;
|
||||
int start = buffer.StartIndex;
|
||||
@@ -288,10 +310,15 @@ public sealed class Frama : ITValuePublisher
|
||||
{
|
||||
int idx = start + offset + i;
|
||||
if (idx >= capacity)
|
||||
{
|
||||
idx -= capacity;
|
||||
}
|
||||
|
||||
double v = data[idx];
|
||||
if (v > max)
|
||||
{
|
||||
max = v;
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
@@ -302,7 +329,9 @@ public sealed class Frama : ITValuePublisher
|
||||
{
|
||||
int count = buffer.Count;
|
||||
if (count == 0 || length <= 0)
|
||||
{
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
int capacity = buffer.Capacity;
|
||||
int start = buffer.StartIndex;
|
||||
@@ -315,12 +344,17 @@ public sealed class Frama : ITValuePublisher
|
||||
{
|
||||
int idx = start + offset + i;
|
||||
if (idx >= capacity)
|
||||
{
|
||||
idx -= capacity;
|
||||
}
|
||||
|
||||
double v = data[idx];
|
||||
if (v < min)
|
||||
{
|
||||
min = v;
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,9 +90,13 @@ public class HemaValidationTests
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
|
||||
@@ -119,9 +119,13 @@ public sealed class Hema : AbstractBase
|
||||
|
||||
double val = input.Value;
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
_lastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = _lastValidValue;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -139,7 +143,10 @@ public sealed class Hema : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
List<long> t = new(len);
|
||||
@@ -163,9 +170,13 @@ public sealed class Hema : AbstractBase
|
||||
{
|
||||
double val = sourceValues[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -214,17 +225,23 @@ public sealed class Hema : AbstractBase
|
||||
double emaFast = state.EmaFastRaw * invFast;
|
||||
double deLag = Math.FusedMultiplyAdd(-_ratio, emaSlow, emaFast) * _invOneMinusRatio;
|
||||
if (!double.IsFinite(deLag))
|
||||
{
|
||||
deLag = input;
|
||||
}
|
||||
|
||||
state.EmaSmoothRaw = Math.FusedMultiplyAdd(state.EmaSmoothRaw, _betaSmooth, _alphaSmooth * deLag);
|
||||
|
||||
double maxDecay = Math.Max(state.DecaySlow, Math.Max(state.DecayFast, state.DecaySmooth));
|
||||
if (!state.IsHot && maxDecay <= CoverageThreshold)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
state.Warmup = maxDecay > CompensatorThreshold;
|
||||
if (!state.Warmup)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
double result = state.EmaSmoothRaw * invSmooth;
|
||||
if (!double.IsFinite(result))
|
||||
@@ -238,11 +255,16 @@ public sealed class Hema : AbstractBase
|
||||
|
||||
double deLagFast = Math.FusedMultiplyAdd(-_ratio, state.EmaSlowRaw, state.EmaFastRaw) * _invOneMinusRatio;
|
||||
if (!double.IsFinite(deLagFast))
|
||||
{
|
||||
deLagFast = input;
|
||||
}
|
||||
|
||||
state.EmaSmoothRaw = Math.FusedMultiplyAdd(state.EmaSmoothRaw, _betaSmooth, _alphaSmooth * deLagFast);
|
||||
|
||||
if (!state.IsHot)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
double fastResult = state.EmaSmoothRaw;
|
||||
if (!double.IsFinite(fastResult))
|
||||
@@ -263,10 +285,16 @@ public sealed class Hema : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(period);
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double n = Math.Max((double)period, 2.0);
|
||||
double alphaSlow = AlphaFromHalfLife(n);
|
||||
@@ -296,9 +324,13 @@ public sealed class Hema : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -323,7 +355,9 @@ public sealed class Hema : AbstractBase
|
||||
double emaFast = emaFastRaw * invFast;
|
||||
double deLag = Math.FusedMultiplyAdd(-ratio, emaSlow, emaFast) * invOneMinusRatio;
|
||||
if (!double.IsFinite(deLag))
|
||||
{
|
||||
deLag = val;
|
||||
}
|
||||
|
||||
emaSmoothRaw = Math.FusedMultiplyAdd(emaSmoothRaw, betaSmooth, alphaSmooth * deLag);
|
||||
double result = emaSmoothRaw * invSmooth;
|
||||
@@ -348,7 +382,10 @@ public sealed class Hema : AbstractBase
|
||||
{
|
||||
double deLag = Math.FusedMultiplyAdd(-ratio, emaSlowRaw, emaFastRaw) * invOneMinusRatio;
|
||||
if (!double.IsFinite(deLag))
|
||||
{
|
||||
deLag = val;
|
||||
}
|
||||
|
||||
emaSmoothRaw = Math.FusedMultiplyAdd(emaSmoothRaw, betaSmooth, alphaSmooth * deLag);
|
||||
double result = emaSmoothRaw;
|
||||
if (!double.IsFinite(result))
|
||||
@@ -424,12 +461,16 @@ public sealed class Hema : AbstractBase
|
||||
{
|
||||
double maxDecay = Math.Max(_betaSlow, Math.Max(_betaFast, _betaSmooth));
|
||||
if (maxDecay <= 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
double steps = Math.Log(CoverageThreshold) / Math.Log(maxDecay);
|
||||
if (double.IsNaN(steps) || double.IsInfinity(steps) || steps <= 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return (int)Math.Ceiling(steps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+47
-11
@@ -195,11 +195,25 @@ public sealed class Htit : AbstractBase
|
||||
{
|
||||
double cap = 1.5 * prevPeriod;
|
||||
double floor = 0.67 * prevPeriod;
|
||||
if (period > cap) period = cap;
|
||||
if (period < floor) period = floor;
|
||||
if (period > cap)
|
||||
{
|
||||
period = cap;
|
||||
}
|
||||
|
||||
if (period < floor)
|
||||
{
|
||||
period = floor;
|
||||
}
|
||||
}
|
||||
if (period < 6)
|
||||
{
|
||||
period = 6;
|
||||
}
|
||||
|
||||
if (period > 50)
|
||||
{
|
||||
period = 50;
|
||||
}
|
||||
if (period < 6) period = 6;
|
||||
if (period > 50) period = 50;
|
||||
|
||||
// Smooth the period (using FMA)
|
||||
_state.Period = Math.FusedMultiplyAdd(0.2, period, 0.8 * prevPeriod);
|
||||
@@ -254,7 +268,10 @@ public sealed class Htit : AbstractBase
|
||||
/// <returns>Output time series with HTIT values</returns>
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return new TSeries([], []);
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return new TSeries([], []);
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var v = new List<double>(len);
|
||||
@@ -293,9 +310,14 @@ public sealed class Htit : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// Stack allocate buffers
|
||||
// priceBuffer needs to be larger for IT calculation (up to 50 bars)
|
||||
@@ -416,11 +438,25 @@ public sealed class Htit : AbstractBase
|
||||
{
|
||||
double cap = 1.5 * p_period;
|
||||
double floor = 0.67 * p_period;
|
||||
if (newPeriod > cap) newPeriod = cap;
|
||||
if (newPeriod < floor) newPeriod = floor;
|
||||
if (newPeriod > cap)
|
||||
{
|
||||
newPeriod = cap;
|
||||
}
|
||||
|
||||
if (newPeriod < floor)
|
||||
{
|
||||
newPeriod = floor;
|
||||
}
|
||||
}
|
||||
if (newPeriod < 6)
|
||||
{
|
||||
newPeriod = 6;
|
||||
}
|
||||
|
||||
if (newPeriod > 50)
|
||||
{
|
||||
newPeriod = 50;
|
||||
}
|
||||
if (newPeriod < 6) newPeriod = 6;
|
||||
if (newPeriod > 50) newPeriod = 50;
|
||||
|
||||
period = Math.FusedMultiplyAdd(0.2, newPeriod, 0.8 * p_period);
|
||||
smoothPeriod = Math.FusedMultiplyAdd(0.33, period, 0.67 * p_smoothPeriod);
|
||||
@@ -475,4 +511,4 @@ public sealed class Htit : AbstractBase
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,4 +310,4 @@ public class JmaTests
|
||||
Assert.True(double.IsFinite(restoredResult.Value));
|
||||
Assert.True(jma.IsHot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +39,4 @@ public class JmaZeroDivTests
|
||||
// With clamping, adapt is slightly non-zero (approx 1e-12), so result is very close to 200.
|
||||
Assert.Equal(200, result.Value, precision: 7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,17 +63,28 @@ public sealed class Jma : AbstractBase
|
||||
public Jma(int period, int phase = 0, double power = 0.45)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
||||
}
|
||||
|
||||
if (!double.IsFinite(power))
|
||||
{
|
||||
throw new ArgumentException("Power must be finite.", nameof(power));
|
||||
}
|
||||
|
||||
// --- Phase parameter: maps -100..100 -> 0.5..2.5 (Jurik convention) ---
|
||||
if (phase < -100)
|
||||
{
|
||||
_phaseParam = 0.5;
|
||||
}
|
||||
else if (phase > 100)
|
||||
{
|
||||
_phaseParam = 2.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
_phaseParam = (phase * 0.01) + 1.5;
|
||||
}
|
||||
|
||||
// --- Length / log / divider parameters (from decompiled JMA) ---
|
||||
// L_raw ~ (period - 1)/2, with a tiny lower bound to avoid log(0)
|
||||
@@ -136,7 +147,10 @@ public sealed class Jma : AbstractBase
|
||||
if (!double.IsFinite(value))
|
||||
{
|
||||
if (_state.Bars == 0)
|
||||
{
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
value = _state.LastPrice;
|
||||
}
|
||||
else
|
||||
@@ -146,7 +160,9 @@ public sealed class Jma : AbstractBase
|
||||
|
||||
_state.Bars++;
|
||||
if (_state.Bars == 1)
|
||||
{
|
||||
return InitializeFirstBar(value);
|
||||
}
|
||||
|
||||
return CalculateJma(value);
|
||||
}
|
||||
@@ -215,8 +231,16 @@ public sealed class Jma : AbstractBase
|
||||
{
|
||||
double ratio = Math.Max(absValue / refVolatility, 0.0);
|
||||
double d = Math.Pow(ratio, _pExponent);
|
||||
if (d > _logParam) d = _logParam;
|
||||
if (d < 1.0) d = 1.0;
|
||||
if (d > _logParam)
|
||||
{
|
||||
d = _logParam;
|
||||
}
|
||||
|
||||
if (d < 1.0)
|
||||
{
|
||||
d = 1.0;
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -271,7 +295,10 @@ public sealed class Jma : 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);
|
||||
@@ -339,9 +366,14 @@ public sealed class Jma : AbstractBase
|
||||
double power = 0.45)
|
||||
{
|
||||
if (output.Length != source.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length.", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var jma = new Jma(period, phase, power);
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
@@ -383,10 +415,17 @@ public sealed class Jma : AbstractBase
|
||||
end = drop + slice - 1;
|
||||
}
|
||||
|
||||
if (start < 0) start = 0;
|
||||
if (end >= count) end = count - 1;
|
||||
if (start < 0)
|
||||
{
|
||||
start = 0;
|
||||
}
|
||||
|
||||
if (end >= count)
|
||||
{
|
||||
end = count - 1;
|
||||
}
|
||||
|
||||
int len = end - start + 1;
|
||||
return sorted.Slice(start, len).SumSIMD() / len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,24 @@ public sealed class Kama : AbstractBase
|
||||
public Kama(int period = 10, int fastPeriod = 2, int slowPeriod = 30)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
if (fastPeriod <= 0)
|
||||
{
|
||||
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
|
||||
}
|
||||
|
||||
if (slowPeriod <= 0)
|
||||
{
|
||||
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
|
||||
}
|
||||
|
||||
if (fastPeriod >= slowPeriod)
|
||||
{
|
||||
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
|
||||
}
|
||||
|
||||
// Buffer needs to hold period + 1 values to calculate Change over 'period' bars
|
||||
// Change = Price[0] - Price[period]
|
||||
@@ -161,7 +172,10 @@ public sealed class Kama : AbstractBase
|
||||
// Avoid division by zero
|
||||
double er = (volatility > 1e-10) ? change / volatility : 0.0;
|
||||
// Cap ER at 1.0 just in case floating point errors push it slightly over
|
||||
if (er > 1.0) er = 1.0;
|
||||
if (er > 1.0)
|
||||
{
|
||||
er = 1.0;
|
||||
}
|
||||
|
||||
// double sc = er * (_fastAlpha - _slowAlpha) + _slowAlpha; // skipcq: S125
|
||||
double sc = Math.FusedMultiplyAdd(er, _fastAlpha - _slowAlpha, _slowAlpha);
|
||||
@@ -184,7 +198,10 @@ public sealed class Kama : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return new TSeries([], []);
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return new TSeries([], []);
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -227,11 +244,30 @@ public sealed class Kama : AbstractBase
|
||||
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, int fastPeriod = 2, int slowPeriod = 30)
|
||||
{
|
||||
if (period <= 0) throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
if (fastPeriod <= 0) throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
|
||||
if (slowPeriod <= 0) throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
|
||||
if (fastPeriod >= slowPeriod) throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
|
||||
if (source.Length != output.Length) throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
if (fastPeriod <= 0)
|
||||
{
|
||||
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
|
||||
}
|
||||
|
||||
if (slowPeriod <= 0)
|
||||
{
|
||||
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
|
||||
}
|
||||
|
||||
if (fastPeriod >= slowPeriod)
|
||||
{
|
||||
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
|
||||
}
|
||||
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
double fastAlpha = 2.0 / (fastPeriod + 1);
|
||||
double slowAlpha = 2.0 / (slowPeriod + 1);
|
||||
@@ -252,9 +288,13 @@ public sealed class Kama : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -287,7 +327,10 @@ public sealed class Kama : AbstractBase
|
||||
}
|
||||
|
||||
bufferIdx = (bufferIdx + 1) % bufSize;
|
||||
if (count < bufSize) count++;
|
||||
if (count < bufSize)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (!kamaInitialized)
|
||||
{
|
||||
@@ -307,7 +350,10 @@ public sealed class Kama : AbstractBase
|
||||
double change = Math.Abs(val - buffer[count == bufSize ? bufferIdx : 0]);
|
||||
|
||||
double er = (volatilitySum > 1e-10) ? change / volatilitySum : 0.0;
|
||||
if (er > 1.0) er = 1.0;
|
||||
if (er > 1.0)
|
||||
{
|
||||
er = 1.0;
|
||||
}
|
||||
|
||||
// double sc = er * (fastAlpha - slowAlpha) + slowAlpha; // skipcq: S125
|
||||
double sc = Math.FusedMultiplyAdd(er, fastAlpha - slowAlpha, slowAlpha);
|
||||
|
||||
@@ -137,8 +137,15 @@ public class MamaTests
|
||||
// Case 2: Update in chunks
|
||||
var chunk1 = new TSeries();
|
||||
var chunk2 = new TSeries();
|
||||
for (int i = 0; i < 25; i++) chunk1.Add(data[i]);
|
||||
for (int i = 25; i < 50; i++) chunk2.Add(data[i]);
|
||||
for (int i = 0; i < 25; i++)
|
||||
{
|
||||
chunk1.Add(data[i]);
|
||||
}
|
||||
|
||||
for (int i = 25; i < 50; i++)
|
||||
{
|
||||
chunk2.Add(data[i]);
|
||||
}
|
||||
|
||||
mama2.Update(chunk1);
|
||||
var result2 = mama2.Update(chunk2);
|
||||
@@ -239,7 +246,10 @@ public class MamaTests
|
||||
const int count = 100;
|
||||
var data = new double[count];
|
||||
var gbm = new GBM(startPrice: 100, seed: 42);
|
||||
for (int i = 0; i < count; i++) data[i] = gbm.Next().Close;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
data[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
var output = new double[count];
|
||||
Mama.Calculate(data, output);
|
||||
@@ -309,7 +319,10 @@ public class MamaTests
|
||||
{
|
||||
var data = new double[60];
|
||||
var gbm = new GBM(startPrice: 100, seed: 42);
|
||||
for (int i = 0; i < 60; i++) data[i] = gbm.Next().Close;
|
||||
for (int i = 0; i < 60; i++)
|
||||
{
|
||||
data[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
// 1. Prime with all but last value
|
||||
var mamaPrimed = new Mama();
|
||||
@@ -336,7 +349,10 @@ public class MamaTests
|
||||
int count = 100;
|
||||
var data = new double[count];
|
||||
var gbm = new GBM(startPrice: 100, seed: 42);
|
||||
for (int i = 0; i < count; i++) data[i] = gbm.Next().Close;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
data[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
var mamaOutput = new double[count];
|
||||
var famaOutput = new double[count];
|
||||
@@ -357,7 +373,10 @@ public class MamaTests
|
||||
int count = 100;
|
||||
var data = new double[count];
|
||||
var gbm = new GBM(startPrice: 100, seed: 42);
|
||||
for (int i = 0; i < count; i++) data[i] = gbm.Next().Close;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
data[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
var output1 = new double[count];
|
||||
var output2 = new double[count];
|
||||
@@ -394,7 +413,10 @@ public class MamaTests
|
||||
int count = 10;
|
||||
var data = new double[count];
|
||||
var gbm = new GBM(startPrice: 100, seed: 42);
|
||||
for (int i = 0; i < count; i++) data[i] = gbm.Next().Close;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
data[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
// Get values from span calculation
|
||||
var mamaOutput = new double[count];
|
||||
@@ -418,7 +440,10 @@ public class MamaTests
|
||||
int count = 100;
|
||||
var data = new double[count];
|
||||
var gbm = new GBM(startPrice: 100, seed: 42);
|
||||
for (int i = 0; i < count; i++) data[i] = gbm.Next().Close;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
data[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
// 1. Streaming Mode (instance method)
|
||||
var mama = new Mama();
|
||||
|
||||
+55
-12
@@ -111,8 +111,16 @@ public sealed class Mama : AbstractBase
|
||||
return 0.0; // Return neutral angle for invalid inputs
|
||||
}
|
||||
|
||||
while (angle <= -Math.PI) angle += TwoPi;
|
||||
while (angle > Math.PI) angle -= TwoPi;
|
||||
while (angle <= -Math.PI)
|
||||
{
|
||||
angle += TwoPi;
|
||||
}
|
||||
|
||||
while (angle > Math.PI)
|
||||
{
|
||||
angle -= TwoPi;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
@@ -192,11 +200,25 @@ public sealed class Mama : AbstractBase
|
||||
double periodCap = _p_state.Period * 1.5;
|
||||
double periodFloor = _p_state.Period * 0.67;
|
||||
|
||||
if (period > periodCap) period = periodCap;
|
||||
if (period < periodFloor) period = periodFloor;
|
||||
if (period > periodCap)
|
||||
{
|
||||
period = periodCap;
|
||||
}
|
||||
|
||||
if (period < MinPeriod) period = MinPeriod;
|
||||
if (period > MaxPeriod) period = MaxPeriod;
|
||||
if (period < periodFloor)
|
||||
{
|
||||
period = periodFloor;
|
||||
}
|
||||
|
||||
if (period < MinPeriod)
|
||||
{
|
||||
period = MinPeriod;
|
||||
}
|
||||
|
||||
if (period > MaxPeriod)
|
||||
{
|
||||
period = MaxPeriod;
|
||||
}
|
||||
|
||||
// Smooth Period (using FMA)
|
||||
_state.Period = Math.FusedMultiplyAdd(SmoothCoef, period, SmoothPrev * _p_state.Period);
|
||||
@@ -248,7 +270,10 @@ public sealed class Mama : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return new TSeries([], []);
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return new TSeries([], []);
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var v = new List<double>(len);
|
||||
@@ -307,7 +332,11 @@ public sealed class Mama : AbstractBase
|
||||
throw new ArgumentOutOfRangeException(nameof(fastLimit), "FastLimit must be > SlowLimit");
|
||||
}
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(output), "Output buffer must be at least as large as the input buffer.");
|
||||
@@ -427,11 +456,25 @@ public sealed class Mama : AbstractBase
|
||||
double periodCap = p_period * 1.5;
|
||||
double periodFloor = p_period * 0.67;
|
||||
|
||||
if (newPeriod > periodCap) newPeriod = periodCap;
|
||||
if (newPeriod < periodFloor) newPeriod = periodFloor;
|
||||
if (newPeriod > periodCap)
|
||||
{
|
||||
newPeriod = periodCap;
|
||||
}
|
||||
|
||||
if (newPeriod < MinPeriod) newPeriod = MinPeriod;
|
||||
if (newPeriod > MaxPeriod) newPeriod = MaxPeriod;
|
||||
if (newPeriod < periodFloor)
|
||||
{
|
||||
newPeriod = periodFloor;
|
||||
}
|
||||
|
||||
if (newPeriod < MinPeriod)
|
||||
{
|
||||
newPeriod = MinPeriod;
|
||||
}
|
||||
|
||||
if (newPeriod > MaxPeriod)
|
||||
{
|
||||
newPeriod = MaxPeriod;
|
||||
}
|
||||
|
||||
// Smooth Period (using FMA)
|
||||
period = Math.FusedMultiplyAdd(SmoothCoef, newPeriod, SmoothPrev * p_period);
|
||||
|
||||
@@ -33,7 +33,11 @@ public sealed class Mgdi : AbstractBase
|
||||
public Mgdi(int period = 14, double k = 0.6)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
|
||||
if (double.IsNaN(k) || double.IsInfinity(k) || k <= 0) throw new ArgumentOutOfRangeException(nameof(k), "k must be a finite value greater than 0");
|
||||
if (double.IsNaN(k) || double.IsInfinity(k) || k <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(k), "k must be a finite value greater than 0");
|
||||
}
|
||||
|
||||
_period = period;
|
||||
_k = k;
|
||||
Name = $"Mgdi({period},{k})";
|
||||
@@ -123,7 +127,10 @@ public sealed class Mgdi : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return new TSeries([], []);
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return new TSeries([], []);
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -167,12 +174,20 @@ public sealed class Mgdi : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period = 14, double k = 0.6)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
|
||||
if (double.IsNaN(k) || double.IsInfinity(k) || k <= 0) throw new ArgumentOutOfRangeException(nameof(k), "k must be a finite value greater than 0");
|
||||
if (double.IsNaN(k) || double.IsInfinity(k) || k <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(k), "k must be a finite value greater than 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;
|
||||
}
|
||||
|
||||
double lastMgdi = 0;
|
||||
double lastValid = 0;
|
||||
@@ -225,4 +240,4 @@ public sealed class Mgdi : AbstractBase
|
||||
{
|
||||
Init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,9 +68,13 @@ public class MmaValidationTests
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -79,16 +83,22 @@ public class MmaValidationTests
|
||||
}
|
||||
|
||||
if (count < window)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
sum -= buffer[head];
|
||||
}
|
||||
|
||||
buffer[head] = val;
|
||||
sum += val;
|
||||
|
||||
head++;
|
||||
if (head == window)
|
||||
{
|
||||
head = 0;
|
||||
}
|
||||
|
||||
double sma = sum / count;
|
||||
double weightedSum = ComputeWeightedSum(buffer, head, count);
|
||||
@@ -101,7 +111,9 @@ public class MmaValidationTests
|
||||
{
|
||||
int idx = head - 1;
|
||||
if (idx < 0)
|
||||
{
|
||||
idx = count - 1;
|
||||
}
|
||||
|
||||
double weightedSum = 0.0;
|
||||
for (int i = 0; i < count; i++)
|
||||
@@ -111,7 +123,9 @@ public class MmaValidationTests
|
||||
|
||||
idx--;
|
||||
if (idx < 0)
|
||||
{
|
||||
idx = count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return weightedSum;
|
||||
|
||||
@@ -74,9 +74,13 @@ public sealed class Mma : AbstractBase
|
||||
|
||||
double val = input.Value;
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
_lastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = _lastValidValue;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -97,7 +101,10 @@ public sealed class Mma : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
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);
|
||||
@@ -123,9 +130,13 @@ public sealed class Mma : AbstractBase
|
||||
{
|
||||
double val = source.Values[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -175,10 +186,16 @@ public sealed class Mma : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length.", nameof(output));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 2);
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int window = Math.Min(Math.Max(2, period), MaxPeriod);
|
||||
double sum = 0.0;
|
||||
@@ -196,9 +213,13 @@ public sealed class Mma : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -207,16 +228,22 @@ public sealed class Mma : AbstractBase
|
||||
}
|
||||
|
||||
if (count < window)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
sum -= buffer[head];
|
||||
}
|
||||
|
||||
buffer[head] = val;
|
||||
sum += val;
|
||||
|
||||
head++;
|
||||
if (head == window)
|
||||
{
|
||||
head = 0;
|
||||
}
|
||||
|
||||
double sma = sum / count;
|
||||
double weightedSum = ComputeWeightedSum(buffer, head, count);
|
||||
@@ -252,7 +279,9 @@ public sealed class Mma : AbstractBase
|
||||
{
|
||||
int count = _buffer.Count;
|
||||
if (count <= 0)
|
||||
{
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
double sma = _buffer.Sum / count;
|
||||
double weightedSum = ComputeWeightedSum(_buffer, count);
|
||||
@@ -260,7 +289,9 @@ public sealed class Mma : AbstractBase
|
||||
double result = Math.FusedMultiplyAdd(weightedSum, 6.0 / denom, sma);
|
||||
|
||||
if (!state.IsHot && count >= _period)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -274,7 +305,9 @@ public sealed class Mma : AbstractBase
|
||||
|
||||
int idx = start + count - 1;
|
||||
if (idx >= capacity)
|
||||
{
|
||||
idx -= capacity;
|
||||
}
|
||||
|
||||
double weightedSum = 0.0;
|
||||
for (int i = 0; i < count; i++)
|
||||
@@ -284,7 +317,9 @@ public sealed class Mma : AbstractBase
|
||||
|
||||
idx--;
|
||||
if (idx < 0)
|
||||
{
|
||||
idx += capacity;
|
||||
}
|
||||
}
|
||||
|
||||
return weightedSum;
|
||||
@@ -295,7 +330,9 @@ public sealed class Mma : AbstractBase
|
||||
{
|
||||
int idx = head - 1;
|
||||
if (idx < 0)
|
||||
{
|
||||
idx = count - 1;
|
||||
}
|
||||
|
||||
double weightedSum = 0.0;
|
||||
for (int i = 0; i < count; i++)
|
||||
@@ -305,9 +342,11 @@ public sealed class Mma : AbstractBase
|
||||
|
||||
idx--;
|
||||
if (idx < 0)
|
||||
{
|
||||
idx = count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return weightedSum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +350,9 @@ public class QemaTests
|
||||
|
||||
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
|
||||
Qema.Batch(source.AsSpan(), output.AsSpan(), 100);
|
||||
@@ -412,7 +414,10 @@ public class QemaTests
|
||||
|
||||
// Verify against a fresh QEMA fed with same data
|
||||
var verifyQema = new Qema(5);
|
||||
foreach (var val in history) verifyQema.Update(new TValue(DateTime.UtcNow, val));
|
||||
foreach (var val in history)
|
||||
{
|
||||
verifyQema.Update(new TValue(DateTime.UtcNow, val));
|
||||
}
|
||||
|
||||
Assert.Equal(verifyQema.Last.Value, qema.Last.Value, 1e-10);
|
||||
Assert.Equal(verifyQema.IsHot, qema.IsHot);
|
||||
@@ -432,7 +437,10 @@ public class QemaTests
|
||||
qema.Prime(history);
|
||||
|
||||
var verifyQema = new Qema(5);
|
||||
foreach (var val in history) verifyQema.Update(new TValue(DateTime.UtcNow, val));
|
||||
foreach (var val in history)
|
||||
{
|
||||
verifyQema.Update(new TValue(DateTime.UtcNow, val));
|
||||
}
|
||||
|
||||
Assert.Equal(verifyQema.Last.Value, qema.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,10 @@ public sealed class Qema : AbstractBase
|
||||
/// <param name="step">Optional time step (not used)</param>
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset state
|
||||
_state1 = EmaState.New();
|
||||
@@ -163,9 +166,13 @@ public sealed class Qema : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
double e1 = ComputeEma(val, _alpha1, _decay1, ref s1);
|
||||
double e2 = ComputeEma(e1, _alpha2, _decay2, ref s2);
|
||||
@@ -200,7 +207,11 @@ public sealed class Qema : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double GetCompensated(EmaState s)
|
||||
{
|
||||
if (s.IsCompensated) return s.Ema;
|
||||
if (s.IsCompensated)
|
||||
{
|
||||
return s.Ema;
|
||||
}
|
||||
|
||||
return s.Ema / (1.0 - s.E);
|
||||
}
|
||||
|
||||
@@ -226,9 +237,13 @@ public sealed class Qema : AbstractBase
|
||||
|
||||
double val = input.Value;
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
_lastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = _lastValidValue;
|
||||
}
|
||||
|
||||
// Cascaded EMAs
|
||||
double e1 = ComputeEma(val, _alpha1, _decay1, ref _state1);
|
||||
@@ -247,7 +262,10 @@ public sealed class Qema : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
List<long> t = new(len);
|
||||
@@ -273,9 +291,13 @@ public sealed class Qema : AbstractBase
|
||||
{
|
||||
double val = sourceValues[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
double e1 = ComputeEma(val, _alpha1, _decay1, ref s1);
|
||||
double e2 = ComputeEma(e1, _alpha2, _decay2, ref s2);
|
||||
@@ -314,7 +336,9 @@ public sealed class Qema : AbstractBase
|
||||
state.E *= decay;
|
||||
|
||||
if (!state.IsHot && state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
if (state.E <= COMPENSATOR_THRESHOLD)
|
||||
{
|
||||
@@ -402,10 +426,16 @@ public sealed class Qema : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(period);
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double alpha1 = Clamp01(2.0 / (period + 1));
|
||||
double r = Math.Pow(1.0 / alpha1, 0.25);
|
||||
@@ -446,9 +476,13 @@ public sealed class Qema : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
// EMA1
|
||||
ema1_val = Math.FusedMultiplyAdd(ema1_val, decay1, alpha1 * val);
|
||||
|
||||
@@ -204,4 +204,4 @@ public class RemaIndicatorTests
|
||||
|
||||
Assert.NotEqual(result1, result2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,4 +55,4 @@ public class RemaIndicator : Indicator, IWatchlistIndicator
|
||||
TValue result = ma.Update(new TValue(item.TimeLeft.Ticks, _priceSelector(item)), isNew: args.IsNewBar());
|
||||
Series.SetValue(result.Value, ma.IsHot, ShowColdValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,7 +447,9 @@ public class RemaTests
|
||||
|
||||
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
|
||||
Rema.Batch(source.AsSpan(), output.AsSpan(), 100);
|
||||
@@ -491,7 +493,10 @@ public class RemaTests
|
||||
|
||||
// Verify against a fresh REMA fed with same data
|
||||
var verifyRema = new Rema(5);
|
||||
foreach (var val in history) verifyRema.Update(new TValue(DateTime.UtcNow, val));
|
||||
foreach (var val in history)
|
||||
{
|
||||
verifyRema.Update(new TValue(DateTime.UtcNow, val));
|
||||
}
|
||||
|
||||
Assert.Equal(verifyRema.Last.Value, rema.Last.Value, 1e-10);
|
||||
Assert.Equal(verifyRema.IsHot, rema.IsHot);
|
||||
@@ -511,7 +516,10 @@ public class RemaTests
|
||||
rema.Prime(history);
|
||||
|
||||
var verifyRema = new Rema(5);
|
||||
foreach (var val in history) verifyRema.Update(new TValue(DateTime.UtcNow, val));
|
||||
foreach (var val in history)
|
||||
{
|
||||
verifyRema.Update(new TValue(DateTime.UtcNow, val));
|
||||
}
|
||||
|
||||
Assert.Equal(verifyRema.Last.Value, rema.Last.Value, 1e-10);
|
||||
}
|
||||
@@ -531,7 +539,10 @@ public class RemaTests
|
||||
public void Calculate_ReturnsCorrectResultsAndHotIndicator()
|
||||
{
|
||||
var series = new TSeries();
|
||||
for (int i = 1; i <= 20; i++) series.Add(DateTime.UtcNow, i * 10);
|
||||
for (int i = 1; i <= 20; i++)
|
||||
{
|
||||
series.Add(DateTime.UtcNow, i * 10);
|
||||
}
|
||||
|
||||
var (results, indicator) = Rema.Calculate(series, 5);
|
||||
|
||||
@@ -640,14 +651,20 @@ public class RemaTests
|
||||
// 3. Streaming Mode
|
||||
var streamingInd = new Rema(period, lambda);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamingInd.Update(series[i]);
|
||||
}
|
||||
|
||||
double streamingResult = streamingInd.Last.Value;
|
||||
|
||||
// 4. Eventing Mode
|
||||
var pubSource = new TSeries();
|
||||
var eventingInd = new Rema(pubSource, period, lambda);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
pubSource.Add(series[i]);
|
||||
}
|
||||
|
||||
double eventingResult = eventingInd.Last.Value;
|
||||
|
||||
Assert.Equal(expected, spanResult, precision: 9);
|
||||
|
||||
@@ -304,7 +304,10 @@ public sealed class RemaValidationTests : IDisposable
|
||||
|
||||
private static double CalculateDiffVariance(double[] values, int startIdx, int count)
|
||||
{
|
||||
if (count < 2) return 0;
|
||||
if (count < 2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate differences
|
||||
double sumDiff = 0;
|
||||
@@ -319,10 +322,13 @@ public sealed class RemaValidationTests : IDisposable
|
||||
n++;
|
||||
}
|
||||
|
||||
if (n < 2) return 0;
|
||||
if (n < 2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
double mean = sumDiff / n;
|
||||
double variance = (sumDiffSq / n) - (mean * mean);
|
||||
return Math.Max(0, variance); // Ensure non-negative due to floating point
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,9 @@ public sealed class Rema : AbstractBase
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(period);
|
||||
if (lambda < 0.0 || lambda > 1.0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(lambda), "Lambda must be between 0 and 1");
|
||||
}
|
||||
|
||||
_alpha = 2.0 / (period + 1);
|
||||
_decay = 1.0 - _alpha;
|
||||
@@ -108,7 +110,10 @@ public sealed class Rema : AbstractBase
|
||||
/// <inheritdoc/>
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_state = State.New();
|
||||
_p_state = State.New();
|
||||
@@ -152,7 +157,9 @@ public sealed class Rema : AbstractBase
|
||||
finally
|
||||
{
|
||||
if (rented != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(rented);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +203,10 @@ public sealed class Rema : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
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);
|
||||
@@ -245,7 +255,9 @@ public sealed class Rema : AbstractBase
|
||||
state.E *= decay;
|
||||
|
||||
if (state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
result = input;
|
||||
}
|
||||
@@ -273,7 +285,9 @@ public sealed class Rema : AbstractBase
|
||||
state.E *= decay;
|
||||
|
||||
if (!state.IsHot && state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
if (state.E <= COMPENSATOR_THRESHOLD)
|
||||
{
|
||||
@@ -311,9 +325,13 @@ public sealed class Rema : AbstractBase
|
||||
{
|
||||
double val = Unsafe.Add(ref srcRef, i);
|
||||
if (!double.IsFinite(val))
|
||||
{
|
||||
val = lastValidValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValidValue = val;
|
||||
}
|
||||
|
||||
double result;
|
||||
|
||||
@@ -326,7 +344,9 @@ public sealed class Rema : AbstractBase
|
||||
state.E *= decay;
|
||||
|
||||
if (state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
result = val;
|
||||
}
|
||||
@@ -345,7 +365,9 @@ public sealed class Rema : AbstractBase
|
||||
state.E *= decay;
|
||||
|
||||
if (!state.IsHot && state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
if (state.E <= COMPENSATOR_THRESHOLD)
|
||||
{
|
||||
@@ -399,13 +421,24 @@ public sealed class Rema : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, double lambda = 0.5)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
if (lambda < 0.0 || lambda > 1.0)
|
||||
throw new ArgumentOutOfRangeException(nameof(lambda), "Lambda must be between 0 and 1");
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (lambda < 0.0 || lambda > 1.0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(lambda), "Lambda must be between 0 and 1");
|
||||
}
|
||||
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
|
||||
|
||||
@@ -98,10 +98,14 @@ public class RgmaTests
|
||||
|
||||
var streamValues = new List<double>(series.Count);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamValues.Add(rgma.Update(series[i]).Value);
|
||||
}
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
Assert.Equal(batch[i].Value, streamValues[i], precision: 10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -117,7 +121,9 @@ public class RgmaTests
|
||||
TSeries batch = Rgma.Batch(series, period, passes);
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
Assert.Equal(batch[i].Value, output[i], precision: 10);
|
||||
}
|
||||
}
|
||||
|
||||
private static TSeries BuildSeries(int count, int seed)
|
||||
|
||||
@@ -27,12 +27,16 @@ public sealed class RgmaValidationTests : IDisposable
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_testData?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -52,7 +56,9 @@ public sealed class RgmaValidationTests : IDisposable
|
||||
int startIdx = rgmaResult.Count - compareCount;
|
||||
|
||||
for (int i = startIdx; i < rgmaResult.Count; i++)
|
||||
{
|
||||
Assert.Equal(emaResult[i].Value, rgmaResult[i].Value, 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
_output.WriteLine("RGMA(passes=1) Batch validated successfully against EMA");
|
||||
@@ -81,7 +87,9 @@ public sealed class RgmaValidationTests : IDisposable
|
||||
int startIdx = rgmaResults.Count - compareCount;
|
||||
|
||||
for (int i = startIdx; i < rgmaResults.Count; i++)
|
||||
{
|
||||
Assert.Equal(emaResults[i], rgmaResults[i], 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
_output.WriteLine("RGMA(passes=1) Streaming validated successfully against EMA");
|
||||
@@ -105,7 +113,9 @@ public sealed class RgmaValidationTests : IDisposable
|
||||
int startIdx = sourceData.Length - compareCount;
|
||||
|
||||
for (int i = startIdx; i < sourceData.Length; i++)
|
||||
{
|
||||
Assert.Equal(emaOutput[i], rgmaOutput[i], 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
_output.WriteLine("RGMA(passes=1) Span validated successfully against EMA");
|
||||
@@ -131,7 +141,9 @@ public sealed class RgmaValidationTests : IDisposable
|
||||
var rgmaStream = new Rgma(period, passCount);
|
||||
var streaming = new double[_testData.Data.Count];
|
||||
for (int i = 0; i < _testData.Data.Count; i++)
|
||||
{
|
||||
streaming[i] = rgmaStream.Update(_testData.Data[i]).Value;
|
||||
}
|
||||
|
||||
// Span
|
||||
var spanOutput = new double[sourceData.Length];
|
||||
|
||||
@@ -111,7 +111,10 @@ public sealed class Rgma : AbstractBase
|
||||
/// <inheritdoc/>
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_state = State.New();
|
||||
_p_state = State.New();
|
||||
@@ -172,9 +175,14 @@ public sealed class Rgma : AbstractBase
|
||||
finally
|
||||
{
|
||||
if (filtersRented != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(filtersRented);
|
||||
}
|
||||
|
||||
if (rented != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(rented);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +197,9 @@ public sealed class Rgma : AbstractBase
|
||||
if (_passes <= 8)
|
||||
{
|
||||
for (int i = 0; i < _passes; i++)
|
||||
{
|
||||
_p_filters[i] = _filters[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -203,7 +213,9 @@ public sealed class Rgma : AbstractBase
|
||||
if (_passes <= 8)
|
||||
{
|
||||
for (int i = 0; i < _passes; i++)
|
||||
{
|
||||
_filters[i] = _p_filters[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -222,7 +234,10 @@ public sealed class Rgma : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
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);
|
||||
@@ -263,22 +278,31 @@ public sealed class Rgma : AbstractBase
|
||||
state.TickCount = 1;
|
||||
state.E *= decay;
|
||||
if (state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
// Stage 0
|
||||
filters[0] = Math.FusedMultiplyAdd(alpha, input - filters[0], filters[0]);
|
||||
for (int i = 1; i < filters.Length; i++)
|
||||
{
|
||||
filters[i] = Math.FusedMultiplyAdd(alpha, filters[i - 1] - filters[i], filters[i]);
|
||||
}
|
||||
|
||||
state.TickCount++;
|
||||
state.E *= decay;
|
||||
if (!state.IsHot && state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
if (state.TickCount >= ResyncInterval)
|
||||
{
|
||||
state.TickCount = 0;
|
||||
}
|
||||
|
||||
return filters[^1];
|
||||
}
|
||||
@@ -298,9 +322,13 @@ public sealed class Rgma : AbstractBase
|
||||
{
|
||||
double x = source[i];
|
||||
if (double.IsFinite(x))
|
||||
{
|
||||
lastValid = x;
|
||||
}
|
||||
else
|
||||
{
|
||||
x = lastValid;
|
||||
}
|
||||
|
||||
double y;
|
||||
if (!state.IsInitialized)
|
||||
@@ -310,22 +338,31 @@ public sealed class Rgma : AbstractBase
|
||||
state.TickCount = 1;
|
||||
state.E *= decay;
|
||||
if (state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
y = x;
|
||||
}
|
||||
else
|
||||
{
|
||||
filters[0] = Math.FusedMultiplyAdd(alpha, x - filters[0], filters[0]);
|
||||
for (int p = 1; p < filters.Length; p++)
|
||||
{
|
||||
filters[p] = Math.FusedMultiplyAdd(alpha, filters[p - 1] - filters[p], filters[p]);
|
||||
}
|
||||
|
||||
state.TickCount++;
|
||||
state.E *= decay;
|
||||
if (!state.IsHot && state.E <= COVERAGE_THRESHOLD)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
if (state.TickCount >= ResyncInterval)
|
||||
{
|
||||
state.TickCount = 0;
|
||||
}
|
||||
|
||||
y = filters[^1];
|
||||
}
|
||||
@@ -361,13 +398,24 @@ public sealed class Rgma : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, int passes = 3)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
if (passes <= 0)
|
||||
throw new ArgumentException("Passes must be greater than 0", nameof(passes));
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (passes <= 0)
|
||||
{
|
||||
throw new ArgumentException("Passes must be greater than 0", nameof(passes));
|
||||
}
|
||||
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double alpha = 2.0 / (period / Math.Sqrt(passes) + 1.0);
|
||||
double decay = 1.0 - alpha;
|
||||
@@ -405,7 +453,9 @@ public sealed class Rgma : AbstractBase
|
||||
finally
|
||||
{
|
||||
if (rented != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(rented);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,4 +470,4 @@ public sealed class Rgma : AbstractBase
|
||||
Array.Fill(_p_filters, double.NaN);
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ public sealed class Rma : AbstractBase
|
||||
public Rma(int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
_ema = new Ema(1.0 / period);
|
||||
Name = $"Rma({period})";
|
||||
@@ -118,10 +120,14 @@ public sealed class Rma : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output span must be at least as long as source span", nameof(output));
|
||||
}
|
||||
|
||||
double alpha = 1.0 / period;
|
||||
Ema.Batch(source, output, alpha);
|
||||
@@ -150,4 +156,4 @@ public sealed class Rma : AbstractBase
|
||||
_ema.Reset();
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-4
@@ -61,11 +61,19 @@ public sealed class T3 : AbstractBase
|
||||
public T3(int period, double vfactor = 0.7)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
if (!double.IsFinite(vfactor))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(vfactor), "Volume factor must be a finite number (not NaN or Infinity)");
|
||||
}
|
||||
|
||||
if (vfactor <= 0 || vfactor > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(vfactor), "Volume factor must be greater than 0 and typically <= 1");
|
||||
}
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
double decay = 1.0 - alpha;
|
||||
@@ -136,7 +144,10 @@ public sealed class T3 : AbstractBase
|
||||
/// <param name="source">Historical data</param>
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset state
|
||||
_state = State.New();
|
||||
@@ -188,7 +199,7 @@ public sealed class T3 : AbstractBase
|
||||
// So the state corresponds to "after processing source".
|
||||
// To get the output value corresponding to the last input, we can calculate it from the state.
|
||||
// But T3 formula uses the *updated* EMAs.
|
||||
// T3 = c1*e6 + c2*e5 + c3*e4 + c4*e3
|
||||
// T3 = c1*e6 + c2*e5 + c3*e4 + c4*e3
|
||||
// The state has the updated EMAs.
|
||||
double result = Math.FusedMultiplyAdd(_params.C4, _state.E3,
|
||||
Math.FusedMultiplyAdd(_params.C3, _state.E4,
|
||||
@@ -235,7 +246,10 @@ public sealed class T3 : 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);
|
||||
@@ -298,9 +312,13 @@ public sealed class T3 : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValidValue;
|
||||
}
|
||||
|
||||
output[i] = Compute(val, p, ref state);
|
||||
}
|
||||
@@ -322,13 +340,24 @@ public sealed class T3 : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, double vfactor = 0.7)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (!double.IsFinite(vfactor))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(vfactor), "Volume factor must be a finite number (not NaN or Infinity)");
|
||||
}
|
||||
|
||||
if (vfactor <= 0 || vfactor > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(vfactor), "Volume factor must be greater than 0 and typically <= 1");
|
||||
}
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
double decay = 1.0 - alpha;
|
||||
@@ -370,4 +399,4 @@ public sealed class T3 : AbstractBase
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,9 @@ public sealed class TemaIndicator : Indicator, IWatchlistIndicator
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
|
||||
TValue result = _ma.Update(new TValue(item.TimeLeft.Ticks, _priceSelector(item)), args.IsNewBar());
|
||||
|
||||
@@ -50,7 +50,10 @@ public sealed class Tema : AbstractBase
|
||||
|
||||
public Tema(int period)
|
||||
{
|
||||
if (period <= 0) throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
_alpha = 2.0 / (period + 1);
|
||||
_decay = 1.0 - _alpha;
|
||||
@@ -76,7 +79,10 @@ public sealed class Tema : AbstractBase
|
||||
|
||||
public Tema(double alpha)
|
||||
{
|
||||
if (alpha <= 0 || alpha >= 1) throw new ArgumentException("Alpha must be strictly between 0 and 1", nameof(alpha));
|
||||
if (alpha <= 0 || alpha >= 1)
|
||||
{
|
||||
throw new ArgumentException("Alpha must be strictly between 0 and 1", nameof(alpha));
|
||||
}
|
||||
|
||||
_alpha = alpha;
|
||||
_decay = 1.0 - alpha;
|
||||
@@ -93,7 +99,10 @@ public sealed class Tema : AbstractBase
|
||||
/// <param name="source">Historical data</param>
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset state
|
||||
_state1 = EmaState.New();
|
||||
@@ -131,9 +140,13 @@ public sealed class Tema : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
double e1 = Compute(val, alpha, decay, ref s1);
|
||||
double e2 = Compute(e1, alpha, decay, ref s2);
|
||||
@@ -157,7 +170,11 @@ public sealed class Tema : AbstractBase
|
||||
|
||||
double GetCompensated(EmaState s)
|
||||
{
|
||||
if (s.IsCompensated) return s.Ema;
|
||||
if (s.IsCompensated)
|
||||
{
|
||||
return s.Ema;
|
||||
}
|
||||
|
||||
return s.Ema / (1.0 - s.E);
|
||||
}
|
||||
|
||||
@@ -196,9 +213,13 @@ public sealed class Tema : AbstractBase
|
||||
// EMA1
|
||||
double val = input.Value;
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
_lastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = _lastValidValue;
|
||||
}
|
||||
|
||||
double e1 = Compute(val, _alpha, _decay, ref _state1);
|
||||
|
||||
@@ -217,7 +238,10 @@ public sealed class Tema : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
List<long> t = new(len);
|
||||
@@ -243,9 +267,13 @@ public sealed class Tema : AbstractBase
|
||||
{
|
||||
double val = sourceValues[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
double e1 = Compute(val, alpha, decay, ref s1);
|
||||
double e2 = Compute(e1, alpha, decay, ref s2);
|
||||
@@ -281,7 +309,9 @@ public sealed class Tema : AbstractBase
|
||||
state.E *= decay;
|
||||
|
||||
if (!state.IsHot && state.E <= 0.05) // COVERAGE_THRESHOLD
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
if (state.E <= 1e-10) // COMPENSATOR_THRESHOLD
|
||||
{
|
||||
@@ -316,7 +346,9 @@ public sealed class Tema : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
Batch(source, output, alpha);
|
||||
@@ -325,11 +357,19 @@ public sealed class Tema : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, double alpha)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (alpha <= 0 || alpha >= 1)
|
||||
throw new ArgumentException("Alpha must be strictly between 0 and 1", nameof(alpha));
|
||||
}
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (alpha <= 0 || alpha >= 1)
|
||||
{
|
||||
throw new ArgumentException("Alpha must be strictly between 0 and 1", nameof(alpha));
|
||||
}
|
||||
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double decay = 1.0 - alpha;
|
||||
double lastValid = 0;
|
||||
@@ -363,9 +403,13 @@ public sealed class Tema : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
// Update EMA1: ema = decay * ema + alpha * input = FMA(decay, ema, alpha * input)
|
||||
ema1_val = Math.FusedMultiplyAdd(decay, ema1_val, alpha * val);
|
||||
@@ -447,4 +491,4 @@ public sealed class Tema : AbstractBase
|
||||
_p_lastValidValue = 0;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,10 @@ public class VamaTests
|
||||
|
||||
// Verify against a fresh VAMA fed with same data
|
||||
var verifyVama = new Vama();
|
||||
foreach (var val in history) verifyVama.Update(new TValue(DateTime.UtcNow, val));
|
||||
foreach (var val in history)
|
||||
{
|
||||
verifyVama.Update(new TValue(DateTime.UtcNow, val));
|
||||
}
|
||||
|
||||
Assert.Equal(verifyVama.Last.Value, vama.Last.Value, 1e-10);
|
||||
|
||||
@@ -315,7 +318,10 @@ public class VamaTests
|
||||
vama.Prime(history);
|
||||
|
||||
var verifyVama = new Vama();
|
||||
foreach (var val in history) verifyVama.Update(new TValue(DateTime.UtcNow, val));
|
||||
foreach (var val in history)
|
||||
{
|
||||
verifyVama.Update(new TValue(DateTime.UtcNow, val));
|
||||
}
|
||||
|
||||
Assert.Equal(verifyVama.Last.Value, vama.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
@@ -77,17 +77,34 @@ public sealed class Vama : AbstractBase
|
||||
public Vama(int baseLength = 20, int shortAtrPeriod = 10, int longAtrPeriod = 50, int minLength = 5, int maxLength = 100)
|
||||
{
|
||||
if (baseLength <= 0)
|
||||
{
|
||||
throw new ArgumentException("Base length must be greater than 0", nameof(baseLength));
|
||||
}
|
||||
|
||||
if (shortAtrPeriod <= 0)
|
||||
{
|
||||
throw new ArgumentException("Short ATR period must be greater than 0", nameof(shortAtrPeriod));
|
||||
}
|
||||
|
||||
if (longAtrPeriod <= 0)
|
||||
{
|
||||
throw new ArgumentException("Long ATR period must be greater than 0", nameof(longAtrPeriod));
|
||||
}
|
||||
|
||||
if (minLength <= 0)
|
||||
{
|
||||
throw new ArgumentException("Min length must be greater than 0", nameof(minLength));
|
||||
}
|
||||
|
||||
if (maxLength <= 0)
|
||||
{
|
||||
throw new ArgumentException("Max length must be greater than 0", nameof(maxLength));
|
||||
}
|
||||
|
||||
if (minLength > maxLength)
|
||||
{
|
||||
throw new ArgumentException("Min length must be less than or equal to max length", nameof(minLength));
|
||||
}
|
||||
|
||||
_baseLength = baseLength;
|
||||
_minLength = minLength;
|
||||
@@ -167,11 +184,17 @@ public sealed class Vama : AbstractBase
|
||||
|
||||
shortAtr.Ema = Math.FusedMultiplyAdd(shortAtr.Ema, _shortDecay, _shortAlpha * trueRange);
|
||||
shortAtr.E *= _shortDecay;
|
||||
if (shortAtr.E <= EPSILON) shortAtr.IsCompensated = true;
|
||||
if (shortAtr.E <= EPSILON)
|
||||
{
|
||||
shortAtr.IsCompensated = true;
|
||||
}
|
||||
|
||||
longAtr.Ema = Math.FusedMultiplyAdd(longAtr.Ema, _longDecay, _longAlpha * trueRange);
|
||||
longAtr.E *= _longDecay;
|
||||
if (longAtr.E <= EPSILON) longAtr.IsCompensated = true;
|
||||
if (longAtr.E <= EPSILON)
|
||||
{
|
||||
longAtr.IsCompensated = true;
|
||||
}
|
||||
|
||||
// Compensated ATR values
|
||||
double shortAtrValue = shortAtr.IsCompensated ? shortAtr.Ema : shortAtr.Ema / (1.0 - shortAtr.E);
|
||||
@@ -187,9 +210,13 @@ public sealed class Vama : AbstractBase
|
||||
// Update circular buffer with source value
|
||||
double sourceValue = input.Close;
|
||||
if (!double.IsFinite(sourceValue))
|
||||
{
|
||||
sourceValue = _lastValidValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidValue = sourceValue;
|
||||
}
|
||||
|
||||
// Remove oldest value from sum if it was valid
|
||||
double oldest = _buffer[_state.BufferHead];
|
||||
@@ -271,7 +298,10 @@ public sealed class Vama : AbstractBase
|
||||
/// </summary>
|
||||
public TSeries Update(TBarSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -298,7 +328,10 @@ public sealed class Vama : AbstractBase
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
@@ -51,7 +51,9 @@ public sealed class VidyaIndicator : Indicator, IWatchlistIndicator
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
|
||||
TValue result = _ma.Update(new TValue(item.TimeLeft.Ticks, _priceSelector(item)), args.IsNewBar());
|
||||
|
||||
@@ -46,7 +46,9 @@ public sealed class Vidya : AbstractBase
|
||||
public Vidya(int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
_period = period;
|
||||
_alpha = 2.0 / (period + 1);
|
||||
@@ -100,7 +102,11 @@ public sealed class Vidya : AbstractBase
|
||||
double price = input.Value;
|
||||
if (!double.IsFinite(price))
|
||||
{
|
||||
if (!_state.IsInitialized) return input;
|
||||
if (!_state.IsInitialized)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
price = _state.CurrentClose;
|
||||
}
|
||||
|
||||
@@ -147,7 +153,10 @@ public sealed class Vidya : 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);
|
||||
@@ -197,7 +206,10 @@ public sealed class Vidya : AbstractBase
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset state
|
||||
Reset();
|
||||
@@ -224,7 +236,10 @@ public sealed class Vidya : AbstractBase
|
||||
for (int i = 1; i < source.Length; i++)
|
||||
{
|
||||
double price = source[i];
|
||||
if (!double.IsFinite(price)) price = prevClose;
|
||||
if (!double.IsFinite(price))
|
||||
{
|
||||
price = prevClose;
|
||||
}
|
||||
|
||||
double change = price - prevClose;
|
||||
double up = change > 0 ? change : 0;
|
||||
@@ -295,11 +310,19 @@ public sealed class Vidya : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
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 != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
|
||||
@@ -346,7 +369,10 @@ public sealed class Vidya : AbstractBase
|
||||
sumDown += down;
|
||||
|
||||
head++;
|
||||
if (head >= period) head = 0;
|
||||
if (head >= period)
|
||||
{
|
||||
head = 0;
|
||||
}
|
||||
|
||||
double sum = sumUp + sumDown;
|
||||
double vi = 0;
|
||||
|
||||
@@ -70,7 +70,9 @@ public class YzvamaTests
|
||||
Assert.False(yzvama.IsHot);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
yzvama.Update(new TValue(DateTime.UtcNow, 100 + i), isNew: true);
|
||||
}
|
||||
|
||||
Assert.True(yzvama.IsHot);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ public class YzvamaValidationTests
|
||||
const double constantValue = 42.5;
|
||||
|
||||
for (int i = 0; i < 300; i++)
|
||||
{
|
||||
yzvama.Update(new TValue(DateTime.UtcNow, constantValue), isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(constantValue, yzvama.Last.Value, Tolerance);
|
||||
}
|
||||
|
||||
@@ -89,17 +89,34 @@ public sealed class Yzvama : AbstractBase
|
||||
public Yzvama(int yzvShortPeriod = 3, int yzvLongPeriod = 50, int percentileLookback = 100, int minLength = 5, int maxLength = 100)
|
||||
{
|
||||
if (yzvShortPeriod <= 0)
|
||||
{
|
||||
throw new ArgumentException("Short YZV period must be greater than 0", nameof(yzvShortPeriod));
|
||||
}
|
||||
|
||||
if (yzvLongPeriod <= 0)
|
||||
{
|
||||
throw new ArgumentException("Long YZV period must be greater than 0", nameof(yzvLongPeriod));
|
||||
}
|
||||
|
||||
if (percentileLookback <= 0)
|
||||
{
|
||||
throw new ArgumentException("Percentile lookback must be greater than 0", nameof(percentileLookback));
|
||||
}
|
||||
|
||||
if (minLength <= 0)
|
||||
{
|
||||
throw new ArgumentException("Min length must be greater than 0", nameof(minLength));
|
||||
}
|
||||
|
||||
if (maxLength <= 0)
|
||||
{
|
||||
throw new ArgumentException("Max length must be greater than 0", nameof(maxLength));
|
||||
}
|
||||
|
||||
if (minLength > maxLength)
|
||||
{
|
||||
throw new ArgumentException("Min length must be less than or equal to max length", nameof(minLength));
|
||||
}
|
||||
|
||||
_percentileLookback = percentileLookback;
|
||||
_minLength = minLength;
|
||||
@@ -163,7 +180,9 @@ public sealed class Yzvama : AbstractBase
|
||||
private static double ComputeYangZhangK(int period)
|
||||
{
|
||||
if (period <= 1)
|
||||
{
|
||||
return 0.34 / (1.34 + 1.0);
|
||||
}
|
||||
|
||||
double ratioN = (period + 1.0) / (period - 1.0);
|
||||
return 0.34 / (1.34 + ratioN);
|
||||
@@ -178,9 +197,13 @@ public sealed class Yzvama : AbstractBase
|
||||
{
|
||||
int mid = lo + ((hi - lo) >> 1);
|
||||
if (sorted[mid] < value)
|
||||
{
|
||||
lo = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
hi = mid;
|
||||
}
|
||||
}
|
||||
return lo;
|
||||
}
|
||||
@@ -254,9 +277,13 @@ public sealed class Yzvama : AbstractBase
|
||||
|
||||
// Sanitize source
|
||||
if (!double.IsFinite(sourceValue))
|
||||
{
|
||||
sourceValue = double.IsFinite(_lastValidSource) ? _lastValidSource : 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidSource = sourceValue;
|
||||
}
|
||||
|
||||
// Compute Yang-Zhang variance components (log returns)
|
||||
double yzvShort = double.NaN;
|
||||
@@ -290,7 +317,10 @@ public sealed class Yzvama : AbstractBase
|
||||
// Update short RMA variance
|
||||
shortVar.Ema = Math.FusedMultiplyAdd(shortVar.Ema, _shortDecay, _shortAlpha * sSqDailyShort);
|
||||
shortVar.E *= _shortDecay;
|
||||
if (shortVar.E <= EPSILON) shortVar.IsCompensated = true;
|
||||
if (shortVar.E <= EPSILON)
|
||||
{
|
||||
shortVar.IsCompensated = true;
|
||||
}
|
||||
|
||||
double shortVarValue = shortVar.IsCompensated ? shortVar.Ema : shortVar.Ema / (1.0 - shortVar.E);
|
||||
yzvShort = shortVarValue >= 0 ? Math.Sqrt(shortVarValue) : double.NaN;
|
||||
@@ -298,7 +328,10 @@ public sealed class Yzvama : AbstractBase
|
||||
// Update long RMA variance (kept for parity with Pine implementation)
|
||||
longVar.Ema = Math.FusedMultiplyAdd(longVar.Ema, _longDecay, _longAlpha * sSqDailyLong);
|
||||
longVar.E *= _longDecay;
|
||||
if (longVar.E <= EPSILON) longVar.IsCompensated = true;
|
||||
if (longVar.E <= EPSILON)
|
||||
{
|
||||
longVar.IsCompensated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,7 +447,10 @@ public sealed class Yzvama : AbstractBase
|
||||
/// </summary>
|
||||
public TSeries Update(TBarSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -441,7 +477,10 @@ public sealed class Yzvama : AbstractBase
|
||||
/// </summary>
|
||||
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);
|
||||
@@ -473,7 +512,9 @@ public sealed class Yzvama : AbstractBase
|
||||
{
|
||||
Reset();
|
||||
foreach (double val in source)
|
||||
{
|
||||
Update(new TValue(DateTime.MinValue, val), isNew: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -484,7 +525,9 @@ public sealed class Yzvama : AbstractBase
|
||||
{
|
||||
Reset();
|
||||
foreach (TValue tv in source)
|
||||
{
|
||||
Update(tv, isNew: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -74,9 +74,13 @@ public class ZlemaValidationTests
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -87,7 +91,9 @@ public class ZlemaValidationTests
|
||||
buffer[head] = val;
|
||||
head++;
|
||||
if (head == bufferSize)
|
||||
{
|
||||
head = 0;
|
||||
}
|
||||
|
||||
double lagged = buffer[head];
|
||||
double signal = Math.FusedMultiplyAdd(2.0, val, -lagged);
|
||||
|
||||
@@ -58,7 +58,9 @@ public sealed class Zlema : AbstractBase
|
||||
public Zlema(double alpha)
|
||||
{
|
||||
if (alpha <= 0.0 || alpha > 1.0 || !double.IsFinite(alpha))
|
||||
{
|
||||
throw new ArgumentException("Alpha must be finite and in (0, 1].", nameof(alpha));
|
||||
}
|
||||
|
||||
_alpha = alpha;
|
||||
_beta = 1.0 - _alpha;
|
||||
@@ -97,9 +99,13 @@ public sealed class Zlema : AbstractBase
|
||||
|
||||
double val = input.Value;
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
_lastValidValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = _lastValidValue;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -123,7 +129,10 @@ public sealed class Zlema : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
List<long> t = new(len);
|
||||
@@ -146,9 +155,13 @@ public sealed class Zlema : AbstractBase
|
||||
{
|
||||
double val = source.Values[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -190,10 +203,16 @@ public sealed class Zlema : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length.", nameof(output));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(period);
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
Calculate(source, output, alpha, period);
|
||||
@@ -202,11 +221,19 @@ public sealed class Zlema : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, double alpha)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length.", nameof(output));
|
||||
if (alpha <= 0.0 || alpha > 1.0 || !double.IsFinite(alpha))
|
||||
throw new ArgumentException("Alpha must be finite and in (0, 1].", nameof(alpha));
|
||||
}
|
||||
|
||||
if (source.Length == 0) return;
|
||||
if (alpha <= 0.0 || alpha > 1.0 || !double.IsFinite(alpha))
|
||||
{
|
||||
throw new ArgumentException("Alpha must be finite and in (0, 1].", nameof(alpha));
|
||||
}
|
||||
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double period = (2.0 / alpha) - 1.0;
|
||||
Calculate(source, output, alpha, period);
|
||||
@@ -231,7 +258,9 @@ public sealed class Zlema : AbstractBase
|
||||
state.E *= _beta;
|
||||
|
||||
if (!state.IsHot && state.Bars >= _lag + 1 && state.E <= CoverageThreshold)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
if (state.E <= CompensatorThreshold)
|
||||
{
|
||||
@@ -246,7 +275,10 @@ public sealed class Zlema : AbstractBase
|
||||
else
|
||||
{
|
||||
if (!state.IsHot && state.Bars >= _lag + 1)
|
||||
{
|
||||
state.IsHot = true;
|
||||
}
|
||||
|
||||
result = state.ZlemaRaw;
|
||||
}
|
||||
|
||||
@@ -257,11 +289,15 @@ public sealed class Zlema : AbstractBase
|
||||
private static int EstimateWarmupPeriod(double beta)
|
||||
{
|
||||
if (beta <= 0.0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
double steps = Math.Log(CoverageThreshold) / Math.Log(beta);
|
||||
if (double.IsNaN(steps) || double.IsInfinity(steps) || steps <= 0.0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return (int)Math.Ceiling(steps);
|
||||
}
|
||||
@@ -317,9 +353,13 @@ public sealed class Zlema : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -330,7 +370,9 @@ public sealed class Zlema : AbstractBase
|
||||
buffer[head] = val;
|
||||
head++;
|
||||
if (head == bufferSize)
|
||||
{
|
||||
head = 0;
|
||||
}
|
||||
|
||||
double lagged = buffer[head];
|
||||
double signal = Math.FusedMultiplyAdd(2.0, val, -lagged);
|
||||
|
||||
Reference in New Issue
Block a user