Hi,
Playing with the CallableBond (floating-rate) I’ve found out something unexpected.
The test bellow is a modified version of runFloating in module Daml.Finance.Instrument.Bond.Test.Callable.
Important parameters:
- issueDate = date 2022 Jan 15
- firstCouponDate = date 2022 Apr 15
- holidays = [date 2022 Apr 15]
- resetRelativeTo = CalculationPeriodStartDate
Given the configuration above, the reset date for the rate reference should be 2022 Jan 17  ( 2022 Jan 15 is Saturday), so I’ve created an observation on 2022 Jan 17.
In the test I electAndVerifyPaymentEffects on  2022 Apr 18 (Monday) because firstCouponDate is a Friday public holiday (2022 Apr 15)
When I run the test I get following error message:
message = “Missing observation for USD/LIBOR/3M at t = 2022-04-18T00:00:00Z”
- I think the error is not correct because the valid observation should be the one on 2022 Jan 17 (resetRelativeTo = CalculationPeriodStartDate) which has been created on the ledger.
- Should this test fail?
Thanks!
Jose
-- Create and lifecycle a floating coupon callable bond.
-- 2Y, 3M Libor + 0.1% p.a.
-- Issuer does not call the bond before maturity.
runFloating' : Script ()
runFloating' = script do
  [custodian, issuer, investor, calendarDataProvider, publicParty] <-
    createParties ["Custodian", "Issuer", "Investor", "Calendar Data Provider", "PublicParty"]
  -- Account and holding factory
  let pp = [("FactoryProvider", S.singleton publicParty)]
  -- Originate commercial-bank cash
  now <- getTime
  cashInstrumentCid <- originate custodian issuer "USD" "US Dollar" pp now
  -- Create and distribute bond
  -- Libor + 0.1% coupon every 3M
  -- CREATE_3M_FLOATING_CALLABLE_BOND_VARIABLES_BEGIN
  let
    issueDate = date 2022 Jan 15
    firstCouponDate = date 2022 Apr 15
    maturityDate = date 2024 Jan 15
    notional = 1.0
    couponRate = 0.001
    capRate = None
    floorRate = None
    couponPeriod = M
    couponPeriodMultiplier = 3
    dayCountConvention = Act360
    businessDayConvention = Following
    referenceRateId = "USD/LIBOR/3M"
    floatingRate = Some FloatingRate with
      referenceRateId
      resetRelativeTo = CalculationPeriodStartDate
      fixingDates = FixingDates with
        periodMultiplier = 0
        period = D
        dayType = Some Business
        businessDayConvention = NoAdjustment
        businessCenters = ["USD"]
    -- CREATE_3M_FLOATING_CALLABLE_BOND_VARIABLES_END
    observations = M.fromList
      [ (dateToDateClockTime $ date 2022 Jan 17, 0.010) ]
    holidayCalendarIds = ["USD"]
    cal =
      HolidayCalendarData with
        id = "USD"
        weekend = [Saturday, Sunday]
        holidays = [date 2022 Apr 15]
  -- A reference data provider publishes the holiday calendar on the ledger
  calendarCid <- submit calendarDataProvider do
    createCmd HolidayCalendar with
      provider = calendarDataProvider
      calendar = cal
      observers = M.fromList pp
  observableCid <- toInterfaceContractId <$> submit issuer do
    createCmd Observation with
      provider = issuer; id = Id $ referenceRateId; observations; observers = M.empty
  bondInstrument <- originateCallableBond issuer issuer "BONDTEST1" "Callable Bond" pp now
    issueDate holidayCalendarIds calendarDataProvider firstCouponDate maturityDate
    dayCountConvention businessDayConvention floatingRate couponRate capRate floorRate couponPeriod
    couponPeriodMultiplier cashInstrumentCid notional
  -- One day before the first coupon date: try to lifecycle and verify that there are no lifecycle
  -- effects.
  verifyNoLifecycleEffects [publicParty] (subtractDays firstCouponDate 1) bondInstrument issuer
    [observableCid]
  let
    amount = 1.0
    electorIsOwner = False
  -- Coupon date 1: Lifecycle and verify that there is an effect for one coupon.
  let
    expectedConsumed = []
    expectedProduced = [qty 0.0027805556 cashInstrumentCid]
  (Some bondInstrumentAfterCoupon1, effectCid) <- electAndVerifyPaymentEffects (date 2022 Apr 18)
    amount bondInstrument electorIsOwner issuer investor [publicParty] "NOT CALLED" [observableCid]
    expectedConsumed expectedProduced
  pure ()
             
            
              
              
              
            
            
           
          
            
            
              Hi Jose,
