Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new AutoCallable structured product #1195

Merged
merged 27 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e45ab4a
add files for new AutoCallable instrument
markus-da Jan 18, 2024
09872d0
run packell
markus-da Jan 18, 2024
f19da56
update instrument name
markus-da Jan 18, 2024
f9ff964
update variable names and comments
markus-da Jan 18, 2024
f231ad8
link to new instrument in test
markus-da Jan 18, 2024
b77112b
implement payoff
markus-da Jan 19, 2024
1150b71
remove unusued code
markus-da Jan 19, 2024
3538096
remove unused instrument variables
markus-da Jan 19, 2024
ea0bc94
update changelog and run packell
markus-da Jan 19, 2024
63058ea
add sanity checks
markus-da Jan 19, 2024
2a1942a
improve comments / descriptions
markus-da Jan 19, 2024
f07bdcc
rename some instrument variables
markus-da Jan 19, 2024
756cb11
fix line width
markus-da Jan 19, 2024
1d9e71d
bump version for docs
markus-da Jan 22, 2024
1242da3
reorder variables in test and clarify Basis1
markus-da Jan 22, 2024
5e22219
specify initial level and barriers as percentage of underlying close …
markus-da Jan 22, 2024
8639539
simplify fixingBusinessCenters
markus-da Jan 22, 2024
1e22cf4
improve comments
markus-da Jan 22, 2024
90593f3
rename initialFixing to putStrike
markus-da Jan 22, 2024
cbaf6c9
Update src/test/daml/Daml/Finance/Instrument/StructuredProduct/Test/U…
markus-da Jan 22, 2024
69f131e
Update src/main/daml/Daml/Finance/Instrument/StructuredProduct/Util.daml
markus-da Jan 22, 2024
82e11fd
check callableSchedule
markus-da Jan 22, 2024
a1d04e1
Merge branch 'auto-callable-yield-notes' of github.com:digital-asset/…
markus-da Jan 22, 2024
49f2326
add putStrike check
markus-da Jan 22, 2024
346a887
add comment
markus-da Jan 23, 2024
be08e1a
Merge branch 'main' into auto-callable-yield-notes
markus-da Jan 23, 2024
363db1d
run packell
markus-da Jan 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ Dependency update (patch).
### Daml.Finance.Instrument.StructuredProduct

Dependency update (patch).
Add new AutoCallable instrument (minor).

### Daml.Finance.Instrument.Swap

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion daml.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.
markus-da marked this conversation as resolved.
Show resolved Hide resolved
-- 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
Original file line number Diff line number Diff line change
@@ -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
markus-da marked this conversation as resolved.
Show resolved Hide resolved
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"
Loading