mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-26 06:18:05 +00:00
feat: Introduce ITValuePublisher interface and refactor indicators for event-driven value updates.
This commit is contained in:
@@ -13,13 +13,13 @@ public class DemaTests
|
||||
var dema = new Dema(period);
|
||||
var ema1 = new Ema(period);
|
||||
var ema2 = new Ema(period);
|
||||
var r = new Random(123); // nosemgrep
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
|
||||
|
||||
// Act & Assert
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
double val = r.NextDouble() * 100;
|
||||
var tVal = new TValue(DateTime.Now.AddMinutes(i), val);
|
||||
var bar = gbm.Next(isNew: true);
|
||||
var tVal = new TValue(bar.Time, bar.Close);
|
||||
|
||||
var dVal = dema.Update(tVal);
|
||||
|
||||
@@ -37,10 +37,12 @@ public class DemaTests
|
||||
// Arrange
|
||||
int period = 10;
|
||||
var source = new TSeries();
|
||||
var r = new Random(123); // nosemgrep
|
||||
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
source.Add(new TValue(DateTime.Now.AddMinutes(i), r.NextDouble() * 100));
|
||||
var bar = gbm.Next(isNew: true);
|
||||
source.Add(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
// Act
|
||||
@@ -63,10 +65,11 @@ public class DemaTests
|
||||
int count = 100;
|
||||
var source = new double[count];
|
||||
var output = new double[count];
|
||||
var r = new Random(123); // nosemgrep
|
||||
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
source[i] = r.NextDouble() * 100;
|
||||
source[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
// Act
|
||||
@@ -89,13 +92,14 @@ public class DemaTests
|
||||
double alpha = 2.0 / (period + 1);
|
||||
var demaPeriod = new Dema(period);
|
||||
var demaAlpha = new Dema(alpha);
|
||||
var r = new Random(123); // nosemgrep
|
||||
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
|
||||
|
||||
// Act & Assert
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
double val = r.NextDouble() * 100;
|
||||
var tVal = new TValue(DateTime.Now.AddMinutes(i), val);
|
||||
var bar = gbm.Next(isNew: true);
|
||||
var tVal = new TValue(bar.Time, bar.Close);
|
||||
|
||||
var pVal = demaPeriod.Update(tVal);
|
||||
var aVal = demaAlpha.Update(tVal);
|
||||
@@ -110,10 +114,12 @@ public class DemaTests
|
||||
// Arrange
|
||||
double alpha = 0.15;
|
||||
var source = new TSeries();
|
||||
var r = new Random(123); // nosemgrep
|
||||
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
source.Add(new TValue(DateTime.Now.AddMinutes(i), r.NextDouble() * 100));
|
||||
var bar = gbm.Next(isNew: true);
|
||||
source.Add(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
// Act
|
||||
@@ -136,10 +142,11 @@ public class DemaTests
|
||||
int count = 100;
|
||||
var source = new double[count];
|
||||
var output = new double[count];
|
||||
var r = new Random(123); // nosemgrep
|
||||
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
source[i] = r.NextDouble() * 100;
|
||||
source[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
// Act
|
||||
@@ -153,4 +160,46 @@ public class DemaTests
|
||||
Assert.Equal(val.Value, output[i], 1e-9);
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void Dema_AllModes_ProduceSameResult()
|
||||
{
|
||||
// Arrange
|
||||
int period = 10;
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
|
||||
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var series = bars.Close;
|
||||
|
||||
// 1. Batch Mode
|
||||
var batchSeries = Dema.Calculate(series, period);
|
||||
double expected = batchSeries.Last.Value;
|
||||
|
||||
// 2. Span Mode
|
||||
var tValues = series.Values.ToArray();
|
||||
var spanInput = new ReadOnlySpan<double>(tValues);
|
||||
var spanOutput = new double[tValues.Length];
|
||||
Dema.Calculate(spanInput, spanOutput, period);
|
||||
double spanResult = spanOutput[^1];
|
||||
|
||||
// 3. Streaming Mode
|
||||
var streamingInd = new Dema(period);
|
||||
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 Dema(pubSource, period);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
pubSource.Add(series[i]);
|
||||
}
|
||||
double eventingResult = eventingInd.Last.Value;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, spanResult, precision: 9);
|
||||
Assert.Equal(expected, streamingResult, precision: 9);
|
||||
Assert.Equal(expected, eventingResult, precision: 9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace QuanTAlib;
|
||||
/// Becomes true when the second EMA converges (approx. 2x EMA convergence time).
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Dema
|
||||
public sealed class Dema : ITValuePublisher
|
||||
{
|
||||
private struct EmaState
|
||||
{
|
||||
@@ -45,8 +45,9 @@ public sealed class Dema
|
||||
private double _lastValidValue;
|
||||
|
||||
public string Name { get; }
|
||||
public TValue Value { get; private set; }
|
||||
public TValue Last { get; private set; }
|
||||
public bool IsHot => _state2.IsHot;
|
||||
public event Action<TValue>? Pub;
|
||||
|
||||
public Dema(int period)
|
||||
{
|
||||
@@ -57,6 +58,11 @@ public sealed class Dema
|
||||
Name = $"Dema({period})";
|
||||
}
|
||||
|
||||
public Dema(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
}
|
||||
|
||||
public Dema(double alpha)
|
||||
{
|
||||
if (alpha <= 0 || alpha > 1) throw new ArgumentException("Alpha must be between 0 and 1", nameof(alpha));
|
||||
@@ -93,8 +99,9 @@ public sealed class Dema
|
||||
double e2 = Compute(e1, _alpha, _decay, ref _state2);
|
||||
|
||||
double result = 2 * e1 - e2;
|
||||
Value = new TValue(input.Time, result);
|
||||
return Value;
|
||||
Last = new TValue(input.Time, result);
|
||||
Pub?.Invoke(Last);
|
||||
return Last;
|
||||
}
|
||||
|
||||
public TSeries Update(TSeries source)
|
||||
@@ -141,7 +148,7 @@ public sealed class Dema
|
||||
_p_state2 = s2;
|
||||
_lastValidValue = lastValid;
|
||||
|
||||
Value = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
@@ -281,6 +288,6 @@ public sealed class Dema
|
||||
_p_state1 = EmaState.New();
|
||||
_p_state2 = EmaState.New();
|
||||
_lastValidValue = 0;
|
||||
Value = default;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user