aboutsummaryrefslogtreecommitdiffstats
path: root/python/trade_dataclasses.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/trade_dataclasses.py')
-rw-r--r--python/trade_dataclasses.py302
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