Skip to content

Commit

Permalink
SCP-4728: Added day count methods
Browse files Browse the repository at this point in the history
  • Loading branch information
yveshauser committed Nov 28, 2022
1 parent 79159b2 commit fa1e595
Showing 1 changed file with 56 additions and 49 deletions.
105 changes: 56 additions & 49 deletions src/Actus/Utility/YearFraction.purs
Expand Up @@ -5,12 +5,13 @@ module Actus.Utility.YearFraction
import Prelude

import Actus.Domain.ContractTerms (DCC(..))
import Data.Date (Date, diff, year)
import Data.DateTime (DateTime, date, day, month)
import Data.Enum (fromEnum)
import Data.Date (Date, Month(..), diff, isLeapYear, lastDayOfMonth, year)
import Data.DateTime (DateTime(..), canonicalDate, date, day, hour, minute, month, second)
import Data.Enum (fromEnum, toEnum)
import Data.Int (ceil)
import Data.Maybe (Maybe)
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Refined (fromInt)
import Data.Time (Time(..))
import Data.Time.Duration (Days(..))
import Effect.Exception (throw)
import Effect.Unsafe (unsafePerformEffect)
Expand All @@ -19,29 +20,34 @@ yearFraction :: forall a. EuclideanRing a => DCC -> DateTime -> DateTime -> Mayb
yearFraction dcc x y o = yearFraction' dcc (date x) (date $ clipToMidnight y) (date <$> o)

yearFraction' :: forall a. EuclideanRing a => DCC -> Date -> Date -> Maybe Date -> a
-- yearFraction' DCC_A_AISDA startDay endDay _
-- | startDay <= endDay
-- = let
-- d1Year = year startDay
-- d2Year = year endDay
-- d1YearFraction = fromInt $ if isLeapYear d1Year then 366 else 365
-- in
-- if d1Year == d2Year
-- then
-- let Days d = diff endDay startDay
-- in (fromInt $ ceil d) / d1YearFraction
-- else
-- let
-- d2YearFraction = if isLeapYear d2Year then fromInt 366 else fromInt 365
-- d1YearLastDay = canonicalDate (fromMaybe (year startDay) $ toEnum $ (fromEnum d1Year) + 1) January (fromJust $ toEnum 1)
-- d2YearLastDay = canonicalDate d2Year January (fromMaybe (day endDay) $ toEnum 1)
-- firstFractionDays = diff d1YearLastDay startDay
-- secondFractionDays = let Days s = diff endDay d2YearLastDay in s
-- in
-- (firstFractionDays / d1YearFraction)
-- + (secondFractionDays / d2YearFraction) + (fromInt $ fromEnum d2Year) - (fromInt $ fromEnum d1Year) - one
-- | otherwise
-- = zero
yearFraction' DCC_A_AISDA startDay endDay _
| startDay <= endDay =
let
d1Year = year startDay
d2Year = year endDay

d1YearFraction = fromInt $ if isLeapYear d1Year then 366 else 365
d2YearFraction = fromInt $ if isLeapYear d2Year then 366 else 365
in
if d1Year == d2Year then
let
Days d = diff endDay startDay
in
(fromInt $ ceil d) / d1YearFraction
else
let
(d1YearLastDay :: Date) = canonicalDate (fromMaybe (year startDay) $ toEnum $ (fromEnum d1Year) + 1) January (fromMaybe (day startDay) $ toEnum 1)
(d2YearLastDay :: Date) = canonicalDate d2Year January (fromMaybe (day endDay) $ toEnum 1)

(firstFractionDays :: a) = let Days s = diff d1YearLastDay startDay in fromInt $ ceil s
(secondFractionDays :: a) = let Days s = diff endDay d2YearLastDay in fromInt $ ceil s
in
(firstFractionDays / d1YearFraction)
+ (secondFractionDays / d2YearFraction)
+ (fromInt $ fromEnum d2Year)
- (fromInt $ fromEnum d1Year)
- one
| otherwise = zero

