from dataclasses import field, dataclass from serenitas.ops.trade_dataclasses import Deal, Fund from serenitas.ops.headers import DealType from serenitas.analytics.dates import prev_business_day import datetime import pandas as pd from serenitas.utils.db import dbconn from typing import ClassVar from .misc import get_dir, dt_from_fname from .custodians import NT, UMB, BNY from functools import partial _bowdst_iam_cp = { "BAML_ISDA": "BOANNY", "CS": "CSITLN", "GS": "GOLINY", "BNP": "BNPBNY", "MS": "MSCILN", "JPM": "JPCBNY", "GS_FCM": "GOLDNY", } _serenitas_iam_cp = { "BAML_ISDA": "BAMSNY", "CS": "CSFBBO", "GS": "GOLDNY", "BNP": "BNPBNY", "MS": "MSCSNY", "JPM": "JPCBNY", "GS_FCM": "GOLDNY", } def get_counterparty(fund, iam_broker): match fund: case "SERCGMAST": return _serenitas_iam_cp[iam_broker] case "BOWDST": return _bowdst_iam_cp[iam_broker] def get_custodian_account(fund, iam_broker): match fund, iam_broker: case ("SERCGMAST", "WF_FCM"): return ("WELLSFCM", "WFNSCLMFCM") case ("SERCGMAST", "BAML_FCM"): return ("BOMLCM", "V0NSCLMFCM") case ("BOWDST", "GS_FCM"): return ("GS", "057363418ICE-CDS") case ("SERCGMAST", _): return ("UMB", "159260.1") case ("BOWDST", _): return ("BNY", "751254") @dataclass class IAMDeal(Deal, deal_type=DealType.IAM, table_name="iams"): trade_date: datetime.date = field(metadata={"globeop": "Trade Date"}) folder: str = field(metadata={"globeop": "Folder"}) broker: str start_money: float = field(metadata={"globeop": "StartMoney"}) currency: str = field(metadata={"globeop": "Currency"}) fund: Fund = field(metadata={"globeop": "Fund"}) maturity: datetime.date = field( default=None, metadata={"globeop": "ExpirationDate"} ) uploaded: bool = False is_offset: bool = False id: int = field(default=None, metadata={"insert": False}) dealid: str = field( default=None, metadata={ "insert": False, "globeop": "Deal Id", }, ) cash_account: str = field( default=None, metadata={"insert": False, "select": False, "globeop": "Cash Account"}, ) custodian: str = field(default=None, metadata={"insert": False, "select": False}) def to_globeop(self, action): obj = super().to_globeop(action) obj["Deal Type"] = "IamDeal" obj["CallNoticeIndicator"] = "24H" if action == "NEW" else None obj["TransactionIndicator"] = "DEPOSIT" if obj["StartMoney"] > 0 else "LOAN" obj["StartMoney"] = abs(obj["StartMoney"]) obj["DealFunction"] = "OTC" obj["MarginType"] = "Variation" obj["Basis"] = "ACT/360" obj["SettlementDate"] = self.trade_date (custodian, cash_account) = get_custodian_account(self.fund, self.broker) obj = obj | { "Custodian": custodian, "Cash Account": cash_account, "Counterparty": get_counterparty(self.fund, self.broker), } return obj @dataclass class CashReport: fund: ClassVar[str] custodian: ClassVar[str] date: datetime.date dtkey: ClassVar _conn: ClassVar[dbconn] = dbconn("dawndb") _staging_queue: ClassVar[set] = set() _insert_sql = "INSERT INTO cash_balances VALUES (%s, %s, %s, %s, %s, %s) ON CONFLICT DO NOTHING" _registry = {} def __init_subclass__(cls, fund, custodian, dtkey): cls.fund = fund cls.custodian = custodian cls.dtkey = dtkey cls._registry[ ( fund, custodian, ) ] = cls def __class_getitem__(cls, key): return cls._registry[key] def get_cash_report(self, report_prefix): self.download_reports(self.date) report_dir = get_dir(self.date) report_dir.mkdir(exist_ok=True, parents=True) p = max( [ f for f in get_dir(self.date).iterdir() if f.name.startswith(report_prefix) ], key=partial(dt_from_fname, dt_format=self.dtkey), default=None, ) if not p: raise ValueError( f"No reports found for fund: {self.fund} date: {self.date}" ) return p @classmethod def commit(cls): with cls._conn.cursor() as c: c.executemany(cls._insert_sql, cls._staging_queue) cls._conn.commit() def stage_from_row(self, row): (account, currency), amount = row self._staging_queue.add( ( prev_business_day(self.date), self.fund, f"{self.custodian} Custody Account {self.fund}", account, currency, amount, ) ) class SeleneNTCashReport( CashReport, NT, fund="ISOSEL", custodian="NT", dtkey="%Y%m%d%H%M" ): def to_db(self): p = self.get_cash_report("cash_") df = pd.read_csv(p, on_bad_lines="warn") df = df[df["T-NARR-LONG"] == "CLOSING BALANCE"] df = df[["Consolidation", "Currency code", "A-TRAN-AMT"]] df.columns = df.columns.str.replace(" |-|_", "", regex=True).str.lower() df = df.set_index(["consolidation", "currencycode"]) for row in df.itertuples(): self.stage_from_row(row) self.commit() self._staging_queue.clear() class SerenitasUMBCashReport( CashReport, UMB, fund="SERCGMAST", custodian="UMB", dtkey="%Y%m%d%H%M" ): def to_db(self): p = self.get_cash_report("umb_") df = pd.read_excel(p, skiprows=3) for row in ( df.groupby(["Portfolio #", "Currency"]).sum()["Current Balance"].items() ): self.stage_from_row(row) self.commit() self._staging_queue.clear() class BowdstBNYCashReport( CashReport, BNY, fund="BOWDST", custodian="BNY", dtkey="%Y%m%d%H%M%S" ): def to_db(self): p = self.get_cash_report("Live-cash") df = pd.read_csv(p) df["Beginning Balance Local"] = df["Beginning Balance Local"].apply( lambda s: "-" + s[1:-1] if s.startswith("(") else s ) df["Beginning Balance Local"] = pd.to_numeric( df["Beginning Balance Local"].str.replace(",", "") ) for row in ( df.groupby(["Account Number", "Local Currency Code"]) .sum()["Beginning Balance Local"] .items() ): self.stage_from_row(row) self.commit() self._staging_queue.clear()