Thank you for reporting this. I have opened up an issue for it: Ensure correct rate fixing date with implied negative date offset · Issue #787 · digital-asset/daml-finance · GitHub
It will probably be fixed in the next couple of weeks.
Regards,
Markus
             
            
              
              
              
            
            
           
          
            
            
              Thanks @Markus_Friberg
I understand that after the fix the test shouldn’t fail. Is this correct?
             
            
              
              
              
            
            
           
          
            
            
              I think so, at least it should use the fixing from 2022 Jan 17.
             
            
              
              
              
            
            
           
          
            
            
              @Markus_Friberg
The thing is it’s taking the value on 2022 Jan 17
Here the same test but with the observation on 2022 Apr 18 .  (dateToDateClockTime $ date 2022 Apr 18, 0.999)
As you can check it doesn’t fail but it is taking the value on 2022 Jan 17.
-- Create and lifecycle a floating coupon callable bond.
-- 2Y, 3M Libor + 0.1% p.a.
-- Issuer does not call the bond before maturity.
runFloating' : Script ()
runFloating' = script do
  [custodian, issuer, investor, calendarDataProvider, publicParty] <-
    createParties ["Custodian", "Issuer", "Investor", "Calendar Data Provider", "PublicParty"]
  -- Account and holding factory
  let pp = [("FactoryProvider", S.singleton publicParty)]
  -- Originate commercial-bank cash
  now <- getTime
  cashInstrumentCid <- originate custodian issuer "USD" "US Dollar" pp now
  -- Create and distribute bond
  -- Libor + 0.1% coupon every 3M
  -- CREATE_3M_FLOATING_CALLABLE_BOND_VARIABLES_BEGIN
  let
    issueDate = date 2022 Jan 15
    firstCouponDate = date 2022 Apr 15
    maturityDate = date 2024 Jan 15
    notional = 1.0
    couponRate = 0.001
    capRate = None
    floorRate = None
    couponPeriod = M
    couponPeriodMultiplier = 3
    dayCountConvention = Act360
    businessDayConvention = Following
    referenceRateId = "USD/LIBOR/3M"
    floatingRate = Some FloatingRate with
      referenceRateId
      resetRelativeTo = CalculationPeriodStartDate
      fixingDates = FixingDates with
        periodMultiplier = 0
        period = D
        dayType = Some Business
        businessDayConvention = NoAdjustment
        businessCenters = ["USD"]
    -- CREATE_3M_FLOATING_CALLABLE_BOND_VARIABLES_END
    observations = M.fromList
      [ (dateToDateClockTime $ date 2022 Jan 17, 0.010)
      , (dateToDateClockTime $ date 2022 Apr 18, 0.999)]
    holidayCalendarIds = ["USD"]
    cal =
      HolidayCalendarData with
        id = "USD"
        weekend = [Saturday, Sunday]
        holidays = [date 2022 Apr 15]
  -- A reference data provider publishes the holiday calendar on the ledger
  calendarCid <- submit calendarDataProvider do
    createCmd HolidayCalendar with
      provider = calendarDataProvider
      calendar = cal
      observers = M.fromList pp
  observableCid <- toInterfaceContractId <$> submit issuer do
    createCmd Observation with
      provider = issuer; id = Id $ referenceRateId; observations; observers = M.empty
  bondInstrument <- originateCallableBond issuer issuer "BONDTEST1" "Callable Bond" pp now
    issueDate holidayCalendarIds calendarDataProvider firstCouponDate maturityDate
    dayCountConvention businessDayConvention floatingRate couponRate capRate floorRate couponPeriod
    couponPeriodMultiplier cashInstrumentCid notional
  -- One day before the first coupon date: try to lifecycle and verify that there are no lifecycle
  -- effects.
  verifyNoLifecycleEffects [publicParty] (subtractDays firstCouponDate 1) bondInstrument issuer
    [observableCid]
  let
    amount = 1.0
    electorIsOwner = False
  -- Coupon date 1: Lifecycle and verify that there is an effect for one coupon.
  let
    expectedConsumed = []
    expectedProduced = [qty 0.0027805556 cashInstrumentCid]
  (Some bondInstrumentAfterCoupon1, effectCid) <- electAndVerifyPaymentEffects (date 2022 Apr 18)
    amount bondInstrument electorIsOwner issuer investor [publicParty] "NOT CALLED" [observableCid]
    expectedConsumed expectedProduced
  pure ()
             
            
              
              
              
            
            
           
          
            
            
              Hi Jose,
