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 decimal import Decimal from typing import Literal import csv import datetime from psycopg.types.numeric import Int2BinaryDumper from psycopg import adapters from serenitas.analytics.dates import ( next_business_day, previous_twentieth, adjust_next_business_day, prev_business_day, ) from serenitas.utils.db2 import dbconn from serenitas.utils.env import DAILY_DIR from serenitas.utils.remote import SftpClient from lru import LRU from psycopg.errors import UniqueViolation import logging logger = logging.getLogger(__name__) Fund = Literal["SERCGMAST", "BRINKER", "BOWDST"] Portfolio = Literal[ "OPTIONS", "IR", "MORTGAGES", "CURVE", "TRANCHE", "CLO", "HEDGE_MAC" ] # deprecated IG, HY, STRUCTURED _funds = {"BAML": "SERCGMAST", "GS": "BOWDST", "WF": "SERCGMAST"} _fcms = { "Bank of America, N.A.": "BAML", "Goldman Sachs": "GS", "BOA": "BAML", "GOLD": "GS", "Wells Fargo Secs": "WF", } _client_name = {"SERCGMAST": "Serenitas", "BOWDST": "HEDGEMARK", "BRINKER": "LMCG"} class BusDayConvention(str, Enum): modified_following = "Modified Following" following = "Following" modified_preceding = "Modified Preceding" second_day_after = "Second-Day-After" end_of_month = "End-of-Month" DayCount = Literal["ACT/360", "ACT/ACT", "30/360", "ACT/365"] IsdaDoc = Literal["ISDA2014", "ISDA2003Cred"] class Frequency(Enum): Quarterly = 4 Monthly = 12 Ccy = Literal["USD", "CAD", "EUR", "YEN"] SwapType = Literal[ "CD_INDEX", "CD_INDEX_TRANCHE", "CD_BASKET_TRANCHE", "ABS_CDS", "BESPOKE" ] OptionType = Literal["RECEIVER", "PAYER"] ClearingFacility = Literal["ICE-CREDIT", "NOT CLEARED"] CdsStrat = Literal[ "HEDGE_CSO", "HEDGE_CLO", "HEDGE_MAC", "HEDGE_MBS", "SER_IGSNR", "SER_IGMEZ", "SER_IGEQY", "SER_IGINX", "SER_HYSNR", "SER_HYMEZ", "SER_HYEQY", "SER_HYINX", "SER_HYCURVE", "SER_IGCURVE", "SER_ITRXCURVE", "XCURVE", "MBSCDS", "IGOPTDEL", "HYOPTDEL", "HYEQY", "HYMEZ", "HYSNR", "HYINX", "IGEQY", "IGMEZ", "IGSNR", "IGINX", "XOEQY", "XOMEZ", "XOINX", "EUEQY", "EUMEZ", "EUSNR", "EUINX", "BSPK", "*", ] BondStrat = Literal[ "M_STR_MAV", "M_STR_MEZZ", "CSO_TRANCH", "M_CLO_BB20", "M_CLO_AAA", "M_CLO_BBB", "M_MTG_IO", "M_MTG_THRU", "M_MTG_GOOD", "M_MTG_B4PR", "M_MTG_RW", "M_MTG_FP", "M_MTG_LMG", "M_MTG_SD", "M_MTG_PR", "M_MTG_CRT_SD", "CRT_LD", "CRT_LD_JNR", "CRT_SD", "IGNORE", "MTG_REPO", ] SwaptionStrat = Literal[ "IGPAYER", "IGREC", "HYPAYER", "HYREC", "STEEP", "DV01", "HEDGE_MAC", ] SpotStrat = Literal[ "M_STR_MAV", "M_STR_MEZZ", "SER_IRTXCURVE", "M_CSH_CASH", "TCSH", "*" ] AssetClass = Literal["CSO", "Subprime", "CLO", "CRT"] @dataclass class Counterparty: name: str class FrequencyDumper(Int2BinaryDumper): def dump(self, f): return super().dump(f.value) adapters.register_dumper(Frequency, FrequencyDumper) def desc_str(index_type, series, tenor): if index_type in ("IG", "HY", "HYBB"): return f"CDX {index_type} CDSI S{series} {tenor}Y" elif index_type == "XO": return f"ITRX XOVER CDSI S{series} {tenor}Y" elif index_type == "EU": return f"ITRX EUR CDSI S{series} {tenor}Y" def is_default_init_field(cls, attr): match getattr(cls, attr, None): case Field(init=False): return False case _: return True class DealKind: def __class_getitem__(cls, trade_type: str): match trade_type: case "cds": return CDSDeal case "swaption": return SwaptionDeal case "termination": return TerminationDeal case _: return None class Deal: _conn: ClassVar = dbconn("dawndb", application_name="autobooker") _registry = {} _table_name: None _sql_fields: ClassVar[list[str]] _sql_insert: ClassVar[str] _sql_select: ClassVar[str] _insert_queue: ClassVar[list] = [] def __class_getitem__(cls, deal_type: DealType): return cls._registry[deal_type] def __init_subclass__(cls, deal_type: DealType, table_name: str, insert_ignore=()): super().__init_subclass__() cls._registry[deal_type] = cls cls._table_name = table_name insert_columns = [c for c in cls.__annotations__ if c not in insert_ignore] place_holders = ",".join(["%s"] * len(insert_columns)) cls._sql_fields = { c: None for c in cls.__annotations__ if is_default_init_field(cls, c) } cls._sql_insert = f"INSERT INTO {table_name}({','.join(insert_columns)}) VALUES({place_holders})" cls._sql_select = ( f"SELECT {','.join(cls._sql_fields)} FROM {table_name} WHERE id=%s" ) def stage(self): self._insert_queue.append( [ getattr(self, f.name) for f in fields(self) if f.metadata.get("insert", True) ] ) @classmethod def commit(cls): with cls._conn.cursor() as c: c.executemany(cls._sql_insert, cls._insert_queue) cls._conn.commit() cls._insert_queue.clear() @classmethod def from_tradeid(cls, trade_id: int): with cls._conn.cursor() as c: c.execute(cls._sql_select, (trade_id,)) r = c.fetchone() return cls(*r) def serialize(self, tag: str): return { f.metadata.get(tag, f.name): getattr(self, f.name) for f in fields(self) } class BbgDeal: _bbg_insert_queue: ClassVar[list] = [] _cache: ClassVar[LRU] = LRU(128) _bbg_sql_insert: ClassVar[str] def __init_subclass__(cls, deal_type, **kwargs): super().__init_subclass__(deal_type, **kwargs) if deal_type == DealType.Bond: cls._bbg_sql_insert = ( f"INSERT INTO bond_tickets VALUES({','.join(['%s'] * 20)})" ) elif deal_type == DealType.CDS: cls._bbg_sql_insert = ( f"INSERT INTO cds_tickets VALUES({','.join(['%s'] * 22)})" ) elif deal_type in (DealType.Fx, DealType.Spot, DealType.FxSwap): cls._bbg_sql_insert = ( f"INSERT INTO fx_tickets VALUES({','.join(['%s'] * 211)})" ) @classmethod def commit(cls): with cls._conn.cursor() as c: try: c.executemany(cls._bbg_sql_insert, cls._bbg_insert_queue) except UniqueViolation as e: logger.warning(e) cls._conn.rollback() else: c.executemany(cls._sql_insert, cls._insert_queue) cls._conn.commit() finally: cls._bbg_insert_queue.clear() cls._insert_queue.clear() @classmethod def process(cls, file_handle, index): for row in csv.DictReader(file_handle): line = {"bbg_ticket_id": index, **row} trade = cls.from_bbg_line(line) trade.stage() type(trade).commit() @classmethod def get_cp_code(cls, bbg_code, code_type): with cls._conn.cursor() as c: c.execute( "SELECT cp_code from bbg_ticket_mapping where bbg_code=%s and code_type=%s", (bbg_code, code_type), ) try: (cp_code,) = c.fetchone() except TypeError: raise ValueError(f"missing {bbg_code} in the db for {code_type}") return cp_code class MTMDeal: _mtm_queue: ClassVar[list] = [] _mtm_headers = None _sftp = SftpClient.from_creds("mtm") product_type: str def __init_subclass__(cls, deal_type, **kwargs): super().__init_subclass__(deal_type, **kwargs) cls._mtm_headers = MTM_HEADERS[deal_type] if deal_type == DealType.Swaption: cls.product_type = "CDISW" elif deal_type == DealType.CDS: cls.product_type = "TRN" elif deal_type == DealType.Termination: cls.product_type = "TERM" @classmethod def mtm_upload(cls): if not cls._mtm_queue: # early exit return buf = StringIO() csvwriter = csv.writer(buf) csvwriter.writerow(cls._mtm_headers) csvwriter.writerows( [row.get(h, None) for h in cls._mtm_headers] for row in cls._mtm_queue ) buf = buf.getvalue().encode() fname = f"MTM.{datetime.datetime.now():%Y%m%d.%H%M%S}.{cls.product_type.capitalize()}.csv" cls._sftp.put(buf, fname) dest = DAILY_DIR / str(datetime.date.today()) / fname dest.write_bytes(buf) cls._mtm_queue.clear() def mtm_stage(self): self._mtm_queue.append(self.to_markit()) @classmethod def from_dict(cls, **kwargs): return cls(**{k: v for k, v in kwargs.items() if k in cls._sql_fields}) @dataclass class CDSDeal( BbgDeal, MTMDeal, Deal, deal_type=DealType.CDS, table_name="cds", insert_ignore=("id", "dealid"), ): fund: Fund = field(metadata={"mtm": "Account Abbreviation"}) account_code: str cp_code: str = field(metadata={"mtm": "Broker Id"}) security_id: str = field(metadata={"mtm": "RED"}) security_desc: str maturity: datetime.date = field(metadata={"mtm": "Maturity Date"}) currency: Ccy = field(metadata={"mtm": "Currency Code"}) protection: Literal["Buy", "Sell"] notional: float = field(metadata={"mtm": "1st Leg Notional"}) fixed_rate: float = field(metadata={"mtm": "1st Leg Rate"}) upfront: float = field(metadata={"mtm": "Initial Payment"}) traded_level: Decimal effective_date: datetime.date = field( default=None, metadata={"mtm": "Effective Date"} ) portfolio: Portfolio = field(default=None) folder: CdsStrat = field(default=None) payment_rolldate: BusDayConvention = BusDayConvention.following day_count: DayCount = "ACT/360" frequency: Frequency = Frequency.Quarterly trade_date: datetime.date = field( default_factory=datetime.date.today(), metadata={"mtm": "Trade Date"} ) upfront_settle_date: datetime.date = field( default_factory=lambda: next_business_day(datetime.date.today()), metadata={"mtm": "First Payment Date"}, ) orig_attach: int = field(default=None, metadata={"mtm": "Attachment Point"}) orig_detach: int = field(default=None, metadata={"mtm": "Exhaustion Point"}) swap_type: SwapType = "CD_INDEX" clearing_facility: ClearingFacility = "ICE-CREDIT" isda_definition: IsdaDoc = "ISDA2014" id: int = field(default=None, metadata={"insert": False}) dealid: str = field(default=None, metadata={"insert": False, "mtm": "Swap ID"}) initial_margin_percentage: float = field( default=None, metadata={"mtm": "Independent Amount (%)"} ) bbg_ticket_id: str = None def __post_init__(self): start_protection = self.trade_date + datetime.timedelta(days=1) effective_date = previous_twentieth(prev_business_day(start_protection)) self.effective_date = adjust_next_business_day(effective_date) def to_markit(self): obj = self.serialize("mtm") if obj["Initial Payment"] >= 0: obj["Transaction Code"] = "Receive" else: obj["Transaction Code"] = "Pay" obj["Initial Payment"] = round(abs(obj["Initial Payment"]), 2) obj["Trade ID"] = obj["Swap ID"] obj["Product Type"] = "TRN" obj["Transaction Type"] = "NEW" obj["Protection"] = "Buy" if obj["protection"] == "Buyer" else "Sell" obj["Entity Matrix"] = "Publisher" obj["Definitions Type"] = "ISDA2014Credit" # obj["Independent Amount (%)"] = obj["initial_margin_percentage"] if "ITRX" in obj["security_desc"]: obj["Include Contractual Supplement"] = "Y" obj["Contractual Supplement"] = "StandardiTraxxEuropeTranche" return obj @classmethod def from_bbg_line(cls, line: dict): if "Seq#" in line and line["Brkr"] != "BSEF": raise ValueError("Ignoring file, we have an allocation file") if line["Coupon"] == "": with cls._conn.cursor() as c: c.execute( "SELECT coupon, index, series, tenor FROM index_desc " "WHERE redindexcode=%s AND maturity =%s", ( line["Red Code"], datetime.datetime.strptime(line["Mat Dt"], "%m/%d/%Y").date(), ), ) coupon, index, series, tenor = c.fetchone() line["Security"] = desc_str(index, series, tenor.removesuffix("yr")) line["Coupon"] = coupon if line["Brkr"] == "BSEF": # BSEF means CDX BLOCK line["Price (Dec)"] = line["Price"] line["Quantity"] = float(line["Qty (M)"]) * 1000 values = [line["bbg_ticket_id"]] + [None] * 21 values[14] = _funds[_fcms[line["Client FCM"]]] values[15] = _fcms[line["Client FCM"]] else: values = list(line.values()) cp_code = cls.get_cp_code(line["Brkr"], "CDS") cls._bbg_insert_queue.append(values) return cls( fund=_funds[_fcms[line["Client FCM"]]], folder="*", portfolio="UNALLOCATED", security_id=line["Red Code"], security_desc=line["Security"].removesuffix(" PRC"), traded_level=Decimal(line["Price (Dec)"]), notional=line["Quantity"], fixed_rate=float(line["Coupon"]) * 0.01, trade_date=datetime.datetime.strptime(line["Trade Dt"], "%m/%d/%Y").date(), maturity=datetime.datetime.strptime(line["Mat Dt"], "%m/%d/%Y").date(), currency=line["Curncy"], protection="Buyer" if line["Side"] == "B" else "Seller", upfront=line["Net"], cp_code=cp_code, account_code=_fcms[line["Client FCM"]], bbg_ticket_id=line["bbg_ticket_id"], ) @dataclass class BondDeal(BbgDeal, Deal, deal_type=DealType.Bond, table_name="bonds"): buysell: bool description: str faceamount: float price: float cp_code: str cusip: str = None isin: str = None identifier: str = None trade_date: datetime.date = field(default_factory=datetime.date.today()) settle_date: datetime.date = field( default_factory=lambda: next_business_day(datetime.date.today()) ) folder: BondStrat = field(default=None) portfolio: Portfolio = field(default=None) asset_class: AssetClass = field(default=None) bbg_ticket_id: str = None @classmethod def from_bbg_line(cls, line: dict): with cls._conn.cursor() as c: c.execute( "SELECT asset_class from securities where figi=%s", (line["FIGI"],), ) results = c.fetchone() asset_class = results[0] if results else None cp_code = cls.get_cp_code(line["Brkr"], "BOND") cls._bbg_insert_queue.append(list(line.values())) return cls( faceamount=Decimal(line["Quantity"]), price=Decimal(line["Price (Dec)"]), cp_code=cp_code, cusip=line["Cusip"], identifier=line["Cusip"], trade_date=datetime.datetime.strptime(line["Trade Dt"], "%m/%d/%Y"), settle_date=datetime.datetime.strptime(line["SetDt"], "%m/%d/%Y"), portfolio="UNALLOCATED", description=line["Security"].removesuffix(" Mtge"), buysell=line["Side"] == "B", bbg_ticket_id=line["bbg_ticket_id"], asset_class=asset_class, ) @dataclass class SwaptionDeal( MTMDeal, Deal, deal_type=DealType.Swaption, table_name="swaptions", insert_ignore=("id", "dealid"), ): buysell: bool fund: Fund = field(metadata={"mtm": "Account Abbreviation"}) cp_code: str = field(metadata={"mtm": "Broker Id"}) security_id: str = field(metadata={"mtm": "RED"}) security_desc: str maturity: datetime.date = field(metadata={"mtm": "Maturity Date"}) currency: Ccy = field(metadata={"mtm": "Currency Code"}) notional: float = field(metadata={"mtm": "1st Leg Notional"}) fixed_rate: float = field(metadata={"mtm": "1st Leg Rate"}) strike: float = field(metadata={"mtm": "Strike Price"}) price: float option_type: OptionType expiration_date: datetime.date = field(metadata={"mtm": "Expiration"}) portfolio: Portfolio = field(default=None) folder: SwaptionStrat = field(default=None) trade_date: datetime.date = field( default_factory=datetime.date.today(), metadata={"mtm": "Trade Date"} ) settle_date: datetime.date = field( default_factory=lambda: next_business_day(datetime.date.today()), metadata={"mtm": "Settle Date"}, ) expiration_date: datetime.date = field( metadata={"mtm": "Swaption Expiration Date"}, ) initial_margin_percentage: float = field( default=None, metadata={"mtm": "Independent Amount (%)"} ) id: int = field(default=None, metadata={"insert": False}) dealid: str = field(default=None, metadata={"insert": False, "mtm": "Swap ID"}) def to_markit(self): obj = self.serialize("mtm") obj["Initial Payment"] = round(obj["price"] * obj["1st Leg Notional"] * 0.01, 2) obj["Trade ID"] = obj["Swap ID"] obj["Product Type"] = self.product_type obj["Transaction Type"] = "NEW" if obj["buysell"]: obj["Transaction Code"] = "Pay" obj["Protection"] = "Buy" if obj["option_type"] == "PAYER" else "Sell" obj["OptionBuySellIndicator"] = "Buy" else: obj["Transaction Code"] = "Receive" obj["Protection"] = "Sell" if obj["option_type"] == "PAYER" else "Buy" obj["OptionBuySellIndicator"] = "Sell" obj["Entity Matrix"] = "Publisher" obj["Clearing House"] = "ICE_FCM_US" obj["Swaption Settlement Type"] = "Physical" obj["Supplement Date"] = datetime.date(2021, 12, 13) obj["Supplement 2 Date"] = datetime.date(2020, 1, 27) if "IG" in obj["security_desc"]: obj["Swaption Quotation Rate Type"] = "Spread" obj["Strike Price"] = obj["Strike Price"] * 0.01 obj["Effective Date"] = obj["Trade Date"] return obj @dataclass class TerminationDeal( MTMDeal, Deal, deal_type=DealType.Termination, table_name="terminations", insert_ignore=("id", "dealid", "orig_cp", "currency", "fund", "product_type"), ): partial_termination: bool termination_fee: float = field(metadata={"mtm": "Initial Payment"}) fee_payment_date: datetime.date = field( metadata={"mtm": "Settle Date", "globeop": "FeePaymentDate"} ) termination_cp: str = field(metadata={"mtm": "Broker Id"}) termination_amount: float = field( metadata={"mtm": "1st Leg Notional", "globeop": "TerminationAmount"} ) termination_date: datetime.date = field( default_factory=datetime.date.today(), metadata={"mtm": "Trade Date", "globeop": "TerminationDate"}, ) id: int = field(default=None, metadata={"insert": False}) dealid: str = field(default=None, metadata={"insert": False, "mtm": "Swap ID"}) factor: float = field(default=1, metadata={"insert": False}) orig_cp: str = field( init=False, metadata={"mtm": "Remaining Party", "insert": False}, ) currency: str = field( init=False, metadata={"mtm": "Currency Code", "insert": False}, ) fund: str = field( init=False, metadata={"mtm": "Account Abbreviation", "insert": False}, ) product_type: str = field( init=False, metadata={"mtm": "Product Type", "insert": False} ) deal_type: str = field( init=False, metadata={"insert": False, "globeop": "DealType"} ) globeop_id: str = field(init=False, default=None, metadata={"globeop": "GoTradeId"}) def __post_init__(self): if self.dealid.startswith("SWPTN"): self.product_type = "CDISW" self.deal_type = "SwaptionDeal" sql_str = ( "SELECT cp_code, currency, fund, globeop_id FROM terminations " "LEFT JOIN swaptions USING (dealid) " "WHERE terminations.id = %s" ) elif self.dealid.startswith("SCCDS"): self.product_type = "TRN" self.deal_type = "CreditDefaultSwapDeal" sql_str = ( "SELECT cp_code, currency, fund, b.globeop_id, " "(detach - attach) / (orig_detach - orig_attach) " "FROM terminations " "LEFT JOIN cds USING (dealid) " "LEFT JOIN LATERAL (" " SELECT globeop_id FROM id_mapping WHERE serenitas_id=cds.id" " ORDER BY date DESC LIMIT 1" ") b ON true " "WHERE terminations.id = %s" ) with self._conn.cursor() as c: c.execute(sql_str, (self.id,)) if self.deal_type == "SwaptionDeal": self.orig_cp, self.currency, self.fund, self.globeop_id = c.fetchone() elif self.deal_type == "CreditDefaultSwapDeal": ( self.orig_cp, self.currency, self.fund, self.globeop_id, self.factor, ) = c.fetchone() def to_markit(self): obj = self.serialize("mtm") if obj["Initial Payment"] >= 0: obj["Transaction Code"] = "Receive" else: obj["Transaction Code"] = "Pay" obj["Initial Payment"] = round(abs(obj["Initial Payment"]), 2) obj["Trade ID"] = obj["Swap ID"] + "-" + str(obj["id"]) obj["Transaction Type"] = ( "Termination" if obj["Remaining Party"] == obj["Broker Id"] else "Assignment" ) obj["Effective Date"] = obj["Trade Date"] + datetime.timedelta(days=1) obj["Product Type"] = obj["product_type"] return obj def to_globeop(self): obj = self.serialize("globeop") obj["TerminationAmount"] *= self.factor obj["FeesPaid"] = ( -obj["termination_fee"] if obj["termination_fee"] < 0 else None ) obj["FeesReceived"] = ( obj["termination_fee"] if obj["termination_fee"] > 0 else None ) obj["Action"] = "UPDATE" obj["Client"] = _client_name[obj["fund"]] obj["SubAction"] = "Termination" if self.termination_cp != self.orig_cp: obj["AssignedCounterparty"] = self.termination_cp obj["PartialTermination"] = "Y" if self.partial_termination else "N" return obj @dataclass class SpotDeal( BbgDeal, Deal, deal_type=DealType.Spot, table_name="spots", insert_ignore=("id", "dealid"), ): folder: SpotStrat portfolio: Portfolio spot_rate: float buy_currency: str buy_amount: float sell_currency: str sell_amount: float fund: Fund cp_code: str cash_account: str commission_currency: str = "USD" commission: float = None id: int = field(default=None, metadata={"insert": False}) dealid: str = field(default=None, metadata={"insert": False}) trade_date: datetime.date = field( default_factory=datetime.date.today(), ) settle_date: datetime.date = field( default_factory=datetime.date.today(), ) bbg_ticket_id: str = None @classmethod def from_bbg_line(cls, line: dict): cp_code = cls.get_cp_code(line["Counterparty Deal Code"], "FX") if line["Side"] == "B": key1, key2 = "buy", "sell" else: key1, key2 = "sell", "buy" d = { f"{key1}_currency": line["Currency 1"], f"{key1}_amount": line["Amount Dealt"], f"{key2}_currency": line["Currency 2"], f"{key2}_amount": line["Counter Amount"], } for key in ("Comp Quote 1",): if line[key] == "": line[key] = None cls._bbg_insert_queue.append(list(line.values())) return cls( folder="*", portfolio="UNALLOCATED", cp_code=cp_code, trade_date=datetime.datetime.strptime(line["Date Of Deal"], "%Y%m%d"), settle_date=datetime.datetime.strptime( line["Value Date Period 1 Currency 1"], "%Y%m%d" ), fund=_fx_funds[line["ALOC Account 1"]], spot_rate=line["Exchange Rate Period 1"], cash_account=_fx_accounts[line["ALOC Account 1"]], bbg_ticket_id=line["bbg_ticket_id"], **d, ) _fx_funds = {"serenitas": "SERCGMAST", "bowdst": "BOWDST", "baml_fcm": "SERCGMAST"} _fx_accounts = {"serenitas": "V0NSCLMAMB", "bowdst": "751254", "baml_fcm": "V0NSCLMSPT"} class FxDeal(BbgDeal, Deal, table_name=None, deal_type=DealType.Fx): @classmethod def from_bbg_line(cls, line: dict): if line["Deal Type"] in ("4", "2"): return SpotDeal.from_bbg_line(line) else: return FxSwapDeal.from_bbg_line(line) @dataclass class FxSwapDeal( BbgDeal, Deal, deal_type=DealType.FxSwap, table_name="fx_swaps", insert_ignore=("id", "dealid"), ): folder: str portfolio: str trade_date: datetime.date near_settle_date: datetime.date near_buy_currency: str near_buy_amount: float near_sell_currency: str near_sell_amount: float near_rate: float far_rate: float far_settle_date: datetime.date far_buy_currency: str far_buy_amount: float far_sell_currency: str far_sell_amount: str fund: Fund cp_code: str cash_account: str id: int = field(default=None, metadata={"insert": False}) dealid: str = field(default=None, metadata={"insert": False}) bbg_ticket_id: str = None @classmethod def from_bbg_line(cls, line: dict): cp_code = cls.get_cp_code(line["Counterparty Deal Code"], "FX") if line["Side"] == "S": key1, key2 = "buy", "sell" else: key1, key2 = "sell", "buy" d = { f"near_{key1}_currency": line["Currency 1"], f"near_{key1}_amount": line["Amount Dealt"], f"far_{key1}_currency": line["Currency 2"], f"far_{key1}_amount": line["Far Counter Amount"], f"near_{key2}_currency": line["Currency 2"], f"near_{key2}_amount": line["Counter Amount"], f"far_{key2}_currency": line["Currency 1"], f"far_{key2}_amount": line["Far Amount Dealt"], } for key in ("Comp Quote 1",): if line[key] == "": line[key] = None cls._bbg_insert_queue.append(list(line.values())) return cls( folder="*", portfolio="UNALLOCATED", cp_code=cp_code, trade_date=datetime.datetime.strptime(line["Date Of Deal"], "%Y%m%d"), near_settle_date=datetime.datetime.strptime( line["Value Date Period 1 Currency 1"], "%Y%m%d" ), far_settle_date=datetime.datetime.strptime( line["Value Date Period 2 Currency 1"], "%Y%m%d" ), fund=_fx_funds[line["ALOC Account 1"]], near_rate=line["Exchange Rate Period 1"], far_rate=line["Exchange Rate Period 2"], cash_account=_fx_accounts[line["ALOC Account 1"]], bbg_ticket_id=line["bbg_ticket_id"], **d, )