I created a class to calculate a Exponential Moving Average:
public class ExponentialMovingAverage {
public Int32 Period { get; set; }
public ExponentialMovingAverage(Int32 period = 20) {
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(period);
Period = period;
}
public override IEnumerable<(DateTimeOffset Stamp, Decimal? ExponentialMovingAverage)> Compute(IEnumerable<(DateTimeOffset Stamp, Decimal? Value)> inputs) {
ArgumentNullException.ThrowIfNull(inputs);
inputs = inputs.OrderBy(x => x.Stamp);
Decimal? previous = null;
Decimal factor = (Decimal)(2d / (Period + 1));
Decimal? sum = 0;
Int32 notNulls = 0;
for (Int32 index = 0; index < inputs.Count(); index++) {
(DateTimeOffset stamp, Decimal? value) = inputs.ElementAt(index);
if (value == null) {
notNulls++;
yield return (stamp, null);
continue;
}
if (index < notNulls + Period - 1) {
sum += value;
yield return (stamp, null);
continue;
}
if (index == notNulls + Period - 1) {
sum += value;
Decimal? sma = sum / Period;
previous = sma;
yield return (stamp, sma);
continue;
}
Decimal? ema = previous + (factor * (value - previous));
previous = ema;
yield return (stamp, ema);
}
}
}
And I have the following tests which are passing:
[Fact]
public void Test_AllNonNullInputs() {
var ema = new ExponentialMovingAverage(3);
var inputs = new List<(DateTimeOffset Stamp, decimal? Value)> {
(new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero), 10),
(new DateTimeOffset(2024, 1, 2, 0, 0, 0, TimeSpan.Zero), 15),
(new DateTimeOffset(2024, 1, 3, 0, 0, 0, TimeSpan.Zero), 20),
(new DateTimeOffset(2024, 1, 4, 0, 0, 0, TimeSpan.Zero), 25),
(new DateTimeOffset(2024, 1, 5, 0, 0, 0, TimeSpan.Zero), 30),
(new DateTimeOffset(2024, 1, 6, 0, 0, 0, TimeSpan.Zero), 35)
};
var output = ema.Compute(inputs).ToList();
Assert.Equal(6, output.Count);
Assert.Null(output[0].ExponentialMovingAverage);
Assert.Null(output[1].ExponentialMovingAverage);
Assert.Equal(15m, output[2].ExponentialMovingAverage);
Assert.Equal(20m, output[3].ExponentialMovingAverage);
Assert.Equal(25m, output[4].ExponentialMovingAverage);
Assert.Equal(30m, output[5].ExponentialMovingAverage);
}
[Fact]
public void Test_FirstTwoInputsAreNull() {
var ema = new ExponentialMovingAverage(3);
var inputs = new List<(DateTimeOffset Stamp, decimal? Value)> {
(new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero), null),
(new DateTimeOffset(2024, 1, 2, 0, 0, 0, TimeSpan.Zero), null),
(new DateTimeOffset(2024, 1, 3, 0, 0, 0, TimeSpan.Zero), 20),
(new DateTimeOffset(2024, 1, 4, 0, 0, 0, TimeSpan.Zero), 25),
(new DateTimeOffset(2024, 1, 5, 0, 0, 0, TimeSpan.Zero), 30),
(new DateTimeOffset(2024, 1, 6, 0, 0, 0, TimeSpan.Zero), 35)
};
var output = ema.Compute(inputs).ToList();
Assert.Equal(6, output.Count);
Assert.Null(output[0].ExponentialMovingAverage);
Assert.Null(output[1].ExponentialMovingAverage);
Assert.Null(output[2].ExponentialMovingAverage);
Assert.Null(output[3].ExponentialMovingAverage);
Assert.Equal(25m, output[4].ExponentialMovingAverage);
Assert.Equal(30m, output[5].ExponentialMovingAverage);
}
The calculation seems slow, for example, when calculating an EMA of an EMA with many inputs:
var ema1 = new ExponentialMovingAverage(3);
var outputs1 = ema1.Compute(inputs);
var ema2 = new ExponentialMovingAverage(3);
var outputs2 = ema2.Compute(ema1);
I have been looking at the use of inputs.ElementAt(index)
and also the use of foreach vs for and List vs Enumerable.
How can I improve the code including its performance?