I have merged some internal changes to Daml Finance main regarding a new ObserveAt contingent claims node. This resolves a date related issue that we knew about before. Do you see your problem also with the latest version? If so, I will have closer look.
Regards,
Markus
             
            
              
              
              1 Like
            
            
           
          
            
            
              Working as expected.
Thanks @Markus_Friberg !
runFloating' : Script ()
runFloating' = script do
  [custodian, issuer, investor, calendarDataProvider, publicParty] <-
    createParties ["Custodian", "Issuer", "Investor", "Calendar Data Provider", "PublicParty"]
  -- Account and holding factory
  let pp = [("FactoryProvider", S.singleton publicParty)]
  -- Originate commercial-bank cash
  now <- getTime
  cashInstrumentCid <- originate custodian issuer "USD" "US Dollar" pp now
  -- Create and distribute bond
  -- Libor + 0.1% coupon every 3M
  -- CREATE_3M_FLOATING_CALLABLE_BOND_VARIABLES_BEGIN
  let
    issueDate = date 2022 Jan 15
    firstCouponDate = date 2022 Apr 15
    maturityDate = date 2024 Jan 15
    notional = 1.0
    couponRate = 0.001
    capRate = None
    floorRate = None
    couponPeriod = M
    couponPeriodMultiplier = 3
    dayCountConvention = Act360
    businessDayConvention = Following
    referenceRateId = "USD/LIBOR/3M"
    floatingRate = Some FloatingRate with
      referenceRateId
      referenceRateType = SingleFixing CalculationPeriodStartDate
      fixingDates = FixingDates with
        periodMultiplier = 0
        period = D
        dayType = Some Business
        businessDayConvention = NoAdjustment
        businessCenters = ["USD"]
    -- CREATE_3M_FLOATING_CALLABLE_BOND_VARIABLES_END
    observations = M.fromList
      [ (dateToDateClockTime $ date 2022 Jan 17, 0.010)]
    holidayCalendarIds = ["USD"]
    cal =
      HolidayCalendarData with
        id = "USD"
        weekend = [Saturday, Sunday]
        holidays = [date 2022 Apr 15]
  -- A reference data provider publishes the holiday calendar on the ledger
  calendarCid <- submit calendarDataProvider do
    createCmd HolidayCalendar with
      provider = calendarDataProvider
      calendar = cal
      observers = M.fromList pp
  observableCid <- toInterfaceContractId <$> submit issuer do
    createCmd Observation with
      provider = issuer; id = Id $ referenceRateId; observations; observers = M.empty
  bondInstrument <- originateCallableBond issuer issuer "BONDTEST1" "Callable Bond" pp now
    issueDate holidayCalendarIds calendarDataProvider firstCouponDate maturityDate
    dayCountConvention businessDayConvention floatingRate couponRate capRate floorRate couponPeriod
    couponPeriodMultiplier cashInstrumentCid notional
  -- One day before the first coupon date: try to lifecycle and verify that there are no lifecycle
  -- effects.
  verifyNoLifecycleEffects [publicParty] (subtractDays firstCouponDate 1) bondInstrument issuer
    [observableCid]
  let
    amount = 1.0
    electorIsOwner = False
  -- Coupon date 1: Lifecycle and verify that there is an effect for one coupon.
  let
    expectedConsumed = []
    expectedProduced = [qty 0.0027805556 cashInstrumentCid]
  (Some bondInstrumentAfterCoupon1, effectCid) <- electAndVerifyPaymentEffects (date 2022 Apr 18)
    amount bondInstrument electorIsOwner issuer investor [publicParty] "NOT CALLED" [observableCid]
    expectedConsumed expectedProduced
  pure ()
             
            
              
              
              
            
            
           
          
            
            
              Perfect, thank you for the confirmation!
             
            
              
              
              1 Like