.cdm-java.6.0.0-dev.82.source-code.observable-asset-calculatedrate-func.rosetta Maven / Gradle / Ivy
namespace cdm.observable.asset.calculatedrate : <"Support for calculated floating rates such as lookback compound or observation shift compound rate.">
version "${project.version}"
import cdm.base.math.*
import cdm.base.datetime.*
import cdm.base.datetime.daycount.*
import cdm.observable.asset.*
import cdm.product.common.schedule.*
import cdm.product.asset.floatingrate.*
import cdm.observable.asset.fro.*
// =====================================================================
//
// Calculated rate functions
//
// these functions perform the necessary calculations on calculated rates,
// such as OIS Compound, lookback compound, etc.
// These calculations are described in Section 7 of the 2021 ISDA Definitions.
//
// ======================================================================
func EvaluateCalculatedRate: <"Evaluate a calculated rate as described in the 2021 ISDA Definitions.">
inputs:
floatingRateOption FloatingRateIndex (1..1) <"The base floating rate index.">
calculationParameters FloatingRateCalculationParameters (1..1) <"Floating rate definition for the calculated rate.">
resetDates ResetDates (0..1) <"Reset structure (needed only for fallback rates, otherwise will be empty).">
calculationPeriod CalculationPeriodBase (1..1) <"Calculation period for which we want to determine the rate.">
priorCalculationPeriod CalculationPeriodBase (0..1) <"The prior calculation period (needed only for set in advance observation shift rate.">
dayCount DayCountFractionEnum (1..1) <"The day count fraction in effect on the stream.">
output:
results FloatingRateSettingDetails (1..1) <"detailed results of the floating rate calculation.">
// get the FRO
alias fro: floatingRateOption
// work out the observation dates needed and the weight for each
alias datesAndWeights:
GenerateObservationDatesAndWeights(
calculationParameters,
resetDates,
calculationPeriod,
priorCalculationPeriod
)
// get the observations
alias observationDates: datesAndWeights -> observationDates
alias observations: IndexValueObservationMultiple(observationDates, fro)
// apply observation parameters (caps/floors)
alias processedObservations: ProcessObservations(calculationParameters, observations)
// perform the calculation (compounding or averaging)
alias calculationMethod: calculationParameters -> calculationMethod
alias isCompounding: calculationMethod = CalculationMethodEnum -> Compounding
alias weights: datesAndWeights -> weights
alias yearFraction: YearFractionForOneDay(dayCount) // the year fraction of 1 business day (needed for compounding formula)
alias calculationResults:
if isCompounding
then ApplyCompoundingFormula(processedObservations, weights, yearFraction)
else ApplyAveragingFormula(observations, weights)
// record the results
set results -> calculationDetails: calculationResults
add results -> calculationDetails -> observations -> observationDates:
datesAndWeights -> observationDates
add results -> calculationDetails -> observations -> weights: datesAndWeights -> weights
add results -> calculationDetails -> observations -> observedRates: observations
add results -> calculationDetails -> observations -> processedRates:
processedObservations
set results -> floatingRate: calculationResults -> calculatedRate
func GenerateObservationDatesAndWeights: <"Apply shifts to generate the list of observation dates and weights for each of those date.">
inputs:
calculationParams FloatingRateCalculationParameters (1..1) <"Floating rate definition for the calculated rate.">
resetDates ResetDates (0..1) <"Reset structure (needed only for fallback rates, otherwise will be empty.">
calculationPeriod CalculationPeriodBase (1..1) <"Calculation period for which we want to determine the rate.">
priorCalculationPeriod CalculationPeriodBase (0..1) <"The prior calculation period (needed only for set in advance observation shift rate.">
output:
results CalculatedRateObservationDatesAndWeights (1..1) <"observation dates and corresponding weight.">
// set up some convenience accessors
alias obsShift: calculationParams -> observationShiftCalculation
alias lockout: calculationParams -> lockoutCalculation
alias specifiedLockout:
if lockout -> offsetDays exists
then lockout -> offsetDays
else 5
alias lockoutDays:
if (lockout exists) then specifiedLockout else 0
alias businessDays: GetAllBusinessCenters(calculationParams -> applicableBusinessDays)
// work out the calculation period for which the rate will actually be computed (might be the prior period or based on resetDates)
alias calculateRelative:
if obsShift -> calculationBase exists
then obsShift -> calculationBase
else ObservationPeriodDatesEnum -> Standard
alias adjustedCalculationPeriod:
ComputeCalculationPeriod(
calculationPeriod,
priorCalculationPeriod,
calculateRelative,
resetDates
)
// generate the (shifted) observation period and then the observation dates
alias observationPeriod:
DetermineObservationPeriod(adjustedCalculationPeriod, calculationParams)
alias observationDates:
GenerateObservationDates(observationPeriod, businessDays, lockoutDays)
// record the results
add results -> observationDates: observationDates
add results -> weights:
GenerateWeightings(
calculationParams,
results -> observationDates,
observationPeriod,
adjustedCalculationPeriod,
lockoutDays
)
// possibly record some other intermediate results to aid debugging/understanding?
func ComputeCalculationPeriod: <"Determine the calculation period to use for computing the calculated rate (it may not be the same as the normal calculation period, for instance if the rate is set in advance.">
inputs:
calculationPeriod CalculationPeriodBase (1..1) <"The current calculation period for which the rate is neede.">
priorCalculationPeriod CalculationPeriodBase (0..1) <"The prior actual or deemed calculation period, if neede.">
calculateRelativeTo ObservationPeriodDatesEnum (0..1) <"How the calculation is done with respect to the base calculation perio.">
resetDates ResetDates (0..1) <"The resetDates structure, if needed, e.g. for fallback rate.">
output:
result CalculationPeriodBase (1..1) <"The calculation period over which the calculated rate should be calculate.">
func ComputeCalculationPeriod(calculateRelativeTo: ObservationPeriodDatesEnum -> SetInAdvance):
set result: priorCalculationPeriod
func ComputeCalculationPeriod(calculateRelativeTo: ObservationPeriodDatesEnum -> Standard):
set result: calculationPeriod
func ComputeCalculationPeriod(calculateRelativeTo: ObservationPeriodDatesEnum -> FixingDate):
// TODO : this should use the code in Evaluate Term Rate to do the offset calculation (needs refactoring)
alias resetRelativeTo: resetDates -> resetRelativeTo
alias isStart: resetRelativeTo = ResetRelativeToEnum -> CalculationPeriodStartDate
alias calcPd:
if (isStart)
then priorCalculationPeriod
else calculationPeriod
alias fixingOffsetDays: resetDates -> fixingDates -> periodMultiplier
alias businessCenters:
GetAllBusinessCenters(resetDates -> resetDatesAdjustments -> businessCenters)
alias endDate:
AddBusinessDays(calcPd -> adjustedEndDate, fixingOffsetDays, businessCenters)
alias startDate:
AddBusinessDays(calcPd -> adjustedStartDate, fixingOffsetDays, businessCenters)
set result -> adjustedEndDate: endDate
set result -> adjustedStartDate: startDate
func DetermineObservationPeriod: <"Determine any applicable offsets/shifts for the period for observing an index, and then generate the date range to be used for observing the index, based on the calculation period, plus any applicable offsets/shift.">
inputs:
adjustedCalculationPeriod CalculationPeriodBase (1..1) <"The calculation period for which the rate is being computed, after any adjustment.">
calculationParams FloatingRateCalculationParameters (1..1) <"Floating rate definition for the calculated rate.">
output:
observationPeriod CalculationPeriodBase (1..1) <"The resulting observation period.">
// convenience alias to categorize the shift types and calculate the business days to use
alias obsShift: calculationParams -> observationShiftCalculation
alias lookback: calculationParams -> lookbackCalculation
alias businessDays: calculationParams -> applicableBusinessDays
alias additionalBusinessDays: obsShift -> additionalBusinessDays
alias allBusinessDays:
[businessDays, additionalBusinessDays]
extract GetAllBusinessCenters(item)
then flatten
// determine the shift amount
alias shift:
if (obsShift exists)
then obsShift -> offsetDays
else if lookback exists
then lookback -> offsetDays
else 0 // shift amount
// default to 5 days if not specified (this is the default value from the 2021 Definitions)
alias shiftDefaulted: if shift exists then shift else 5
// calculate and return the shifted observation period
set observationPeriod:
GenerateObservationPeriod(
adjustedCalculationPeriod,
allBusinessDays,
shiftDefaulted
)
func GenerateObservationPeriod: <"Generate the date range to be used for observing the index, based on the calculation period, plus any applicable offsets/shifts.">
inputs:
calculationPeriod CalculationPeriodBase (1..1) <"The calculation period for which the rate is being compute.">
businessCenters BusinessCenterEnum (0..*) <"The business centers to be used for shifting.">
shiftDays int (0..1) <"The amount of any shift.">
output:
observationPeriod CalculationPeriodBase (1..1) <"The resulting observation period.">
// calculate the starting and ending dates
alias calcStart: calculationPeriod -> adjustedStartDate
alias calcEnd: calculationPeriod -> adjustedEndDate
alias obsStart: AddBusinessDays(calcStart, -1 * shiftDays, businessCenters)
alias obsEnd: AddBusinessDays(calcEnd, -1 * shiftDays, businessCenters)
// record results
set observationPeriod -> adjustedStartDate: obsStart
set observationPeriod -> adjustedEndDate: obsEnd
func GenerateObservationDates: <"Generate the list of observation dates given an observation period.">
inputs:
observationPeriod CalculationPeriodBase (1..1) <"The given observation period.">
businessCenters BusinessCenterEnum (0..*) <"The observation date.">
lockoutDays int (0..1) <"The number of lockout date.">
output:
observationDates date (0..*) <"The resulting list of observation date.">
// work out the final date of the observations - we skip observing on the last day of the observation period
alias days: 1 + (if (lockoutDays exists) then lockoutDays else 0)
alias endDate:
AddBusinessDays(observationPeriod -> adjustedEndDate, -1 * days, businessCenters)
// create the list of observation dates - all business days between the start and end dates
add observationDates:
GenerateDateList(observationPeriod -> adjustedStartDate, endDate, businessCenters)
func GenerateWeightings: <"Determine the weighting dates and the corresponding weights to be used for weighting observation.">
inputs:
calculationParams FloatingRateCalculationParameters (1..1) <"Floating rate definition for the calculated rate.">
observationDates date (0..*)
observationPeriod CalculationPeriodBase (1..1) <"The resulting observation period.">
adjustedCalculationPeriod CalculationPeriodBase (1..1) <"The calculation period for which the rate is being computed, after any adjustment.">
lockoutDays int (1..1) <"The number of lockout day.">
output:
weights number (0..*) <"A vector of weights, typically numbers between 1 and 3.">
alias weightingDates:
DetermineWeightingDates(
calculationParams,
observationDates,
observationPeriod,
adjustedCalculationPeriod,
lockoutDays
)
set weights: GenerateWeights(weightingDates)
func DetermineWeightingDates: <"Determine the dates to be used for weighting observation.">
inputs:
calculationParams FloatingRateCalculationParameters (1..1) <"Floating rate definition for the calculated rate.">
observationDates date (0..*)
observationPeriod CalculationPeriodBase (1..1) <"The resulting observation period.">
adjustedCalculationPeriod CalculationPeriodBase (1..1) <"The calculation period for which the rate is being computed, after any adjustment.">
lockoutDays int (1..1) <"The number of lockout day.">
output:
weightingDates date (0..*)
// set up some convenience aliases
alias obsShift: calculationParams -> observationShiftCalculation
alias lookback: calculationParams -> lookbackCalculation
alias businessCenters:
GetAllBusinessCenters(calculationParams -> applicableBusinessDays)
// work out the date list for calculating weights
alias baseWeightingDates:
if obsShift exists
then observationDates
else GenerateObservationDates(
adjustedCalculationPeriod,
businessCenters,
lockoutDays
)
alias wtPeriod:
if (lookback exists)
then adjustedCalculationPeriod
else observationPeriod
alias weightingDatesAll:
AppendDateToList(baseWeightingDates, wtPeriod -> adjustedEndDate) // including final date to compute weight
add weightingDates: weightingDatesAll
func ProcessObservations: <"Apply daily observation parameters to rate observation. These are discussed in the 2021 ISDA Definitions, section 7.2.3 and 7.2.4.">
inputs:
calculationParameters FloatingRateCalculationParameters (1..1) <"Floating rate definition for the calculated rate.">
rawObservations number (0..*)
output:
processedObservations number (0..*)
// set up convenience aliases
alias params: calculationParameters -> observationParameters
alias cap: if params exists then params -> observationCapRate
alias floor: if params exists then params -> observationFloorRate
// apply the daily cap and floor rates using vector math operations
alias cappedObservations:
if cap exists
then VectorScalarOperation(ArithmeticOperationEnum -> Min, rawObservations, cap)
else rawObservations
alias flooredObservations:
if floor exists
then VectorScalarOperation(
ArithmeticOperationEnum -> Max,
cappedObservations,
floor
)
else cappedObservations
add processedObservations: flooredObservations
func GenerateWeights: <"Recursively creates a list of weights based on the date difference between successive days.">
inputs:
weightingDates date (0..*) <"A list of dates for which weightings are require.">
output:
weights number (0..*) <"A vector of weights, typically numbers between 1 and 3.">
alias active: weightingDates count > 1 // do we still have more than one date in the list?
alias refDate: weightingDates last // find the last date in the supplied list of dates
alias remainingDates: PopOffDateList(weightingDates) // determine the prior list of dates, i.e. omitting the last in the list
alias prevDate: remainingDates last // find the second to last date in the supplied list of dates
alias diff: DateDifference(prevDate, refDate) // the weight is the date difference between the supplied ref date and the last one in the list
alias remainingWeights: GenerateWeights(remainingDates) // recursively generate weights for earlier part of the list
// if we have >= 1 in the list, add the date difference to the list of weights from the prior part of the list, else return nothing
add weights:
if active then AppendToVector(remainingWeights, diff * 1.0) // else null (empty list)
// -----------------------------------------------
//
// Calculated rate formula processing (compounding or averaging)
//
// -----------------------------------------------
func ApplyCompoundingFormula: <"Implements the compounding formula: Product of ( 1 + (rate * weight) / basis), then backs out the final rate. This is used to support section 7.3 of the 2021 ISDA Definitions.">
inputs:
observations number (0..*) <"A vector of observation value.">
weights number (0..*) <"A vector of weights (should be same size as observations, 1 weight per observation.">
yearFrac number (1..1) <"Year fraction of a single day (i.e. 1/basis.">
output:
results CalculatedRateDetails (1..1) <"Details of the compounding calculation.">
// weight the observations
alias weightedObservations:
VectorOperation(ArithmeticOperationEnum -> Multiply, observations, weights)
// scale the weighted observations based on the basis
alias scaledAndWeightedObservations:
VectorScalarOperation(
ArithmeticOperationEnum -> Multiply,
weightedObservations,
yearFrac
)
// compute series of growth factors by adding 1 to the scaled and weighted observations
alias growthFactors:
VectorScalarOperation(
ArithmeticOperationEnum -> Add,
scaledAndWeightedObservations,
1.0
)
// compute a growth curve by successively applying the growth factors
alias growthCurve: VectorGrowthOperation(1.0, growthFactors)
// find the final value of the growth curve (the product of all the growth factors times)
alias finalValue: growthCurve last
// find the values to scale by to compute the rate
alias totalWeight: weights sum
alias overallYearFrac: totalWeight * yearFrac
// compute the final calculated rate
alias calculatedRate: (finalValue - 1) / overallYearFrac
// record results
set results -> aggregateValue: finalValue
set results -> aggregateWeight: totalWeight
set results -> calculatedRate: calculatedRate
add results -> compoundedGrowth: growthCurve
add results -> growthFactor: growthFactors
add results -> weightedRates: weightedObservations
func ApplyAveragingFormula: <"Implements the weighted arithmetic averaging formula. Sums the weighted rates and divides by the total weight. This is used to support section 7.4 of the 2021 ISDA Definitions.">
inputs:
observations number (0..*) <"a vector of observation value.">
weights number (0..*) <"a vector of weights (should be same size as observations, 1 weight per observation.">
output:
results CalculatedRateDetails (1..1) <"Details of the averaging calculation.">
// weight the observations
alias weightedObservations:
VectorOperation(ArithmeticOperationEnum -> Multiply, observations, weights)
// sum the weighted observations
alias totalWeightedObservations: weightedObservations sum
// sum the weights
alias totalWeight: weights sum
// compute the final calculated rate
alias calculatedRate: totalWeightedObservations / totalWeight
// record results
set results -> aggregateValue: totalWeightedObservations
set results -> aggregateWeight: totalWeight
set results -> calculatedRate: calculatedRate
add results -> weightedRates: weightedObservations
© 2015 - 2025 Weber Informatics LLC | Privacy Policy