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()