Mathema(MCPx) Structured Derivatives Product Definition and Payoff Script Usage Guide
Mathema(MCPx) Structured Derivatives Product Definition and Payoff Script Usage Guide
Table of Contents
- Overview
- Product Structure Definition
- Schedule Definition
- Payoff Script Syntax
- Product Examples
- Best Practices
Overview
Structured derivatives product definitions use YAML format to describe the structural characteristics, observation schedules, and payoff calculation logic of over-the-counter (OTC) derivatives. Each product definition consists of three main sections:
- Structure Definition (structure): Defines product fields, parameters, and constraints.
- Schedules (schedules): Defines sequences of observation dates (optional).
- Payoff Script (payoff): Defines payoff calculation logic.
Product Structure Definition
Basic Structure
packages:
- name: ProductName
structure:
buy_sell_field: "" # Buy/sell direction field (empty string indicates internal processing)
dates: [] # List of date fields
strikes: [] # Strike/barrier price fields
barriers: [] # List of barrier prices
yields: [] # Yield fields
percentages: [] # Percentage fields
arguments: [] # Argument fields
amounts: [] # Amount fields
calendars: [] # Calendar fields
basis: Act360 # Day count basis
strikes_relation: "" # Strike price relationship constraint (optional)
schedules: [] # Schedule definitions (optional)
payoff: {} # Payoff script definitionField Type Descriptions
1. Date Fields (dates)
Define key dates in the product lifecycle:
dates:
- StartDate # Product start date
- ExpiryDate # Product expiry date
- EndDate # Product end date (final settlement date)
- PremiumDate # Premium payment dateUsage:
- Referenced in schedules via
@StartDate - Referenced in Payoff scripts via the string
"@StartDate" - Used in date calculation functions (e.g.,
daysBetween,monthsBetween)
2. Strike/Barrier Price Fields (strikes)
Define the product's strike or barrier prices:
strikes:
- UpperBarrier # Upper barrier price
- LowerBarrier # Lower barrier price
- Strike # Strike priceConstraints:
strikes_relation: UpperBarrier > LowerBarrier # Define relationships between strike prices3. Barrier Price List (barriers)
Used to define additional barrier prices (semantic difference from strikes):
barriers:
- KnockInBarrier
- KnockOutBarrier4. Yield Fields (yields)
Define yield parameters:
yields:
- LowYield # Low yield
- MidYield # Mid yield
- HighYield # High yield5. Percentage Fields (percentages)
Define percentage parameters:
percentages:
- ParticipateRate # Participation rate6. Argument Fields (arguments)
Define other product parameters; supports only numbers, not strings:
arguments:
- CallPutType # Call/put type (0=Call, 1=Put)
- ObservationRate # Observation price
- LockPeriod # Lock-up period (in months)7. Amount Fields (amounts)
Define amount parameters:
amounts:
- Notional # Notional amount8. Calendar Fields (calendars)
Define trading calendars. After definition here, a corresponding field name must exist in the product parameters:
calendars:
- Calendar # Trading calendar9. Day Count Basis (basis)
Define the day count basis:
Act360: Actual days/360Act365Fixed: Fixed 365 days
Schedule Definition
Schedules are used to define sequences of observation dates throughout the product lifecycle.
Basic Syntax
schedules:
- name: ScheduleName # Schedule name (used in Payoff scripts)
start: "@StartDate" # Start date (reference to a date field)
end: "@EndDate" # End date
frequency: Daily # Frequency: Daily, Weekly, Monthly, Quarterly, Yearly
calendar: "@Calendar" # Trading calendar
date_adjuster: Following # Date adjustment rule
end_to_end: true # Whether to include the end date
long_stub: false # Long stub handling
end_stub: false # End stub handlingFrequency Options
Daily: DailyWeekly: WeeklyMonthly: MonthlyQuarterly: QuarterlyYearly: Yearly
Date Adjustment Rules (date_adjuster)
Actual: Use actual date, no adjustment.Following: Adjust to the next business day.ModifiedFollowing: Modified Following rule.Preceding: Adjust to the previous business day.
Example
schedules:
# Knock-out observation schedule (monthly observation)
- name: KnockOutObservationSchedule
start: "@StartDate"
end: "@ExpiryDate"
frequency: Monthly
calendar: "@Calendar"
date_adjuster: Following
end_to_end: true
long_stub: false
end_stub: false
# Knock-in observation schedule (daily observation)
- name: KnockInObservationSchedule
start: "@StartDate"
end: "@ExpiryDate"
frequency: Daily
calendar: "@Calendar"
date_adjuster: Actual
end_to_end: true
long_stub: false
end_stub: falsePayoff Script Syntax
The Payoff script defines the logic for calculating product payoffs at specific points in time. The script executes on specific dates or schedules.
Execution Timings
Payoff scripts can execute at the following timings:
- ReferenceDate: Reference date (product start date), used for variable initialization.
- Date Fields: Such as
ExpiryDate,EndDate, etc. - Schedule Names: Such as
ObservationSchedule,KnockOutObservationSchedule, etc.
Basic Syntax
1. Variable Assignment
VariableName = Value # Simple assignment
VariableName = Expression # Expression assignmentExample:
InitialPrice = Spot() # Get current underlying price
Yield = 0 # Initialize yield
aLive = 1 # Set status flag2. Conditional Statements
if (Condition) then
Statement1
Statement2
endIf
if (Condition) then
Statement1
else
Statement2
endIf
if (Condition1) then
Statement1
else if (Condition2) then
Statement2
else
Statement3
endIfExample:
if (Spot() >= UpperBarrier) then
IsKnockedOut = 1
endIf
if (aLive = 0) then
Yield = MidYield
else
Yield = LowYield
endIf3. Built-in Functions
Price-Related Functions
Spot(): Gets the current underlying price.Spot(date): Gets the underlying price at the specified date.
Time-Related Functions
t(): Gets the annualized time from the reference date to the current date (in years).currentDate(): Gets the current date.daysBetween(date1, date2): Calculates the number of days between two dates.monthsBetween(date1, date2): Calculates the number of months between two dates.
Example:
DaysHeld = daysBetween("@StartDate", currentDate())
MonthsFromStart = monthsBetween("@StartDate", currentDate())
MaturityYears = daysBetween("@StartDate", "@EndDate") / 365.0Mathematical Functions
max(a, b): Returns the greater of two values.min(a, b): Returns the lesser of two values.abs(x): Returns the absolute value.exp(x): Exponential function.log(x): Natural logarithm.sqrt(x): Square root.
Example:
Yield = LowYield + max(0, Spot() - ObservationRate) / ObservationRate * ParticipateRate
PriceRatio = max(0, FinalPrice / InitialPrice)4. Payment Statement
The final payment is defined via the opt pays statement:
opt pays Expression # Basic payment
opt pays Expression1 + Expression2 # Compound paymentExample:
opt pays Notional * Yield * t() # Pay coupon
opt pays FinalPayment + Notional # Pay final payoff and principal
opt pays Notional * (HighYield * M1/N + LowYield * M2/N) * t() # Range accrual paymentNote: The difference between 'pays' and '=' is that 'pays' automatically discounts the result.
5. Auxiliary Payment Statements
Besides opt pays, other auxiliary payments can be defined:
df pays Expression # Discount factor payment
ratio1 pays Expression # Ratio 1 payment
ratio2 pays Expression # Ratio 2 paymentExample:
df pays N/N # Discount factor = 1
ratio1 pays N_KO/N/df # Knock-out ratio
ratio2 pays N_LOW/N/df # Knock-in ratioVariable Scope
- Variables maintain their state throughout the product lifecycle.
- Variables initialized in
ReferenceDatecan be accessed at all subsequent timings. - Variable values are updated when schedules or dates are executed.
Complete Example
# ReferenceDate: Initialization
ReferenceDate: |
InitialPrice = Spot()
IsKnockedOut = 0
IsKnockedIn = 0
Yield = 0
# KnockInObservationSchedule: Daily knock-in check
KnockInObservationSchedule: |
if (IsKnockedOut = 0 and IsKnockedIn = 0) then
if (Spot() <= LowerBarrier) then
IsKnockedIn = 1
endIf
endIf
# ExpiryDate: Expiry settlement
ExpiryDate: |
if (IsKnockedOut = 1) then
FinalPayment = CouponPayment
else if (IsKnockedIn = 0) then
FinalPayment = Notional * (MidYield / 100.0) * MaturityYears
else
FinalPayment = Notional * (FinalPrice / InitialPrice - 1)
endIf
# EndDate: Final payment
EndDate: |
opt pays FinalPayment + NotionalProduct Examples
1. Digital Call
Product Description: Pays a fixed yield if the underlying price at expiry is above the barrier; otherwise pays 0.
packages:
- name: DigitaCall
structure:
dates:
- StartDate
- ExpiryDate
- EndDate
- PremiumDate
strikes:
- UpperBarrier
arguments:
- HighYield
amounts:
- Notional
calendars:
- Calendar
basis: Act360
payoff:
ReferenceDate: |
FinalRate = HighYield
ExpiryDate: |
if (Spot() >= UpperBarrier and FinalRate != 0) then
FinalRate = HighYield
else
FinalRate = 0
endIf
EndDate: |
opt pays t() * Notional * FinalRateKey Points:
- No schedules, judgement only at expiry.
- Uses
FinalRatevariable to record final yield. - Uses
t()to calculate annualized time.
2. Shark Fin
Product Description: During the observation period, if the underlying price touches the barrier, the product knocks out and receives a mid-level yield; otherwise, calculates the yield based on underlying price performance, but with an upper limit.
packages:
- name: SharkFin
structure:
dates:
- StartDate
- EndDate
- ExpiryDate
- PremiumDate
strikes:
- Barrier
- Strike
percentages:
- ParticipateRate
arguments:
- LowYield
- MidYield
- HighYield
- CallPutType
- ObservationRate
amounts:
- Notional
calendars:
- Calendar
basis: Act360
schedules:
- name: ObservationSchedule
start: "@StartDate"
end: "@EndDate"
frequency: Daily
calendar: "@Calendar"
date_adjuster: Actual
end_to_end: true
payoff:
ReferenceDate: |
aLive = 1
Yield = 0
ObservationRate = Spot()
N = 1
N_KO = 0
N_LOW = 0
ObservationSchedule: |
if (CallPutType = 0) then
if (aLive = 1 and Spot() >= Barrier) then
aLive = 0
endIf
else
if (aLive = 1 and Spot() < Barrier) then
aLive = 0
endIf
endif
ExpiryDate: |
if (aLive = 0) then
Yield = MidYield
N_KO = 1
else
if (CallPutType = 0) then
Yield = LowYield + (max(0, Spot() - ObservationRate) / ObservationRate) * ParticipateRate
if (Spot() < ObservationRate) then
N_LOW = 1
endIf
else
Yield = LowYield + (max(0, ObservationRate - Spot()) / ObservationRate) * ParticipateRate
if (Spot() > ObservationRate) then
N_LOW = 1
endIf
endIf
endIf
if (Yield > HighYield) then
Yield = HighYield
endif
EndDate: |
opt pays Notional * Yield * t()
df pays N/N
ratio1 pays N_KO/N/df
ratio2 pays N_LOW/N/dfKey Points:
- Uses
aLiveflag to track if the product has knocked out. - Daily check for knock-out condition during the observation period.
- Uses
CallPutTypeto distinguish between call and put. - Yield has an upper limit protection (
HighYield).
3. Range Accrual
Product Description: During the observation period, if the underlying price is within the range, receive a high yield; otherwise receive a low yield. The final payoff is calculated based on the proportion of days within the range.
packages:
- name: RangeAccrual
structure:
dates:
- StartDate
- ExpiryDate
- EndDate
strikes:
- LowerBarrier
- UpperBarrier
strikes_relation: UpperBarrier > LowerBarrier
arguments:
- HighYield
- LowYield
amounts:
- Notional
calendars:
- Calendar
basis: Act360
schedules:
- name: ObservationSchedule
start: "@StartDate"
end: "@EndDate"
frequency: Daily
calendar: "@Calendar"
date_adjuster: Following
end_to_end: true
payoff:
ReferenceDate: |
M1 = 0 # Days within range
M2 = 0 # Days outside range
N = 0 # Total observation days
ObservationSchedule: |
N = N + 1
if (Spot() >= LowerBarrier and Spot() <= UpperBarrier) then
M1 = M1 + 1
else
M2 = M2 + 1
endIf
EndDate: |
opt pays Notional * (HighYield * M1/N + LowYield * M2/N) * t()Key Points:
- Uses counters
M1,M2,Nto count days. - Daily check if price is within range.
- Final payoff calculated proportionally.
4. Double No Touch
Product Description: If the underlying price never touches either the upper or lower barrier throughout the entire observation period, pays a high yield; otherwise pays a low yield.
packages:
- name: DoubleNoTouch
structure:
dates:
- StartDate
- EndDate
- ExpiryDate
strikes:
- LowerBarrier
- UpperBarrier
strikes_relation: UpperBarrier > LowerBarrier
arguments:
- LowYield
- HighYield
amounts:
- Notional
calendars:
- Calendar
basis: Act360
schedules:
- name: ObservationSchedule
start: "@StartDate"
end: "@EndDate"
frequency: Daily
calendar: "@Calendar"
date_adjuster: Actual
end_to_end: true
payoff:
ReferenceDate: |
aLive = 1
ObservationSchedule: |
if (aLive = 1 and (Spot() <= LowerBarrier or Spot() >= UpperBarrier)) then
aLive = 0
endIf
EndDate: |
if (aLive = 1) then
opt pays Notional * HighYield
else
opt pays Notional * LowYield
endIfKey Points:
- Uses
aLiveflag to track whether barriers have been touched. - Once touched, immediately sets
aLive = 0. - Final payment determined based on
aLivestatus.
5. Snowball Structure
Product Description: A complex product combining auto-call and knock-in mechanisms. Please refer to the detailed comments in the Snowball.yaml file.
Key Features:
- Knock-out observation: Monthly observation, starting after lock-up period.
- Knock-in observation: Daily observation.
- Three payoff scenarios:
- Early knock-out: Pays knock-out coupon.
- No knock-out and no knock-in: Pays dividend coupon.
- Knock-in but no knock-out: Bears downside loss.
Best Practices
1. Variable Naming
- Use meaningful variable names:
IsKnockedOutinstead offlag1. - Use camelCase:
InitialPrice,FinalPayment. - Use
Isprefix for boolean flags:IsKnockedIn,IsKnockedOut.
2. Initialization
- Initialize all variables in
ReferenceDate. - Set initial values for counters:
N = 0,M1 = 0. - Set initial values for status flags:
aLive = 1,IsKnockedOut = 0.
3. Condition Checking
- Use explicit comparison operators:
=for equality comparison. - Check boundary conditions:
if (m_timeToEnd < delta_time_years). - Use logical operators to combine conditions:
and,or.
4. Time Calculation
- Use
t()to get annualized time (in years). - Use
daysBetweenandmonthsBetweenfor date calculations. - Note the use of quotes for date string references:
"@StartDate"requires quotes.
5. Payment Calculation
- Use
opt paysto define final payment. - Ensure payment amount considers time factor:
* t(). - Use auxiliary payments to record intermediate results:
df pays,ratio1 pays.
6. Debugging
- Use auxiliary payments to output intermediate variable values.
- Set flag variables at key timings.
- Check if variables are within expected ranges.
7. Performance Optimization
- Avoid complex calculations in schedules.
- Move complex calculations to expiry or end dates.
- Use counters instead of repeated calculations.
Frequently Asked Questions
Q1: How to reference date fields in Payoff scripts?
A: Use the "@DateFieldName" format, note that quotes are required:
DaysHeld = daysBetween("@StartDate", currentDate())Q2: How to get the current underlying price?
A: Use the Spot() function:
CurrentPrice = Spot()Q3: How to calculate annualized time?
A: Use the t() function, which returns the annualized time from the reference date to the current date:
opt pays Notional * Yield * t()Q4: How are dates in schedules referenced?
A: In schedule definitions, use @DateFieldName (without quotes):
start: "@StartDate"
end: "@EndDate"Q5: How to define multiple schedules?
A: Define multiple schedules in the schedules list:
schedules:
- name: Schedule1
...
- name: Schedule2
...Q6: What is the variable scope?
A: Variables maintain their state throughout the entire product lifecycle. Variables initialized in ReferenceDate can be accessed and modified at all subsequent timings.
Summary
The structured derivatives product definition system provides a flexible and powerful way to define complex OTC derivatives. By properly using structure definitions, schedules, and Payoff scripts, various types of structured products can be described, from simple digital options to complex snowball structures.
Key Takeaways:
- Clearly define product fields and parameters.
- Reasonably design observation schedules.
- Use Payoff scripts to implement payoff calculation logic.
- Follow best practices to ensure code readability and maintainability.
For more product examples, please refer to other YAML files in the structured_products directory.