aboutsummaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/Dawn/__init__.py5
-rw-r--r--python/Dawn/models.py6
-rw-r--r--python/Dawn/static/wire.js42
-rw-r--r--python/Dawn/templates/wire_blotter.html25
-rw-r--r--python/Dawn/templates/wire_entry.html149
-rw-r--r--python/Dawn/views.py155
-rw-r--r--python/dawn_utils.py3
7 files changed, 279 insertions, 106 deletions
diff --git a/python/Dawn/__init__.py b/python/Dawn/__init__.py
index 87be366c..d571f5aa 100644
--- a/python/Dawn/__init__.py
+++ b/python/Dawn/__init__.py
@@ -1,10 +1,13 @@
-from flask import Flask
import logging
+
+from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import MetaData
+from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.config.from_envvar('CONF')
+csrf = CSRFProtect(app)
db = SQLAlchemy(app, metadata=MetaData(schema=app.config['SCHEMA']))
if not app.debug:
diff --git a/python/Dawn/models.py b/python/Dawn/models.py
index fd33926b..4a5727b9 100644
--- a/python/Dawn/models.py
+++ b/python/Dawn/models.py
@@ -35,7 +35,7 @@ class Accounts(db.Model):
name = db.Column(db.String)
custodian = db.Column(db.String)
cash_account = db.Column(db.String)
- counterpaty = db.Column(db.String, db.ForeignKey('counterparties.code'))
+ counterparty = db.Column(db.String, db.ForeignKey('counterparties.code'))
BOND_STRAT = ENUM('M_STR_MAV', 'M_STR_MEZZ', 'CSO_TRANCH',
'M_CLO_BB20', 'M_CLO_AAA', 'M_CLO_BBB', 'M_MTG_IO', 'M_MTG_THRU',
@@ -260,9 +260,7 @@ class CashFlowDeal(db.Model):
lastupdate = db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now())
action = db.Column(ACTION)
folder = db.Column(CASH_STRAT, nullable=False)
- code = db.Column(db.String(5), db.ForeignKey('accounts.code'),
- info={'choices': [('IB', 'pomme')],
- 'label': 'account'}, nullable=False)
+ code = db.Column(db.String(5), db.ForeignKey('accounts.code'), nullable=False)
amount = db.Column(db.Float, nullable=False)
trade_date = db.Column(db.Date, nullable=False)
settle_date = db.Column(db.Date, nullable=False)
diff --git a/python/Dawn/static/wire.js b/python/Dawn/static/wire.js
new file mode 100644
index 00000000..37f2a1c8
--- /dev/null
+++ b/python/Dawn/static/wire.js
@@ -0,0 +1,42 @@
+var outgoing_count = 0;
+var incoming_count = 0;
+
+function addWire(direction, wire_data) {
+ return function() {
+ if( this != window.window) {
+ this.remove();
+ }
+ var template = document.getElementById("fragment");
+ var clone = document.importNode(template.content, true);
+ count = direction == "outgoing" ? ++outgoing_count : ++incoming_count;
+
+ clone.querySelectorAll("label").forEach(
+ label => label.setAttribute("for",
+ direction + "-" + label.getAttribute("for") + "-" + count));
+ clone.querySelectorAll(".form-control").forEach(
+ elem => {elem.id = direction + "-" + elem.id + "-" + count;
+ elem.setAttribute("name",
+ direction + "-" + elem.getAttribute("name") + "-" + count)
+ });
+ clone.getElementById("btn").id = direction + "-btn";
+ document.getElementById(direction).appendChild(clone);
+ for(var key in wire_data) {
+ document.getElementById(direction + "-" + key + "-" + count).value = wire_data[key];
+ }
+ document.getElementById(direction + "-btn").addEventListener("click", addWire(direction));
+ }
+}
+
+$(function() {
+ for (let w of outgoing_wires) {
+ addWire("outgoing", w)();
+ }
+ for (let w of incoming_wires) {
+ console.log(w);
+ addWire("incoming", w)();
+ }
+ if (outgoing_wires.length == 0 && incoming_wires.length == 0) {
+ addWire("outgoing")();
+ addWire("incoming")();
+ }
+})
diff --git a/python/Dawn/templates/wire_blotter.html b/python/Dawn/templates/wire_blotter.html
index 8397e5df..9f70621d 100644
--- a/python/Dawn/templates/wire_blotter.html
+++ b/python/Dawn/templates/wire_blotter.html
@@ -6,34 +6,19 @@
<td>Deal ID</td>
<td>Trade Date</td>
<td>Settle Date</td>
- <td>Buy/Sell</td>
- <td>Quantity</td>
- <td>Type</td>
- <td>Maturity</td>
- <td>Price</td>
- <td>Commission</td>
- <td>Description</td>
- <td>Ticker</td>
- <td>Counterparty</td>
<td>Strategy</td>
+ <td>Account</td>
+ <td>Amount</td>
</tr>
</thead>
{% for trade in trades %}
<tr>
- <td><a href="{{url_for('wire_manage', wire_id=wire.id)}}">{{wire.dealid}}</a></td>
+ <td><a href="{{url_for('wire_manage', wire_id=trade.id)}}">{{trade.dealid}}</a></td>
<td>{{trade.trade_date}}</td>
<td>{{trade.settle_date}}</td>
- <td>{% if trade.buysell %}Buy{% else %}Sell{% endif %}</td>
- <td>{{trade.quantity}}</td>
- <td>{{trade.swap_type}}</td>
- <td>{{trade.maturity}}</td>
- <td>{{trade.price}}</td>
- <td>{{trade.commission}}</td>
- <td>{{trade.security_desc}}</td>
- <td>{{trade.bbg_ticker}}</td>
- <td><a href="{{url_for('edit_counterparty',
- cpcode=trade.counterparty.code)}}">{{trade.counterparty.name}}</a></td>
<td>{{trade.folder}}</td>
+ <td>{{trade.account.name}}</td>
+ <td>{{trade.amount}}</td>
</tr>
{% endfor %}
</table>
diff --git a/python/Dawn/templates/wire_entry.html b/python/Dawn/templates/wire_entry.html
index a5da6b2b..84176890 100644
--- a/python/Dawn/templates/wire_entry.html
+++ b/python/Dawn/templates/wire_entry.html
@@ -4,48 +4,149 @@
<meta charset="utf-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
</head>
- <body style="max-width:1024px; margin:0 auto">
- <datalist id="index_list"></datalist>
+ <body style="max-width:512px; margin:20px auto">
<form method="POST" class="form-horizontal"
action="{{action_url}}" enctype="multipart/form-data">
- {% for field in form if field.type != 'BooleanField' %}
- <div class="form-group {% if field.id in form.errors %}has-error{% endif %}">
- {% if field.type != 'CSRFTokenField' %}
- <label class="control-label col-md-2" for="{{ field.id }}">
- {{ field.label.text }}
+ <div class="form-group ">
+ <label class="control-label col-md-4" for="action">
+ action
</label>
- {% endif %}
- <div class="col-md-3">
- {{ field(class_="form-control") }}
+ <div class="col-md-6">
+ <select class="form-control" id="action" name="action">
+ <option value="NEW"
+ {% if action and action == 'NEW' %}
+ selected="selected"
+ {% endif %}>NEW</option>
+ <option value="UPDATE"
+ {% if action and action == 'UPDATE' %}
+ selected="selected"
+ {% endif %}>UPDATE</option>
+ <option value="CANCEL"
+ {% if action and action == 'CANCEL' %}
+ selected="selected"
+ {% endif %}>CANCEL</option>
+ </select>
+ </div>
+ </div>
+ <fieldset id="outgoing">
+ <legend>Outgoing amounts</legend>
+ </fieldset>
+ <fieldset id="incoming">
+ <legend>Incoming amounts</legend>
+ </fieldset>
+ <hr>
+ <div class="form-group">
+ </div>
+
+ <div class="form-group">
+ <label class="control-label col-md-4" for="trade_date">
+ trade_date
+ </label>
+
+ <div class="col-md-6">
+ <input class="form-control" id="trade_date" name="trade_date" value="{{trade_date}}" type="date">
</div>
- {% if field.id in form.errors %}
+ </div>
+
+ <div class="form-group">
+
+ <label class="control-label col-md-4" for="settle_date">
+ settle_date
+ </label>
+
+ <div class="col-md-6">
+ <input class="form-control" id="settle_date" name="settle_date" value="{{settle_date}}" type="date">
+ </div>
+
+ </div>
+
+ <div class="form-group ">
+
<div class="col-md-3">
- {{form.errors[field.id][0]}}
- </div>{% endif %}
+ <input class="form-control" id="csrf_token" name="csrf_token" value="{{csrf_token()}}" type="hidden">
+ </div>
+
</div>
- {% endfor %}
+
<div class="form-group">
- <div class="col-md-offset-2 col-md-3">
+ <div class="col-md-offset-4 col-md-6">
<div class="checkbox">
<label>
- <input id="upload_globeop" name="upload_globeop" type="checkbox" value="y">Upload to globeop?
+ <input id="upload_globeop" name="upload_globeop" value="y" type="checkbox">Upload to globeop?
</label>
</div>
</div>
</div>
<div class="form-group">
- <div class="col-md-offset-2 col-md-3">
+ <div class="col-md-offset-4 col-md-6">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
- {% if 'cds' or 'swaption' in action_url %}
- <script type="text/javascript" src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
- <script type="text/javascript"
- src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
- integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS"
+ <template id="fragment">
+ <div class="form-group">
+ <label class="control-label col-md-4" for="folder">
+ folder
+ </label>
+ <div class="col-md-6">
+ <select class="form-control" id="folder" name="folder">
+ <option value="None">{{strat}}</option>
+ {% for strat in strategies %}
+ <option value="{{strat[0]}}">{{strat[1]}}</option>
+ {% endfor %}
+ </select>
+ </div>
+
+ </div>
+ <div class="form-group ">
+
+ <label class="control-label col-md-4" for="code">
+ account
+ </label>
+
+ <div class="col-md-6">
+ <select class="form-control" id="code" name="code">
+ <option value="None"></option>
+ {% for account in accounts %}
+ <option value="{{account[0]}}">{{account[1]}}</option>
+ {% endfor %}
+ </select>
+ </div>
+
+ </div>
+
+ <div class="form-group ">
+ <label class="control-label col-md-4" for="amount">amount</label>
+
+ <div class="col-md-6">
+ <input class="form-control" id="amount" name="amount" value="" type="text">
+ </div>
+ <div class="col-md-2">
+ <input id="btn" type="button" class="btn" value="+">
+ </div>
+ </div>
+ </template>
+ <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
+ integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
+ crossorigin="anonymous"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"
+ integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh"
+ crossorigin="anonymous"></script>
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"
+ integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ"
crossorigin="anonymous"></script>
- <script type="text/javascript" src="{{ url_for('static', filename='dawn.js') }}"></script>
- {% endif %}
+ <script type="text/javascript" src="{{ url_for('static', filename='wire.js') }}"></script>
+ <script type="text/javascript">
+ {% if outgoing_wires is defined %}
+ var outgoing_wires = {{outgoing_wires|tojson|safe}}
+ {% else %}
+ var outgoing_wires = [];
+ {% endif %}
+ {% if incoming_wires is defined %}
+ var incoming_wires = {{incoming_wires|tojson|safe}}
+ {% else %}
+ var incoming_wires = [];
+ {% endif %}
+ </script>
</body>
</html>
diff --git a/python/Dawn/views.py b/python/Dawn/views.py
index 761fdd5b..5acec034 100644
--- a/python/Dawn/views.py
+++ b/python/Dawn/views.py
@@ -1,23 +1,28 @@
+import datetime
+import os
+import pandas as pd
+import psycopg2
+import redis
+import socket
+
from flask import (request, render_template, redirect,
url_for, send_from_directory, send_file, g, jsonify)
-from .models import (ModelForm,
+
+from .models import (ModelForm, CASH_STRAT,
BondDeal, CDSDeal, SwaptionDeal, FutureDeal, CashFlowDeal,
Counterparties, Accounts)
from sqlalchemy.exc import IntegrityError
from wtforms.fields import BooleanField
-import pandas as pd
+
from pandas.tseries.offsets import CustomBusinessDay
from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, GoodFriday
-import os
-import datetime
-import redis
+
from .utils import load_counterparties, bump_rev, simple_serialize
from PyPDF2 import PdfFileMerger
from io import BytesIO
from . import app
-import socket
-import psycopg2
+from . import db
fed_cal = get_calendar('USFederalHolidayCalendar')
bond_cal = HolidayCalendarFactory('BondCalendar', fed_cal, GoodFriday)
@@ -36,7 +41,7 @@ def cp_choices(kind='bond'):
with_entities(Counterparties.code, Counterparties.name))
def account_codes():
- return Accounts.query.order_by('code').with_entities(Accounts.code, Accounts.code)
+ return Accounts.query.order_by('code').with_entities(Accounts.code, Accounts.name)
def get_queue():
q = getattr(g, 'queue', None)
@@ -96,13 +101,6 @@ class FutureForm(ModelForm):
include_foreign_keys = True
exclude = ['dealid', 'lastupdate']
-class WireForm(ModelForm):
- upload_globeop = BooleanField(label="Upload to globeop?")
- class Meta:
- model = CashFlowDeal
- include_foreign_keys = True
- exclude = ['dealid', 'lastupdate']
-
def get_deal(kind):
if kind == 'cds':
return CDSDeal
@@ -164,9 +162,6 @@ def get_trade(tradeid, kind):
Deal = get_deal(kind)
return Deal.query.get(tradeid) if tradeid else Deal()
-def get_wire(wiredid):
- CashFlowDeal.query.get(wireid)
-
def save_ticket(trade, old_ticket_name):
if trade.ticket:
if old_ticket_name:
@@ -180,42 +175,88 @@ def save_ticket(trade, old_ticket_name):
else:
trade.ticket = old_ticket_name
+def split_direction(g, direction):
+ if direction == "outgoing":
+ return [{"folder": cf.folder, "amount": -cf.amount, "code": cf.code}
+ for cf in g if cf.amount < 0]
+ elif direction == "incoming":
+ return [{"folder": cf.folder, "amount": cf.amount, "code": cf.code}
+ for cf in g if cf.amount > 0]
+ else:
+ raise ValueError("direction can be one of 'outgoing' or 'incoming'")
+
+def gen_cashflow_deals(form):
+ action = form.get("action")
+ to_date = lambda s: datetime.datetime.strptime(s, "%Y-%m-%d")
+ trade_date = form.get("trade_date", None, to_date)
+ settle_date = form.get("settle_date", None, to_date)
+ for direction in ["incoming", "outgoing"]:
+ count = 1
+ while True:
+ if f"{direction}-code-{count}" not in form:
+ break
+ else:
+ r = [form.get(f"{direction}-{field}-{count}") for field \
+ in ["folder", "code", "amount"]]
+ count += 1
+ if direction == "outgoing":
+ r[2] = -float(r[2])
+ elif direction == "incoming":
+ r[2] = float(r[2])
+ else:
+ raise ValueError("direction needs to be 'outgoing' or 'incoming'")
+ yield CashFlowDeal(trade_date=trade_date,
+ settle_date=settle_date,
+ action=action,
+ folder=r[0],
+ code=r[1],
+ amount=r[2])
+
@app.route('/wires/<int:wire_id>', methods = ['GET', 'POST'])
-@app.route('/wires/', defaults = {'wire_id': None}, methods = ['GET', 'POST'])
+@app.route('/wires/', defaults={'wire_id': None}, methods=['GET', 'POST'])
def wire_manage(wire_id):
- if wire_id is None:
- wire = CashFlowDeal()
- else:
- wire = CashFlowDeal().query.get(wire_id)
- form = WireForm()
- form.code.choices = form.code.choices + list(account_codes())
- if form.validate_on_submit():
- form.populate_obj(wire)
- session = form.get_session()
- if not wire_id:
- session.add(wire)
+ if request.method == 'POST':
+ for wire in gen_cashflow_deals(request.form):
+ print(wire)
+ db.session.add(wire)
+
try:
- session.commit()
+ db.session.commit()
except IntegrityError as e:
app.logger.error(e)
- session.rollback()
- return render_template('wire_entry.html', form=form,
- action_url=
- url_for('wire_manage', wire_id=wire_id))
+ db.session.rollback()
+ return render_template('wire_entry.html',
+ strategies=[(e, e) for e in CASH_STRAT.enums],
+ accounts=account_codes(),
+ outgoing_wires=split_direction(
+ gen_cashflow_deals(request.form), "outgoing"),
+ incoming_wires=split_direction(
+ gen_cashflow_deals(request.form), "incoming"),
+ action=request.form.get('action'),
+ trade_date=request.form.get('trade_date'),
+ settle_date=request.form.get('settle_date'))
else:
- if form.upload_globeop.data:
+ if request.form.get('upload_globeop') == 'y':
q = get_queue()
- q.rpush('wires', simple_serialize(wire))
- return redirect(url_for('list_trades'))
- else:
- form = get_wire_form(wire)
- form.code.choices = form.code.choices + list(account_codes())
- return render_template('wire_entry.html', form=form,
- action_url = url_for('wire_manage', wire_id=wire_id))
+ for wire in gen_cashflow_deals(request.form):
+ q.rpush('wires', 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=[(e, e) for e in CASH_STRAT.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(),
+ settle_date=wire.settle_date if wire_id else '',
+ action_url=url_for('wire_manage', wire_id=wire_id),
+ action=wire.action if wire_id else None)
+
-@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'])
+@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)()
@@ -241,24 +282,24 @@ def trade_manage(tradeid, kind):
else:
if form.upload_globeop.data:
q = get_queue()
- q.rpush('{0}_trades'.format(kind), simple_serialize(trade))
+ q.rpush(f'{kind}_trades', simple_serialize(trade))
return redirect(url_for('list_trades', kind=kind))
else:
form = get_form(trade, kind)
form.cp_code.choices = form.cp_code.choices + list(cp_choices(kind))
return render_template('trade_entry.html', form=form,
- action_url = url_for('trade_manage', tradeid=tradeid, kind=kind))
+ action_url=url_for('trade_manage', tradeid=tradeid, kind=kind))
@app.route('/', defaults={'kind': 'bond'})
@app.route('/blotter/<kind>')
@app.route('/blotter/', defaults={'kind': 'bond'})
def list_trades(kind):
if kind == 'wire':
- Deal = CashFlowDeal()
+ Deal = CashFlowDeal
else:
Deal = get_deal(kind)
trade_list = Deal.query.order_by(Deal.trade_date.desc(), Deal.id.desc())
- return render_template('{}_blotter.html'.format(kind), trades=trade_list.all())
+ return render_template(f'{kind}_blotter.html', trades=trade_list.all())
@app.route('/tickets/<int:tradeid>')
def download_ticket(tradeid):
@@ -273,18 +314,18 @@ def download_ticket(tradeid):
return send_file(fh, mimetype='application/pdf')
@app.route('/counterparties/<path:instr>', methods = ['GET'])
-@app.route('/counterparties/', defaults = {'instr': None}, 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'],
+ 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())
+ 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'])
+@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)
@@ -310,7 +351,7 @@ def edit_counterparty(cpcode):
else:
return render_template('edit_cp.html', form=CounterpartyForm(obj=cp), code=cpcode)
-@app.route('/_ajax', methods = ['GET'])
+@app.route('/_ajax', methods=['GET'])
def get_bbg_id():
bbg_id = request.args.get('bbg_id')
try:
diff --git a/python/dawn_utils.py b/python/dawn_utils.py
index 1c49d591..60a90d1f 100644
--- a/python/dawn_utils.py
+++ b/python/dawn_utils.py
@@ -28,6 +28,9 @@ identifier = COALESCE(identifier, cusip, isin)', NEW.id);
ELSIF (TG_TABLE_NAME = 'futures') THEN
stub := 'SCFUT';
sqlstr := format(sqlstr, 'dealid = $1||id', NEW.id);
+ ELSIF (TG_TABLE_NAME = 'wires') THEN
+ stub := 'SCCSH';
+ sqlstr := format(sqlstr, 'dealid = $1||id', NEW.id);
END IF;
EXECUTE sqlstr USING stub;
RETURN NEW;