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