aboutsummaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/globeop_monitor.py42
-rw-r--r--python/lmcg_monitor.py50
-rw-r--r--python/position_file_bowdst.py10
-rw-r--r--python/report_ops/__main__.py17
-rw-r--r--python/report_ops/misc.py10
-rw-r--r--python/report_ops/utils.py38
-rw-r--r--python/report_ops/wires.py39
-rw-r--r--python/strat_cash_realloc.py57
8 files changed, 189 insertions, 74 deletions
diff --git a/python/globeop_monitor.py b/python/globeop_monitor.py
index cdd0dbb2..ae7fea86 100644
--- a/python/globeop_monitor.py
+++ b/python/globeop_monitor.py
@@ -1,8 +1,12 @@
import datetime
import logging
+from dataclasses import replace
from serenitas.analytics.dates import prev_business_day
from serenitas.utils.db import dbconn
+from serenitas.ops.trade_dataclasses import WireDeal
+from serenitas.ops.funds import Service
from report_ops.utils import GFSMonitor, check_cleared_cds
+from collateral.common import CASH_STRATEGY_MAPPING, STRATEGY_CASH_MAPPING
def check_gfs(date, fund, conn):
@@ -20,6 +24,42 @@ def check_gfs(date, fund, conn):
GFSMonitor._staging_queue.clear()
+def reallocate_strategy_cash(date, fund, conn):
+ service = Service[fund]
+ with conn.cursor() as c:
+ c.execute(
+ "SELECT 1 FROM wires WHERE trade_date=%s AND fund=%s AND author='auto'",
+ (
+ date,
+ fund,
+ ),
+ )
+ if c.fetchone():
+ return
+ c.execute(
+ "SELECT * FROM list_orphaned_cash(%s, %s) WHERE abs(amount) > 1",
+ (
+ date,
+ fund,
+ ),
+ )
+ for row in c:
+ if row.folder not in CASH_STRATEGY_MAPPING:
+ obj = WireDeal(**row._asdict())
+ offset = replace(
+ obj, folder=STRATEGY_CASH_MAPPING[obj.folder], amount=-obj.amount
+ )
+ obj.stage()
+ offset.stage()
+ if not WireDeal._insert_queue:
+ return
+ for wire in WireDeal.commit(returning=True):
+ service.push_trade(wire, "NEW")
+ buf, dest = service.build_buffer("wire")
+ service.upload(buf, dest.name)
+ service().clear()
+
+
if __name__ == "__main__":
import argparse
@@ -40,3 +80,5 @@ if __name__ == "__main__":
):
check_gfs(args.cob, fund, conn)
check_cleared_cds(args.cob, fund, conn)
+ for fund in ("BOWDST",):
+ reallocate_strategy_cash(args.cob, fund, conn)
diff --git a/python/lmcg_monitor.py b/python/lmcg_monitor.py
new file mode 100644
index 00000000..5f8230cd
--- /dev/null
+++ b/python/lmcg_monitor.py
@@ -0,0 +1,50 @@
+import datetime
+import argparse
+from serenitas.analytics.dates import prev_business_day
+from serenitas.utils.db import dbconn
+from serenitas.utils.exchange import ExchangeMessage
+from report_ops.utils import CDXNotionalMonitor
+
+
+def monitor_scotia(date, conn):
+ with conn.cursor() as c:
+ c.execute(
+ "SELECT 1 FROM cash_balances cb WHERE fund='ISOSEL' AND account_number = '476960681512' AND date=%s;"
+ )
+ if not (_ := c.fetchone()):
+ em = ExchangeMessage()
+ em.send_email(
+ f"*Action Requested* Scotia Balance Missing {date}",
+ "Please enter missing cash balance to the blotter.",
+ to_recipients=("Nyops@lmcg.com",),
+ )
+
+
+def monitor_cds_notional(fund, date, conn):
+ with conn.cursor() as c:
+ c.execute(
+ "SELECT *, notional as serenitas_notional, globeop_notional / factor as admin_notional FROM list_cds_marks(%s, NULL, %s) WHERE abs((notional * factor) - globeop_notional) > 100;",
+ (date, fund),
+ )
+ for row in c:
+ d = row._asdict()
+ d["difference"] = d["serenitas_notional"] - d["globeop_notional"]
+ CDXNotionalMonitor.stage(d)
+ CDXNotionalMonitor.email(fund)
+ CDXNotionalMonitor._staging_queue.clear()
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "cob",
+ nargs="?",
+ type=datetime.date.fromisoformat,
+ default=prev_business_day(datetime.date.today()),
+ help="working date",
+ )
+ args = parser.parse_args()
+ conn = dbconn("dawndb")
+ monitor_scotia(args.cob, conn)
+ for fund in ("SERCGMAST", "BOWDST", "ISOSEL"):
+ monitor_cds_notional(fund, args.cob, conn)
diff --git a/python/position_file_bowdst.py b/python/position_file_bowdst.py
index 6b34b8b4..00b30e5c 100644
--- a/python/position_file_bowdst.py
+++ b/python/position_file_bowdst.py
@@ -2,20 +2,20 @@ from report_ops.sma import build_position_file
import argparse
from serenitas.utils.remote import Client
from serenitas.utils.exchange import ExchangeMessage, FileAttachment
-from report_ops.misc import _recipients, _cc_recipients
+from report_ops.misc import _monthend_nav_recipients, _cc_recipients
import datetime
from serenitas.analytics.dates import prev_business_day
asset_splits = {
- "OTC": [
+ "OTC": (
"future",
"tranche",
"ir_swaption",
"cdx_swaption",
"irs",
"cdx",
- ],
- "BOND": ["bond"],
+ ),
+ "BOND": ("bond",),
}
@@ -40,7 +40,7 @@ def main(cob, fund, upload):
em.send_email(
subject=f"Position_files for Bowdoin Street as of {cob}",
body=f"Please see monthend positions for Bowdoin Street as of {cob}. They have been uploaded to the SFTP as well.",
- to_recipients=_recipients[fund],
+ to_recipients=_monthend_nav_recipients[fund],
cc_recipients=_cc_recipients[fund],
reply_to=_cc_recipients[fund],
attach=attachments,
diff --git a/python/report_ops/__main__.py b/python/report_ops/__main__.py
index a5172a24..856fe928 100644
--- a/python/report_ops/__main__.py
+++ b/python/report_ops/__main__.py
@@ -97,11 +97,18 @@ if args.isosel_reports:
logger.warning(e)
if args.wire_reports:
- for fund in ("BOWDST", "ISOSEL"):
- try:
- Wire[fund].to_db(args.date)
- except ValueError as e:
- logger.warning(e)
+ for fund, custodians in _fund_custodians.items():
+ for custodian in custodians:
+ report = Wire[
+ (
+ fund,
+ custodian,
+ )
+ ]
+ try:
+ report.to_db(args.date)
+ except ValueError as e:
+ logger.warning(e)
if args.send_to_custodians:
for account in (
diff --git a/python/report_ops/misc.py b/python/report_ops/misc.py
index 3492baf1..684ea6dd 100644
--- a/python/report_ops/misc.py
+++ b/python/report_ops/misc.py
@@ -43,6 +43,16 @@ _sma_recipients = {
"BRINKER": ("CSG.Team.Armata@bbh.com",),
}
+_monthend_nav_recipients = {
+ "BOWDST": _recipients["BOWDST"]
+ + (
+ "Hedgemark.FA@sscinc.com",
+ "HM-acctgprim@bnymellon.com",
+ "catherine.porter@bnymellon.com",
+ "Meghana.shroff@bnymellon.com",
+ ),
+}
+
_settlement_recipients = {
"BOWDST": _sma_recipients["BOWDST"],
"SERCGMAST": (
diff --git a/python/report_ops/utils.py b/python/report_ops/utils.py
index 80bc72d5..035b2e13 100644
--- a/python/report_ops/utils.py
+++ b/python/report_ops/utils.py
@@ -102,7 +102,7 @@ def check_cleared_cds(date, fund, conn):
_tolerance = {"IG": 0.10, "HY": 0.20, "EU": 0.20, "XO": 0.30}
with conn.cursor() as c:
c.execute(
- "SELECT *, abs(price-globeop_price) AS difference FROM list_cds_marks(%s, NULL, %s)",
+ "SELECT *, abs(price-globeop_price) AS difference FROM list_cds_marks(%s, NULL, %s) WHERE abs((notional * factor) - globeop_notional) < 100;",
(date, fund),
)
for row in c:
@@ -350,6 +350,42 @@ class CDXQuoteMonitor(
)
+class CDXNotionalMonitor(
+ Monitor,
+ headers=(
+ "security_desc",
+ "security_id",
+ "maturity",
+ "admin_notional",
+ "serenitas_notional",
+ "difference",
+ ),
+ num_format=[("{0:,.2f}", 3), ("{0:,.2f}", 4), ("{0:,.2f}", 5)],
+):
+ @classmethod
+ def email(cls, fund):
+ if not cls._staging_queue:
+ return
+ cls._em.send_email(
+ f"CDX Notional Mismatches: {fund}",
+ HTMLBody(
+ f"""
+<html>
+ <head>
+ <style>
+ table, th, td {{ border: 1px solid black; border-collapse: collapse;}}
+ th, td {{ padding: 5px; }}
+ </style>
+ </head>
+ <body>
+ Good morning,<br><br>Mismatched cleared cds notional mismatches below:<br><br>{cls.to_tabulate()}
+ </body>
+</html>"""
+ ),
+ to_recipients=_cc_recipients[fund],
+ )
+
+
class SettlementMonitor(
Monitor,
headers=("date", "account", "currency", "projected_balance"),
diff --git a/python/report_ops/wires.py b/python/report_ops/wires.py
index 50f8284d..a2f5ce1d 100644
--- a/python/report_ops/wires.py
+++ b/python/report_ops/wires.py
@@ -1,8 +1,9 @@
from dataclasses import dataclass
+from typing import Literal
import datetime
from serenitas.ops.trade_dataclasses import Deal, Ccy
from typing import ClassVar
-from .custodians import NT, BNY
+from .custodians import NT, BNY, UMB
from .misc import get_dir, dt_from_fname
from dataclasses import field
from csv import DictReader
@@ -10,11 +11,14 @@ from functools import partial
_nt_to_currency = {"EURO - EUR": "EUR", "U.S. DOLLARS - USD": "USD"}
+CUSTODIAN = Literal["UMB", "NT", "BNY"]
+
@dataclass
class Wire(Deal, table_name="custodian_wires", deal_type="custodian_wires"):
date: datetime.date
- fund: ClassVar[str]
+ fund: ClassVar[CUSTODIAN]
+ custodian: ClassVar[str]
entry_date: datetime.date
value_date: datetime.date
pay_date: datetime.date
@@ -24,13 +28,19 @@ class Wire(Deal, table_name="custodian_wires", deal_type="custodian_wires"):
unique_ref: str
dtkey: ClassVar = field(metadata={"insert": False, "select": False})
- def __init_subclass__(cls, fund, dtkey, **kwargs):
+ def __init_subclass__(cls, fund, custodian, dtkey, **kwargs):
cls._sql_insert = (
cls._sql_insert.removesuffix("RETURNING *")
+ "ON CONFLICT (unique_ref) DO NOTHING RETURNING *"
)
cls.fund = fund
- cls._registry[fund] = cls
+ cls.custodian = custodian
+ cls._registry[
+ (
+ fund,
+ custodian,
+ )
+ ] = cls
cls.dtkey = dtkey
def __post_init__(self):
@@ -53,7 +63,7 @@ class Wire(Deal, table_name="custodian_wires", deal_type="custodian_wires"):
return p
-class BowdstWire(Wire, BNY, fund="BOWDST", dtkey="%Y%m%d%H%M%S"):
+class BowdstBNYWire(Wire, BNY, fund="BOWDST", custodian="BNY", dtkey="%Y%m%d%H%M%S"):
@classmethod
def from_report_line(cls, line: dict):
return cls(
@@ -79,7 +89,7 @@ class BowdstWire(Wire, BNY, fund="BOWDST", dtkey="%Y%m%d%H%M%S"):
cls.commit()
-class SeleneWire(Wire, NT, fund="ISOSEL", dtkey="%Y%m%d%H%M"):
+class SeleneNTWire(Wire, NT, fund="ISOSEL", custodian="NT", dtkey="%Y%m%d%H%M"):
@classmethod
def from_report_line(cls, line: dict):
return cls(
@@ -102,3 +112,20 @@ class SeleneWire(Wire, NT, fund="ISOSEL", dtkey="%Y%m%d%H%M"):
if "sponsor" in line["narrative"].lower():
cls.from_report_line(line).stage()
cls.commit()
+
+
+class SerenitasUMBWire(
+ Wire, UMB, fund="SERCGMAST", custodian="UMB", dtkey="%Y%m%d%H%M"
+):
+ @classmethod
+ def from_report_line(cls, line: dict):
+ return
+
+ @classmethod
+ def to_db(cls, date):
+ # conn = cls._conn
+ # with conn.cursor() as c:
+ # c.execute("DELETE FROM custodian_wires WHERE date=%s AND fund=%s AND custodian=%s", (date, cls.fund, cls.custodian,))
+ # conn.commit()
+ # the unique ref will be generated from the row + the date and reports will only be delivered at 7pm daily
+ pass
diff --git a/python/strat_cash_realloc.py b/python/strat_cash_realloc.py
deleted file mode 100644
index 0bfafce0..00000000
--- a/python/strat_cash_realloc.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from dataclasses import replace
-from serenitas.ops.funds import Bowdst
-from serenitas.ops.trade_dataclasses import WireDeal
-import datetime
-from serenitas.analytics.dates import prev_business_day
-from collateral.common import CASH_STRATEGY_MAPPING, STRATEGY_CASH_MAPPING
-
-
-def generate_csv(date, fund="BOWDST"):
- with WireDeal._conn.cursor() as c:
- c.execute(
- "SELECT 1 FROM wires WHERE trade_date=%s AND fund=%s AND author='auto'",
- (
- date,
- fund,
- ),
- )
- if c.fetchone():
- return
- c.execute(
- "SELECT * FROM list_orphaned_cash(%s, %s) WHERE abs(amount) > 1",
- (
- date,
- fund,
- ),
- )
- for row in c:
- if row.folder not in CASH_STRATEGY_MAPPING:
- obj = WireDeal(**row._asdict())
- offset = replace(
- obj, folder=STRATEGY_CASH_MAPPING[obj.folder], amount=-obj.amount
- )
- obj.stage()
- offset.stage()
- if not WireDeal._insert_queue:
- return
- for wire in WireDeal.commit(returning=True):
- Bowdst.staging_queue.append(wire.to_globeop("NEW"))
- buf, dest = Bowdst.build_buffer("wire")
- Bowdst.upload(buf, dest.name)
- Bowdst().clear()
-
-
-if __name__ == "__main__":
- import argparse
-
- parser = argparse.ArgumentParser()
- parser.add_argument(
- "workdate",
- nargs="?",
- type=datetime.date.fromisoformat,
- default=prev_business_day(datetime.date.today()),
- help="working date",
- )
- args = parser.parse_args()
-
- generate_csv(args.workdate)