mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-05 12:37:43 +00:00
refactor: clean up Ema and EmaVector tests for consistency, update TValue equality check
This commit is contained in:
@@ -11,7 +11,7 @@ public class EmaTests
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new Ema(0));
|
||||
Assert.Throws<ArgumentException>(() => new Ema(-1));
|
||||
|
||||
|
||||
var ema = new Ema(10);
|
||||
Assert.NotNull(ema);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class EmaTests
|
||||
Assert.Throws<ArgumentException>(() => new Ema(0.0));
|
||||
Assert.Throws<ArgumentException>(() => new Ema(-0.1));
|
||||
Assert.Throws<ArgumentException>(() => new Ema(1.1));
|
||||
|
||||
|
||||
var ema = new Ema(0.5);
|
||||
Assert.NotNull(ema);
|
||||
}
|
||||
@@ -31,11 +31,11 @@ public class EmaTests
|
||||
public void Ema_Calc_ReturnsValue()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
Assert.Equal(0, ema.Value.Value);
|
||||
|
||||
|
||||
TValue result = ema.Update(new TValue(DateTime.UtcNow, 100));
|
||||
|
||||
|
||||
Assert.True(result.Value > 0);
|
||||
Assert.Equal(result.Value, ema.Value.Value);
|
||||
}
|
||||
@@ -44,13 +44,13 @@ public class EmaTests
|
||||
public void Ema_Calc_IsNew_AcceptsParameter()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
ema.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
|
||||
double value1 = ema.Value;
|
||||
|
||||
|
||||
ema.Update(new TValue(DateTime.UtcNow, 105), isNew: true);
|
||||
double value2 = ema.Value;
|
||||
|
||||
|
||||
// Values should change with new bars
|
||||
Assert.NotEqual(value1, value2);
|
||||
}
|
||||
@@ -59,14 +59,14 @@ public class EmaTests
|
||||
public void Ema_Calc_IsNew_False_UpdatesValue()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
ema.Update(new TValue(DateTime.UtcNow, 100));
|
||||
ema.Update(new TValue(DateTime.UtcNow, 110), isNew: true);
|
||||
double beforeUpdate = ema.Value;
|
||||
|
||||
|
||||
ema.Update(new TValue(DateTime.UtcNow, 120), isNew: false);
|
||||
double afterUpdate = ema.Value;
|
||||
|
||||
|
||||
// Update should change the value
|
||||
Assert.NotEqual(beforeUpdate, afterUpdate);
|
||||
}
|
||||
@@ -75,15 +75,15 @@ public class EmaTests
|
||||
public void Ema_Reset_ClearsState()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
ema.Update(new TValue(DateTime.UtcNow, 100));
|
||||
ema.Update(new TValue(DateTime.UtcNow, 105));
|
||||
double valueBefore = ema.Value;
|
||||
|
||||
|
||||
ema.Reset();
|
||||
|
||||
|
||||
Assert.Equal(0, ema.Value.Value);
|
||||
|
||||
|
||||
// After reset, should accept new values
|
||||
ema.Update(new TValue(DateTime.UtcNow, 50));
|
||||
Assert.NotEqual(0, ema.Value.Value);
|
||||
@@ -94,12 +94,12 @@ public class EmaTests
|
||||
public void Ema_Properties_Accessible()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
Assert.Equal(0, ema.Value.Value);
|
||||
Assert.False(ema.IsHot);
|
||||
|
||||
|
||||
ema.Update(new TValue(DateTime.UtcNow, 100));
|
||||
|
||||
|
||||
Assert.NotEqual(0, ema.Value.Value);
|
||||
}
|
||||
|
||||
@@ -107,10 +107,10 @@ public class EmaTests
|
||||
public void Ema_IsHot_BecomesTrueAfterWarmup()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
// Initially IsHot should be false
|
||||
Assert.False(ema.IsHot);
|
||||
|
||||
|
||||
// Feed values until it warms up
|
||||
// Warmup condition is state.E <= 1e-10
|
||||
// state.E starts at 1.0 and decays by (1 - alpha) each step
|
||||
@@ -120,14 +120,14 @@ public class EmaTests
|
||||
// n * log(0.8181) <= log(1e-10)
|
||||
// n * -0.200 <= -23.02
|
||||
// n >= 115 steps roughly
|
||||
|
||||
|
||||
int steps = 0;
|
||||
while (!ema.IsHot && steps < 1000)
|
||||
{
|
||||
ema.Update(new TValue(DateTime.UtcNow, 100));
|
||||
steps++;
|
||||
}
|
||||
|
||||
|
||||
Assert.True(ema.IsHot);
|
||||
Assert.True(steps > 0); // Should take some steps
|
||||
}
|
||||
@@ -137,14 +137,14 @@ public class EmaTests
|
||||
{
|
||||
int period = 20;
|
||||
double alpha = 2.0 / (period + 1);
|
||||
|
||||
|
||||
var emaPeriod = new Ema(period);
|
||||
var emaAlpha = new Ema(alpha);
|
||||
|
||||
|
||||
// Both should accept Calc calls and produce same result
|
||||
TValue result1 = emaPeriod.Update(new TValue(DateTime.UtcNow, 100));
|
||||
TValue result2 = emaAlpha.Update(new TValue(DateTime.UtcNow, 100));
|
||||
|
||||
|
||||
Assert.Equal(result1.Value, result2.Value, 1e-10);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ public class EmaTests
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
|
||||
|
||||
|
||||
// Feed 10 new values
|
||||
TValue tenthInput = default;
|
||||
for (int i = 0; i < 10; i++)
|
||||
@@ -162,20 +162,20 @@ public class EmaTests
|
||||
tenthInput = new TValue(bar.Time, bar.Close);
|
||||
ema.Update(tenthInput, isNew: true);
|
||||
}
|
||||
|
||||
|
||||
// Remember EMA state after 10 values
|
||||
double emaAfterTen = ema.Value;
|
||||
|
||||
|
||||
// Generate 9 corrections with isNew=false (different values)
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
var bar = gbm.Next(isNew: false);
|
||||
ema.Update(new TValue(bar.Time, bar.Close), isNew: false);
|
||||
}
|
||||
|
||||
|
||||
// Feed the remembered 10th input again with isNew=false
|
||||
TValue finalEma = ema.Update(tenthInput, isNew: false);
|
||||
|
||||
|
||||
// EMA should match the original state after 10 values
|
||||
Assert.Equal(emaAfterTen, finalEma.Value, 1e-10);
|
||||
}
|
||||
@@ -186,7 +186,7 @@ public class EmaTests
|
||||
var emaIterative = new Ema(10);
|
||||
var emaBatch = new Ema(10);
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
|
||||
|
||||
|
||||
// Generate data
|
||||
var series = new TSeries();
|
||||
for (int i = 0; i < 100; i++)
|
||||
@@ -194,17 +194,19 @@ public class EmaTests
|
||||
var bar = gbm.Next(isNew: true);
|
||||
series.Add(bar.Time, bar.Close);
|
||||
}
|
||||
|
||||
|
||||
Assert.True(series.Count > 0);
|
||||
|
||||
// Calculate iteratively
|
||||
var iterativeResults = new TSeries();
|
||||
foreach (var item in series)
|
||||
{
|
||||
iterativeResults.Add(emaIterative.Update(item));
|
||||
}
|
||||
|
||||
|
||||
// Calculate batch
|
||||
var batchResults = emaBatch.Update(series);
|
||||
|
||||
|
||||
// Compare
|
||||
Assert.Equal(iterativeResults.Count, batchResults.Count);
|
||||
for (int i = 0; i < iterativeResults.Count; i++)
|
||||
@@ -219,10 +221,10 @@ public class EmaTests
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
ema.Update(new TValue(DateTime.UtcNow, 100));
|
||||
|
||||
|
||||
// This should compile and work because TValue has implicit conversion to double
|
||||
double result = ema.Value;
|
||||
|
||||
|
||||
Assert.Equal(100.0, result, 1e-10);
|
||||
}
|
||||
|
||||
@@ -230,15 +232,14 @@ public class EmaTests
|
||||
public void Ema_NaN_Input_UsesLastValidValue()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
// Feed some valid values
|
||||
ema.Update(new TValue(DateTime.UtcNow, 100));
|
||||
ema.Update(new TValue(DateTime.UtcNow, 110));
|
||||
double valueBeforeNaN = ema.Value;
|
||||
|
||||
|
||||
// Feed NaN - should use last valid value (110)
|
||||
var resultAfterNaN = ema.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
|
||||
|
||||
// Result should be finite (not NaN)
|
||||
Assert.True(double.IsFinite(resultAfterNaN.Value));
|
||||
// EMA should continue to evolve (may differ slightly due to substitution)
|
||||
@@ -249,15 +250,15 @@ public class EmaTests
|
||||
public void Ema_Infinity_Input_UsesLastValidValue()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
// Feed some valid values
|
||||
ema.Update(new TValue(DateTime.UtcNow, 100));
|
||||
ema.Update(new TValue(DateTime.UtcNow, 110));
|
||||
|
||||
|
||||
// Feed positive infinity - should use last valid value
|
||||
var resultAfterPosInf = ema.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity));
|
||||
Assert.True(double.IsFinite(resultAfterPosInf.Value));
|
||||
|
||||
|
||||
// Feed negative infinity - should use last valid value
|
||||
var resultAfterNegInf = ema.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity));
|
||||
Assert.True(double.IsFinite(resultAfterNegInf.Value));
|
||||
@@ -267,22 +268,22 @@ public class EmaTests
|
||||
public void Ema_MultipleNaN_ContinuesWithLastValid()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
// Feed valid values
|
||||
ema.Update(new TValue(DateTime.UtcNow, 100));
|
||||
ema.Update(new TValue(DateTime.UtcNow, 110));
|
||||
ema.Update(new TValue(DateTime.UtcNow, 120));
|
||||
|
||||
|
||||
// Feed multiple NaN values
|
||||
var r1 = ema.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
var r2 = ema.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
var r3 = ema.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
|
||||
|
||||
// All results should be finite
|
||||
Assert.True(double.IsFinite(r1.Value));
|
||||
Assert.True(double.IsFinite(r2.Value));
|
||||
Assert.True(double.IsFinite(r3.Value));
|
||||
|
||||
|
||||
// EMA should converge toward last valid value (120) with repeated substitution
|
||||
// Values should be getting closer to 120
|
||||
Assert.True(r3.Value > r1.Value || Math.Abs(r3.Value - 120) < Math.Abs(r1.Value - 120));
|
||||
@@ -292,7 +293,7 @@ public class EmaTests
|
||||
public void Ema_BatchCalc_HandlesNaN()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
// Create series with NaN values interspersed
|
||||
var series = new TSeries();
|
||||
series.Add(DateTime.UtcNow.Ticks, 100);
|
||||
@@ -301,9 +302,9 @@ public class EmaTests
|
||||
series.Add(DateTime.UtcNow.Ticks + 3, 120);
|
||||
series.Add(DateTime.UtcNow.Ticks + 4, double.PositiveInfinity);
|
||||
series.Add(DateTime.UtcNow.Ticks + 5, 130);
|
||||
|
||||
|
||||
var results = ema.Update(series);
|
||||
|
||||
|
||||
// All results should be finite
|
||||
foreach (var result in results)
|
||||
{
|
||||
@@ -315,14 +316,14 @@ public class EmaTests
|
||||
public void Ema_Reset_ClearsLastValidValue()
|
||||
{
|
||||
var ema = new Ema(10);
|
||||
|
||||
|
||||
// Feed values including NaN
|
||||
ema.Update(new TValue(DateTime.UtcNow, 100));
|
||||
ema.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
|
||||
|
||||
// Reset
|
||||
ema.Reset();
|
||||
|
||||
|
||||
// After reset, first valid value should establish new baseline
|
||||
var result = ema.Update(new TValue(DateTime.UtcNow, 50));
|
||||
Assert.Equal(50.0, result.Value, 1e-10);
|
||||
|
||||
@@ -15,7 +15,6 @@ public class EmaValidationTests
|
||||
private readonly TBarSeries _bars;
|
||||
private readonly TSeries _data;
|
||||
private readonly List<Quote> _skenderQuotes;
|
||||
private readonly Random _rnd = new(42);
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public EmaValidationTests(ITestOutputHelper output)
|
||||
@@ -25,7 +24,7 @@ public class EmaValidationTests
|
||||
// 1. Generate 1000 records using GBM feed
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.2);
|
||||
_bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
|
||||
// 2. Extract Close TSeries
|
||||
_data = _bars.Close;
|
||||
|
||||
@@ -35,7 +34,7 @@ public class EmaValidationTests
|
||||
{
|
||||
_skenderQuotes.Add(new Quote
|
||||
{
|
||||
Date = new DateTime(_bars.Open.Times[i]),
|
||||
Date = new DateTime(_bars.Open.Times[i], DateTimeKind.Utc),
|
||||
Open = (decimal)_bars.Open[i].Value,
|
||||
High = (decimal)_bars.High[i].Value,
|
||||
Low = (decimal)_bars.Low[i].Value,
|
||||
@@ -60,7 +59,7 @@ public class EmaValidationTests
|
||||
var sResult = _skenderQuotes.GetEma(period).ToList();
|
||||
|
||||
// Compare last 100 records
|
||||
VerifyData(qResult, sResult, period);
|
||||
VerifyData(qResult, sResult);
|
||||
}
|
||||
_output.WriteLine("EMA validated successfully against Skender");
|
||||
}
|
||||
@@ -82,15 +81,15 @@ public class EmaValidationTests
|
||||
|
||||
// Calculate TA-Lib EMA
|
||||
var retCode = TALib.Functions.Ema<double>(tData, 0..^0, output, out var outRange, period);
|
||||
|
||||
|
||||
// Check success
|
||||
Assert.Equal(Core.RetCode.Success, retCode);
|
||||
|
||||
// TA-Lib skips the lookback period, so output[0] corresponds to input[lookback]
|
||||
int lookback = TALib.Functions.EmaLookback(period);
|
||||
|
||||
|
||||
// Compare last 100 records
|
||||
VerifyData_Talib(qResult, output, outRange, lookback, period);
|
||||
VerifyData_Talib(qResult, output, outRange, lookback);
|
||||
}
|
||||
_output.WriteLine("EMA validated successfully against TA-Lib");
|
||||
}
|
||||
@@ -114,21 +113,21 @@ public class EmaValidationTests
|
||||
double[][] inputs = { tData };
|
||||
double[] options = { (double)period };
|
||||
double[][] outputs = { new double[tData.Length] };
|
||||
|
||||
|
||||
emaIndicator.Run(inputs, options, outputs);
|
||||
var tResult = outputs[0];
|
||||
|
||||
// Compare last 100 records
|
||||
VerifyData(qResult, tResult.ToList(), period);
|
||||
VerifyData(qResult, tResult.ToList());
|
||||
}
|
||||
_output.WriteLine("EMA validated successfully against Tulip");
|
||||
}
|
||||
|
||||
private void VerifyData(TSeries qSeries, List<double> tSeries, int period)
|
||||
private static void VerifyData(TSeries qSeries, List<double> tSeries)
|
||||
{
|
||||
// Ensure we have enough data
|
||||
Assert.Equal(qSeries.Count, tSeries.Count);
|
||||
|
||||
|
||||
int count = qSeries.Count;
|
||||
int skip = count - 100; // Last 100 records
|
||||
|
||||
@@ -136,17 +135,17 @@ public class EmaValidationTests
|
||||
{
|
||||
double qValue = qSeries[i].Value;
|
||||
double tValue = tSeries[i];
|
||||
if (tValue == 0) continue;
|
||||
if (Math.Abs(tValue) < 1e-10) continue;
|
||||
|
||||
Assert.Equal(tValue, qValue, 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyData(TSeries qSeries, List<EmaResult> sSeries, int period)
|
||||
private static void VerifyData(TSeries qSeries, List<EmaResult> sSeries)
|
||||
{
|
||||
// Ensure we have enough data
|
||||
Assert.Equal(qSeries.Count, sSeries.Count);
|
||||
|
||||
|
||||
int count = qSeries.Count;
|
||||
int skip = count - 100; // Last 100 records
|
||||
|
||||
@@ -163,24 +162,24 @@ public class EmaValidationTests
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyData_Talib(TSeries qSeries, double[] tOutput, Range outRange, int lookback, int period)
|
||||
private static void VerifyData_Talib(TSeries qSeries, double[] tOutput, Range outRange, int lookback)
|
||||
{
|
||||
int count = qSeries.Count;
|
||||
int skip = count - 100; // Last 100 records
|
||||
|
||||
// outRange.End.Value is the number of elements written to tOutput
|
||||
int validCount = outRange.End.Value - outRange.Start.Value;
|
||||
|
||||
|
||||
for (int i = skip; i < count; i++)
|
||||
{
|
||||
double qValue = qSeries[i].Value;
|
||||
|
||||
|
||||
// Calculate index in tOutput
|
||||
// If i < lookback, we don't have a value from TA-Lib
|
||||
if (i < lookback) continue;
|
||||
|
||||
|
||||
int tIndex = i - lookback;
|
||||
|
||||
|
||||
// Check if tIndex is within valid range
|
||||
if (tIndex >= validCount) continue;
|
||||
|
||||
|
||||
+29
-18
@@ -3,28 +3,39 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
internal struct EmaState
|
||||
{
|
||||
public double Ema { get; set; }
|
||||
public double E { get; set; }
|
||||
public bool IsHot { get; set; }
|
||||
|
||||
public static EmaState New() => new() { Ema = 0, E = 1.0, IsHot = false };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exponential Moving Average (EMA) - IIR filter with exponential warmup compensator.
|
||||
/// Provides valid output from first bar with O(1) complexity.
|
||||
/// EMA: Exponential Moving Average
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Algorithm uses exponential smoothing with compensator for immediate valid results.
|
||||
/// Reference: https://github.com/mihakralj/pinescript/blob/main/indicators/trends_IIR/ema.md
|
||||
/// EMA needs very short history buffer and calculates the EMA value using just the
|
||||
/// previous EMA value. The weight of the new datapoint (alpha) is alpha = 2 / (period + 1)
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Uses no buffer, relying only on the previous EMA value.
|
||||
/// - The weight of new data points is calculated as alpha = 2 / (period + 1).
|
||||
/// - Provides a balance between responsiveness and smoothing. No overshooting. Significant lag
|
||||
///
|
||||
/// Calculation method:
|
||||
/// This implementation can use SMA for the first Period bars as a seeding value for EMA when useSma is true.
|
||||
///
|
||||
/// Sources:
|
||||
/// - https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages
|
||||
/// - https://www.investopedia.com/ask/answers/122314/what-exponential-moving-average-ema-formula-and-how-ema-calculated.asp
|
||||
/// - https://blog.fugue88.ws/archives/2017-01/The-correct-way-to-start-an-Exponential-Moving-Average-EMA
|
||||
/// </remarks>
|
||||
public class Ema
|
||||
{
|
||||
private struct State
|
||||
{
|
||||
public double Ema;
|
||||
public double E;
|
||||
public bool IsHot;
|
||||
public static State New() => new() { Ema = 0, E = 1.0, IsHot = false };
|
||||
}
|
||||
|
||||
private readonly double _alpha;
|
||||
private EmaState _state = EmaState.New();
|
||||
private EmaState _p_state = EmaState.New();
|
||||
private State _state = State.New();
|
||||
private State _p_state = State.New();
|
||||
private double _lastValidValue;
|
||||
|
||||
/// <summary>
|
||||
@@ -81,7 +92,7 @@ public class Ema
|
||||
/// Assumes input has already been validated via GetValidValue().
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static double Compute(double input, double alpha, ref EmaState state)
|
||||
private static double Compute(double input, double alpha, ref State state)
|
||||
{
|
||||
state.Ema += alpha * (input - state.Ema);
|
||||
|
||||
@@ -144,7 +155,7 @@ public class Ema
|
||||
var sourceTimes = source.Times;
|
||||
|
||||
// Local state for batch processing
|
||||
EmaState state = _state;
|
||||
State state = _state;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
@@ -180,7 +191,7 @@ public class Ema
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_state = EmaState.New();
|
||||
_state = State.New();
|
||||
_p_state = _state;
|
||||
_lastValidValue = 0;
|
||||
Value = default;
|
||||
|
||||
@@ -168,7 +168,7 @@ public class EmaVectorTests
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var tVal = new TValue(new DateTime(t[i]), v[i]);
|
||||
var tVal = new TValue(new DateTime(t[i], DateTimeKind.Utc), v[i]);
|
||||
var streamRes = emaVectorStream.Update(tVal);
|
||||
|
||||
for (int j = 0; j < periods.Length; j++)
|
||||
|
||||
@@ -23,7 +23,9 @@ public class EmaVector
|
||||
/// <summary>
|
||||
/// Current EMA values for all periods.
|
||||
/// </summary>
|
||||
public TValue[] Values { get; private set; }
|
||||
public ReadOnlySpan<TValue> Values => _values;
|
||||
|
||||
private readonly TValue[] _values;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes EmaVector with specified periods.
|
||||
@@ -37,7 +39,7 @@ public class EmaVector
|
||||
_Es = new double[_count];
|
||||
_p_emas = new double[_count];
|
||||
_p_Es = new double[_count];
|
||||
Values = new TValue[_count];
|
||||
_values = new TValue[_count];
|
||||
|
||||
for (int i = 0; i < _count; i++)
|
||||
{
|
||||
@@ -59,7 +61,7 @@ public class EmaVector
|
||||
_Es = new double[_count];
|
||||
_p_emas = new double[_count];
|
||||
_p_Es = new double[_count];
|
||||
Values = new TValue[_count];
|
||||
_values = new TValue[_count];
|
||||
|
||||
for (int i = 0; i < _count; i++)
|
||||
{
|
||||
@@ -100,7 +102,7 @@ public class EmaVector
|
||||
ResetAt(i);
|
||||
}
|
||||
_lastValidValue = 0;
|
||||
Array.Clear(Values);
|
||||
Array.Clear(_values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -177,7 +179,7 @@ public class EmaVector
|
||||
// Store result
|
||||
for (int j = 0; j < vecCount; j++)
|
||||
{
|
||||
Values[i + j] = new TValue(input.Time, vecResult[j]);
|
||||
_values[i + j] = new TValue(input.Time, vecResult[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,10 +200,10 @@ public class EmaVector
|
||||
}
|
||||
}
|
||||
|
||||
Values[i] = new TValue(input.Time, result);
|
||||
_values[i] = new TValue(input.Time, result);
|
||||
}
|
||||
|
||||
return Values;
|
||||
return _values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -305,7 +307,7 @@ public class EmaVector
|
||||
resultSeries[i] = new TSeries(tLists[i], vLists[i]);
|
||||
var lastT = CollectionsMarshal.AsSpan(tLists[i])[len - 1];
|
||||
var lastV = CollectionsMarshal.AsSpan(vLists[i])[len - 1];
|
||||
Values[i] = new TValue(lastT, lastV);
|
||||
_values[i] = new TValue(lastT, lastV);
|
||||
}
|
||||
|
||||
return resultSeries;
|
||||
|
||||
@@ -48,7 +48,7 @@ public readonly struct TValue : IEquatable<TValue>
|
||||
public override string ToString() => $"[{AsDateTime:yyyy-MM-dd HH:mm:ss}, {Value:F2}]";
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(TValue other) => Time == other.Time && Value == other.Value;
|
||||
public bool Equals(TValue other) => Time == other.Time && Math.Abs(Value - other.Value) < 1e-9;
|
||||
|
||||
public override bool Equals(object? obj) => obj is TValue other && Equals(other);
|
||||
public override int GetHashCode() => HashCode.Combine(Time, Value);
|
||||
|
||||
Reference in New Issue
Block a user