diff --git a/core-java-modules/core-java-calculating-moving-averages/pom.xml b/core-java-modules/core-java-calculating-moving-averages/pom.xml new file mode 100644 index 0000000000..751e187ca5 --- /dev/null +++ b/core-java-modules/core-java-calculating-moving-averages/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + org.example + core-java-calculating-moving-averages + 1.0-SNAPSHOT + + + core-java-modules + com.baeldung.core-java-modules + 0.0.1-SNAPSHOT + + + + 17 + 17 + UTF-8 + + + + + org.apache.commons + commons-math3 + 3.6.1 + + + \ No newline at end of file diff --git a/core-java-modules/core-java-calculating-moving-averages/src/main/java/com/baeldung/ExponentialMovingAverage.java b/core-java-modules/core-java-calculating-moving-averages/src/main/java/com/baeldung/ExponentialMovingAverage.java new file mode 100644 index 0000000000..c2ec316ddb --- /dev/null +++ b/core-java-modules/core-java-calculating-moving-averages/src/main/java/com/baeldung/ExponentialMovingAverage.java @@ -0,0 +1,26 @@ +package com.baeldung; + +public class ExponentialMovingAverage { + + private final double alpha; + private Double previousEMA; + + public ExponentialMovingAverage(double alpha) { + if (alpha <= 0 || alpha > 1) { + throw new IllegalArgumentException("Alpha must be in the range (0, 1]"); + } + this.alpha = alpha; + this.previousEMA = null; + } + + public double calculateEMA(double newValue) { + if (previousEMA == null) { + // First data point, EMA is same as the value itself + previousEMA = newValue; + } else { + // Calculate EMA using the formula: EMA = alpha * newValue + (1 - alpha) * previousEMA + previousEMA = alpha * newValue + (1 - alpha) * previousEMA; + } + return previousEMA; + } +} diff --git a/core-java-modules/core-java-calculating-moving-averages/src/main/java/com/baeldung/MovingAverageByCircularBuffer.java b/core-java-modules/core-java-calculating-moving-averages/src/main/java/com/baeldung/MovingAverageByCircularBuffer.java new file mode 100644 index 0000000000..286d2cf62e --- /dev/null +++ b/core-java-modules/core-java-calculating-moving-averages/src/main/java/com/baeldung/MovingAverageByCircularBuffer.java @@ -0,0 +1,31 @@ +package com.baeldung; + +public class MovingAverageByCircularBuffer { + + private final double[] buffer; + private int head; + private int count; + + public MovingAverageByCircularBuffer(int windowSize) { + this.buffer = new double[windowSize]; + } + + public void add(double value) { + buffer[head] = value; + head = (head + 1) % buffer.length; + if (count < buffer.length) { + count++; + } + } + + public double getMovingAverage() { + if (count == 0) { + return Double.NaN; + } + double sum = 0; + for (int i = 0; i < count; i++) { + sum += buffer[i]; //buffer[(head + i) % buffer.length]; + } + return sum / count; + } +} diff --git a/core-java-modules/core-java-calculating-moving-averages/src/main/java/com/baeldung/MovingAverageWithApacheCommonsMath.java b/core-java-modules/core-java-calculating-moving-averages/src/main/java/com/baeldung/MovingAverageWithApacheCommonsMath.java new file mode 100644 index 0000000000..34e84e0a62 --- /dev/null +++ b/core-java-modules/core-java-calculating-moving-averages/src/main/java/com/baeldung/MovingAverageWithApacheCommonsMath.java @@ -0,0 +1,21 @@ +package com.baeldung; + +import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; + +public class MovingAverageWithApacheCommonsMath { + + private final DescriptiveStatistics stats; + + public MovingAverageWithApacheCommonsMath(int windowSize) { + this.stats = new DescriptiveStatistics(windowSize); + } + + public void add(double value) { + stats.addValue(value); + } + + public double getMovingAverage() { + return stats.getMean(); + } + +} diff --git a/core-java-modules/core-java-calculating-moving-averages/src/test/java/com/baeldung/ExponentialMovingAverageTest.java b/core-java-modules/core-java-calculating-moving-averages/src/test/java/com/baeldung/ExponentialMovingAverageTest.java new file mode 100644 index 0000000000..0ff3760796 --- /dev/null +++ b/core-java-modules/core-java-calculating-moving-averages/src/test/java/com/baeldung/ExponentialMovingAverageTest.java @@ -0,0 +1,38 @@ +package com.baeldung; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ExponentialMovingAverageTest { + @Test(expected = IllegalArgumentException.class) + public void when_alpha_is_invalid_should_throw_exception() { + new ExponentialMovingAverage(0); // Alpha outside valid range + } + + @Test + public void when_first_value_is_added_EMA_should_be_same_as_value() { + ExponentialMovingAverage ema = new ExponentialMovingAverage(0.5); + assertEquals(10.0, ema.calculateEMA(10.0), 0.001); + } + + @Test + public void when_values_are_added_EMA_should_update_correctly() { + ExponentialMovingAverage ema = new ExponentialMovingAverage(0.4); + assertEquals(10.0, ema.calculateEMA(10.0), 0.001); + assertEquals(14.0, ema.calculateEMA(20.0), 0.001); + assertEquals(20.4, ema.calculateEMA(30.0), 0.001); + } + + @Test + public void when_alpha_is_closer_to_one_EMA_should_respond_faster_to_changes() { + ExponentialMovingAverage ema1 = new ExponentialMovingAverage(0.2); + ExponentialMovingAverage ema2 = new ExponentialMovingAverage(0.8); + + assertEquals(10.0, ema1.calculateEMA(10.0), 0.001); + assertEquals(10.0, ema2.calculateEMA(10.0), 0.001); + + assertEquals(12.0, ema1.calculateEMA(20.0), 0.001); // Responds slower + assertEquals(18.0, ema2.calculateEMA(20.0), 0.001); // Responds faster + } +} diff --git a/core-java-modules/core-java-calculating-moving-averages/src/test/java/com/baeldung/MovingAverageByCircularBufferTest.java b/core-java-modules/core-java-calculating-moving-averages/src/test/java/com/baeldung/MovingAverageByCircularBufferTest.java new file mode 100644 index 0000000000..33ab8e8014 --- /dev/null +++ b/core-java-modules/core-java-calculating-moving-averages/src/test/java/com/baeldung/MovingAverageByCircularBufferTest.java @@ -0,0 +1,35 @@ +package com.baeldung; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MovingAverageByCircularBufferTest { + +@Test +public void when_initial_average_is_calculated_it_should_be_zero() { + MovingAverageByCircularBuffer ma = new MovingAverageByCircularBuffer(5); + assertEquals(Double.NaN, ma.getMovingAverage(), 0.001); // Empty buffer +} + +@Test +public void when_values_are_added_average_should_update_correctly() { + MovingAverageByCircularBuffer ma = new MovingAverageByCircularBuffer(3); + ma.add(10); + assertEquals(10.0, ma.getMovingAverage(), 0.001); + ma.add(20); + assertEquals(15.0, ma.getMovingAverage(), 0.001); + ma.add(30); + assertEquals(20.0, ma.getMovingAverage(), 0.001); +} + +@Test +public void when_window_size_is_full_oldest_value_should_be_overwritten() { + MovingAverageByCircularBuffer ma = new MovingAverageByCircularBuffer(2); + ma.add(10); + ma.add(20); + assertEquals(15.0, ma.getMovingAverage(), 0.001); + ma.add(30); // Overwrites 10 + assertEquals(25.0, ma.getMovingAverage(), 0.001); +} +} diff --git a/core-java-modules/core-java-calculating-moving-averages/src/test/java/com/baeldung/MovingAverageWithApacheCommonsMathTest.java b/core-java-modules/core-java-calculating-moving-averages/src/test/java/com/baeldung/MovingAverageWithApacheCommonsMathTest.java new file mode 100644 index 0000000000..82880e63b2 --- /dev/null +++ b/core-java-modules/core-java-calculating-moving-averages/src/test/java/com/baeldung/MovingAverageWithApacheCommonsMathTest.java @@ -0,0 +1,36 @@ +package com.baeldung; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MovingAverageWithApacheCommonsMathTest { + + @Test + public void when_initial_average_is_calculated_it_should_be_NAN() { + MovingAverageWithApacheCommonsMath movingAverageCalculator = new MovingAverageWithApacheCommonsMath(5); + assertEquals(Double.NaN, movingAverageCalculator.getMovingAverage(), 0.001); + } + + @Test + public void when_values_are_added_average_should_update_correctly() { + MovingAverageWithApacheCommonsMath movingAverageCalculator = new MovingAverageWithApacheCommonsMath(3); + movingAverageCalculator.add(10); + assertEquals(10.0, movingAverageCalculator.getMovingAverage(), 0.001); + movingAverageCalculator.add(20); + assertEquals(15.0, movingAverageCalculator.getMovingAverage(), 0.001); + movingAverageCalculator.add(30); + assertEquals(20.0, movingAverageCalculator.getMovingAverage(), 0.001); + } + + @Test + public void when_window_size_is_full_oldest_value_should_be_dropped() { + MovingAverageWithApacheCommonsMath ma = new MovingAverageWithApacheCommonsMath(2); + ma.add(10); + ma.add(20); + assertEquals(15.0, ma.getMovingAverage(), 0.001); + ma.add(30); + assertEquals(25.0, ma.getMovingAverage(), 0.001); // 10 dropped, 20 and 30 remain + } + +} diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index 00c40151e4..9d5a19104e 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -224,6 +224,7 @@ java-rmi java-spi java-websocket + core-java-calculating-moving-averages