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