import csv import datetime import logging import os import psycopg2 import re import sys import yaml from csv import reader from pathlib import Path BASE_DIR = Path(os.environ["BASE_DIR"]) # we do not want to depend on serenitas.utils logger = logging.getLogger(__name__) pattern1 = re.compile(r"REINVEST\[\w+::REINV_TBA(\d)\]\[DEAL,(\d+)\]=.*$") # reinv end date pattern2 = re.compile(r"(STANDARD_VAR\[)\w+(::#REINV_END,\d+\]=)(\d.*$)") # reinvprices 1 pattern3 = re.compile(r"STANDARD_VAR\[\w+::#PRICE100_TBA(\d),(\d+)\]=") pattern5 = re.compile(r"STANDARD_VAR\[\w+::#REINVPCT_TBA(\d),(\d+)\]=") pattern7 = re.compile("KINGS3") pattern8 = re.compile(r"(#COLLATREINV_REINV_PCT_EXT\[)\w+(::\*\]\[DEAL,\d+\])=100") pattern9 = re.compile(r"(?PSEVERITY\[\w+,\d+\]=)mkt\(70\)") # we use 84 so that it's both dividable by 2 and 3 global_reinvfloatpercentage = 84 global_reinvfixedpercentage = 16 def get_reinv_assets(conn, dealname, workdate): sqlstr = "SELECT * FROM et_historicaldealinfo(%s, %s) WHERE ReinvFlag" d = {} with conn.cursor() as c: c.execute(sqlstr, (dealname, workdate)) for line in c: d[line.issuername] = (line.fixedorfloat, line.liborfloor) conn.commit() return d def get_recovery(conn, dealname, workdate, defaultrecovery=50): """ compute average price of defaulted assets """ sqlstr = ( "select sum(coalesce(price, %s) * currentbalance)/sum(currentbalance) AS recov " "from et_aggdealinfo_historical(%s, %s) where defaultedflag is True" ) with conn.cursor() as c: try: c.execute(sqlstr, (defaultrecovery, dealname, workdate)) (recovery,) = c.fetchone() except (TypeError, psycopg2.DataError): recovery = defaultrecovery finally: if recovery is None: recovery = defaultrecovery conn.commit() return float(recovery) def get_reinvenddate(conn, dealname, workdate): sqlstr = "SELECT reinv_end_date FROM historical_clo_universe(%s, %s)" with conn.cursor() as c: c.execute(sqlstr, (dealname, workdate)) (reinvenddate,) = c.fetchone() conn.commit() if reinvenddate: return reinvenddate.strftime("%Y%m%d") else: raise Exception("missing reinvestment end date") def generate_scenarios(workdate, dealname, conn): prometheus = BASE_DIR / "Scenarios" / "prometheus.sss" n_scenarios = 100 basedir = BASE_DIR / "Scenarios" / f"Intex curves_{workdate}" defaultedprice = get_recovery(conn, dealname, workdate) replace = r"\g{0:.3f}".format(defaultedprice) try: with (basedir / "csv" / f"{dealname}.config").open() as fh: try: config = yaml.load(fh, Loader=yaml.FullLoader) except AttributeError: config = yaml.load(fh) except IOError: logger.error(f"{dealname}: config file doesn't exist") return reinvflag = config["reinvflag"] if reinvflag: reinvenddate = get_reinvenddate(conn, dealname, workdate) reinv_assets = get_reinv_assets(conn, dealname, workdate) n_float_assets = len([v for v in reinv_assets.values() if v[0] == "FLOAT"]) n_fixed_assets = len([v for v in reinv_assets.values() if v[0] == "FIXED"]) rollingmat = config["rollingmat"] if n_fixed_assets == 0: reinvfixedpercentage = 0 else: reinvfixedpercentage = global_reinvfixedpercentage / n_fixed_assets if n_float_assets > 0: reinvfloatpercentage = ( 100 - n_fixed_assets * reinvfixedpercentage ) / n_float_assets try: with (basedir / "csv" / f"{dealname}-reinvprices.csv").open("r") as fh: dr = csv.DictReader(fh) reinvprices = {f: [] for f in dr.fieldnames} for line in dr: for f in dr.fieldnames: try: val = float(line[f]) except ValueError: logger.error("Incorrect value in reinvprices for {dealname}") else: reinvprices[f].append(f"{val:.3f}") except IOError: reinvflag = False sssfile = basedir / "sss" / f"{dealname}.sss" (basedir / "sss").mkdir(exist_ok=True) cdrscenarios = basedir / "csv" / f"{dealname}-cdr.csv" recoveryscenarios = basedir / "csv" / f"{dealname}-recovery.csv" fhsss = sssfile.open("w") fhcdr = cdrscenarios.open("r") fhrecovery = recoveryscenarios.open("r") csvcdr = reader(fhcdr) csvrecovery = reader(fhrecovery) cdrline = next(csvcdr) cdrline = "\t".join(["{0:.3f}".format(float(cdr)) for cdr in cdrline]) recoveryline = next(csvrecovery) recoveryline = "\t".join( ["{0:.3f}".format(float(recovery)) for recovery in recoveryline] ) i = 1 with prometheus.open() as fh: for line in fh: line = line.rstrip() if "DEAL_NAME" in line: newline = f"DEAL_NAME={dealname.upper()}\r\n" fhsss.write(newline) continue if not reinvflag and pattern8.match(line): line = re.sub(pattern8, r"\1{0}\2=0".format(dealname.upper()), line) fhsss.write(line + "\r\n") continue if not reinvflag and "DO_REINV" in line: fhsss.write("DO_REINV=0" + "\r\n") continue m = pattern1.match(line) if reinvflag and m: reinv_number, scen_number = m.groups() key = "REINV_TBA" + reinv_number if key in reinv_assets: fixedorfloat, liborfloor = reinv_assets[key] if fixedorfloat == "FLOAT": coupon = 4 elif fixedorfloat == "FIXED": coupon = 7 line = "REINVEST[{0}::REINV_TBA{1}][DEAL,{2}]=".format( dealname.upper(), reinv_number, scen_number ) line += "COUP_SPR={0}|AMORT=Bullet|USE_REINVEST_PIP=1|MAT_DATE={1}|".format( coupon, rollingmat ) if liborfloor is not None: line += f"|ARM_LIFE_FLOOR={liborfloor}" fhsss.write(line + "\r\n") continue if reinvflag and pattern2.match(line): line = re.sub(pattern2, r"\1{0}\2{1}", line).format( dealname.upper(), reinvenddate ) fhsss.write(line + "\r\n") continue m = pattern3.match(line) if reinvflag and m: reinv_number, scen_number = m.groups() reinv_name = "REINV_TBA" + reinv_number if reinv_name in reinvprices: line = "STANDARD_VAR[{0}::#PRICE100_TBA{1},{2}]={3}".format( dealname.upper(), reinv_number, scen_number, " ".join(reinvprices[reinv_name]), ) fhsss.write(line + "\r\n") continue m = pattern5.match(line) if reinvflag and m: reinv_number, scen_number = m.groups() reinv_name = "REINV_TBA" + reinv_number if reinv_name in reinv_assets: if reinv_assets[reinv_name][0] == "FIXED": line = "STANDARD_VAR[{0}::#REINVPCT_TBA{1},{2}]={3}".format( dealname.upper(), reinv_number, scen_number, reinvfixedpercentage, ) elif reinv_assets[reinv_name][0] == "FLOAT": line = "STANDARD_VAR[{0}::#REINVPCT_TBA{1},{2}]={3}".format( dealname.upper(), reinv_number, scen_number, reinvfloatpercentage, ) fhsss.write(line + "\r\n") continue if pattern7.search(line): line = re.sub(pattern7, dealname.upper(), line) fhsss.write(line + "\r\n") continue begin = f"LOSS_RATE[DEAL,{i}]" if begin in line: fhsss.write(f"{begin}={cdrline}\r\n") continue begin = f"LOSS_SEVERITY[DEAL,{i}]" if begin in line: fhsss.write(f"{begin}={recoveryline}\r\n") i = i + 1 if i <= n_scenarios: cdrline = next(csvcdr) cdrline = ( "\t".join(["{0:.3f}".format(float(cdr)) for cdr in cdrline]) + "\r\n" ) recoveryline = next(csvrecovery) recoveryline = ( "\t".join( [ "{0:.3f}".format(float(recovery)) for recovery in recoveryline ] ) + "\r\n" ) continue if "LOSS_NONPERF_SEVERITY" in line: line = re.sub(pattern9, replace, line) fhsss.write(line + "\r\n") fhsss.close() fhrecovery.close() fhcdr.close() logger.info(f"generated scenarios for: {dealname}") if __name__ == "__main__": from serenitas.utils.db import dbconn if len(sys.argv) > 1: workdate = sys.argv[1] else: workdate = str(datetime.date.today()) if len(sys.argv) > 2: dealnames = sys.argv[2:] else: dealnames = [ d.stem for d in ( BASE_DIR / "Scenarios" / f"Intex curves_{workdate}" / "csv" ).iterdir() if d.suffix == ".RData" ] ET = dbconn("etdb") for dealname in dealnames: generate_scenarios(workdate, dealname, ET) ET.close()