packages feed

hasquant-0.6.0.0: test/hspec/QuantLib/Spec/TermStructure/Commodity.hs

module QuantLib.Spec.TermStructure.Commodity (spec) where

import Control.Exception(evaluate)
import Data.Maybe(isNothing, isJust)

import Test.Hspec hiding(before)

import QuantLib.Time.Date
import QuantLib.Time.Calendar(calendar, CalendarConstructor(..))
import QuantLib.Time.Schedule(dayCounter, DayCounterConstructor(..))
import QuantLib.Commodity
import QuantLib.TermStructure.Commodity

spec :: Spec
spec = do
  describe "CommodityCurve" $ do
    it "round-trips name/commodityType/unitOfMeasure/currency/dates/prices, non-empty" $ do
      ho <- commodityType "HO" "Heating Oil"
      bbl <- barrelUnitOfMeasure
      usd <- commoditySettingsCurrency
      cal <- calendar TARGET
      dc <- dayCounter Actual365FixedStandard
      let dates = [1 `january` 2024, 1 `february` 2024, 1 `march` 2024]
          prices = [70.0, 71.5, 72.0]
      curve <- commodityCurve "HO curve" ho usd bbl cal dates prices dc
      commodityCurveName curve `shouldReturn` "HO curve"
      ct <- commodityCurveCommodityType curve
      ct `shouldBe` ho
      uom <- commodityCurveUnitOfMeasure curve
      uom `shouldBe` bbl
      ccy <- commodityCurveCurrency curve
      ccy `shouldBe` usd
      commodityCurveDates curve `shouldReturn` dates
      commodityCurvePrices curve `shouldReturn` prices
      commodityCurveEmpty curve `shouldBe` False

    it "has no basis curve until one is set, and finds it afterwards" $ do
      ho <- commodityType "HO" "Heating Oil"
      bbl <- barrelUnitOfMeasure
      usd <- commoditySettingsCurrency
      cal <- calendar TARGET
      dc <- dayCounter Actual365FixedStandard
      let dates = [1 `january` 2024, 1 `february` 2024]
      curve <- commodityCurve "HO curve" ho usd bbl cal dates [70.0, 71.0] dc
      basis <- commodityCurve "HO basis" ho usd bbl cal dates [1.0, 1.0] dc
      before <- commodityCurveBasisOfCurve curve
      isNothing before `shouldBe` True
      setCommodityCurveBasisOfCurve curve basis
      found <- commodityCurveBasisOfCurve curve
      isJust found `shouldBe` True

    it "prices at a node date via forward-flat interpolation, plus any chained basis price" $ do
      ho <- commodityType "HO" "Heating Oil"
      bbl <- barrelUnitOfMeasure
      usd <- commoditySettingsCurrency
      cal <- calendar TARGET
      dc <- dayCounter Actual365FixedStandard
      let d0 = 1 `january` 2024
          d1 = 1 `february` 2024
      curve <- commodityCurve "HO curve" ho usd bbl cal [d0, d1] [70.0, 71.0] dc
      p0 <- commodityCurvePrice curve d0
      p0 `shouldBe` 70.0
      basis <- commodityCurve "HO basis" ho usd bbl cal [d0, d1] [1.0, 1.0] dc
      setCommodityCurveBasisOfCurve curve basis
      p0' <- commodityCurvePrice curve d0
      p0' `shouldBe` 71.0
      basisPrice <- commodityCurveBasisOfPrice curve d0
      basisPrice `shouldBe` 1.0

    it "commodityCurvePriceNearby with no contracts and offset 0 reproduces the flat price exactly" $ do
      ho <- commodityType "HO" "Heating Oil"
      bbl <- barrelUnitOfMeasure
      usd <- commoditySettingsCurrency
      cal <- calendar TARGET
      dc <- dayCounter Actual365FixedStandard
      let d0 = 1 `january` 2024
          d1 = 1 `february` 2024
      curve <- commodityCurve "HO curve" ho usd bbl cal [d0, d1] [70.0, 71.0] dc
      flat <- commodityCurvePrice curve d0
      rolled <- commodityCurvePriceNearby curve d0 [] 0
      rolled `shouldBe` flat

    it "underlyingPriceDate rolls onto the nearbyOffset'th exchange contract at or after the query date" $ do
      ho <- commodityType "HO" "Heating Oil"
      bbl <- barrelUnitOfMeasure
      usd <- commoditySettingsCurrency
      cal <- calendar TARGET
      dc <- dayCounter Actual365FixedStandard
      let d0 = 1 `january` 2024
          d1 = 1 `february` 2024
      curve <- commodityCurve "HO curve" ho usd bbl cal [d0, d1] [70.0, 71.0] dc
      let jan1 = 1 `january` 2024
          feb1 = 1 `february` 2024
          mar1 = 1 `march` 2024
          us1 = 5 `january` 2024
          us2 = 5 `february` 2024
          us3 = 5 `march` 2024
          ecs = [ (jan1, ("HOF24", jan1, us1, 31 `january` 2024))
                , (feb1, ("HOG24", feb1, us2, 28 `february` 2024))
                , (mar1, ("HOH24", mar1, us3, 31 `march` 2024))
                ]
      commodityCurveUnderlyingPriceDate curve jan1 ecs 1 `shouldReturn` us1
      commodityCurveUnderlyingPriceDate curve jan1 ecs 2 `shouldReturn` us2
      commodityCurveUnderlyingPriceDate curve jan1 ecs 3 `shouldReturn` us3
      commodityCurveUnderlyingPriceDate curve jan1 ecs 4 `shouldThrow` anyException
      commodityCurveUnderlyingPriceDate curve jan1 ecs 0 `shouldThrow` anyException

  describe "DateInterval" $ do
    it "isDateBetween respects the includeFirst/includeLast flags" $ do
      let d0 = 1 `january` 2024
          d1 = 31 `january` 2024
          mid = 15 `january` 2024
          iv = (d0, d1)
      isDateBetween iv mid True True `shouldBe` True
      isDateBetween iv d0 True True `shouldBe` True
      isDateBetween iv d0 False True `shouldBe` False
      isDateBetween iv d1 True True `shouldBe` True
      isDateBetween iv d1 True False `shouldBe` False

    it "intersection overlaps two ranges, or Nothing if disjoint" $ do
      let iv1 = (1 `january` 2024, 31 `january` 2024)
          iv2 = (15 `january` 2024, 15 `february` 2024)
          iv3 = (1 `march` 2024, 31 `march` 2024)
      intersection iv1 iv2 `shouldBe` Just (15 `january` 2024, 31 `january` 2024)
      intersection iv1 iv3 `shouldBe` Nothing

  describe "PricingPeriod" $ do
    it "carries its dates/payment date/quantity, rejecting an end before the start" $ do
      ho <- commodityType "HO" "Heating Oil"
      bbl <- barrelUnitOfMeasure
      let s = 1 `january` 2024
          e = 31 `january` 2024
          p = 5 `february` 2024
          q = (ho, bbl, 1000)
          pp = pricingPeriod s e p q
      pricingPeriodStartDate pp `shouldBe` s
      pricingPeriodEndDate pp `shouldBe` e
      pricingPeriodPaymentDate pp `shouldBe` p
      pricingPeriodQuantity pp `shouldBe` q
      evaluate (pricingPeriod e s p q) `shouldThrow` anyErrorCall

-- vim: set ff=unix ts=8 sts=2 sw=2 et: