diff options
Diffstat (limited to 'python/api_quotes')
| -rw-r--r-- | python/api_quotes/__init__.py | 0 | ||||
| -rw-r--r-- | python/api_quotes/__main__.py | 23 | ||||
| -rw-r--r-- | python/api_quotes/api.py | 45 | ||||
| -rw-r--r-- | python/api_quotes/quotes.py | 66 |
4 files changed, 134 insertions, 0 deletions
diff --git a/python/api_quotes/__init__.py b/python/api_quotes/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/python/api_quotes/__init__.py diff --git a/python/api_quotes/__main__.py b/python/api_quotes/__main__.py new file mode 100644 index 00000000..e0cf8708 --- /dev/null +++ b/python/api_quotes/__main__.py @@ -0,0 +1,23 @@ +from .api import MarkitAPI +from .quotes import Quote +import pandas as pd +import logging + +logger = logging.getLogger(__name__) + +if __name__ == "__main__": + asset_class = "CD" + after = None + while True: + if data := MarkitAPI.get_data(asset_class, after): + for row in data: + try: + quote = Quote.from_markit_line(row) + except ValueError as e: + logger.error(f"Couldn't pase {row['quoteid']}: {e}") + else: + quote.stage() + quote.commit() + after = f"{row['receiveddatetime']},{asset_class}-9480-{row['quoteid']}" + else: + break diff --git a/python/api_quotes/api.py b/python/api_quotes/api.py new file mode 100644 index 00000000..af479d67 --- /dev/null +++ b/python/api_quotes/api.py @@ -0,0 +1,45 @@ +from serenitas.utils.misc import get_credential_path +import json +import posixpath +from urllib.parse import urljoin +from typing import ClassVar +import requests +import pandas as pd + + +def load_api_key(): + with get_credential_path("markit_api").open() as fh: + creds = json.load(fh) + base_url = creds.pop("url") + r = requests.post( + urljoin(base_url, "apikey"), + data=creds, + ) + return base_url, r.text + + +def lowercase_keys(d): + return {k.lower(): v for k, v in d.items()} + + +class MarkitAPI: + base_url, api_key = load_api_key() + + @classmethod + def get_data(cls, asset_class, after=None, service="latest"): + params = { + "format": "json", + "assetClass": asset_class, + "apikey": cls.api_key, + "limit": 1000, + "sortBy": "receivedDateTime", + "descending": "true", + "dateformat": "MILLISECONDSSINCEEPOCH", + } + if after: + params["after"] = after + print(params) + path = posixpath.join("parsing", "Quote", service) + url = urljoin(cls.base_url, path) + r = requests.get(url, params) + return map(lowercase_keys, json.loads(r.text)) diff --git a/python/api_quotes/quotes.py b/python/api_quotes/quotes.py new file mode 100644 index 00000000..28ca708b --- /dev/null +++ b/python/api_quotes/quotes.py @@ -0,0 +1,66 @@ +from serenitas.ops.trade_dataclasses import Deal +from dataclasses import dataclass +import datetime +from typing import Literal +from serenitas.utils.db2 import dbconn + +firmness = Literal["FIRM", "INDICATIVE"] +asset_class = Literal["CD"] + + +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 + + +@dataclass +class Quote(Deal, table_name="markit_singlename_quotes", deal_type=None): + quoteid: int + assetclass: asset_class + 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 from_markit_line(cls, d): + additional_attributes = { + "maturity": maturity_dt(d), + "msg_id": d["message"]["id"], + "quotedate": datetime.datetime.fromtimestamp( + d["receiveddatetime"] / 1000 + ).replace(tzinfo=datetime.timezone.utc), + "quotesource": d["sourceshortname"], + "tenor": f"{d['tenor']}Y", + } + d.update(additional_attributes) + return cls.from_dict(**d) + + @property + def message(self): + return QuoteDetails.from_tradeid(self.msg_id) + + +Quote.init_dbconn(dbconn("serenitasdb")) +Quote._sql_insert = Quote._sql_insert.replace( + "RETURNING *", "ON CONFLICT (quoteid) DO NOTHING RETURNING *" +) |
