aboutsummaryrefslogtreecommitdiffstats
path: root/python/api_quotes
diff options
context:
space:
mode:
Diffstat (limited to 'python/api_quotes')
-rw-r--r--python/api_quotes/__init__.py0
-rw-r--r--python/api_quotes/__main__.py23
-rw-r--r--python/api_quotes/api.py45
-rw-r--r--python/api_quotes/quotes.py66
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 *"
+)