primo commit
This commit is contained in:
10
README.md
Normal file
10
README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Pricer Python port (WIP)
|
||||||
|
|
||||||
|
This folder hosts the Python conversion of the C# pricer.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Uses the same `appsettings.json` from the parent `Pricer` project.
|
||||||
|
- Target: feature parity with the C# console app and LibraryPricer.
|
||||||
|
|
||||||
|
Run (once implemented):
|
||||||
|
python app.py
|
||||||
23
VALIDATION.md
Normal file
23
VALIDATION.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Validation checklist (C# vs Python)
|
||||||
|
|
||||||
|
Goal: verify output parity for the same ISIN and inputs.
|
||||||
|
|
||||||
|
1) Build inputs
|
||||||
|
- Use the same ISIN and the same prompts in both apps.
|
||||||
|
- Set the same number of simulations.
|
||||||
|
- Use the same dividend flag (s/n).
|
||||||
|
- Optional: set the same RNG seed in Python.
|
||||||
|
|
||||||
|
2) Compare core outputs
|
||||||
|
- Correlation matrix and volatility values.
|
||||||
|
- Fair value output line (value and date).
|
||||||
|
- Percent "Sopra/Sotto" and average gain/loss.
|
||||||
|
- Valore atteso.
|
||||||
|
|
||||||
|
3) Edge cases
|
||||||
|
- Ask = 0 and Bid > 0 (reference price changes to Bid).
|
||||||
|
- Ex-date warning when DaysToExDate > DaysToObs and last ExDate > last ObsDate.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- Monte Carlo output is stochastic; use a seed to reduce variance.
|
||||||
|
- If parity differs, compare intermediate inputs printed in the tables.
|
||||||
11
app.py
Normal file
11
app.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent
|
||||||
|
if str(ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
from pricer.cli import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
14
appsettings.json
Normal file
14
appsettings.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"FirstSolutionDB": "Driver={ODBC Driver 17 for SQL Server};Server=26.69.45.60;Database=FirstSolutionDB;UID=sa;PWD=Skyline72;"
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"**/bin",
|
||||||
|
"**/bower_components",
|
||||||
|
"**/jspm_packages",
|
||||||
|
"**/node_modules",
|
||||||
|
"**/obj",
|
||||||
|
"**/platforms"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
1
pricer/__init__.py
Normal file
1
pricer/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Pricer package."""
|
||||||
BIN
pricer/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/analytics.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/analytics.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_airbag-893.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_caso_airbag-893.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_airbag-893.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_airbag-893.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_airbag_one_star-980.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_caso_airbag_one_star-980.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_airbag_one_star-980.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_airbag_one_star-980.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_airbag_twinwin-1020.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_caso_airbag_twinwin-1020.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_airbag_twinwin-1020.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_airbag_twinwin-1020.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_one_star-961.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_caso_one_star-961.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_one_star-961.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_one_star-961.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_relief-925.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_caso_relief-925.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_relief-925.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_relief-925.py312.nbi
Normal file
Binary file not shown.
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_relief_one_star-1000.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_relief_one_star-1000.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_sigma-909.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_caso_sigma-909.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_sigma-909.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_sigma-909.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_sigma_one_star-990.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_caso_sigma_one_star-990.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_sigma_one_star-990.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_sigma_one_star-990.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_standard-877.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_caso_standard-877.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_standard-877.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_standard-877.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_twinwin-941.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_caso_twinwin-941.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_twinwin-941.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_twinwin-941.py312.nbi
Normal file
Binary file not shown.
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_caso_twinwin_one_star-1010.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_caso_twinwin_one_star-1010.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_max_row-859.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_max_row-859.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_max_row-859.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_max_row-859.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_min_overall-872.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_min_overall-872.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_min_overall-872.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_min_overall-872.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_min_row-854.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_min_row-854.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_min_row-854.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_min_row-854.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_payoff_for_path-1031.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_payoff_for_path-1031.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_payoff_for_path-1031.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_payoff_for_path-1031.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_payoffs_for_paths-1114.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_payoffs_for_paths-1114.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_payoffs_for_paths-1114.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_payoffs_for_paths-1114.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_second_min_row-864.py312.1.nbc
Normal file
BIN
pricer/__pycache__/calc._nj_second_min_row-864.py312.1.nbc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc._nj_second_min_row-864.py312.nbi
Normal file
BIN
pricer/__pycache__/calc._nj_second_min_row-864.py312.nbi
Normal file
Binary file not shown.
BIN
pricer/__pycache__/calc.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/calc.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/cli.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/cli.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/comparison.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/comparison.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/config.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/config.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/db.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/db.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/engines.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/engines.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/models.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/payoffs.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/payoffs.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/report.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/report.cpython-312.pyc
Normal file
Binary file not shown.
BIN
pricer/__pycache__/utils.cpython-312.pyc
Normal file
BIN
pricer/__pycache__/utils.cpython-312.pyc
Normal file
Binary file not shown.
1445
pricer/calc.py
Normal file
1445
pricer/calc.py
Normal file
File diff suppressed because it is too large
Load Diff
342
pricer/cli.py
Normal file
342
pricer/cli.py
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from tabulate import tabulate
|
||||||
|
from . import calc
|
||||||
|
from .comparison import clone_context, compare_fair_values
|
||||||
|
from .db import default_sql_query
|
||||||
|
from .payoffs import PayoffContext
|
||||||
|
|
||||||
|
|
||||||
|
def _prompt_with_default(prompt: str, default: str) -> str:
|
||||||
|
value = input(prompt)
|
||||||
|
return default if not value else value
|
||||||
|
|
||||||
|
|
||||||
|
def _print_table(title: str, headers: List[str], rows: List[List[object]]) -> None:
|
||||||
|
print()
|
||||||
|
print(title)
|
||||||
|
print(tabulate(rows, headers=headers, tablefmt="github"))
|
||||||
|
|
||||||
|
|
||||||
|
def get_caso_string(airbag: int, sigma: int, relief: int, twinwin: int, onestar: int) -> str:
|
||||||
|
componenti = []
|
||||||
|
if airbag == 1:
|
||||||
|
componenti.append("Airbag")
|
||||||
|
if sigma == 1:
|
||||||
|
componenti.append("Sigma")
|
||||||
|
if relief == 1:
|
||||||
|
componenti.append("Relief")
|
||||||
|
if twinwin == 1:
|
||||||
|
componenti.append("TwinWin")
|
||||||
|
if onestar == 1:
|
||||||
|
componenti.append("OneStar")
|
||||||
|
|
||||||
|
return " + ".join(componenti) if componenti else "Standard"
|
||||||
|
|
||||||
|
|
||||||
|
def restart_or_exit():
|
||||||
|
print()
|
||||||
|
print("Premere Invio per finire o R per riavviare il programma...")
|
||||||
|
restart = input()
|
||||||
|
if restart and restart.upper() == "R":
|
||||||
|
main()
|
||||||
|
else:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
version = "2.2 - [12/06/2025]"
|
||||||
|
print(f"Pricer v.{version}")
|
||||||
|
|
||||||
|
fs_db = default_sql_query()
|
||||||
|
|
||||||
|
isin = _prompt_with_default("Inserire ISIN (default IT0006767633): ", "IT0006767633")
|
||||||
|
|
||||||
|
check_isin = fs_db.check_isin(isin)
|
||||||
|
if check_isin is not None:
|
||||||
|
check_value = check_isin[0] if check_isin else None
|
||||||
|
if check_value == "KO":
|
||||||
|
print(f"ISIN {isin} non processabile!")
|
||||||
|
print("Certificati nel pricer devono avere le seguenti caratteristiche : ")
|
||||||
|
print("- Categoria: Coupon,StepUp,Bonus (con Barriera Discreta)")
|
||||||
|
print("- Status: In Quotazione")
|
||||||
|
print("- Direction: Long")
|
||||||
|
print("- Basket Type : Worst")
|
||||||
|
print("- Currency : EUR")
|
||||||
|
print("- CedolaVariabile : FALSE")
|
||||||
|
print("- Darwin : FALSE")
|
||||||
|
print("- Domino : FALSE")
|
||||||
|
print("- Magnet : FALSE")
|
||||||
|
restart_or_exit()
|
||||||
|
|
||||||
|
prezzi_eod = _prompt_with_default(
|
||||||
|
"Num. Prezzi EOD per calcolo matrice correlazione e volatilita (default 252): ", "252"
|
||||||
|
)
|
||||||
|
num_prezzi_eod = int(prezzi_eod)
|
||||||
|
|
||||||
|
corr_matrix = calc.correl_certificates(isin, num_prezzi_eod, True)
|
||||||
|
volatility = calc.volatility_certificates(isin, num_prezzi_eod, True)
|
||||||
|
|
||||||
|
is_dividend = _prompt_with_default("Considerare i dividendi ? s/n (default s): ", "s")
|
||||||
|
|
||||||
|
num_sims = int(_prompt_with_default("Inserire # simulazioni montecarlo (default = 10000) : ", "10000"))
|
||||||
|
t_montecarlo_choice = time.perf_counter()
|
||||||
|
|
||||||
|
details_ul_model = fs_db.get_details_ul(isin)
|
||||||
|
|
||||||
|
nomi_sottostanti = [r.sottostante for r in details_ul_model]
|
||||||
|
corr_rows = calc.array_to_table(corr_matrix, nomi_sottostanti)
|
||||||
|
_print_table(f"Matrix Correl {isin}", nomi_sottostanti, corr_rows)
|
||||||
|
|
||||||
|
details_ctf_model = fs_db.get_details_ctf(isin)
|
||||||
|
details_event_model = fs_db.get_details_event(isin)
|
||||||
|
details_event_exdate_model = fs_db.get_details_event_exdate(isin)
|
||||||
|
|
||||||
|
ctf = details_ctf_model[0]
|
||||||
|
tasso_interesse = ctf.tasso_interesse
|
||||||
|
days_to_maturity = ctf.days_to_maturity
|
||||||
|
pdi_style = ctf.pdi_style
|
||||||
|
pdi_strike = ctf.pdi_strike
|
||||||
|
pdi_barrier = round(ctf.pdi_barrier, 4)
|
||||||
|
capital_value = ctf.capital_value
|
||||||
|
nominal_amount = ctf.nominal_amount
|
||||||
|
bid = ctf.bid
|
||||||
|
ask = ctf.ask
|
||||||
|
last_date_price = ctf.last_date_price
|
||||||
|
coupon_in_memory = ctf.coupon_in_memory
|
||||||
|
prot_min_val = ctf.prot_min_val
|
||||||
|
airbag = ctf.air_bag
|
||||||
|
fattore_airbag = ctf.fattore_airbag
|
||||||
|
one_star = ctf.one_star
|
||||||
|
trigger_onestar = round(ctf.trigger_onestar, 4)
|
||||||
|
twin_win = ctf.twin_win
|
||||||
|
sigma = ctf.sigma
|
||||||
|
relief = ctf.relief
|
||||||
|
domino = ctf.domino
|
||||||
|
category = ctf.category
|
||||||
|
cap = ctf.cap
|
||||||
|
leva = ctf.leva
|
||||||
|
|
||||||
|
prices_ul = [r.spot_price_normalized for r in details_ul_model]
|
||||||
|
dividends = [r.dividend for r in details_ul_model]
|
||||||
|
if is_dividend != "s":
|
||||||
|
dividends = [0.0 for _ in dividends]
|
||||||
|
num_sottostanti = len(details_ul_model)
|
||||||
|
|
||||||
|
days_to_obs = [r.days_to_obs for r in details_event_model]
|
||||||
|
days_to_ex_date = [r.days_to_ex_date for r in details_event_exdate_model]
|
||||||
|
days_to_obs_y_fract = [r.days_to_obs_y_fract for r in details_event_model]
|
||||||
|
coupon_values = [r.coupon_value for r in details_event_model]
|
||||||
|
coupon_triggers = [r.coupon_trigger for r in details_event_model]
|
||||||
|
autocall_values = [r.autocall_value for r in details_event_model]
|
||||||
|
autocall_triggers = [r.autocall_trigger for r in details_event_model]
|
||||||
|
memory_flags = [r.memory for r in details_event_model]
|
||||||
|
num_eventi = len(details_event_model)
|
||||||
|
|
||||||
|
input_ul_rows = []
|
||||||
|
for i in range(num_sottostanti):
|
||||||
|
input_ul_rows.append(
|
||||||
|
[
|
||||||
|
nomi_sottostanti[i],
|
||||||
|
round(prices_ul[i], 4),
|
||||||
|
round(dividends[i], 4),
|
||||||
|
round(volatility[i], 4),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
_print_table(
|
||||||
|
f"Dati Input Sottostanti {isin}",
|
||||||
|
["Sottostante", "Price", "Dividend", "Volatility"],
|
||||||
|
input_ul_rows,
|
||||||
|
)
|
||||||
|
|
||||||
|
input_ctf_rows = [
|
||||||
|
[
|
||||||
|
round(tasso_interesse, 4),
|
||||||
|
days_to_maturity,
|
||||||
|
pdi_style,
|
||||||
|
pdi_barrier,
|
||||||
|
round(capital_value * nominal_amount, 5),
|
||||||
|
round(coupon_in_memory, 5),
|
||||||
|
round(prot_min_val, 3),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
_print_table(
|
||||||
|
f"Dati Input Certificato {isin}",
|
||||||
|
[
|
||||||
|
"TassoInteresse",
|
||||||
|
"DaysToMaturity",
|
||||||
|
"PDI Style",
|
||||||
|
"PDI Barrier",
|
||||||
|
"Capital Value",
|
||||||
|
"Coupon In Memoria",
|
||||||
|
"Prot Min Val %",
|
||||||
|
],
|
||||||
|
input_ctf_rows,
|
||||||
|
)
|
||||||
|
|
||||||
|
input_flag_rows = [
|
||||||
|
[
|
||||||
|
airbag,
|
||||||
|
round(fattore_airbag, 3),
|
||||||
|
one_star,
|
||||||
|
trigger_onestar,
|
||||||
|
twin_win,
|
||||||
|
sigma,
|
||||||
|
relief,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
_print_table(
|
||||||
|
f"Dati Input Flags {isin}",
|
||||||
|
[
|
||||||
|
"AirBag",
|
||||||
|
"Fattore Airbag",
|
||||||
|
"OneStar",
|
||||||
|
"TriggerOnestar",
|
||||||
|
"TwinWin",
|
||||||
|
"Sigma",
|
||||||
|
"Relief",
|
||||||
|
],
|
||||||
|
input_flag_rows,
|
||||||
|
)
|
||||||
|
|
||||||
|
input_event_rows = []
|
||||||
|
for i in range(num_eventi):
|
||||||
|
input_event_rows.append(
|
||||||
|
[
|
||||||
|
days_to_obs[i],
|
||||||
|
round(days_to_obs_y_fract[i], 4),
|
||||||
|
round(coupon_values[i] * nominal_amount, 6),
|
||||||
|
round(coupon_triggers[i], 6),
|
||||||
|
round(autocall_values[i] * nominal_amount, 6),
|
||||||
|
round(autocall_triggers[i], 6),
|
||||||
|
memory_flags[i],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
_print_table(
|
||||||
|
f"Dati Input Eventi {isin}",
|
||||||
|
[
|
||||||
|
"DaysToObs.",
|
||||||
|
"DaysToObsYFract",
|
||||||
|
"Cpn. Value",
|
||||||
|
"Cpn. Trigger",
|
||||||
|
"Autoc. Value",
|
||||||
|
"Autoc. Trigger",
|
||||||
|
"Memory",
|
||||||
|
],
|
||||||
|
input_event_rows,
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Elaborazione Fair value in corso...")
|
||||||
|
|
||||||
|
context_original = PayoffContext(
|
||||||
|
days_to_maturity=days_to_maturity,
|
||||||
|
r=tasso_interesse,
|
||||||
|
capital_value=capital_value,
|
||||||
|
prot_min_val=prot_min_val,
|
||||||
|
pdi_barrier=pdi_barrier,
|
||||||
|
pdi_style=pdi_style,
|
||||||
|
coupon_in_memory=coupon_in_memory,
|
||||||
|
days_to_obs_y_fract=days_to_obs_y_fract,
|
||||||
|
coupon_triggers=coupon_triggers,
|
||||||
|
coupon_values=coupon_values,
|
||||||
|
autocall_triggers=autocall_triggers,
|
||||||
|
autocall_values=autocall_values,
|
||||||
|
memory_flags=memory_flags,
|
||||||
|
caso=get_caso_string(airbag, sigma, relief, twin_win, one_star),
|
||||||
|
pdi_strike=pdi_strike,
|
||||||
|
fattore_airbag=fattore_airbag,
|
||||||
|
trigger_one_star=trigger_onestar,
|
||||||
|
cap=cap,
|
||||||
|
days_to_obs=days_to_obs,
|
||||||
|
airbag=airbag,
|
||||||
|
sigma=sigma,
|
||||||
|
relief=relief,
|
||||||
|
twin_win=twin_win,
|
||||||
|
one_star=one_star,
|
||||||
|
leva=leva,
|
||||||
|
volatility=volatility,
|
||||||
|
prices_ul=prices_ul,
|
||||||
|
nominal_amount=nominal_amount,
|
||||||
|
)
|
||||||
|
|
||||||
|
context_for_compare = clone_context(context_original)
|
||||||
|
|
||||||
|
fvalues = calc.fair_value_array(
|
||||||
|
prices_ul,
|
||||||
|
corr_matrix,
|
||||||
|
num_sottostanti,
|
||||||
|
num_sims,
|
||||||
|
tasso_interesse,
|
||||||
|
days_to_maturity,
|
||||||
|
dividends,
|
||||||
|
volatility,
|
||||||
|
days_to_obs,
|
||||||
|
days_to_obs_y_fract,
|
||||||
|
coupon_values,
|
||||||
|
coupon_triggers,
|
||||||
|
autocall_values,
|
||||||
|
autocall_triggers,
|
||||||
|
memory_flags,
|
||||||
|
coupon_in_memory,
|
||||||
|
pdi_style,
|
||||||
|
pdi_strike,
|
||||||
|
pdi_barrier,
|
||||||
|
capital_value,
|
||||||
|
prot_min_val,
|
||||||
|
airbag,
|
||||||
|
sigma,
|
||||||
|
twin_win,
|
||||||
|
relief,
|
||||||
|
fattore_airbag,
|
||||||
|
one_star,
|
||||||
|
trigger_onestar,
|
||||||
|
cap,
|
||||||
|
leva,
|
||||||
|
).fair_value_array
|
||||||
|
|
||||||
|
fv = [int(value * nominal_amount / 100) for value in fvalues]
|
||||||
|
|
||||||
|
t_engine_choice = time.perf_counter()
|
||||||
|
print(f"Tempo fino alla scelta del motore: {t_engine_choice - t_montecarlo_choice:.2f} sec")
|
||||||
|
|
||||||
|
compare_fair_values(
|
||||||
|
num_sims,
|
||||||
|
prices_ul,
|
||||||
|
corr_matrix,
|
||||||
|
num_sottostanti,
|
||||||
|
days_to_maturity,
|
||||||
|
tasso_interesse,
|
||||||
|
dividends,
|
||||||
|
context_for_compare,
|
||||||
|
nominal_amount,
|
||||||
|
isin,
|
||||||
|
ask,
|
||||||
|
bid,
|
||||||
|
last_date_price,
|
||||||
|
category,
|
||||||
|
)
|
||||||
|
|
||||||
|
t_expected_done = time.perf_counter()
|
||||||
|
print(f"Tempo fino al valore atteso: {t_expected_done - t_engine_choice:.2f} sec")
|
||||||
|
|
||||||
|
print("----------------------------------------")
|
||||||
|
|
||||||
|
if len(days_to_ex_date) > len(days_to_obs):
|
||||||
|
last_obs = days_to_obs[-1]
|
||||||
|
last_ex = days_to_ex_date[-1]
|
||||||
|
|
||||||
|
if last_obs < last_ex:
|
||||||
|
print("ATTENZIONE: POSSIBILE SOTTOSTIMA DEL FAIR VALUE")
|
||||||
|
print("\n Il calcolo del fair value presume che il coupon sia gia stato pagato,")
|
||||||
|
print(" ma in realta non e ancora maturato (la Ex-Date non e ancora stata raggiunta).")
|
||||||
|
print(" Il titolo incorpora ancora quel coupon nel suo prezzo, ma il pricer lo ha gia scontato.")
|
||||||
|
print("\n Inoltre, se erano presenti coupon in memoria, questi sono stati azzerati nel calcolo,")
|
||||||
|
print(" come se il pagamento fosse gia avvenuto.")
|
||||||
|
print(" In realta il diritto al pagamento non e ancora stato acquisito.")
|
||||||
|
print(" Questo comporta una POSSIBILE SOTTOSTIMA SIGNIFICATIVA del FAIR VALUE.")
|
||||||
|
print("\n SUGGERIMENTO: verificare manualmente l'ultimo evento osservato")
|
||||||
|
print(" e valutare l'impatto potenziale sul fair value atteso.")
|
||||||
|
|
||||||
|
restart_or_exit()
|
||||||
107
pricer/comparison.py
Normal file
107
pricer/comparison.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from . import calc
|
||||||
|
from .payoffs import PayoffContext
|
||||||
|
from .report import generate_report
|
||||||
|
|
||||||
|
|
||||||
|
def clone_context(original: PayoffContext) -> PayoffContext:
|
||||||
|
return PayoffContext(
|
||||||
|
days_to_maturity=original.days_to_maturity,
|
||||||
|
r=original.r,
|
||||||
|
capital_value=original.capital_value,
|
||||||
|
prot_min_val=original.prot_min_val,
|
||||||
|
pdi_barrier=original.pdi_barrier,
|
||||||
|
pdi_style=original.pdi_style,
|
||||||
|
coupon_in_memory=original.coupon_in_memory,
|
||||||
|
days_to_obs_y_fract=list(original.days_to_obs_y_fract),
|
||||||
|
coupon_triggers=list(original.coupon_triggers),
|
||||||
|
coupon_values=list(original.coupon_values),
|
||||||
|
autocall_triggers=list(original.autocall_triggers),
|
||||||
|
autocall_values=list(original.autocall_values),
|
||||||
|
memory_flags=list(original.memory_flags),
|
||||||
|
caso=original.caso,
|
||||||
|
pdi_strike=original.pdi_strike,
|
||||||
|
fattore_airbag=original.fattore_airbag,
|
||||||
|
trigger_one_star=original.trigger_one_star,
|
||||||
|
cap=original.cap,
|
||||||
|
days_to_obs=list(original.days_to_obs),
|
||||||
|
airbag=original.airbag,
|
||||||
|
sigma=original.sigma,
|
||||||
|
relief=original.relief,
|
||||||
|
twin_win=original.twin_win,
|
||||||
|
one_star=original.one_star,
|
||||||
|
leva=original.leva,
|
||||||
|
volatility=list(original.volatility),
|
||||||
|
prices_ul=list(original.prices_ul),
|
||||||
|
nominal_amount=original.nominal_amount,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def compare_fair_values(
|
||||||
|
num_simulations: int,
|
||||||
|
prices_ul: List[float],
|
||||||
|
corr_matrix: List[List[float]],
|
||||||
|
num_assets: int,
|
||||||
|
days_to_maturity: int,
|
||||||
|
rate: float,
|
||||||
|
dividends: List[float],
|
||||||
|
context: PayoffContext,
|
||||||
|
nominal_amount: float,
|
||||||
|
isin: str,
|
||||||
|
ask: float,
|
||||||
|
bid: float,
|
||||||
|
last_date,
|
||||||
|
category: str,
|
||||||
|
) -> None:
|
||||||
|
print(f"Motore GBM con payoff engine '{context.caso}'")
|
||||||
|
|
||||||
|
results = calc.simulate_payoffs(
|
||||||
|
num_simulations,
|
||||||
|
prices_ul,
|
||||||
|
corr_matrix,
|
||||||
|
days_to_maturity,
|
||||||
|
rate,
|
||||||
|
dividends,
|
||||||
|
context.volatility,
|
||||||
|
context.days_to_obs,
|
||||||
|
context.days_to_obs_y_fract,
|
||||||
|
context.coupon_values,
|
||||||
|
context.coupon_triggers,
|
||||||
|
context.autocall_values,
|
||||||
|
context.autocall_triggers,
|
||||||
|
context.memory_flags,
|
||||||
|
context.coupon_in_memory,
|
||||||
|
context.pdi_style,
|
||||||
|
context.pdi_strike,
|
||||||
|
context.pdi_barrier,
|
||||||
|
context.capital_value,
|
||||||
|
context.prot_min_val,
|
||||||
|
context.airbag,
|
||||||
|
context.sigma,
|
||||||
|
context.twin_win,
|
||||||
|
context.relief,
|
||||||
|
context.fattore_airbag,
|
||||||
|
context.one_star,
|
||||||
|
context.trigger_one_star,
|
||||||
|
context.cap,
|
||||||
|
)
|
||||||
|
results = [float(val) * 100.0 for val in results]
|
||||||
|
|
||||||
|
mean = sum(results) / num_simulations
|
||||||
|
variance = sum((val - mean) ** 2 for val in results) / num_simulations
|
||||||
|
stddev = variance ** 0.5
|
||||||
|
|
||||||
|
fair_value_scaled = mean * nominal_amount / 100.0
|
||||||
|
fair_values = [int(val * nominal_amount / 100) for val in results]
|
||||||
|
|
||||||
|
generate_report(
|
||||||
|
isin=isin,
|
||||||
|
category=category,
|
||||||
|
fair_value=fair_value_scaled,
|
||||||
|
fair_values=fair_values,
|
||||||
|
ask=ask,
|
||||||
|
bid=bid,
|
||||||
|
num_sims=num_simulations,
|
||||||
|
last_date_price=last_date,
|
||||||
|
)
|
||||||
17
pricer/config.py
Normal file
17
pricer/config.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection_string(name="FirstSolutionDB", settings_path=None):
|
||||||
|
"""Load connection string from appsettings.json in the parent project."""
|
||||||
|
if settings_path is None:
|
||||||
|
local_settings = Path(__file__).resolve().parents[1] / "appsettings.json"
|
||||||
|
settings_path = local_settings if local_settings.exists() else Path(__file__).resolve().parents[2] / "appsettings.json"
|
||||||
|
else:
|
||||||
|
settings_path = Path(settings_path)
|
||||||
|
|
||||||
|
data = json.loads(settings_path.read_text(encoding="utf-8"))
|
||||||
|
conn_strings = data.get("ConnectionStrings", {})
|
||||||
|
if name not in conn_strings:
|
||||||
|
raise KeyError(f"Connection string '{name}' not found in {settings_path}")
|
||||||
|
return conn_strings[name]
|
||||||
202
pricer/db.py
Normal file
202
pricer/db.py
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
from typing import Any, Dict, Iterable, List, Tuple
|
||||||
|
import pyodbc
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from .config import get_connection_string
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
def _rows_to_dicts(cursor, rows):
|
||||||
|
columns = [col[0] for col in cursor.description]
|
||||||
|
return [dict(zip(columns, row)) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_keys(row: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
normalized = {}
|
||||||
|
for k, v in row.items():
|
||||||
|
if isinstance(v, Decimal):
|
||||||
|
normalized[k.lower()] = float(v)
|
||||||
|
else:
|
||||||
|
normalized[k.lower()] = v
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
class SqlDataAccess:
|
||||||
|
def load_data(self, stored_proc: str, params: Dict[str, Any], connection_string: str):
|
||||||
|
placeholders = ", ".join([f"@{k}=?" for k in params.keys()])
|
||||||
|
sql = f"EXEC {stored_proc} {placeholders}" if placeholders else f"EXEC {stored_proc}"
|
||||||
|
values = list(params.values())
|
||||||
|
|
||||||
|
with pyodbc.connect(connection_string) as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute(sql, values)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
return _rows_to_dicts(cur, rows)
|
||||||
|
|
||||||
|
|
||||||
|
class SqlQuery:
|
||||||
|
def __init__(self, connection_string: str):
|
||||||
|
self._connection_string = connection_string
|
||||||
|
self._db = SqlDataAccess()
|
||||||
|
|
||||||
|
def get_hist_price_ul(self, isin: str, num_prezzi_eod: int):
|
||||||
|
rows = self._db.load_data(
|
||||||
|
"pricer_ULPrices1",
|
||||||
|
{"Isin": isin, "numPrezzi": num_prezzi_eod},
|
||||||
|
self._connection_string,
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
models.HistPriceULModel(
|
||||||
|
id_underlyings=r["idunderlyings"],
|
||||||
|
sottostante=r["sottostante"],
|
||||||
|
px_close=r["px_close"],
|
||||||
|
px_date=r["px_date"],
|
||||||
|
)
|
||||||
|
for r in map(_normalize_keys, rows)
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_details_ul(self, isin: str):
|
||||||
|
rows = self._db.load_data(
|
||||||
|
"pricer_ULDetails",
|
||||||
|
{"Isin": isin},
|
||||||
|
self._connection_string,
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
models.DetailsULModel(
|
||||||
|
id_underlyings=r["idunderlyings"],
|
||||||
|
sottostante=r["sottostante"],
|
||||||
|
maturity_date=r.get("maturitydate"),
|
||||||
|
last_price=r["lastprice"],
|
||||||
|
strike=r["strike"],
|
||||||
|
spot_price_normalized=r["spotpricenormalized"],
|
||||||
|
dividend=r["dividend"],
|
||||||
|
volatility=r["volatility"],
|
||||||
|
days_to_maturity=r.get("daystomaturity", 0),
|
||||||
|
tasso_interesse=r.get("tassointeresse", 0.0),
|
||||||
|
)
|
||||||
|
for r in map(_normalize_keys, rows)
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_details_event(self, isin: str):
|
||||||
|
rows = self._db.load_data(
|
||||||
|
"pricer_EventDetails",
|
||||||
|
{"Isin": isin},
|
||||||
|
self._connection_string,
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
models.DetailsEventModel(
|
||||||
|
days_to_obs=r["daystoobs"],
|
||||||
|
days_to_ex_date=r.get("daystoexdate", 0),
|
||||||
|
days_to_obs_y_fract=r["daystoobsyfract"],
|
||||||
|
coupon_value=r["couponvalue"],
|
||||||
|
coupon_trigger=r["coupontrigger"],
|
||||||
|
autocall_value=r["autocallvalue"],
|
||||||
|
autocall_trigger=r["autocalltrigger"],
|
||||||
|
memory=r["memory"],
|
||||||
|
)
|
||||||
|
for r in map(_normalize_keys, rows)
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_details_event_exdate(self, isin: str):
|
||||||
|
rows = self._db.load_data(
|
||||||
|
"pricer_EventDetails_ExDate",
|
||||||
|
{"Isin": isin},
|
||||||
|
self._connection_string,
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
models.DetailsEventModel(
|
||||||
|
days_to_obs=r.get("daystoobs", 0),
|
||||||
|
days_to_ex_date=r["daystoexdate"],
|
||||||
|
days_to_obs_y_fract=r.get("daystoobsyfract", 0.0),
|
||||||
|
coupon_value=r.get("couponvalue", 0.0),
|
||||||
|
coupon_trigger=r.get("coupontrigger", 0.0),
|
||||||
|
autocall_value=r.get("autocallvalue", 0.0),
|
||||||
|
autocall_trigger=r.get("autocalltrigger", 0.0),
|
||||||
|
memory=r.get("memory", 0),
|
||||||
|
)
|
||||||
|
for r in map(_normalize_keys, rows)
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_details_ctf(self, isin: str):
|
||||||
|
rows = self._db.load_data(
|
||||||
|
"pricer_CTFDetails",
|
||||||
|
{"Isin": isin},
|
||||||
|
self._connection_string,
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
models.DetailsCTFModel(
|
||||||
|
tasso_interesse=r["tassointeresse"],
|
||||||
|
days_to_maturity=r["daystomaturity"],
|
||||||
|
pdi_style=r["pdi_style"],
|
||||||
|
pdi_strike=r["pdi_strike"],
|
||||||
|
pdi_barrier=r["pdi_barrier"],
|
||||||
|
capital_value=r["capitalvalue"],
|
||||||
|
nominal_amount=r["nominalamount"],
|
||||||
|
bid=r["bid"],
|
||||||
|
ask=r["ask"],
|
||||||
|
last_date_price=r["lastdateprice"],
|
||||||
|
coupon_in_memory=r["couponinmemory"],
|
||||||
|
prot_min_val=r["protminval"],
|
||||||
|
air_bag=r["airbag"],
|
||||||
|
fattore_airbag=r["fattoreairbag"],
|
||||||
|
one_star=r["onestar"],
|
||||||
|
trigger_onestar=r["triggeronestar"],
|
||||||
|
twin_win=r["twinwin"],
|
||||||
|
sigma=r["sigma"],
|
||||||
|
relief=r["relief"],
|
||||||
|
domino=r["domino"],
|
||||||
|
category=r["category"],
|
||||||
|
cap=r["cap"],
|
||||||
|
leva=r["leva"],
|
||||||
|
)
|
||||||
|
for r in map(_normalize_keys, rows)
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_isin_universe(self):
|
||||||
|
rows = self._db.load_data(
|
||||||
|
"pricer_LoadISINUniverse",
|
||||||
|
{},
|
||||||
|
self._connection_string,
|
||||||
|
)
|
||||||
|
values = []
|
||||||
|
for row in rows:
|
||||||
|
if isinstance(row, dict):
|
||||||
|
if len(row) == 1:
|
||||||
|
values.append(next(iter(row.values())))
|
||||||
|
else:
|
||||||
|
values.append(row)
|
||||||
|
else:
|
||||||
|
values.append(row)
|
||||||
|
return values
|
||||||
|
|
||||||
|
def check_isin(self, isin: str):
|
||||||
|
rows = self._db.load_data(
|
||||||
|
"pricer_CheckISIN1",
|
||||||
|
{"Isin": isin},
|
||||||
|
self._connection_string,
|
||||||
|
)
|
||||||
|
values = []
|
||||||
|
for row in rows:
|
||||||
|
if isinstance(row, dict):
|
||||||
|
if len(row) == 1:
|
||||||
|
values.append(next(iter(row.values())))
|
||||||
|
else:
|
||||||
|
values.append(row)
|
||||||
|
else:
|
||||||
|
values.append(row)
|
||||||
|
return values
|
||||||
|
|
||||||
|
def bulk_update_certificates(self, table_name: str, list_fair_values: List[models.FairValues]) -> None:
|
||||||
|
if not list_fair_values:
|
||||||
|
return
|
||||||
|
|
||||||
|
sql = f"UPDATE {table_name} SET FairValue = ? WHERE ISIN = ?"
|
||||||
|
data = [(fv.fair_value, fv.isin) for fv in list_fair_values]
|
||||||
|
with pyodbc.connect(self._connection_string) as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.executemany(sql, data)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def default_sql_query():
|
||||||
|
return SqlQuery(get_connection_string("FirstSolutionDB"))
|
||||||
93
pricer/models.py
Normal file
93
pricer/models.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HistPriceULModel:
|
||||||
|
id_underlyings: int
|
||||||
|
sottostante: str
|
||||||
|
px_close: float
|
||||||
|
px_date: datetime
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DetailsULModel:
|
||||||
|
id_underlyings: int
|
||||||
|
sottostante: str
|
||||||
|
maturity_date: Optional[datetime]
|
||||||
|
last_price: float
|
||||||
|
strike: float
|
||||||
|
spot_price_normalized: float
|
||||||
|
dividend: float
|
||||||
|
volatility: float
|
||||||
|
days_to_maturity: int
|
||||||
|
tasso_interesse: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DetailsEventModel:
|
||||||
|
days_to_obs: int
|
||||||
|
days_to_ex_date: int
|
||||||
|
days_to_obs_y_fract: float
|
||||||
|
coupon_value: float
|
||||||
|
coupon_trigger: float
|
||||||
|
autocall_value: float
|
||||||
|
autocall_trigger: float
|
||||||
|
memory: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DetailsCTFModel:
|
||||||
|
tasso_interesse: float
|
||||||
|
days_to_maturity: int
|
||||||
|
pdi_style: str
|
||||||
|
pdi_strike: float
|
||||||
|
pdi_barrier: float
|
||||||
|
capital_value: float
|
||||||
|
nominal_amount: float
|
||||||
|
bid: float
|
||||||
|
ask: float
|
||||||
|
last_date_price: datetime
|
||||||
|
coupon_in_memory: float
|
||||||
|
prot_min_val: float
|
||||||
|
air_bag: int
|
||||||
|
fattore_airbag: float
|
||||||
|
one_star: int
|
||||||
|
trigger_onestar: float
|
||||||
|
twin_win: int
|
||||||
|
sigma: int
|
||||||
|
relief: int
|
||||||
|
domino: int
|
||||||
|
category: str
|
||||||
|
cap: float
|
||||||
|
leva: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FairValues:
|
||||||
|
isin: str
|
||||||
|
fair_value: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GarchParams:
|
||||||
|
omega: float
|
||||||
|
alpha: float
|
||||||
|
beta: float
|
||||||
|
sigma0: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UnderlyingStats:
|
||||||
|
nome: str
|
||||||
|
prezzi: List[float]
|
||||||
|
log_returns: List[float]
|
||||||
|
volatility: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PrezziSottostanti:
|
||||||
|
id_underlyings: int
|
||||||
|
sottostante: str
|
||||||
|
prezzi_close: List[float]
|
||||||
560
pricer/payoffs.py
Normal file
560
pricer/payoffs.py
Normal 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
|
||||||
|
|
||||||
71
pricer/report.py
Normal file
71
pricer/report.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from .utils import round_to_multiple
|
||||||
|
|
||||||
|
|
||||||
|
def generate_report(
|
||||||
|
isin: str,
|
||||||
|
category: str,
|
||||||
|
fair_value: float,
|
||||||
|
fair_values: List[int],
|
||||||
|
ask: float,
|
||||||
|
bid: float,
|
||||||
|
num_sims: int,
|
||||||
|
last_date_price,
|
||||||
|
) -> None:
|
||||||
|
rif_prezzo = ask
|
||||||
|
riferimento = "Ask"
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
if ask == 0 and bid > 0:
|
||||||
|
rif_prezzo = bid
|
||||||
|
riferimento = "Bid"
|
||||||
|
print(f"Ask = 0, preso come valore di riferimento il Bid: {bid:.2f}")
|
||||||
|
|
||||||
|
casi_over_barriera = []
|
||||||
|
casi_under_barriera = []
|
||||||
|
casi_over_ask = []
|
||||||
|
casi_under_ask = []
|
||||||
|
|
||||||
|
for value in fair_values:
|
||||||
|
if value < 100:
|
||||||
|
casi_under_barriera.append(value)
|
||||||
|
else:
|
||||||
|
casi_over_barriera.append(value)
|
||||||
|
|
||||||
|
if value >= rif_prezzo:
|
||||||
|
casi_over_ask.append(value)
|
||||||
|
else:
|
||||||
|
casi_under_ask.append(value)
|
||||||
|
|
||||||
|
perc_over_barriera = round_to_multiple(100 * len(casi_over_barriera) / len(fair_values), 0.01)
|
||||||
|
perc_over_ask = round_to_multiple(100 * len(casi_over_ask) / len(fair_values), 0.01)
|
||||||
|
perc_under_ask = 100 - perc_over_ask
|
||||||
|
|
||||||
|
print(f"Caso calcolo Fair value {isin} = {category}")
|
||||||
|
print(
|
||||||
|
f"Fair value {isin} ({num_sims} iter.) = {fair_value:.2f} (Bid = {bid}, Ask = {ask}, Last updated on {last_date_price})"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"Sopra {riferimento} nel {perc_over_ask:.2f}% dei casi, sotto {riferimento} nel {perc_under_ask:.2f}% dei casi"
|
||||||
|
)
|
||||||
|
|
||||||
|
media_over_ask = round_to_multiple(sum(casi_over_ask) / len(casi_over_ask) if casi_over_ask else 0.0, 0.01)
|
||||||
|
media_under_ask = round_to_multiple(sum(casi_under_ask) / len(casi_under_ask) if casi_under_ask else 0.0, 0.01)
|
||||||
|
print(f"Valore medio casi sopra {riferimento}: {media_over_ask:.2f}")
|
||||||
|
print(f"Valore medio casi sotto {riferimento}: {media_under_ask:.2f}")
|
||||||
|
|
||||||
|
gain_medio = round_to_multiple(media_over_ask - rif_prezzo, 0.01)
|
||||||
|
loss_medio = round_to_multiple(rif_prezzo - media_under_ask, 0.01)
|
||||||
|
print(f"Gain medio: {media_over_ask:.2f} - {rif_prezzo:.2f} = {gain_medio:.2f}")
|
||||||
|
print(f"Loss medio: {rif_prezzo:.2f} - {media_under_ask:.2f} = {loss_medio:.2f}")
|
||||||
|
|
||||||
|
perc_media_gain = round_to_multiple(100 * gain_medio / rif_prezzo, 0.01)
|
||||||
|
perc_media_loss = round_to_multiple(100 * loss_medio / rif_prezzo, 0.01)
|
||||||
|
print(f"Perc media Gain: {gain_medio:.2f} / {rif_prezzo:.2f} = {perc_media_gain:.2f}%")
|
||||||
|
print(f"Perc media Loss: {loss_medio:.2f} / {rif_prezzo:.2f} = {perc_media_loss:.2f}%")
|
||||||
|
|
||||||
|
valore_atteso = round_to_multiple((perc_over_ask / 100) * gain_medio - (perc_under_ask / 100) * loss_medio, 0.01)
|
||||||
|
perc_valore_atteso = round_to_multiple(100 * valore_atteso / rif_prezzo, 0.01)
|
||||||
|
print(f"Valore atteso: {valore_atteso:.2f} ({perc_valore_atteso:.2f}% su {riferimento})")
|
||||||
28
pricer/utils.py
Normal file
28
pricer/utils.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import math
|
||||||
|
from typing import Iterable, List
|
||||||
|
|
||||||
|
|
||||||
|
def round_to_multiple(value: float, multiple: float) -> float:
|
||||||
|
if multiple == 0:
|
||||||
|
return value
|
||||||
|
return round(value / multiple) * multiple
|
||||||
|
|
||||||
|
|
||||||
|
def cumulative_sums(values: List[float]) -> List[float]:
|
||||||
|
if not values:
|
||||||
|
return []
|
||||||
|
results = [0.0] * len(values)
|
||||||
|
results[0] = values[0]
|
||||||
|
for i in range(1, len(values)):
|
||||||
|
results[i] = results[i - 1] + values[i]
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def standard_deviation(values: Iterable[float]) -> float:
|
||||||
|
vals = list(values)
|
||||||
|
n = len(vals)
|
||||||
|
if n <= 1:
|
||||||
|
return 0.0
|
||||||
|
avg = sum(vals) / n
|
||||||
|
sum_sq = sum((v - avg) ** 2 for v in vals)
|
||||||
|
return math.sqrt(sum_sq / (n - 1))
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
numpy
|
||||||
|
pyodbc
|
||||||
|
tabulate
|
||||||
|
numba
|
||||||
Reference in New Issue
Block a user