diff --git a/CHANGELOG.md b/CHANGELOG.md index 53786c972..304531bf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,7 @@ Dependency update (patch). ### Daml.Finance.Instrument.StructuredProduct Dependency update (patch). +Add new AutoCallable instrument (minor). ### Daml.Finance.Instrument.Swap @@ -119,6 +120,8 @@ Dependency update (patch). ### Daml.Finance.Interface.Instrument.StructuredProduct +Add new AutoCallable instrument (minor). + ### Daml.Finance.Interface.Instrument.Swap ### Daml.Finance.Interface.Instrument.Token diff --git a/daml.yaml b/daml.yaml index faf67e418..558edb80d 100644 --- a/daml.yaml +++ b/daml.yaml @@ -10,7 +10,7 @@ sdk-version: 2.8.0 daml-version: 2.8.0 name: daml-finance source: src/test/daml -version: 1.4.0 +version: 1.4.1 dependencies: - daml-prim - daml-stdlib diff --git a/package/main/daml/Daml.Finance.Instrument.StructuredProduct/daml.yaml b/package/main/daml/Daml.Finance.Instrument.StructuredProduct/daml.yaml index c814eb6e4..f89693f06 100644 --- a/package/main/daml/Daml.Finance.Instrument.StructuredProduct/daml.yaml +++ b/package/main/daml/Daml.Finance.Instrument.StructuredProduct/daml.yaml @@ -4,17 +4,19 @@ sdk-version: 2.8.0 name: daml-finance-instrument-structuredproduct source: daml -version: 0.1.1 +version: 0.2.0 dependencies: - daml-prim - daml-stdlib data-dependencies: + - .lib/daml-finance/ContingentClaims.Core/2.0.1/contingent-claims-core-2.0.1.dar - .lib/daml-finance/Daml.Finance.Claims/2.1.1/daml-finance-claims-2.1.1.dar - .lib/daml-finance/Daml.Finance.Data/3.0.1/daml-finance-data-3.0.1.dar - .lib/daml-finance/Daml.Finance.Interface.Claims/3.0.0/daml-finance-interface-claims-3.0.0.dar - .lib/daml-finance/Daml.Finance.Interface.Instrument.Base/3.0.0/daml-finance-interface-instrument-base-3.0.0.dar - .lib/daml-finance/Daml.Finance.Interface.Instrument.Option/0.3.0/daml-finance-interface-instrument-option-0.3.0.dar - - .lib/daml-finance/Daml.Finance.Interface.Instrument.StructuredProduct/0.1.0/daml-finance-interface-instrument-structuredproduct-0.1.0.dar + - .lib/daml-finance/Daml.Finance.Interface.Instrument.StructuredProduct/0.2.0/daml-finance-interface-instrument-structuredproduct-0.2.0.dar + - .lib/daml-finance/Daml.Finance.Interface.Instrument.Types/1.0.0/daml-finance-interface-instrument-types-1.0.0.dar - .lib/daml-finance/Daml.Finance.Interface.Types.Common/2.0.0/daml-finance-interface-types-common-2.0.0.dar - .lib/daml-finance/Daml.Finance.Interface.Types.Date/2.1.0/daml-finance-interface-types-date-2.1.0.dar - .lib/daml-finance/Daml.Finance.Interface.Util/2.1.0/daml-finance-interface-util-2.1.0.dar diff --git a/package/main/daml/Daml.Finance.Interface.Instrument.StructuredProduct/daml.yaml b/package/main/daml/Daml.Finance.Interface.Instrument.StructuredProduct/daml.yaml index 7c2b71bed..2c2ccc199 100644 --- a/package/main/daml/Daml.Finance.Interface.Instrument.StructuredProduct/daml.yaml +++ b/package/main/daml/Daml.Finance.Interface.Instrument.StructuredProduct/daml.yaml @@ -4,7 +4,7 @@ sdk-version: 2.8.0 name: daml-finance-interface-instrument-structuredproduct source: daml -version: 0.1.0 +version: 0.2.0 dependencies: - daml-prim - daml-stdlib diff --git a/package/test/daml/Daml.Finance.Instrument.StructuredProduct.Test/daml.yaml b/package/test/daml/Daml.Finance.Instrument.StructuredProduct.Test/daml.yaml index 85a7587bc..81c344067 100644 --- a/package/test/daml/Daml.Finance.Instrument.StructuredProduct.Test/daml.yaml +++ b/package/test/daml/Daml.Finance.Instrument.StructuredProduct.Test/daml.yaml @@ -11,8 +11,8 @@ dependencies: - daml-script data-dependencies: - .lib/daml-finance/Daml.Finance.Data/3.0.1/daml-finance-data-3.0.1.dar - - .lib/daml-finance/Daml.Finance.Instrument.StructuredProduct/0.1.1/daml-finance-instrument-structuredproduct-0.1.1.dar - - .lib/daml-finance/Daml.Finance.Interface.Instrument.StructuredProduct/0.1.0/daml-finance-interface-instrument-structuredproduct-0.1.0.dar + - .lib/daml-finance/Daml.Finance.Instrument.StructuredProduct/0.2.0/daml-finance-instrument-structuredproduct-0.2.0.dar + - .lib/daml-finance/Daml.Finance.Interface.Instrument.StructuredProduct/0.2.0/daml-finance-interface-instrument-structuredproduct-0.2.0.dar - .lib/daml-finance/Daml.Finance.Interface.Types.Common/2.0.0/daml-finance-interface-types-common-2.0.0.dar - .lib/daml-finance/Daml.Finance.Interface.Types.Date/2.1.0/daml-finance-interface-types-date-2.1.0.dar - .lib/daml-finance/Daml.Finance.Interface.Util/2.1.0/daml-finance-interface-util-2.1.0.dar diff --git a/src/main/daml/Daml/Finance/Instrument/StructuredProduct/AutoCallable/Factory.daml b/src/main/daml/Daml/Finance/Instrument/StructuredProduct/AutoCallable/Factory.daml new file mode 100644 index 000000000..376145942 --- /dev/null +++ b/src/main/daml/Daml/Finance/Instrument/StructuredProduct/AutoCallable/Factory.daml @@ -0,0 +1,74 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +module Daml.Finance.Instrument.StructuredProduct.AutoCallable.Factory where + +import DA.Set (singleton) +import Daml.Finance.Instrument.StructuredProduct.AutoCallable.Instrument qualified as AutoCallable (Instrument(..)) +import Daml.Finance.Interface.Claims.Claim qualified as Claim (GetClaims(..), I, getClaims) +import Daml.Finance.Interface.Instrument.Base.Instrument qualified as BaseInstrument (createReference) +import Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Factory qualified as AutoCallableFactory (Create(..), I, View(..)) +import Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Types (AutoCallable(..)) +import Daml.Finance.Interface.Types.Common.Types (PartiesMap) +import Daml.Finance.Interface.Util.Disclosure qualified as Disclosure (I, View(..), flattenObservers) +import Daml.Finance.Util.Disclosure (addObserversImpl, removeObserversImpl, setObserversImpl) + +-- | Type synonym for `Factory`. +type T = Factory + +-- | Factory template for instrument creation. +template Factory + with + provider : Party + -- ^ The factory's provider. + observers : PartiesMap + -- ^ The factory's observers. + where + signatory provider + observer Disclosure.flattenObservers observers + + interface instance AutoCallableFactory.I for Factory where + view = AutoCallableFactory.View with provider + create' AutoCallableFactory.Create{ + autoCallable = AutoCallable{instrument; description; referenceAssetId; putStrike; + couponBarrier; callBarrier; finalBarrier; currency; lastEventTimestamp; couponRate; + observationSchedule; periodicSchedule; holidayCalendarIds; calendarDataProvider; + dayCountConvention; notional; prevEvents}; + observers} = do + let + acInstrument = AutoCallable.Instrument with + depository = instrument.depository + issuer = instrument.issuer + id = instrument.id + version = instrument.version + holdingStandard = instrument.holdingStandard + description + referenceAssetId + putStrike + couponBarrier + callBarrier + finalBarrier + couponRate + observationSchedule + periodicSchedule + holidayCalendarIds + calendarDataProvider + dayCountConvention + currency + notional + lastEventTimestamp + observers + prevEvents + cid <- toInterfaceContractId <$> create acInstrument + BaseInstrument.createReference instrument.depository $ toInterfaceContractId cid + -- Get the claims in order to run the associated checks (e.g. verify that the schedules + -- are valid). + Claim.getClaims (toInterface @Claim.I acInstrument) $ + Claim.GetClaims with actor = instrument.issuer + pure cid + + interface instance Disclosure.I for Factory where + view = Disclosure.View with disclosureControllers = singleton provider; observers + setObservers = setObserversImpl @Factory @Disclosure.I this None + addObservers = addObserversImpl @Factory @Disclosure.I this None + removeObservers = removeObserversImpl @Factory @Disclosure.I this None diff --git a/src/main/daml/Daml/Finance/Instrument/StructuredProduct/AutoCallable/Instrument.daml b/src/main/daml/Daml/Finance/Instrument/StructuredProduct/AutoCallable/Instrument.daml new file mode 100644 index 000000000..4fee0f6f1 --- /dev/null +++ b/src/main/daml/Daml/Finance/Instrument/StructuredProduct/AutoCallable/Instrument.daml @@ -0,0 +1,164 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +module Daml.Finance.Instrument.StructuredProduct.AutoCallable.Instrument where + +import DA.Date (daysSinceEpochToDate) +import DA.Set (singleton) +import Daml.Finance.Data.Reference.HolidayCalendar (getHolidayCalendars, rollSchedule) +import Daml.Finance.Data.Time.DateClock (dateToDateClockTime) +import Daml.Finance.Instrument.StructuredProduct.Util (createAutoCallableClaims) +import Daml.Finance.Interface.Claims.Claim qualified as Claim (GetClaims(..), I, View(..)) +import Daml.Finance.Interface.Claims.Dynamic.Instrument qualified as DynamicInstrument (CreateNewVersion(..), I, View(..)) +import Daml.Finance.Interface.Claims.Types (EventData) +import Daml.Finance.Interface.Instrument.Base.Instrument qualified as BaseInstrument (I, View(..), createReference, disclosureUpdateReference, instrumentKey) +import Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Instrument qualified as AutoCallable (I, View(..)) +import Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Types (AutoCallable(..)) +import Daml.Finance.Interface.Types.Common.Types (HoldingStandard(..), Id(..), InstrumentKey(..), PartiesMap) +import Daml.Finance.Interface.Types.Date.DayCount (DayCountConventionEnum) +import Daml.Finance.Interface.Types.Date.Schedule (PeriodicSchedule(..)) +import Daml.Finance.Interface.Util.Disclosure qualified as Disclosure (I, View(..), flattenObservers) +import Daml.Finance.Util.Date.Calendar (merge) +import Daml.Finance.Util.Disclosure (addObserversImpl, removeObserversImpl, setObserversImpl) + +-- | Type synonym for `Instrument`. +type T = Instrument + +-- | This template models an AutoCallable instrument that pays a conditional coupon. +-- It is an AutoCallable Barrier Reverse Convertible where the KI barrier is observed at maturity. +-- It is a single-underlying product. +-- The instrument is automatically called (redeemed early) if the call barrier is hit. +-- The conditional coupon is paid in each coupon period unless the coupon barrier has been hit. +-- Both the call barrier and the coupon barrier are observed only on the last observation date of +-- each period. +template Instrument + with + depository : Party + -- ^ The depository of the instrument. + issuer : Party + -- ^ The issuer of the instrument. + id : Id + -- ^ An identifier of the instrument. + version : Text + -- ^ The instrument's version. + holdingStandard : HoldingStandard + -- ^ The holding standard for holdings referencing this instrument. + description : Text + -- ^ A description of the instrument. + referenceAssetId : Text + -- ^ The reference asset ID. For example, in case of an AAPL underlying this should be a valid + -- reference to the AAPL fixings to be used for the payoff calculation. + putStrike : Decimal + -- ^ The strike of the put (as a percentage of the underlying closing price on the first + -- observation date). + couponBarrier : Decimal + -- ^ The coupon barrier (as a percentage of the underlying closing price on the first + -- observation date). + callBarrier : Decimal + -- ^ The barrier used to automatically call the instrument (as a percentage of the underlying + -- closing price on the first observation date). + finalBarrier : Decimal + -- ^ The barrier used to determine the final redemption amount (as a percentage of the + -- underlying closing price on the first observation date). + couponRate : Decimal + -- ^ The fixed coupon rate, either per annum or per coupon period (depending on the + -- dayCountConvention below). + observationSchedule : PeriodicSchedule + -- ^ The schedule for the observation dates. These are used to observe the barrier, determine + -- whether the instrument is automatically called and to determine the final redemption + -- amount. + periodicSchedule : PeriodicSchedule + -- ^ The schedule for the periodic coupon payments. + holidayCalendarIds : [Text] + -- ^ The identifiers of the holiday calendars to be used for the coupon schedule. + calendarDataProvider : Party + -- ^ The reference data provider to use for the holiday calendar. + dayCountConvention : DayCountConventionEnum + -- ^ The day count convention used to calculate day count fractions. For example: Act360. + currency : InstrumentKey + -- ^ The currency of the product. For example, if the product pays in USD this should be a USD + -- cash instrument. + notional : Decimal + -- ^ The notional of the product. This is the face value corresponding to one unit of the + -- product. For example, if one product unit corresponds to 1000 USD, this should be 1000.0. + observers : PartiesMap + -- ^ The observers of the instrument. + lastEventTimestamp : Time + -- ^ (Market) time of the last recorded lifecycle event. If no event has occurred yet, the + -- time of creation should be used. + prevEvents : [EventData] + -- ^ A list of previous events that have been lifecycled on this instrument so far. + where + signatory depository, issuer + observer Disclosure.flattenObservers observers + + interface instance Claim.I for Instrument where + view = Claim.View with acquisitionTime = dateToDateClockTime $ daysSinceEpochToDate 0 + getClaims Claim.GetClaims{actor} = do + -- get the initial claims tree (as of the instrument's acquisition time) + + let + getCalendars = getHolidayCalendars actor calendarDataProvider + floatingRate = None + capRate = None + floorRate = None + fixingBusinessCenters = [] + + assertMsg "Currently only put strike of 100% supported" $ putStrike == 1.00 + fixingCals <- getHolidayCalendars issuer calendarDataProvider fixingBusinessCenters + (schedule, _) <- rollSchedule getCalendars periodicSchedule holidayCalendarIds + (callableSchedule, _) <- rollSchedule getCalendars observationSchedule holidayCalendarIds + assertMsg "The callable schedule must have the same length as the coupon schedule" $ + length schedule == length callableSchedule + let + useAdjustedDatesForDcf = True + callableClaims = createAutoCallableClaims dateToDateClockTime schedule callableSchedule + periodicSchedule useAdjustedDatesForDcf couponRate dayCountConvention notional currency + floatingRate capRate floorRate referenceAssetId couponBarrier callBarrier finalBarrier + putStrike $ merge fixingCals + + pure [callableClaims] + + interface instance BaseInstrument.I for Instrument where + view = BaseInstrument.View with + depository; issuer; id; version; holdingStandard; description + validAsOf = lastEventTimestamp + getKey = BaseInstrument.instrumentKey this + + interface instance AutoCallable.I for Instrument where + view = AutoCallable.View with + autoCallable = AutoCallable with + instrument = BaseInstrument.instrumentKey this + description + referenceAssetId + putStrike + couponBarrier + callBarrier + finalBarrier + couponRate + observationSchedule + periodicSchedule + holidayCalendarIds + calendarDataProvider + dayCountConvention + currency + notional + lastEventTimestamp + prevEvents + + interface instance DynamicInstrument.I for Instrument where + view = DynamicInstrument.View with lifecycler = issuer; lastEventTimestamp; prevEvents + createNewVersion DynamicInstrument.CreateNewVersion{version; lastEventTimestamp; + prevEvents} = do + cid <- create this with version; lastEventTimestamp; prevEvents + BaseInstrument.createReference issuer $ toInterfaceContractId cid + pure $ toInterfaceContractId cid + + interface instance Disclosure.I for Instrument where + view = Disclosure.View with disclosureControllers = singleton issuer; observers + setObservers = setObserversImpl @Instrument this $ + Some . BaseInstrument.disclosureUpdateReference $ BaseInstrument.instrumentKey this + addObservers = addObserversImpl @Instrument this $ + Some . BaseInstrument.disclosureUpdateReference $ BaseInstrument.instrumentKey this + removeObservers = removeObserversImpl this . + Some . BaseInstrument.disclosureUpdateReference $ BaseInstrument.instrumentKey this diff --git a/src/main/daml/Daml/Finance/Instrument/StructuredProduct/Util.daml b/src/main/daml/Daml/Finance/Instrument/StructuredProduct/Util.daml new file mode 100644 index 000000000..0bf5b5b05 --- /dev/null +++ b/src/main/daml/Daml/Finance/Instrument/StructuredProduct/Util.daml @@ -0,0 +1,77 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +module Daml.Finance.Instrument.StructuredProduct.Util where + +import ContingentClaims.Core.Claim (Claim, Inequality(..), and, at, cond, one, scale, when, zero) +import ContingentClaims.Core.Observation (Observation(..)) +import DA.List (head, last) +import DA.Optional (isSome) +import Daml.Finance.Claims.Util.Builders (calculateRatePayment, prepareAndTagClaims) +import Daml.Finance.Interface.Claims.Types (Deliverable, Observable, TaggedClaim(..)) +import Daml.Finance.Interface.Instrument.Types.FloatingRate (FloatingRate) +import Daml.Finance.Interface.Types.Date.Calendar (HolidayCalendarData) +import Daml.Finance.Interface.Types.Date.DayCount (DayCountConventionEnum) +import Daml.Finance.Interface.Types.Date.Schedule (PeriodicSchedule(..), Schedule) +import Prelude hiding (and, or, (<=)) + +type O = Observation Date Decimal Observable +type C = Claim Date Decimal Deliverable Observable + +-- | Find out which schedule periods of scheduleA exist in scheduleB. +includes : Schedule -> Schedule -> [Bool] +includes scheduleA scheduleB = + map (\a -> isSome $ find (\b -> b.adjustedEndDate == a.adjustedEndDate) scheduleB) scheduleA + +-- | Calculate the claims for an auto-callable with a contingent coupon on each payment date +-- and a redemption amount at maturity (unless auto-called previously). +createAutoCallableClaims : (Date -> Time) -> Schedule -> Schedule -> PeriodicSchedule + -> Bool -> Decimal -> DayCountConventionEnum -> Decimal -> Deliverable -> Optional FloatingRate + -> Optional Decimal -> Optional Decimal -> Text -> Decimal -> Decimal -> Decimal -> Decimal + -> HolidayCalendarData -> TaggedClaim +createAutoCallableClaims dateToTime paymentSchedule callableSchedule periodicSchedule + useAdjustedDatesForDcf couponRate dayCountConvention notional cashInstrument floatingRate + capRate floorRate spot couponBarrier callBarrier finalBarrier putStrike + fixingCalendars = + let + notionalAmount = scale (Const notional) $ one cashInstrument + + principal = notionalAmount + initialObservationDate = (.adjustedStartDate) $ + if null callableSchedule then error "empty callableSchedule" else head callableSchedule + + combineTagClaim (couponPeriod, callPeriod) notCalledClaim = + let + cpn = calculateRatePayment couponPeriod dayCountConvention useAdjustedDatesForDcf + periodicSchedule floatingRate couponRate notionalAmount fixingCalendars capRate floorRate + (callDate, paymentDate) = if callPeriod.adjustedEndDate > couponPeriod.adjustedEndDate + then error "each call date must be before or at the corresponding payment date" + else (callPeriod.adjustedEndDate, couponPeriod.adjustedEndDate) + spotOnObservationDate = ObserveAt spot callDate + couponBarrierLevel = Const couponBarrier * ObserveAt spot initialObservationDate + couponBarrierHit = Lte (spotOnObservationDate, couponBarrierLevel) + coupon = cond couponBarrierHit zero cpn + called = when (at paymentDate) $ and coupon principal + notCalled = when (at paymentDate) $ and coupon notCalledClaim + callBarrierLevel = Const callBarrier * ObserveAt spot initialObservationDate + autoExerciseCondition = Lte (callBarrierLevel, spotOnObservationDate) + tailClaim = when (at callDate) $ cond autoExerciseCondition called notCalled + in + tailClaim + + finalCouponDate = (.adjustedEndDate) $ last paymentSchedule + finalObservationDate = (.adjustedEndDate) $ last callableSchedule + spotOnObservationDate = ObserveAt spot finalObservationDate + finalBarrierLevel = Const finalBarrier * ObserveAt spot initialObservationDate + barrierHit = Lte (spotOnObservationDate, finalBarrierLevel) + putStrikeLevel = Const putStrike * ObserveAt spot initialObservationDate + -- This currently only works for strike = initialFixing (100%) + perf = spotOnObservationDate / putStrikeLevel + perfomanceScaledPrincipal = scale perf notionalAmount + redemptionPayment = cond barrierHit perfomanceScaledPrincipal principal + notCalledFinal = when (at finalCouponDate) redemptionPayment + claims = foldr (\p acc -> combineTagClaim p acc) notCalledFinal $ + zip paymentSchedule callableSchedule + + in + prepareAndTagClaims dateToTime [claims] "AutoCallable payment" diff --git a/src/main/daml/Daml/Finance/Interface/Instrument/StructuredProduct/AutoCallable/Factory.daml b/src/main/daml/Daml/Finance/Interface/Instrument/StructuredProduct/AutoCallable/Factory.daml new file mode 100644 index 000000000..a2cadb8d6 --- /dev/null +++ b/src/main/daml/Daml/Finance/Interface/Instrument/StructuredProduct/AutoCallable/Factory.daml @@ -0,0 +1,41 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +module Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Factory where + +import Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Instrument qualified as Instrument (I) +import Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Types (AutoCallable) +import Daml.Finance.Interface.Types.Common.Types (PartiesMap) +import Daml.Finance.Interface.Util.Disclosure qualified as Disclosure (I) + +-- | Type synonym for `Factory`. +type I = Factory + +-- | Type synonym for `View`. +type V = View + +-- | View of `Factory`. +data View = View + with + provider : Party + -- ^ The provider of the `Factory`. + deriving (Eq, Show) + +-- | Factory interface to instantiate AutoCallable instruments. +interface Factory requires Disclosure.I where + viewtype V + + create' : Create -> Update (ContractId Instrument.I) + -- ^ Implementation of `Create` choice. + + nonconsuming choice Create : ContractId Instrument.I + -- ^ Create a new instrument. + with + autoCallable : AutoCallable + -- ^ Attributes to create an AutoCallable. + observers : PartiesMap + -- ^ The instrument's observers. + controller + autoCallable.instrument.depository, autoCallable.instrument.issuer + do + create' this arg diff --git a/src/main/daml/Daml/Finance/Interface/Instrument/StructuredProduct/AutoCallable/Instrument.daml b/src/main/daml/Daml/Finance/Interface/Instrument/StructuredProduct/AutoCallable/Instrument.daml new file mode 100644 index 000000000..c75cce3c6 --- /dev/null +++ b/src/main/daml/Daml/Finance/Interface/Instrument/StructuredProduct/AutoCallable/Instrument.daml @@ -0,0 +1,34 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +module Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Instrument where + +import Daml.Finance.Interface.Instrument.Base.Instrument qualified as BaseInstrument (I) +import Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Types (AutoCallable) +import Daml.Finance.Interface.Util.Disclosure qualified as Disclosure (I) + +-- | Type synonym for `Instrument`. +type I = Instrument + +-- | Type synonym for `View`. +type V = View + +-- | View of `Instrument`. +data View = View + with + autoCallable : AutoCallable + -- ^ Attributes of an AutoCallable. + deriving (Eq, Show) + +-- | Instrument interface representing an AutoCallable. +interface Instrument requires BaseInstrument.I, Disclosure.I where + viewtype V + + nonconsuming choice GetView : V + -- ^ Retrieves the interface view. + with + viewer : Party + -- ^ The party retrieving the view. + controller viewer + do + pure $ view this diff --git a/src/main/daml/Daml/Finance/Interface/Instrument/StructuredProduct/AutoCallable/Types.daml b/src/main/daml/Daml/Finance/Interface/Instrument/StructuredProduct/AutoCallable/Types.daml new file mode 100644 index 000000000..c3d682467 --- /dev/null +++ b/src/main/daml/Daml/Finance/Interface/Instrument/StructuredProduct/AutoCallable/Types.daml @@ -0,0 +1,59 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +module Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Types where + +import Daml.Finance.Interface.Claims.Types (EventData) +import Daml.Finance.Interface.Types.Common.Types (InstrumentKey(..)) +import Daml.Finance.Interface.Types.Date.DayCount (DayCountConventionEnum) +import Daml.Finance.Interface.Types.Date.Schedule (PeriodicSchedule(..)) + +-- | Describes the attributes of an AutoCallable instrument that pays a conditional coupon. +data AutoCallable = AutoCallable + with + instrument : InstrumentKey + -- ^ The instrument's key. + description : Text + -- ^ The description of the option. + referenceAssetId : Text + -- ^ The reference asset ID. For example, in case of an AAPL underlying this should be a valid + -- reference to the AAPL fixings to be used for the payoff calculation. + putStrike : Decimal + -- ^ The strike of the put (as a percentage of the underlying closing price on the first + -- observation date). + couponBarrier : Decimal + -- ^ The coupon barrier (as a percentage of the underlying closing price on the first + -- observation date). + callBarrier : Decimal + -- ^ The barrier used to automatically call the instrument (as a percentage of the underlying + -- closing price on the first observation date). + finalBarrier : Decimal + -- ^ The barrier used to determine the final redemption amount (as a percentage of the + -- underlying closing price on the first observation date). + couponRate : Decimal + -- ^ The fixed coupon rate, either per annum or per coupon period (depending on the + -- dayCountConvention below). + observationSchedule : PeriodicSchedule + -- ^ The schedule for the observation dates. These are used to observe the barrier, determine + -- whether the instrument is automatically called and to determine the final redemption + -- amount. + periodicSchedule : PeriodicSchedule + -- ^ The schedule for the periodic coupon payments. + holidayCalendarIds : [Text] + -- ^ The identifiers of the holiday calendars to be used for the coupon schedule. + calendarDataProvider : Party + -- ^ The reference data provider to use for the holiday calendar. + dayCountConvention : DayCountConventionEnum + -- ^ The day count convention used to calculate day count fractions. For example: Act360. + currency : InstrumentKey + -- ^ The currency of the product. For example, if the product pays in USD this should be a USD + -- cash instrument. + notional : Decimal + -- ^ The notional of the product. This is the face value corresponding to one unit of the + -- product. For example, if one product unit corresponds to 1000 USD, this should be 1000.0. + lastEventTimestamp : Time + -- ^ (Market) time of the last recorded lifecycle event. If no event has occurred yet, the + -- time of creation should be used. + prevEvents : [EventData] + -- ^ A list of previous events that have been lifecycled on this instrument so far. + deriving (Eq, Show) diff --git a/src/test/daml/Daml/Finance/Instrument/StructuredProduct/Test/AutoCallable.daml b/src/test/daml/Daml/Finance/Instrument/StructuredProduct/Test/AutoCallable.daml new file mode 100644 index 000000000..c6fb21c8b --- /dev/null +++ b/src/test/daml/Daml/Finance/Instrument/StructuredProduct/Test/AutoCallable.daml @@ -0,0 +1,185 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +module Daml.Finance.Instrument.StructuredProduct.Test.AutoCallable where + +import DA.Date (DayOfWeek(..), Month(..), date) +import DA.Map qualified as Map (fromList) +import DA.Set qualified as Set (singleton) +import Daml.Finance.Data.Numeric.Observation (Observation(..)) +import Daml.Finance.Data.Reference.HolidayCalendar (HolidayCalendar(..)) +import Daml.Finance.Data.Time.DateClock (dateToDateClockTime) +import Daml.Finance.Instrument.StructuredProduct.Test.Util (originateAutoCallable) +import Daml.Finance.Interface.Types.Common.Types (HoldingStandard(..), Id(..)) +import Daml.Finance.Interface.Types.Date.Calendar (BusinessDayConventionEnum(..), HolidayCalendarData(..)) +import Daml.Finance.Interface.Types.Date.DayCount (DayCountConventionEnum(..)) +import Daml.Finance.Interface.Types.Date.RollConvention (PeriodEnum(..)) +import Daml.Finance.Interface.Util.Common (qty) +import Daml.Finance.Test.Util.Common (createParties) +import Daml.Finance.Test.Util.Instrument (originate) +import Daml.Finance.Test.Util.Lifecycle (lifecycleAndVerifyPaymentEffects, verifyNoLifecycleEffects) +import Daml.Finance.Test.Util.Time (createPaymentPeriodicSchedule) +import Daml.Script + +-- Define and lifecycle an AutoCallable Yield Note. +run : Script () +run = script do + [custodian, issuer, calendarDataProvider, publicParty] <- + createParties ["Custodian", "Issuer", "Calendar Data Provider", "PublicParty"] + + -- Account and holding factory + let pp = [("FactoryProvider", Set.singleton publicParty)] + + -- Distribute commercial-bank cash + now <- getTime + let observers = [("PublicParty", Set.singleton publicParty)] + cashInstrument <- originate custodian issuer "USD" TransferableFungible "US Dollars" observers now + + -- Create and distribute option + let + couponBarrierHigh = 0.75 + callBarrierLow = 0.95 + finalBarrierHigh = 1.00 + -- CREATE_AUTO_CALLABLE_VARIABLES_BEGIN + putStrike = 1.00 -- 100% of the underlying closing price on the initial fixing date + couponBarrier = 0.65 -- 65% of the underlying closing price on the initial fixing date + callBarrier = 1.00 + finalBarrier = 0.875 + referenceAssetId = "AAPL-CLOSE" + couponRate = 0.05 + dayCountConvention = Basis1 -- coupon rate is paid each period, not per annum. + businessDayConvention = Following + couponPeriod = M + couponPeriodMultiplier = 3 + initialFixingDate = date 2024 Jan 10 + issueDate = date 2024 Jan 16 + firstRegularObervationDate = date 2024 Mar 28 + firstCouponDate = date 2024 Apr 2 + secondCouponDate = date 2024 Jul 2 + expiryDate = date 2024 Sep 28 + maturityDate = date 2024 Oct 2 + -- CREATE_AUTO_CALLABLE_VARIABLES_END + holidayCalendarIds = ["USD"] + calendar = + HolidayCalendarData with + id = "USD" + weekend = [Saturday, Sunday] + holidays = [] + couponSchedule = createPaymentPeriodicSchedule firstCouponDate holidayCalendarIds + businessDayConvention couponPeriod couponPeriodMultiplier issueDate maturityDate + observationSchedule = createPaymentPeriodicSchedule firstRegularObervationDate + holidayCalendarIds businessDayConvention couponPeriod couponPeriodMultiplier + initialFixingDate expiryDate + notional = 1.0 + + -- CREATE_AUTO_CALLABLE_OBSERVATIONS_BEGIN + let + observations = Map.fromList + [ (dateToDateClockTime $ date 2024 Jan 10, 40.0) + , (dateToDateClockTime $ date 2024 Mar 28, 28.78) + , (dateToDateClockTime $ date 2024 Jun 28, 39.78) + , (dateToDateClockTime $ date 2024 Sep 30, 36.0) + ] + observableCid <- toInterfaceContractId <$> submit issuer do + createCmd Observation with + provider = issuer; id = Id referenceAssetId; observations; observers = mempty + -- CREATE_AUTO_CALLABLE_OBSERVATIONS_END + + -- A reference data provider publishes the holiday calendar on the ledger + calendarCid <- submit calendarDataProvider do + createCmd HolidayCalendar with + provider = calendarDataProvider + calendar + observers = Map.fromList pp + + ----------------------------------------------------------------------- + -- 1. AutoCallable without barrier events (and no early redemption) -- + ----------------------------------------------------------------------- + + acInstrument <- originateAutoCallable issuer issuer "AC" TransferableFungible "AutoCallable" + observers now putStrike couponBarrier callBarrier finalBarrier cashInstrument + referenceAssetId couponRate observationSchedule couponSchedule holidayCalendarIds + dayCountConvention notional calendarDataProvider publicParty + + let + expectedConsumed = [] + expectedProduced = [qty 0.05 cashInstrument] + Some acInstrumentAfterCouponDate1 <- lifecycleAndVerifyPaymentEffects [publicParty] + firstCouponDate acInstrument issuer [observableCid] expectedConsumed + expectedProduced + + let + expectedConsumed = [] + expectedProduced = [qty 0.05 cashInstrument] + Some acInstrumentAfterCouponDate2 <- lifecycleAndVerifyPaymentEffects [publicParty] + secondCouponDate acInstrumentAfterCouponDate1 issuer [observableCid] expectedConsumed + expectedProduced + + let + expectedConsumed = [] + expectedProduced = [qty 1.05 cashInstrument] + lifecycleAndVerifyPaymentEffects [publicParty] maturityDate acInstrumentAfterCouponDate2 issuer + [observableCid] expectedConsumed expectedProduced + + -------------------------------------------------------------------- + -- 2. AutoCallable with barrier events (and no early redemption) -- + -------------------------------------------------------------------- + + acInstrument <- originateAutoCallable issuer issuer "AC2" TransferableFungible "AutoCallable" + observers now putStrike couponBarrierHigh callBarrier finalBarrierHigh cashInstrument + referenceAssetId couponRate observationSchedule couponSchedule holidayCalendarIds + dayCountConvention notional calendarDataProvider publicParty + + -- The first coupon is not paid because the coupon barrier is hit + let + expectedConsumed = [] + expectedProduced = [] + Some acInstrumentAfterCouponDate1 <- lifecycleAndVerifyPaymentEffects [publicParty] + firstCouponDate acInstrument issuer [observableCid] expectedConsumed + expectedProduced + + -- The second coupon is paid (no barrier hit) + let + expectedConsumed = [] + expectedProduced = [qty 0.05 cashInstrument] + Some acInstrumentAfterCouponDate2 <- lifecycleAndVerifyPaymentEffects [publicParty] + secondCouponDate acInstrumentAfterCouponDate1 issuer [observableCid] expectedConsumed + expectedProduced + + -- Redemption amount is less than principal because the final barrier is hit + let + expectedConsumed = [] + expectedProduced = [qty 0.95 cashInstrument] + lifecycleAndVerifyPaymentEffects [publicParty] maturityDate acInstrumentAfterCouponDate2 issuer + [observableCid] expectedConsumed expectedProduced + + -------------------------------------------- + -- 3. AutoCallable with early redemption -- + -------------------------------------------- + + acInstrument <- originateAutoCallable issuer issuer "AC3" TransferableFungible "AutoCallable" + observers now putStrike couponBarrierHigh callBarrierLow finalBarrier cashInstrument + referenceAssetId couponRate observationSchedule couponSchedule holidayCalendarIds + dayCountConvention notional calendarDataProvider publicParty + + -- The first coupon is not paid because the coupon barrier is hit + let + expectedConsumed = [] + expectedProduced = [] + Some acInstrumentAfterCouponDate1 <- lifecycleAndVerifyPaymentEffects [publicParty] + firstCouponDate acInstrument issuer [observableCid] expectedConsumed + expectedProduced + + -- Auto-call on the second coupon date + let + expectedConsumed = [] + expectedProduced = [qty 1.05 cashInstrument] + Some acInstrumentAfterCouponDate2 <- lifecycleAndVerifyPaymentEffects [publicParty] + secondCouponDate acInstrumentAfterCouponDate1 issuer [observableCid] expectedConsumed + expectedProduced + + -- Ensure no lifecycle effects on a called instrument + verifyNoLifecycleEffects [publicParty] maturityDate acInstrumentAfterCouponDate2 issuer + [observableCid] + + pure () diff --git a/src/test/daml/Daml/Finance/Instrument/StructuredProduct/Test/Util.daml b/src/test/daml/Daml/Finance/Instrument/StructuredProduct/Test/Util.daml index bf548f3f6..4c210dbe3 100644 --- a/src/test/daml/Daml/Finance/Instrument/StructuredProduct/Test/Util.daml +++ b/src/test/daml/Daml/Finance/Instrument/StructuredProduct/Test/Util.daml @@ -4,7 +4,10 @@ module Daml.Finance.Instrument.StructuredProduct.Test.Util where import DA.Map qualified as Map (fromList) +import Daml.Finance.Instrument.StructuredProduct.AutoCallable.Factory qualified as AutoCallable (Factory(..)) import Daml.Finance.Instrument.StructuredProduct.BarrierReverseConvertible.Factory qualified as BarrierReverseConvertible (Factory(..)) +import Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Factory qualified as AutoCallableFactory (Create(..), I(..)) +import Daml.Finance.Interface.Instrument.StructuredProduct.AutoCallable.Types (AutoCallable(..)) import Daml.Finance.Interface.Instrument.StructuredProduct.BarrierReverseConvertible.Factory qualified as BarrierReverseConvertibleFactory (Create(..), I(..)) import Daml.Finance.Interface.Instrument.StructuredProduct.BarrierReverseConvertible.Types (BarrierReverseConvertible(..)) import Daml.Finance.Interface.Types.Common.Types (HoldingStandard(..), Id(..), InstrumentKey(..), Parties) @@ -58,3 +61,51 @@ originateBarrierReverseConvertible depository issuer label holdingStandard descr observers = Map.fromList observers -- CREATE_BARRIER_REVERSE_CONVERTIBLE_INSTRUMENT_END pure instrument + +-- | Originate an AutoCallable instrument. +originateAutoCallable : Party -> Party -> Text -> HoldingStandard -> Text -> [(Text, Parties)] + -> Time -> Decimal -> Decimal -> Decimal -> Decimal -> InstrumentKey -> Text + -> Decimal -> PeriodicSchedule -> PeriodicSchedule -> [Text] -> DayCountConventionEnum -> Decimal + -> Party -> Party -> Script InstrumentKey +originateAutoCallable depository issuer label holdingStandard description observers + lastEventTimestamp putStrike couponBarrier callBarrier finalBarrier currency referenceAssetId + couponRate observationSchedule periodicSchedule holidayCalendarIds dayCountConvention notional + calendarDataProvider publicParty = do + -- Create an AutoCallable factory + factoryCid <- toInterfaceContractId @AutoCallableFactory.I <$> submit issuer do + createCmd AutoCallable.Factory with + provider = issuer + observers = mempty + + -- CREATE_AUTO_CALLABLE_INSTRUMENT_BEGIN + let + instrument = InstrumentKey with + issuer + depository + id = Id label + version = "0" + holdingStandard + + cid <- submitMulti [issuer] [publicParty] do + exerciseCmd factoryCid AutoCallableFactory.Create with + autoCallable = AutoCallable with + instrument + description + putStrike + couponBarrier + callBarrier + finalBarrier + referenceAssetId + couponRate + observationSchedule + periodicSchedule + holidayCalendarIds + calendarDataProvider + dayCountConvention + notional + currency + lastEventTimestamp + prevEvents = [] + observers = Map.fromList observers + -- CREATE_AUTO_CALLABLE_INSTRUMENT_END + pure instrument