primo commit

This commit is contained in:
fredmaloggia
2026-01-05 19:15:03 +01:00
commit f707b9c95b
61 changed files with 2928 additions and 0 deletions

560
pricer/payoffs.py Normal file
View File

@@ -0,0 +1,560 @@
import math
from dataclasses import dataclass
from typing import List
import numpy as np
class PayoffEvaluator:
def evaluate(self, path: List[List[float]], context: "PayoffContext") -> float:
raise NotImplementedError
@dataclass
class PayoffContext:
days_to_maturity: int
r: float
capital_value: float
prot_min_val: float
pdi_barrier: float
pdi_style: str
coupon_in_memory: float
days_to_obs_y_fract: List[float]
coupon_triggers: List[float]
coupon_values: List[float]
autocall_triggers: List[float]
autocall_values: List[float]
memory_flags: List[int]
caso: str
pdi_strike: float
fattore_airbag: float
trigger_one_star: float
cap: float
days_to_obs: List[int]
volatility: List[float]
airbag: int
sigma: int
relief: int
twin_win: int
one_star: int
leva: float
nominal_amount: float
prices_ul: List[float]
class PayoffFactory:
@staticmethod
def get_evaluator(caso: str) -> PayoffEvaluator:
mapping = {
"Standard": PayoffStandard(),
"Airbag": PayoffAirbag(),
"Sigma": PayoffSigma(),
"Relief": PayoffRelief(),
"TwinWin": PayoffTwinWin(),
"OneStar": PayoffOneStar(),
"Airbag + OneStar": PayoffAirbagOneStar(),
"Sigma + OneStar": PayoffSigmaOneStar(),
"Relief + OneStar": PayoffReliefOneStar(),
"TwinWin + OneStar": PayoffTwinWinOneStar(),
"Airbag + TwinWin": PayoffAirbagTwinWin(),
}
if caso not in mapping:
raise ValueError(f"Payoff '{caso}' non gestito.")
return mapping[caso]
def _row_min(path, index: int) -> float:
if isinstance(path, np.ndarray):
return float(path[index].min())
return min(path[index])
def _row_max(path, index: int) -> float:
if isinstance(path, np.ndarray):
return float(path[index].max())
return max(path[index])
def _overall_min(path) -> float:
if isinstance(path, np.ndarray):
return float(path.min())
return _overall_min(path)
class PayoffStandard(PayoffEvaluator):
def __init__(self):
self.last_label = ""
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
payoff = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
payoff = (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
self.last_label = (
f"Autocall ? Trigger={c.autocall_triggers[k]:.2f}, Min={min_val:.2f} ? Payoff={payoff:.2f}"
)
return payoff
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
min_mat = _row_min(path, c.days_to_maturity)
min_total = _overall_min(path)
barrier_check = min_mat if c.pdi_style == "European" else min_total
if barrier_check >= c.pdi_barrier:
payoff = (c.capital_value + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
self.last_label = (
f"RIMBORSO standard ? minMat={min_mat:.4f}, minTotal={min_total:.4f} ? Payoff={payoff:.2f}"
)
else:
payoff = max(c.prot_min_val, min_mat / 100.0) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
self.last_label = (
f"PERDITA standard ? minMat={min_mat:.4f}, minTotal={min_total:.4f} ? Payoff={payoff:.2f}"
)
return payoff
self.last_label = "UNKNOWN"
return 0.0
class PayoffAirbag(PayoffEvaluator):
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
return (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
min_mat = _row_min(path, c.days_to_maturity)
min_total = _overall_min(path)
sopra_barriera = (
(c.pdi_style == "European" and min_mat >= c.pdi_barrier)
or (c.pdi_style == "American" and min_total >= c.pdi_barrier)
)
if sopra_barriera:
payoff = c.capital_value + memory
else:
payoff = max(c.prot_min_val, (min_mat * c.fattore_airbag / 100.0))
return payoff * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
return 0.0
class PayoffTwinWin(PayoffEvaluator):
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
return (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
min_mat = _row_min(path, c.days_to_maturity)
min_total = _overall_min(path)
sopra_strike = min_mat >= c.pdi_strike and c.autocall_triggers[k] == 999
sopra_barriera = (
(c.pdi_style == "European" and min_mat >= c.pdi_barrier)
or (c.pdi_style == "American" and min_total >= c.pdi_barrier)
)
if sopra_strike:
payoff = memory + min(min_mat / 100.0, c.cap)
elif sopra_barriera:
payoff = (200.0 - min_mat) * c.capital_value / 100.0 + memory
else:
payoff = max(c.prot_min_val, min_mat / 100.0) + memory
return payoff * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
return 0.0
class PayoffOneStar(PayoffEvaluator):
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
return (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
max_mat = _row_max(path, c.days_to_obs[k])
min_mat = _row_min(path, c.days_to_maturity)
min_total = _overall_min(path)
sopra_trigger = max_mat >= c.trigger_one_star
sopra_barriera = (
(c.pdi_style == "European" and min_mat >= c.pdi_barrier)
or (c.pdi_style == "American" and min_total >= c.pdi_barrier)
)
capitale = c.capital_value if (sopra_trigger or sopra_barriera) else max(c.prot_min_val, min_mat / 100.0)
cedola_finale = 0.0
if min_mat >= c.coupon_triggers[k]:
cedola_finale = memory + c.coupon_values[k]
return (
capitale * math.exp(-c.r * c.days_to_obs_y_fract[k])
+ cedola_finale * math.exp(-c.r * c.days_to_obs_y_fract[k])
+ coupons
)
return 0.0
class PayoffSigma(PayoffEvaluator):
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
return (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
min_mat = _row_min(path, c.days_to_maturity)
min_total = _overall_min(path)
sopra_barriera = (
(c.pdi_style == "European" and min_mat >= c.pdi_barrier)
or (c.pdi_style == "American" and min_total >= c.pdi_barrier)
)
if sopra_barriera:
payoff = c.capital_value + memory
else:
payoff = max(c.prot_min_val, (min_mat + c.pdi_strike - c.pdi_barrier) / 100.0)
return payoff * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
return 0.0
class PayoffRelief(PayoffEvaluator):
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
return (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
prices_at_mat = path[c.days_to_maturity]
sorted_prices = sorted(prices_at_mat)
second_min = sorted_prices[1] if len(sorted_prices) >= 2 else sorted_prices[0]
min_mat = min(prices_at_mat)
min_total = _overall_min(path)
sopra_barriera = (
(c.pdi_style == "European" and min_mat >= c.pdi_barrier)
or (c.pdi_style == "American" and min_total >= c.pdi_barrier)
)
if sopra_barriera:
payoff = c.capital_value + memory
else:
payoff = max(c.prot_min_val, second_min / 100.0)
return payoff * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
return 0.0
class PayoffAirbagOneStar(PayoffEvaluator):
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
return (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
max_mat = _row_max(path, c.days_to_obs[k])
min_mat = _row_min(path, c.days_to_maturity)
min_total = _overall_min(path)
sopra_trigger = max_mat >= c.trigger_one_star
sopra_barriera = (
(c.pdi_style == "European" and min_mat >= c.pdi_barrier)
or (c.pdi_style == "American" and min_total >= c.pdi_barrier)
)
if sopra_trigger or sopra_barriera:
capitale = c.capital_value
else:
perc = max(c.prot_min_val, min_mat / 100.0) * c.fattore_airbag
capitale = min(c.capital_value, perc)
cedola_finale = 0.0
if min_mat >= c.coupon_triggers[k]:
cedola_finale = memory + c.coupon_values[k]
return (
capitale * math.exp(-c.r * c.days_to_obs_y_fract[k])
+ cedola_finale * math.exp(-c.r * c.days_to_obs_y_fract[k])
+ coupons
)
return 0.0
class PayoffSigmaOneStar(PayoffEvaluator):
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
return (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
max_mat = _row_max(path, c.days_to_obs[k])
min_mat = _row_min(path, c.days_to_maturity)
min_total = _overall_min(path)
sopra_trigger = max_mat >= c.trigger_one_star
sopra_barriera = (
(c.pdi_style == "European" and min_mat >= c.pdi_barrier)
or (c.pdi_style == "American" and min_total >= c.pdi_barrier)
)
if sopra_trigger or sopra_barriera:
capitale = c.capital_value
else:
capitale = max(c.prot_min_val, (min_mat + c.pdi_strike - c.pdi_barrier) / 100.0)
cedola_finale = 0.0
if min_mat >= c.coupon_triggers[k]:
cedola_finale = memory + c.coupon_values[k]
return (
capitale * math.exp(-c.r * c.days_to_obs_y_fract[k])
+ cedola_finale * math.exp(-c.r * c.days_to_obs_y_fract[k])
+ coupons
)
return 0.0
class PayoffReliefOneStar(PayoffEvaluator):
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
return (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
prices_at_mat = path[c.days_to_maturity]
sorted_prices = sorted(prices_at_mat)
second_min = sorted_prices[1] if len(sorted_prices) >= 2 else sorted_prices[0]
min_mat = min(prices_at_mat)
max_mat = _row_max(path, c.days_to_obs[k])
min_total = _overall_min(path)
sopra_trigger = max_mat >= c.trigger_one_star
sopra_barriera = (
(c.pdi_style == "European" and min_mat >= c.pdi_barrier)
or (c.pdi_style == "American" and min_total >= c.pdi_barrier)
)
if sopra_trigger or sopra_barriera:
capitale = c.capital_value
else:
capitale = max(c.prot_min_val, second_min / 100.0)
cedola_finale = 0.0
if min_mat >= c.coupon_triggers[k]:
cedola_finale = memory + c.coupon_values[k]
return (
capitale * math.exp(-c.r * c.days_to_obs_y_fract[k])
+ cedola_finale * math.exp(-c.r * c.days_to_obs_y_fract[k])
+ coupons
)
return 0.0
class PayoffTwinWinOneStar(PayoffEvaluator):
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
return (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
max_mat = _row_max(path, c.days_to_obs[k])
min_mat = _row_min(path, c.days_to_maturity)
min_total = _overall_min(path)
sopra_strike = min_mat >= c.pdi_strike
sopra_barriera = (
(c.pdi_style == "European" and min_mat >= c.pdi_barrier)
or (c.pdi_style == "American" and min_total >= c.pdi_barrier)
)
if sopra_strike:
capitale = min(min_mat / 100.0, c.cap)
elif sopra_barriera:
capitale = (2 * c.capital_value) - (min_mat / 100.0)
else:
if max_mat >= c.trigger_one_star:
capitale = c.capital_value
else:
capitale = max(c.prot_min_val, min_mat / 100.0)
cedola_finale = 0.0
if min_mat >= c.coupon_triggers[k]:
cedola_finale = memory + c.coupon_values[k]
return (
capitale * math.exp(-c.r * c.days_to_obs_y_fract[k])
+ cedola_finale * math.exp(-c.r * c.days_to_obs_y_fract[k])
+ coupons
)
return 0.0
class PayoffAirbagTwinWin(PayoffEvaluator):
def evaluate(self, path: List[List[float]], c: PayoffContext) -> float:
memory = c.coupon_in_memory
coupons = 0.0
for k in range(len(c.days_to_obs)):
min_val = _row_min(path, c.days_to_obs[k])
if min_val >= c.autocall_triggers[k]:
return (c.autocall_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k]) + coupons
if min_val >= c.coupon_triggers[k]:
coupons += (c.coupon_values[k] + memory) * math.exp(-c.r * c.days_to_obs_y_fract[k])
memory = 0.0
else:
memory += c.memory_flags[k] * c.coupon_values[k]
if k == len(c.days_to_obs) - 1:
min_mat = _row_min(path, c.days_to_maturity)
if min_mat >= c.pdi_barrier:
if c.autocall_triggers[k] == 999 and min_mat >= c.pdi_strike:
return (
math.exp(-c.r * c.days_to_obs_y_fract[k])
* (memory + min(min_mat / 100.0, c.cap))
+ coupons
)
return (
math.exp(-c.r * c.days_to_obs_y_fract[k])
* ((2 * c.capital_value) + memory - (min_mat / 100.0))
+ coupons
)
return (
max(c.prot_min_val, min_mat / 100.0)
* math.exp(-c.r * c.days_to_obs_y_fract[k])
* c.fattore_airbag
+ coupons
)
return 0.0