yearFraction' DCC_A_360 startDay endDay _
| startDay <= endDay = let Days daysDiff = diff endDay startDay in (fromInt $ ceil daysDiff) / fromInt 360
Expand All @@ -51,22 +57,22 @@ yearFraction' DCC_A_365 startDay endDay _
| startDay <= endDay = let Days daysDiff = diff endDay startDay in (fromInt $ ceil daysDiff) / fromInt 365
| otherwise = zero

-- yearFraction' DCC_E30_360ISDA _ _ Nothing = error "DCC_E30_360ISDA requires maturity date"
-- yearFraction' DCC_E30_360ISDA startDay endDay (Just maturityDate)
-- | startDay <= endDay
-- = let
-- d1ChangedDay = if isLastDayOfMonth startDay then 30 else day startDay
-- d2ChangedDay = if isLastDayOfMonth endDay && not (endDay == maturityDate && (month endDay == 2)) then 30 else day endDay
-- in
-- ( 360.0
-- * ((year endDay) - (year startDay))
-- + 30.0
-- * ((month endDay) - (month startDay))
-- + (d2ChangedDay - d1ChangedDay)
-- )
-- / 360.0
-- | otherwise
-- = 0.0
yearFraction' DCC_E30_360ISDA _ _ Nothing = error "DCC_E30_360ISDA requires maturity date"
yearFraction' DCC_E30_360ISDA startDay endDay (Just maturityDate)
| startDay <= endDay =
let
d1ChangedDay = if isLastDayOfMonth startDay then 30 else fromEnum $ day startDay
d2ChangedDay = if isLastDayOfMonth endDay && not (endDay == maturityDate && (Just (month endDay) == toEnum 2)) then 30 else fromEnum $ day endDay
in
( fromInt $
360
* ((fromEnum $ year endDay) - (fromEnum $ year startDay))
+ 30
* ((fromEnum $ month endDay) - (fromEnum $ month startDay))
+ (d2ChangedDay - d1ChangedDay)
)
/ (fromInt 360)
| otherwise = zero

yearFraction' DCC_E30_360 startDay endDay _
| startDay <= endDay =
Expand All @@ -75,9 +81,9 @@ yearFraction' DCC_E30_360 startDay endDay _
d2ChangedDay = if fromEnum (day endDay) == 31 then 30 else fromEnum (day endDay)
in
( fromInt $
(360)
360
* ((fromEnum $ year endDay) - (fromEnum $ year startDay))
+ (30)
+ 30
* ((fromEnum $ month endDay) - (fromEnum $ month startDay))
+ (d2ChangedDay - d1ChangedDay)
) / (fromInt 360)
Expand All @@ -88,10 +94,11 @@ yearFraction' dcc _ _ _ = error $ "Unsupported day count convention: " <> show d
error :: forall a. String -> a
error = unsafePerformEffect <<< throw

-- isLastDayOfMonth :: Date -> Boolean
-- isLastDayOfMonth d = lastDayOfMonth (year d) (month d) == (day d)
isLastDayOfMonth :: Date -> Boolean
isLastDayOfMonth d = lastDayOfMonth (year d) (month d) == (day d)

-- |Advance to midnight, if one second before midnight - see note in ACTUS specification (2.8. Date/Time)
clipToMidnight :: DateTime -> DateTime
-- clipToMidnight (DateTime _ t) | Just (hour t) == toEnum 23 && Just (minute t) == toEnum 59 && Just (second t) == toEnum 59 = adjust ...
clipToMidnight lt = lt
clipToMidnight (DateTime d t) | Just (hour t) == toEnum 23 && Just (minute t) == toEnum 59 && Just (second t) == toEnum 59 = let t' = Time <$> toEnum 24 <*> toEnum 0 <*> toEnum 0 <*> toEnum 0 in DateTime d (fromMaybe t t')
clipToMidnight dt = dt

0 comments on commit fa1e595

Please sign in to comment.