import datetime from exchangelib import HTMLBody from serenitas.analytics.dates import prev_business_day from serenitas.utils.db import dbconn from serenitas.ops.dataclass_mapping import _citco_cp_isda from report_ops.utils import check_cleared_cds, Monitor from report_ops.misc import _recon_recipients, _cc_recipients class BondFactorMonitor( Monitor, headers=( "date", "citco_security_id", "security_id", "serenitas_factor", "admin_factor", "difference", ), num_format=[("{0:,.2f}", 3), ("{0:,.2f}", 4), ("{0:,.2f}", 5)], ): @classmethod def email(cls, fund): if not cls._staging_queue: return cls._em.send_email( f"*ACTION REQUESTED* Incorrect Factors: {fund}", HTMLBody( f""" Hello,

We see the below factor mismatches. Could you please correct the below?

{cls.to_tabulate()} """ ), to_recipients=_recon_recipients[fund], cc_recipients=_cc_recipients[fund], ) class SwaptionExerciseNotification( Monitor, headers=( "swaption_fix_id", "security_desc", "fund", "expiration_date", "strike", "clearing_agent", "putcall", "notional", "swaption_expiration_status", "exercised_fix_id", ), num_format=[("{0:,.4f}", 4), ("{0:,.2f}", 7)], ): @classmethod def email(cls, fund, expiration_date): if not cls._staging_queue: return cls._em.send_email( f"Citco Exercise Notification: {fund} Expiration: {expiration_date}", HTMLBody( f""" Hello,

We would like to notify you of the exercised/expired swaptions:

{cls.to_tabulate()} """ ), to_recipients=_recon_recipients[fund], cc_recipients=_cc_recipients[fund], ) def check_bond_factors(date, fund, conn): with conn.cursor() as c: c.execute( "SELECT citco_security_id, security_id, serenitas_factor, admin_factor, serenitas_factor-admin_factor as difference FROM compare_citco_bonds (%s, %s) WHERE abs(serenitas_factor - admin_factor) > .01;", (date, fund), ) for row in c: d = row._asdict() | {"date": date} BondFactorMonitor.stage(d) BondFactorMonitor.email(fund) BondFactorMonitor.clear() def notify_swaption_exercise(expiration_date, fund, conn): with conn.cursor() as c: c.execute( "SELECT * FROM get_exercised_swaptions(%s, %s)", ( expiration_date, fund, ), ) for row in c: d = row._asdict() if d["security_desc"].startswith("CDX IG"): d["strike"] /= 100 d["putcall"] = "PUT" if d["option_type"] == "PAYER" else "CALL" d["notional"] = d["notional"] * (1 if d["buysell"] else -1) d["swaption_fix_id"] = d["dealid"] d["exercised_fix_id"] = d["new_trade"] d["clearing_agent"] = _citco_cp_isda[d["cp_code"]] d["swaption_expiration_status"] = ( "EXERCISED" if d["new_trade"] else "EXPIRED" ) SwaptionExerciseNotification.stage(d) SwaptionExerciseNotification.email(fund, expiration_date) SwaptionExerciseNotification.clear() if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() parser.add_argument( "cob", nargs="?", type=datetime.date.fromisoformat, default=prev_business_day(datetime.date.today()), help="working date", ) args = parser.parse_args() conn = dbconn("dawndb") for fund in ("ISOSEL",): check_cleared_cds(args.cob, fund, conn) check_bond_factors(args.cob, fund, conn) notify_swaption_exercise(args.cob, fund, conn)