from dataclasses import dataclass import datetime from typing import ClassVar, Literal from enum import Enum from serenitas.utils.db2 import dbconn FIRMNESS = Literal["FIRM", "INDICATIVE"] class AssetClass(Enum): ABS = "ABS" CD = "CD" TRS = "TRS" TR = "TR" def maturity_dt(d): try: return datetime.date( int(d["maturityyear"]), int(d["maturitymonth"]), int(d["maturityday"]) ) except ( ValueError, KeyError, ): # Sometimes maturity isn't included but we still have tenor return class MarkitQuote: _conn: ClassVar _registry: ClassVar[dict] = {} _table_name: ClassVar[str | None] _sql_insert: ClassVar[str] _insert_queue: ClassVar[list] @classmethod def init_dbconn(cls, conn=None): cls._conn = conn or dbconn( "serenitasdb", application_name="markit_quotes", autocommit=True ) def __init_subclass__(cls, asset_class, table_name: str): cls._registry[asset_class] = cls cls._table_name = table_name place_holders = ",".join(["%s"] * len(cls.__annotations__)) cls._sql_insert = f"INSERT INTO {table_name}({','.join(cls.__annotations__)}) VALUES({place_holders}) ON CONFLICT DO NOTHING" cls._insert_queue = [] def __class_getitem__(cls, asset_class: AssetClass): return cls._registry[asset_class] @classmethod def enrich_dict(cls, d): return d | { "msg_id": d["message"]["id"], "quotedate": datetime.datetime.fromtimestamp(d["receiveddatetime"] / 1000), "quotesource": d["sourceshortname"], } @classmethod def from_markit_line(cls, d): return cls.from_dict(cls.enrich_dict(d)) @classmethod def from_dict(cls, d): return cls(**{k: d[k] for k in cls.__annotations__ if k in d}) @classmethod def already_uploaded(cls): with cls._conn.cursor(binary=True) as c: c.execute(f"SELECT distinct msg_id AS msg_id FROM {cls._table_name}") return set(row.msg_id for row in c) def stage(self): self._insert_queue.append( tuple([getattr(self, col) for col in self.__annotations__]) ) @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() # TODO # @property # def message(self): # return QuoteDetails.from_tradeid(self.msg_id) @dataclass class SingleNameQuote( MarkitQuote, asset_class=AssetClass.CD, table_name="markit_singlename_quotes" ): quoteid: int msg_id: str quotesource: str confidence: int redcode: str = None ticker: str = None maturity: datetime.date = None tenor: int = None runningcoupon: int = None bidconventionalspread: float = None bidupfront: float = None bidsize: float = None askconventionalspread: float = None askupfront: float = None asksize: float = None firmness: FIRMNESS = None quotedate: datetime.datetime = None @classmethod def enrich_dict(cls, d): return { "maturity": maturity_dt(d), "tenor": f"{d['tenor']}Y", } | super().enrich_dict(d) @dataclass class BondQuote( MarkitQuote, asset_class=AssetClass.ABS, table_name="markit_bond_quotes" ): quoteid: int msg_id: str quotesource: str confidence: int identifier: str = None cusip: str = None bidprice: float = None bidsize: float = None askprice: float = None asksize: float = None pricelevel: float = None subtype: str = None quotetype: str = None firmness: FIRMNESS = None quotedate: datetime.datetime = None @classmethod def enrich_dict(cls, d): return { "identifier": d["internalinstrumentidentifier"], "pricelevel": d.get("pricelevelnormalized"), } | super().enrich_dict(d) @dataclass class TRSQuote(MarkitQuote, asset_class=AssetClass.TRS, table_name="markit_trs_quotes"): quoteid: int msg_id: str quotesource: str confidence: int maturity: datetime.date identifier: str = None bidlevel: float = None asklevel: float = None nav: float = None ref: float = None firmness: FIRMNESS = None funding_benchmark: str = None quotedate: datetime.datetime = None @classmethod def enrich_dict(cls, d): return { "identifier": d["ticker"], "ref": d.get("reference"), "nav": d.get("inavparsed"), "funding_benchmark": d.get("parsedbenchmark"), "maturity": maturity_dt(d), } | super().enrich_dict(d) @dataclass class TrancheQuote( MarkitQuote, asset_class=AssetClass.TR, table_name="markit_tranche_quotes" ): quoteid: int msg_id: str quotesource: str confidence: int maturity: datetime.date identifier: str = None bidlevel: float = None asklevel: float = None nav: float = None ref: float = None attach: int = None detach: int = None tenor: int = 5 firmness: FIRMNESS = None quotedate: datetime.datetime = None @classmethod def enrich_dict(cls, d): return { "identifier": d["ticker"], "ref": d.get("reference"), "nav": d.get("inavparsed"), "funding_benchmark": d.get("parsedbenchmark"), "maturity": maturity_dt(d), } | super().enrich_dict(d)