aboutsummaryrefslogtreecommitdiffstats
path: root/python/Dawn/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/Dawn/views.py')
-rw-r--r--python/Dawn/views.py625
1 files changed, 0 insertions, 625 deletions
diff --git a/python/Dawn/views.py b/python/Dawn/views.py
deleted file mode 100644
index 56bf0497..00000000
--- a/python/Dawn/views.py
+++ /dev/null
@@ -1,625 +0,0 @@
-import datetime
-import os
-import psycopg2
-import redis
-import socket
-
-from flask import (
- abort,
- request,
- render_template,
- redirect,
- url_for,
- send_from_directory,
- send_file,
- g,
- jsonify,
- session,
-)
-
-from .models import (
- ModelForm,
- CASH_STRAT,
- CCY,
- BondDeal,
- CDSDeal,
- FUND,
- SwaptionDeal,
- FutureDeal,
- CashFlowDeal,
- CapFloorDeal,
- SpotDeal,
- Counterparties,
- Accounts,
- Termination,
-)
-
-from sqlalchemy.exc import IntegrityError
-from sqlalchemy.sql import text
-from sqlalchemy.sql.expression import func
-from wtforms.fields import BooleanField
-
-from .utils import bump_rev, simple_serialize
-from PyPDF2 import PdfFileMerger
-from io import BytesIO
-from . import app
-from . import db
-
-
-def cp_choices(kind="bond"):
- if kind == "bond":
- return Counterparties.query.order_by("name").with_entities(
- Counterparties.code, Counterparties.name
- )
- elif kind in ["future", "spot"]:
- return []
- elif kind in ["cds", "swaption", "capfloor"]:
- return (
- Counterparties.query.order_by("name")
- .filter(Counterparties.name.ilike("%CDS%"))
- .with_entities(Counterparties.code, Counterparties.name)
- )
-
-
-def account_codes():
- return Accounts.query.order_by("code").with_entities(Accounts.code, Accounts.name)
-
-
-def fcm_accounts():
- return (
- Accounts.query.order_by("code")
- .filter(Accounts.name.ilike("%FCM%"))
- .with_entities(Accounts.code, Accounts.name)
- )
-
-
-def get_queue():
- q = getattr(g, "queue", None)
- if q is None:
- hostname = socket.gethostname()
- if hostname == "ziggy":
- q = g.queue = redis.Redis(unix_socket_path="/run/redis/redis.sock")
- else:
- q = g.queue = redis.Redis(host="ziggy")
- return q
-
-
-def get_db():
- db = getattr(g, "_database", None)
- if db is None:
- db = g._database = psycopg2.connect(
- database="serenitasdb", user="serenitas_user", host="debian"
- )
- return db
-
-
-@app.teardown_appcontext
-def close_connection(exception):
- db = getattr(g, "_database", None)
- if db is not None:
- db.close()
-
-
-class CounterpartyForm(ModelForm):
- class Meta:
- model = Counterparties
- include_primary_keys = True
-
-
-class BondForm(ModelForm):
- upload_globeop = BooleanField(label="Upload to globeop?")
-
- class Meta:
- model = BondDeal
- include_foreign_keys = True
- exclude = [
- "dealid",
- "lastupdate", # we generate it with a trigger at the server level
- "principal_payment",
- "accrued_payment",
- ]
-
-
-class CDSForm(ModelForm):
- upload_globeop = BooleanField(label="Upload to globeop?")
-
- class Meta:
- model = CDSDeal
- include_foreign_keys = True
- exclude = [
- "dealid",
- "lastupdate",
- "custodian",
- "cashaccount",
- "attach",
- "detach",
- ]
-
-
-class SwaptionForm(ModelForm):
- upload_globeop = BooleanField(label="Upload to globeop?")
-
- class Meta:
- model = SwaptionDeal
- include_foreign_keys = True
- exclude = ["dealid", "lastupdate"]
-
-
-class TerminationForm(ModelForm):
- upload_globeop = BooleanField(label="Upload to globeop?")
-
- class Meta:
- model = Termination
- include_foreign_keys = True
-
-
-class FutureForm(ModelForm):
- upload_globeop = BooleanField(label="Upload to globeop?")
-
- class Meta:
- model = FutureDeal
- include_foreign_keys = True
- exclude = ["dealid", "lastupdate"]
-
-
-class SpotForm(ModelForm):
- upload_globeop = BooleanField(label="Upload to globeop?")
-
- class Meta:
- model = SpotDeal
- include_foreign_keys = True
- exclude = ["dealid", "lastupdate"]
-
-
-class CapFloorForm(ModelForm):
- upload_globeop = BooleanField(label="Upload to globeop?")
-
- class Meta:
- model = CapFloorDeal
- include_foreign_keys = True
- exclude = ["dealid", "lastupdate"]
-
-
-def get_deal(kind):
- if kind == "cds":
- return CDSDeal
- elif kind == "bond":
- return BondDeal
- elif kind == "swaption":
- return SwaptionDeal
- elif kind == "future":
- return FutureDeal
- elif kind == "wire":
- return CashFlowDeal
- elif kind == "capfloor":
- return CapFloorDeal
- elif kind == "spot":
- return SpotDeal
- else:
- raise RuntimeError(f"Unknown Deal type: {kind}")
-
-
-def _get_form(kind):
- if kind == "cds":
- return CDSForm
- elif kind == "bond":
- return BondForm
- elif kind == "swaption":
- return SwaptionForm
- elif kind == "future":
- return FutureForm
- elif kind == "capfloor":
- return CapFloorForm
- elif kind == "spot":
- return SpotForm
- else:
- raise RuntimeError("Unknown Deal type")
-
-
-def get_form(trade, kind):
- Form = _get_form(kind)
- if trade.id:
- form = Form(obj=trade)
- else:
- form = Form()
- # add extra empty fields
- empty_choice = (None, "")
- for attr in ["folder", "buysell", "asset_class", "swaption_type"]:
- try:
- dropdown = getattr(form, attr)
- if dropdown.choices[0] != empty_choice:
- dropdown.choices.insert(0, empty_choice)
- except AttributeError:
- continue
-
- if kind == "cds":
- form.account_code.choices = fcm_accounts()
- form.portfolio.choices = [
- c for c in form.portfolio.choices if c[0] not in ("IR", "IG", "HY")
- ]
- form.folder.choices = [
- c
- for c in form.folder.choices
- if (c[0] is None or (not c[0].startswith("SER_") or c[0].endswith("CURVE")))
- ]
- elif kind == "swaption":
- form.portfolio.choices = [("OPTIONS", "OPTIONS"), ("IR", "IR")]
- form.cp_code.choices = form.cp_code.choices + list(cp_choices(kind))
- return form
-
-
-def get_trade(tradeid, kind):
- Deal = get_deal(kind)
- return Deal.query.get(tradeid) if tradeid else Deal()
-
-
-def save_ticket(trade, old_ticket_name):
- if trade.ticket:
- if old_ticket_name:
- new_name = bump_rev(old_ticket_name)
- else:
- new_name = f"{trade.trade_date} {trade.id} {trade.description}.pdf"
- trade.ticket.save(os.path.join(app.config["TICKETS_FOLDER"], new_name))
- trade.ticket = new_name
- else:
- trade.ticket = old_ticket_name
-
-
-def save_confirm(trade, old_confirm):
- d = {"C": "Cap", "F": "Floor"}
- if trade.trade_confirm:
- file_name = f"{trade.trade_date} {d[trade.cap_or_floor]}.pdf"
- trade.trade_confirm.save(os.path.join(app.config["CONFIRMS_FOLDER"], file_name))
- trade.trade_confirm = file_name
- else:
- trade.trade_confirm = old_confirm
-
-
-def split_direction(g, direction):
- if direction == "outgoing":
- return [
- {
- "folder": cf.folder,
- "amount": -cf.amount,
- "code": cf.code,
- "currency": cf.currency,
- "action": cf.action,
- }
- for cf in g
- if cf.amount < 0
- ]
- elif direction == "incoming":
- return [
- {
- "folder": cf.folder,
- "amount": cf.amount,
- "code": cf.code,
- "currency": cf.currency,
- "action": cf.action,
- }
- for cf in g
- if cf.amount > 0
- ]
- else:
- raise ValueError("direction can be one of 'outgoing' or 'incoming'")
-
-
-def gen_cashflow_deals(form, sql_session, wire_id=None):
- to_date = datetime.date.fromisoformat
- d = {
- "action": form.get("action"),
- "trade_date": form.get("trade_date", None, to_date),
- }
-
- for direction in ["incoming", "outgoing"]:
- count = 1
- while True:
- if f"{direction}-code-{count}" not in form:
- break
- else:
- d.update(
- {
- field: form.get(f"{direction}-{field}-{count}")
- for field in ["folder", "code", "amount", "currency"]
- }
- )
- count += 1
- if direction == "outgoing":
- d["amount"] = -float(d["amount"])
- elif direction == "incoming":
- d["amount"] = float(d["amount"])
- else:
- raise ValueError("direction needs to be 'outgoing' or 'incoming'")
- if wire_id:
- cf = CashFlowDeal.query.get(wire_id)
- for k, v in d.items():
- setattr(cf, k, v)
- yield cf
- else:
- cf = CashFlowDeal(**d)
- sql_session.add(cf)
- yield cf
-
-
-@app.route("/wires/<int:wire_id>", methods=["GET", "POST"])
-@app.route("/wires/", defaults={"wire_id": None}, methods=["GET", "POST"])
-def wire_manage(wire_id):
- if request.method == "POST":
- wires = list(gen_cashflow_deals(request.form, db.session, wire_id))
-
- try:
- db.session.commit()
- except IntegrityError as e:
- app.logger.error(e)
- db.session.rollback()
- return render_template(
- "wire_entry.html",
- strategies=CASH_STRAT.enums,
- currencies=CCY.enums,
- accounts=account_codes(),
- outgoing_wires=split_direction(wires, "outgoing"),
- incoming_wires=split_direction(wires, "incoming"),
- action=request.form.get("action"),
- trade_date=request.form.get("trade_date"),
- )
- else:
- if request.form.get("upload_globeop") == "y":
- q = get_queue()
- for wire in wires:
- q.rpush("wire_SERCGMAST", simple_serialize(wire))
- return redirect(url_for("list_trades", kind="wire"))
-
- wire = CashFlowDeal() if wire_id is None else CashFlowDeal.query.get(wire_id)
- return render_template(
- "wire_entry.html",
- strategies=CASH_STRAT.enums,
- currencies=CCY.enums,
- accounts=account_codes(),
- outgoing_wires=split_direction([wire], "outgoing") if wire_id else [],
- incoming_wires=split_direction([wire], "incoming") if wire_id else [],
- trade_date=wire.trade_date if wire_id else datetime.date.today(),
- action_url=url_for("wire_manage", wire_id=wire_id),
- action=wire.action if wire_id else None,
- )
-
-
-@app.route("/trades/<kind>/<dealid>/terminate", methods=["GET", "POST"])
-def terminate(dealid, kind):
- termination = Termination()
- form = TerminationForm(dealid=dealid)
- form.termination_cp.choices = form.termination_cp.choices + list(cp_choices(kind))
- table = kind if kind.endswith("s") else kind + "s"
- if form.validate_on_submit():
- form.populate_obj(termination)
- sql_session = form.get_session()
- rec = db.session.execute(
- "SELECT notional, coalesce(terminated_amount, 0.), currency, b.globeop_id, "
- "cp_code, fund "
- f"FROM {table} "
- "LEFT JOIN "
- "(SELECT dealid, sum(termination_amount) AS terminated_amount "
- " FROM terminations group by dealid) term USING (dealid) "
- "LEFT JOIN LATERAL ( "
- " SELECT globeop_id FROM id_mapping "
- " WHERE serenitas_id=id AND date <= :date "
- " ORDER BY date DESC LIMIT 1"
- ") b ON true "
- "WHERE dealid = :dealid",
- {"dealid": dealid, "date": termination.termination_date},
- )
- notional, terminated_amount, currency, globeop_id, cp_code, fund = next(rec)
- is_assignment = True
- if not termination.partial_termination:
- termination.termination_amount = notional - terminated_amount
- if termination.termination_cp is None:
- termination.termination_cp = cp_code
- is_assignment = False
- sql_session.add(termination)
- try:
- sql_session.commit()
- except IntegrityError as e:
- app.logger.error(e)
- sql_session.rollback()
- else:
- buf = simple_serialize(
- termination,
- ccy=currency,
- globeopid=globeop_id,
- is_assignment=is_assignment,
- )
- q = get_queue()
- q.rpush(f"{kind}_{fund}_termination", buf)
- return redirect(url_for("list_trades", kind=kind))
- else:
- return render_template(
- "termination.html",
- form=form,
- action_url=url_for("terminate", dealid=dealid, kind=kind),
- )
-
-
-@app.route("/trades/<kind>/<int:tradeid>", methods=["GET", "POST"])
-@app.route("/trades/<kind>/", defaults={"tradeid": None}, methods=["GET", "POST"])
-@app.route(
- "/trades/", defaults={"tradeid": None, "kind": "bond"}, methods=["GET", "POST"]
-)
-def trade_manage(tradeid, kind):
- trade = get_trade(tradeid, kind)
- form = _get_form(kind)()
-
- if kind == "bond":
- old_ticket_name = trade.ticket
- if kind == "capfloor":
- old_confirm = trade.trade_confirm
- if kind == "cds":
- form.account_code.choices = fcm_accounts()
- form.cp_code.choices = form.cp_code.choices + list(cp_choices(kind))
- if form.validate_on_submit():
- form.populate_obj(trade)
- sql_session = form.get_session()
- if not tradeid:
- sql_session.add(trade)
- if kind == "bond":
- save_ticket(trade, old_ticket_name)
- if kind == "capfloor":
- save_confirm(trade, old_confirm)
- if kind == "cds":
- if trade.swap_type != "CD_INDEX":
- if trade.fund == "SERCGMAST":
- trade.account_code = "BAC"
- elif trade.fund == "BRINKER":
- trade.account_code = "BBH"
- elif trade.fund == "BOWDST":
- trade.account_code = "BONY"
- else:
- raise ValueError("Unknown fund")
- trade.cashaccount = trade.fcm_account.cash_account
- trade.custodian = trade.fcm_account.custodian
- try:
- sql_session.commit()
- except IntegrityError as e:
- app.logger.error(e)
- sql_session.rollback()
- return render_template(
- "trade_entry.html",
- form=form,
- action_url=url_for("trade_manage", tradeid=tradeid, kind=kind),
- )
- else:
- buf = simple_serialize(trade, upload=form.upload_globeop.data)
- q = get_queue()
- q.rpush(f"{kind}_{form.fund.data}", buf)
- return redirect(url_for("list_trades", kind=kind))
- else:
- if form.errors:
- app.logger.error(form.errors)
- form = get_form(trade, kind)
- return render_template(
- "trade_entry.html",
- form=form,
- action_url=url_for("trade_manage", tradeid=tradeid, kind=kind),
- )
-
-
-@app.route("/", defaults={"kind": "bond", "fund": None})
-@app.route("/<kind>/<fund>")
-@app.route("/<kind>", defaults={"fund": None})
-def list_trades(kind, fund):
- try:
- Deal = get_deal(kind)
- except RuntimeError as e:
- app.logger.error(e)
- abort(404)
- else:
- if kind not in ("cds", "swaption"):
- trade_list = Deal.query.order_by(Deal.trade_date.desc(), Deal.id.desc())
- if fund is not None:
- trade_list = trade_list.filter(
- Deal.fund == func.cast(func.upper(fund), FUND)
- )
- else:
- if fund is not None:
- sql_str = text(
- f"SELECT * FROM {kind}_trades WHERE fund=UPPER(:fund)::fund"
- )
- trade_list = db.session.execute(sql_str, {"fund": fund})
- else:
- trade_list = db.session.execute(f"SELECT * FROM {kind}_trades")
- return render_template(f"{kind}_blotter.html", trades=trade_list)
-
-
-@app.route("/tickets/<int:tradeid>")
-def download_ticket(tradeid):
- trade = BondDeal.query.get(tradeid)
- pdf = PdfFileMerger()
- pdf.append(os.path.join(app.config["TICKETS_FOLDER"], trade.ticket))
- pdf.append(os.path.join(app.config["CP_FOLDER"], trade.counterparty.instructions))
- fh = BytesIO()
- pdf.write(fh)
- pdf.close()
- fh.seek(0)
- return send_file(fh, mimetype="application/pdf")
-
-
-@app.route("/confirms/<int:tradeid>")
-def download_confirm(tradeid):
- trade = CapFloorDeal.query.get(tradeid)
- return send_file(
- os.path.join(app.config["CONFIRMS_FOLDER"], trade.trade_confirm),
- mimetype="application/pdf",
- )
-
-
-@app.route("/counterparties/<path:instr>", methods=["GET"])
-@app.route("/counterparties/", defaults={"instr": None}, methods=["GET"])
-def list_counterparties(instr):
- if instr:
- return send_from_directory(
- filename=instr,
- directory=app.config["CP_FOLDER"],
- mimetype="application/pdf",
- )
- else:
- cp_list = Counterparties.query.order_by(Counterparties.name)
- return render_template("counterparties.html", counterparties=cp_list.all())
-
-
-@app.route("/edit_cp/<cpcode>", methods=["GET", "POST"])
-@app.route("/edit_cp/", defaults={"cpcode": None}, methods=["GET", "POST"])
-def edit_counterparty(cpcode):
- if cpcode:
- cp = Counterparties.query.get(cpcode)
- else:
- cp = Counterparties()
- cp_form = CounterpartyForm()
-
- old_instructions = cp.instructions or None
- if cp_form.validate_on_submit():
- cp_form.populate_obj(cp)
- sql_session = cp_form.get_session()
- if not cpcode:
- sql_session.add(cp)
- instructions = cp_form.instructions
- if not instructions.data:
- cp.instructions = old_instructions
- else:
- cp.instructions = cp.name + ".pdf"
- instructions.data.save(
- os.path.join(app.config["CP_FOLDER"], cp.instructions)
- )
- sql_session.commit()
- return redirect(url_for("list_counterparties"))
- else:
- return render_template(
- "edit_cp.html", form=CounterpartyForm(obj=cp), code=cpcode
- )
-
-
-@app.route("/_ajax", methods=["GET"])
-def get_bbg_id():
- bbg_id = request.args.get("bbg_id")
- trade_date = request.args.get("trade_date", datetime.date.today())
- try:
- _, indextype, _, series, tenor = bbg_id.split()
- except ValueError:
- return "not a valid bloomberg description", 400
- indextype = indextype[:2]
- tenor = tenor[:-1] + "yr"
- series = int(series[1:])
- sql_str = (
- "SELECT redindexcode, maturity, coupon "
- "FROM index_desc "
- "WHERE index=%s and series=%s and tenor=%s "
- " and lastdate >=%s ORDER BY version"
- )
- db = get_db()
- with db.cursor() as c:
- c.execute(sql_str, (indextype, series, tenor, trade_date))
- redcode, maturity, coupon = c.fetchone()
- return jsonify(
- {
- "maturity": str(maturity),
- "redcode": redcode,
- "coupon": coupon,
- }
- )