primo commit
This commit is contained in:
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))
|
||||
Reference in New Issue
Block a user