From 9b4b5f3d366fdaefa1dd6f11c0f55a499b07d172 Mon Sep 17 00:00:00 2001 From: Kevin Tam Date: Sat, 1 Feb 2020 20:54:42 +0800 Subject: [PATCH] Sadly undo scala spire type trickery of N:Fractional because it gobbles up RAM: https://github.com/typelevel/spire/issues/879 --- .../core/TimeSeriesInterpolator.scala | 51 ++++++++++++++----- .../com/gainstrack/report/PriceState.scala | 2 +- .../report/SingleFXConversion.scala | 2 +- .../test/TestTimeSeriesInterpolator.scala | 14 ++--- web/src/main/scala/JettyLauncher.scala | 2 +- 5 files changed, 47 insertions(+), 24 deletions(-) diff --git a/core/src/main/scala/com/gainstrack/core/TimeSeriesInterpolator.scala b/core/src/main/scala/com/gainstrack/core/TimeSeriesInterpolator.scala index 95a32200..c82de932 100644 --- a/core/src/main/scala/com/gainstrack/core/TimeSeriesInterpolator.scala +++ b/core/src/main/scala/com/gainstrack/core/TimeSeriesInterpolator.scala @@ -9,27 +9,23 @@ import scala.collection.{GenMap, SortedMap} class TimeSeriesInterpolator { import TimeSeriesInterpolator._ - - -// @deprecated("Pass SortedColumnMap for performance") -// def getValue[FROM,N](timeSeries:SortedMap[LocalDate,FROM], date:LocalDate)(implicit interpolator:Interpolator[FROM,N]) : Option[N] = { -// getValue(SortedColumnMap.from(timeSeries),date) -// } - def getValue[FROM,N](timeSeries:SortedColumnMap[LocalDate, FROM], date:LocalDate)(implicit interpolator:Interpolator[FROM,N]) : Option[N] = { val nearest = getNearest(timeSeries,date) interpolator(nearest, date) } - def interpValue[N:Fractional](timeSeries:SortedColumnMap[LocalDate, N], date:LocalDate) : Option[Double] = { - getValue(timeSeries, date)(linear) - } - -// @deprecated("Pass SortedColumnMap for performance") -// def interpValue[N:Fractional](timeSeries:SortedMap[LocalDate,N], date:LocalDate) : Option[Double] = { +// def interpValue[N:Fractional](timeSeries:SortedColumnMap[LocalDate, N], date:LocalDate) : Option[Double] = { // getValue(timeSeries, date)(linear) // } + def interpValueFraction(timeSeries:SortedColumnMap[LocalDate, Fraction], date:LocalDate) : Option[Double] = { + getValue(timeSeries, date)(linearFraction) + } + + def interpValueDouble(timeSeries:SortedColumnMap[LocalDate, Double], date:LocalDate) : Option[Double] = { + getValue(timeSeries, date)(linearDouble) + } + def getNearest[N](timeSeries:SortedColumnMap[LocalDate,N], date:LocalDate) : InterpolationOption[N] = { if (timeSeries.isEmpty) { Empty() @@ -65,7 +61,10 @@ object TimeSeriesInterpolator { type Interpolator[FROM,N] = ((InterpolationOption[FROM], LocalDate) => Option[N]) - def linear[N:Fractional]: Interpolator[N,Double] = (nearest, date) => { + // def linear[N:Fractional]: Interpolator[N,Double] = (nearest, date) => { + + def linearFraction: Interpolator[Fraction,Double] = (nearest, date) => { + type N= Fraction import spire.implicits._ val ret: Option[Double] = nearest match { @@ -89,6 +88,30 @@ object TimeSeriesInterpolator { ret } + def linearDouble: Interpolator[Double,Double] = (nearest, date) => { + type N = Double + import spire.implicits._ + + val ret: Option[Double] = nearest match { + case _: Empty[N] => None + case e: Exact[N] => { + Some(e.value.toDouble()) + } + case e: ExtrapolateLow[N] => Some(e.value.toDouble) + case e: ExtrapolateHigh[N] => Some(e.value.toDouble) + case e: Interpolate[N] => { + // Linear interpolation + val all: N = Fractional[N].fromLong(ChronoUnit.DAYS.between(e.before._1, e.after._1)) + val n: N = Fractional[N].fromLong(ChronoUnit.DAYS.between(e.before._1, date)) + val ratio: Double = (n / all).toDouble + val diff: Double = (e.after._2 - e.before._2).toDouble + val ret: Double = (diff * ratio) + e.before._2.toDouble + Some(ret) + } + case _ => None + } + ret + } val step : Interpolator[Fraction, Fraction] = (nearest,date) => { nearest match { diff --git a/core/src/main/scala/com/gainstrack/report/PriceState.scala b/core/src/main/scala/com/gainstrack/report/PriceState.scala index e9fc651b..2ff6bd67 100644 --- a/core/src/main/scala/com/gainstrack/report/PriceState.scala +++ b/core/src/main/scala/com/gainstrack/report/PriceState.scala @@ -116,7 +116,7 @@ class PriceFXConverter(val ccys: Set[AssetId], val prices: Map[AssetPair, Sorted } else { val timeSeries:SortedColumnMap[LocalDate, Double] = prices.getOrElse(tuple, SortedColumnMap()) - val ret:Option[Double] = interp.interpValue(timeSeries, date).map(x => x) + val ret:Option[Double] = interp.interpValueDouble(timeSeries, date).map(x => x) ret } } diff --git a/core/src/main/scala/com/gainstrack/report/SingleFXConversion.scala b/core/src/main/scala/com/gainstrack/report/SingleFXConversion.scala index dc1fd325..c8db124d 100644 --- a/core/src/main/scala/com/gainstrack/report/SingleFXConversion.scala +++ b/core/src/main/scala/com/gainstrack/report/SingleFXConversion.scala @@ -15,7 +15,7 @@ case class SingleFXConversion(data:Map[AssetId, SortedColumnMap[LocalDate, Doubl } else if (fx2 == baseCcy) { data.get(fx1).flatMap ( series => - interp.getValue(series, date)(TimeSeriesInterpolator.linear) + interp.getValue(series, date)(TimeSeriesInterpolator.linearDouble) ) } else { diff --git a/core/src/test/scala/com/gainstrack/core/test/TestTimeSeriesInterpolator.scala b/core/src/test/scala/com/gainstrack/core/test/TestTimeSeriesInterpolator.scala index ce5bef90..e7ee08c3 100644 --- a/core/src/test/scala/com/gainstrack/core/test/TestTimeSeriesInterpolator.scala +++ b/core/src/test/scala/com/gainstrack/core/test/TestTimeSeriesInterpolator.scala @@ -13,20 +13,20 @@ class TestTimeSeriesInterpolator extends FlatSpec { val interp = new TimeSeriesInterpolator { - implicit val linear = TimeSeriesInterpolator.linear[Fraction] + // implicit val linear = TimeSeriesInterpolator.linearFraction "TimeSeriesTest" should "extrapolate before start" in { - assert(interp.interpValue(data, parseDate("2018-01-01")) == Some(1)) + assert(interp.interpValueFraction(data, parseDate("2018-01-01")) == Some(1)) } it should "extrapolate after end" in { - assert(interp.interpValue(data, parseDate("2020-01-01")) == Some(365)) + assert(interp.interpValueFraction(data, parseDate("2020-01-01")) == Some(365)) } it should "return exact values" in { - assert(interp.interpValue(data, parseDate("2019-01-01")) == Some(1)) - assert(interp.interpValue(data, parseDate("2019-12-31")) == Some(365)) + assert(interp.interpValueFraction(data, parseDate("2019-01-01")) == Some(1)) + assert(interp.interpValueFraction(data, parseDate("2019-12-31")) == Some(365)) } it should "linearly interpolate in between" in { - assert(interp.interpValue(data, parseDate("2019-01-02")) == Some(2)) - assert(interp.interpValue(data, parseDate("2019-12-30")) == Some(364)) + assert(interp.interpValueFraction(data, parseDate("2019-01-02")) == Some(2)) + assert(interp.interpValueFraction(data, parseDate("2019-12-30")) == Some(364)) } } diff --git a/web/src/main/scala/JettyLauncher.scala b/web/src/main/scala/JettyLauncher.scala index f8024bbc..28342017 100644 --- a/web/src/main/scala/JettyLauncher.scala +++ b/web/src/main/scala/JettyLauncher.scala @@ -34,6 +34,6 @@ object JettyLauncher { // this is my entry object as specified in sbt project de import com.gainstrack.core._ val interp = new TimeSeriesInterpolator val timeSeries2: SortedColumnMap[LocalDate, Double] = SortedColumnMap() - interp.interpValue(timeSeries2, today()) + interp.interpValueDouble(timeSeries2, today()) } } \ No newline at end of file