import datetime import argparse from dataclasses import field, dataclass from serenitas.ops.funds import Service from serenitas.ops.trade_dataclasses import Deal, Fund from serenitas.ops.headers import DealType from serenitas.analytics.dates import prev_business_day, next_business_day from serenitas.utils.db import dbconn _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", "BAML_FCM": "BAMSNY", "CS": "CSFBBO", "GS": "GOLDNY", "BNP": "BNPBNY", "MS": "MSCSNY", "JPM": "JPCBNY", "WELLS": "WELFEI", "CITI": "CITINY", "BARCLAYS": "BARCNY", } def get_custodian_account(fund, iam_broker): match fund, iam_broker: case ("SERCGMAST", "WELLS"): 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") def get_counterparty(fund, iam_broker): match fund: case "SERCGMAST": return _serenitas_iam_cp[iam_broker] case "BOWDST": return _bowdst_iam_cp[iam_broker] @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"}) portfolio: str = field(metadata={"globeop": "Portfolio"}) 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["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"] = "Net" obj["Collateralized"] = "Y" obj["Basis"] = "ACT/360" obj["SettlementDate"] = self.trade_date obj["ClearingFacilityCcpTradeRef"] = ( "ICE-CLEAR" if (self.broker.endswith("_FCM") or self.broker == "WELLS") else None ) (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 def cancel_old_iam_trades(fund: str, cob: datetime.date, conn) -> "Iterable": """Finds and cancels old IAM trades""" with conn.cursor() as cursor: cursor.execute( "DELETE FROM iams WHERE trade_date=%s AND fund=%s RETURNING *", (cob, fund), ) for row in cursor: trade_data = row._asdict() yield IAMDeal.from_dict(**trade_data) conn.commit() def generate_new_iam_trades(fund: str, cob: datetime.date, conn) -> "Iterable": """Generates new IAM deals""" with conn.cursor() as cursor: cursor.execute( "SELECT * FROM list_iam(%s, %s)", (cob, fund), ) for row in cursor: trade_data = row._asdict() | {"trade_date": cob} yield IAMDeal.from_dict(**trade_data) def generate_new_iam_offset_trades(fund: str, cob: datetime.date, conn) -> "Iterable": """Generates offsets if the sma has already updated these IAM deals""" _ignore = {"BOWDST": ("GS_FCM",)} with conn.cursor() as cursor: cursor.execute( "SELECT broker, currency, fund, sum(-start_money) AS start_money " "FROM list_iam(%s, %s) GROUP BY (broker, currency, fund);", (cob, fund), ) for row in cursor: if row.broker not in _ignore[fund]: trade_data = row._asdict() | { "is_offset": True, "folder": "M_CSH_CASH", "portfolio": "CASH", "trade_date": cob, } yield IAMDeal.from_dict(**trade_data) def update_matured_iam_trades(fund: str, prev_date: datetime.date, conn) -> "Iterable": """Sets previous days as matured""" with conn.cursor() as cursor: cursor.execute( "UPDATE iams SET maturity=%s WHERE maturity is NULL AND trade_date<=%s AND fund=%s RETURNING *", (next_business_day(prev_date), prev_date, fund), ) for row in cursor: trade_data = row._asdict() | {} yield IAMDeal.from_dict(**trade_data) conn.commit() def build_iam(fund: str, cob: datetime.date, conn, upload: bool): """Generates IAM file for globeop""" service = Service[fund] for old_iam in cancel_old_iam_trades(fund, cob, conn): service.push_trade(old_iam, "CANCEL") for new_iam in generate_new_iam_trades(fund, cob, conn): new_iam.stage() if fund == "BOWDST": for new_iam_offset in generate_new_iam_offset_trades(fund, cob, conn): new_iam_offset.stage() for iam in IAMDeal.commit(returning=True): service.push_trade(iam, "NEW") for update_iam in update_matured_iam_trades(fund, prev_business_day(cob), conn): service.push_trade(update_iam, "UPDATE") buf, dest = service.build_buffer(trade_type="iam") if upload: service.upload(buf, dest.name) service().clear() def parse_args(): """Parses command line arguments""" parser = argparse.ArgumentParser(description="Generate IAM file for globeop") parser.add_argument( "cob", nargs="?", type=datetime.date.fromisoformat, default=prev_business_day(datetime.date.today()), ) parser.add_argument("-n", "--no-upload", action="store_true", help="do not upload") return parser.parse_args() def main(): """Generates IAM files for globeop""" conn = dbconn("dawndb") args = parse_args() for fund in ("SERCGMAST", "BOWDST"): build_iam(fund, args.cob, conn, not args.no_upload) if __name__ == "__main__": main()