diff options
Diffstat (limited to 'python/trade_dataclasses.py')
| -rw-r--r-- | python/trade_dataclasses.py | 302 |
1 files changed, 299 insertions, 3 deletions
diff --git a/python/trade_dataclasses.py b/python/trade_dataclasses.py index c18f2ece..e9c8a097 100644 --- a/python/trade_dataclasses.py +++ b/python/trade_dataclasses.py @@ -1,8 +1,8 @@ from dataclasses import dataclass, field, fields, Field from enum import Enum from io import StringIO -from headers import DealType, MTM_HEADERS -from typing import ClassVar +from headers import DealType, MTM_HEADERS, HEADERS +from typing import ClassVar, Tuple, Union from decimal import Decimal from typing import Literal import csv @@ -21,6 +21,9 @@ from serenitas.utils.remote import SftpClient from lru import LRU from psycopg.errors import UniqueViolation import logging +from serenitas.utils.remote import FtpClient, SftpClient +from pyisda.date import previous_twentieth + logger = logging.getLogger(__name__) Fund = Literal["SERCGMAST", "BRINKER", "BOWDST"] @@ -188,6 +191,63 @@ class DealKind: return None +def get_admin_headers(fund, trade_type): + if fund in ("SERCGMAST", "BOWDST", "BRINKER"): + try: + return HEADERS[trade_type] + except: + from headers.globeop_upload import globeop_IRS, globeop_TRS + + return globeop_TRS + + +def get_fname( + trade_type: Union[str, Tuple[str, str]], + fund: str = "SERCGMAST", +): + d = { + "bond": "Mortgages", + "cds": "CreditDefaultSwapDeal", + "swaption": "SwaptionDeal", + "future": "Future", + "wire": "CashFlowDeal", + "spot": "SpotDeal", + "fx_swap": "FxSwapDeal", + "capfloor": "CapFloor", + "repo": "RepoDeal", + "termination": "Termination", + "trs": "TRS", + "irs": "IRS", + } + trade_tag: str + if isinstance(trade_type, tuple): + trade_tag = d[trade_type[0]] + trade_type[1] + else: + trade_tag = d[trade_type] + + timestamp = datetime.datetime.now() + if fund == "BRINKER": + return f"LMCG_BBH_SWAP_TRADES_P.{timestamp:%Y%m%d%H%M%S}.csv" + elif fund == "SERCGMAST": + return f"Serenitas.ALL.{timestamp:%Y%m%d.%H%M%S}.{trade_tag}.csv" + elif fund == "BOWDST": + return f"Bowdst.ALL.{timestamp:%Y%m%d.%H%M%S}.{trade_tag}.csv" + + +def upload_buf(buf, dest, fund): + match fund: + case "SERCGMAST": + ftp = FtpClient.from_creds("globeop") + ftp.client.cwd("incoming") + ftp.put(buf, dest) + case "BOWDST": + sftp = SftpClient.from_creds("hm_globeop") + sftp.put(buf, dest) + case "BRINKER": + sftp = SftpClient.from_creds("bbh") + sftp.put(buf, dest) + + class Deal: _conn: ClassVar = dbconn("dawndb", application_name="autobooker") _registry = {} @@ -196,6 +256,7 @@ class Deal: _sql_insert: ClassVar[str] _sql_select: ClassVar[str] _insert_queue: ClassVar[list] = [] + _admin_queue: ClassVar[list] = [] def __class_getitem__(cls, deal_type: DealType): return cls._registry[deal_type] @@ -225,6 +286,28 @@ class Deal: ) @classmethod + def admin_upload(cls, fund, trade_type, upload): + if not cls._admin_queue: # early exit + return + buf = StringIO() + csvwriter = csv.writer(buf) + headers = get_admin_headers(fund, trade_type) + csvwriter.writerow(headers) + csvwriter.writerows( + [row.get(h, None) for h in headers] for row in cls._admin_queue + ) + buf = buf.getvalue().encode() + fname = get_fname(trade_type, fund) + dest = DAILY_DIR / str(datetime.date.today()) / fname + dest.parent.mkdir(exist_ok=True) + dest.write_bytes(buf) + if upload: + upload_buf(buf, fname, fund) + + def admin_stage(self): + self._admin_queue.append(self.to_admin()) + + @classmethod def commit(cls): with cls._conn.cursor() as c: c.executemany(cls._sql_insert, cls._insert_queue) @@ -316,6 +399,8 @@ class MTMDeal: cls.product_type = "TRN" elif deal_type == DealType.Termination: cls.product_type = "TERM" + elif deal_type == DealType.TRS: + cls.product_type = "CDI" @classmethod def mtm_upload(cls): @@ -669,7 +754,7 @@ class TerminationDeal( obj["Product Type"] = obj["product_type"] return obj - def to_globeop(self): + def to_admin(self): obj = self.serialize("globeop") obj["TerminationAmount"] *= self.factor obj["FeesPaid"] = ( @@ -834,3 +919,214 @@ class FxSwapDeal( bbg_ticket_id=line["bbg_ticket_id"], **d, ) + + +@dataclass +class TRSDeal( + MTMDeal, + Deal, + deal_type=DealType.TRS, + table_name="trs", + insert_ignore=("id", "dealid", "orig_cp", "currency", "product_type"), +): + fund: str = field( + metadata={"mtm": "Account Abbreviation", "globeop": "Fund"}, + ) + portfolio: str = field(metadata={"globeop": "Portfolio"}) + folder: str = field(metadata={"globeop": "Folder"}) + cash_account: str = field(metadata={"globeop": "Cash Account"}) + cp_code: str = field(metadata={"globeop": "Counterparty", "mtm": "Broker Id"}) + trade_date: datetime.date = field( + metadata={"globeop": "Trade Date", "mtm": "Trade Date"} + ) + effective_date: datetime.date = field( + init=False, metadata={"mtm": "Effective Date"} + ) + maturity_date: datetime.date = field(metadata={"mtm": "Maturity Date"}) + funding_index: str + buysell: bool + underlying_security: str + price: float = field(metadata={"mtm": "Initial Fixing Amount"}) + accrued: float = field(metadata={"mtm": "Initial Payment"}) + funding_freq: str + funding_daycount: str + funding_payment_roll_convention: str + funding_arrears: bool + asset_freq: str + asset_daycount: str + asset_payment_roll_convention: str + initial_margin_percentage: float = field( + metadata={"globeop": "InitialMarginPercent", "mtm": "Independent Amount (%)"} + ) + notional: float = field(metadata={"mtm": "1st Leg Notional"}) + currency: str = field(metadata={"mtm": "Currency Code"}) + interest_calc_method: str + compound_avg_frequency: str + fixing_frequency: str + cpty_id: str + id: int = field(default=None, metadata={"insert": False}) + dealid: str = field( + default=None, metadata={"insert": False, "mtm": "Swap ID", "globeop": "Deal Id"} + ) + + def __post_init__(self): + self.effective_date = self.trade_date + datetime.timedelta(days=1) + + def to_markit(self): + _trs_red = {"IBOXHY": "4J623JAA8"} + _mtm_index = {"SOFRRATE": "USD-SOFR-COMPOUND"} + obj = self.serialize("mtm") + obj["Trade ID"] = obj["Swap ID"] + obj["Initial Payment Currency"] = obj["Currency Code"] + if obj["Initial Payment"] >= 0: + obj["Transaction Code"] = "Receive" + else: + obj["Transaction Code"] = "Pay" + obj["Initial Payment"] = round(abs(obj["Initial Payment"]), 2) + obj["Product Sub Type"] = "IBOXX" # Hardcoded for now + obj["RED"] = _trs_red[obj["underlying_security"]] + obj["Transaction Type"] = "New" + obj["Protection"] = "Buy" if obj["buysell"] else "Sell" + obj["Master Document Date"] = datetime.date(2020, 12, 18) + obj["Supplement Date"] = datetime.date(2015, 2, 18) + obj["Product Type"] = self.product_type + obj["Independent Amount Payer"] = obj["Account Abbreviation"] + obj["2nd Leg Index"] = _mtm_index[obj["funding_index"]] + obj["2nd Leg Spread"] = 0 + obj["2nd Leg Initial Floating Rate"] = obj["Initial Payment"] / ( + (obj["Effective Date"] - previous_twentieth(obj["Effective Date"])).days + * obj["1st Leg Notional"] + / 360 + ) + return obj + + def to_admin(self): + obj = self.serialize("globeop") + if obj["buysell"]: + key1, key2 = "Pay", "Receive" + else: + key1, key2 = "Receive", "Pay" + d = { + f"{key1}LegRateType": "Floating", + f"{key1}UnderlyingType": "Interest", + f"{key1}FloatRate": obj["funding_index"], + f"{key1}FixedRate": 0, + f"{key1}Daycount": obj["funding_daycount"], + f"{key1}Frequency": obj["funding_freq"], + f"{key1}EffectiveDate": obj["effective_date"], + f"{key1}MaturityDate": obj["maturity_date"], + f"{key1}Notional": obj["notional"], + f"{key1}PaymentBDC": obj["funding_payment_roll_convention"], + f"{key1}Arrears": "Y" if obj["funding_arrears"] else "N", + f"{key1}InterestCalcMethod": obj["interest_calc_method"], + f"{key1}CompoundAverageFrequency": obj["compound_avg_frequency"], + f"{key1}Currency": obj["currency"], + f"{key1}FixingFrequency": obj["fixing_frequency"], + f"{key2}LegRateType": "Fixed", + f"{key2}UnderlyingType": "Bond", + f"{key2}UnderlyingSecurity": obj["underlying_security"], + f"{key2}Daycount": obj["asset_daycount"], + f"{key2}Frequency": obj["asset_freq"], + f"{key2}EffectiveDate": obj["effective_date"], + f"{key2}MaturityDate": obj["maturity_date"], + f"{key2}Notional": obj["notional"], + f"{key2}PaymentBDC": obj["asset_payment_roll_convention"], + f"{key2}Price": obj["price"], + f"{key2}Currency": obj["currency"], + } + obj["SwapType"] = "TOTAL_RETURN_SWAP" + obj["Deal Type"] = "TotalReturnSwapDeal" + obj["Action"] = "NEW" # Need to figure this out + obj["Client"] = "Serenitas" + obj["State"] = "Valid" + obj["Custodian"] = "BAC" + obj.update(d) + return obj + + +@dataclass +class IRSDeal( + # MTMDeal, + Deal, + deal_type=DealType.IRS, + table_name="irs", + insert_ignore=("id", "dealid", "orig_cp", "product_type"), +): + fund: str = field( + metadata={"mtm": "Account Abbreviation", "globeop": "Fund"}, + ) + portfolio: str = field(metadata={"globeop": "Portfolio"}) + folder: str = field(metadata={"globeop": "Folder"}) + cash_account: str = field(metadata={"globeop": "Folder"}) + cp_code: str = field(metadata={"globeop": "GiveUpCounterparty"}) + trade_date: datetime.date = field(metadata={"globeop": "Trade Date"}) + effective_date: datetime.date + maturity_date: datetime.date + payreceive: bool + fixed_rate: float + fixed_daycount: str + fixed_payment_freq: str + fixed_bdc: str + notional: float + float_index: str + float_daycount: str + float_payment_freq: str + float_bdc: str + float_arrears: bool + float_fixing_freq: str + pay_interest_calc_method: str + clearing_facility: str = field(metadata={"globeop": "ClearingFacility"}) + swap_type: str = field(metadata={"globeop": "SwapType"}) + cleared_trade_id: str + currency: str + custodian: int = field( + default=None, metadata={"insert": False, "globeop": "Custodian"}, init=False + ) + id: int = field(default=None, metadata={"insert": False}) + dealid: str = field( + default=None, metadata={"insert": False, "mtm": "Swap ID", "globeop": "Deal Id"} + ) + + def __post_init__(self): + with self._conn.cursor() as c: + c.execute( + "SELECT cp_code from accounts2 where cash_account=%s", + (self.cash_account,), + ) + (self.custodian,) = c.fetchone() + + def to_admin(self): + obj = self.serialize("globeop") + if obj["payreceive"]: + key1, key2 = "Receive", "Pay" + else: + key1, key2 = "Pay", "Receive" + d = { + f"{key1}LegRateType": "Float", + f"{key1}FloatRate": obj["float_index"], + f"{key1}Daycount": obj["float_daycount"], + f"{key1}Frequency": obj["float_payment_freq"], + f"{key1}PaymentBDC": obj["float_bdc"], + f"{key1}EffectiveDate": obj["effective_date"], + f"{key1}MaturityDate": obj["maturity_date"], + f"{key1}Notional": obj["notional"], + f"{key1}ResetArrears": "Y" if obj["float_arrears"] else "N", + f"{key1}Currency": obj["currency"], + f"{key1}FixingFrequency": obj["float_fixing_freq"], + f"{key1}InterestCalcMethod": obj["pay_interest_calc_method"], + f"{key2}LegRateType": "Fixed", + f"{key2}FixedRate": obj["fixed_rate"], + f"{key2}Daycount": obj["fixed_daycount"], + f"{key2}Frequency": obj["fixed_payment_freq"], + f"{key2}PaymentBDC": obj["fixed_bdc"], + f"{key2}EffectiveDate": obj["effective_date"], + f"{key2}MaturityDate": obj["maturity_date"], + f"{key2}Notional": obj["notional"], + f"{key2}Currency": obj["currency"], + } + obj["Deal Type"] = "InterestRateSwapDeal" + obj["Action"] = "NEW" # Need to figure this out + obj["Client"] = "Serenitas" + obj["State"] = "Valid" + obj.update(d) + return obj |
