Skip to content

Instantly share code, notes, and snippets.

@agolovenko
Created October 16, 2021 20:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save agolovenko/26a59f235384d2ab296d8a42838c830c to your computer and use it in GitHub Desktop.
Save agolovenko/26a59f235384d2ab296d8a42838c830c to your computer and use it in GitHub Desktop.
Scala implementation for Exponential Moving Average indicator
object Ema {
def apply(period: Int)(data: Vector[Double]): Vector[Double] = {
if (period < 1) throw new IllegalArgumentException(s"Period ($period) is smaller than 1")
if (period > data.size) throw new IllegalArgumentException(s"Period ($period) is bigger than data size (${data.size})")
val factor = 2d / (period + 1)
data.iterator
.drop(1)
.scanLeft(data.head) { (prevEma, value) => value * factor + (1d - factor) * prevEma }
.toVector
}
}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class EmaSpec extends AnyWordSpec with Matchers {
"validates input" in {
an[IllegalArgumentException] shouldBe thrownBy { Ema(period = 0)(Vector.fill(0)(0d)) }
an[IllegalArgumentException] shouldBe thrownBy { Ema(period = 0)(Vector.fill(1)(0d)) }
an[IllegalArgumentException] shouldBe thrownBy { Ema(period = 2)(Vector.fill(1)(0d)) }
}
"returns same size" in {
Ema(period = 1)(Vector.fill(1)(0d)).size shouldBe 1
Ema(period = 3)(Vector.fill(16)(0d)).size shouldBe 16
}
"calculates correctly for still data" in {
val data = Vector.fill(16)(1d)
Ema(period = 16)(data) should contain theSameElementsInOrderAs data
}
"calculates correctly for moving data" in {
val data = Vector(1d, 2d, 3d, 4d)
Ema(period = 3)(data) should contain theSameElementsInOrderAs Vector(1d, 1.5d, 2.25d, 3.125d)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment