Added endpoints for pnl leaderboard and a short readme

This commit is contained in:
Nicholas Clarke 2021-07-19 09:41:12 -07:00
parent 8a6e5a5829
commit 0281677a1e
2 changed files with 272 additions and 9 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Mango transaction log
Webserver to provide api access to mango transactions database (deposits, withdraws, liquidations, pnl leaderboard).

278
server.py
View File

@ -2,6 +2,7 @@ from flask import Flask, g, jsonify, request
import os
import psycopg2
from flask_cors import CORS
from datetime import datetime, timedelta
def create_app(debug=False):
@ -18,30 +19,121 @@ app = create_app()
cors = CORS(app)
def connect_pg_db():
# TODO: refactor - remove duplicated functions
def connect_transactions_db():
"""Connects to the specific database."""
conn = psycopg2.connect(os.environ.get('DATABASE_URL'))
conn = psycopg2.connect(os.environ.get('TRANSACTIONS_DATABASE_URL'))
return conn
def get_pg_db():
def get_transactions_db():
"""Opens a new database connection if there is none yet for the
current application context.
"""
if not hasattr(g, 'pg_db'):
g.pg_db = connect_pg_db()
if not hasattr(g, 'transactions_db'):
g.pg_db = connect_transactions_db()
return g.pg_db
def connect_trades_db():
"""Connects to the specific database."""
conn = psycopg2.connect(os.environ.get('TRADES_DATABASE_URL'))
return conn
def get_trades_db():
"""Opens a new database connection if there is none yet for the
current application context.
"""
if not hasattr(g, 'trades_db'):
g.pg_db = connect_trades_db()
return g.pg_db
@app.route('/stats/activity_feed/<margin_account>')
def activity_feed(margin_account):
try:
db = get_transactions_db()
cur = db.cursor()
limit = request.args.get('limit')
offset = request.args.get('offset')
if limit is None:
limit = 10_000 # default limit
offset = 0
else:
if offset is None:
offset = 0
sql = """
select array_to_json(array_agg(row_to_json(ordered_u))) from
(
select * from
(
select 'Withdraw' as activity_type, w.block_datetime, row_to_json(w) as activity_details from
(
select
dw.margin_account, dw.signature, dw.owner, dw.symbol, dw.quantity, dw.usd_equivalent, dw.block_datetime, dw.mango_group
from deposit_withdraw dw
where
dw.margin_account = %(margin_account)s
and dw.side = 'Withdraw'
) w
union all
select 'Deposit' as activity_type, d.block_datetime, row_to_json(d) as activity_details from
(
select
dw.margin_account, dw.signature, dw.owner, dw.symbol, dw.quantity, dw.usd_equivalent, dw.block_datetime, dw.mango_group
from deposit_withdraw dw
where
dw.margin_account = %(margin_account)s
and dw.side = 'Deposit'
) d
union all
select 'Liquidation' as activity_type, ld.block_datetime, row_to_json(ld) as activity_details from
(
select l.*,
(
select array_to_json(array_agg(row_to_json(lh)))
from (
select symbol, start_assets, start_liabs, end_assets, end_liabs, price
from liquidation_holdings
where signature = l.signature
) lh
) as balances
from liquidations l
where l.liqee = %(margin_account)s
) ld
) u
order by u.block_datetime desc
limit %(limit)s offset %(offset)s
) ordered_u
"""
cur.execute(sql, {'margin_account': margin_account, 'limit': limit, 'offset': offset})
data = cur.fetchone()[0]
if data is None:
return jsonify([])
else:
return jsonify(data)
except Exception as e:
print(e)
@app.route('/stats/withdraws/<margin_account>')
def withdraws(margin_account):
try:
# margin_account = 'FKCBDQwmTj6HeJ1uU93go7xcUN2XX1myeHyzfK5iAj3X'
db = get_pg_db()
db = get_transactions_db()
cur = db.cursor()
limit = request.args.get('limit')
@ -85,7 +177,7 @@ def deposits(margin_account):
# margin_account = 'HmrkFSrqnECzFgENsiAsCQ8TzCfCyDz8oUuMtZzmSaAj'
db = get_pg_db()
db = get_transactions_db()
cur = db.cursor()
limit = request.args.get('limit')
@ -130,7 +222,7 @@ def liquidations(margin_account):
# margin_account = 'FucJ8CAfqSVuPr2zGhDxjyxkYvb5Qd1Maqqbc5JrPbYb'
db = get_pg_db()
db = get_transactions_db()
cur = db.cursor()
limit = request.args.get('limit')
@ -179,7 +271,7 @@ def prices(mango_group):
# mango_group = '2oogpTYm1sp6LPZAWD3bp2wsFpnV2kXL1s52yyFhW5vp'
db = get_pg_db()
db = get_transactions_db()
cur = db.cursor()
# TODO - think about optimising this more (indexes) - and caching
@ -240,6 +332,174 @@ def prices(mango_group):
except Exception as e:
print(e)
@app.route('/stats/pnl_leaderboard')
def pnl_leaderboard():
try:
db = get_trades_db()
cur = db.cursor()
start_date = request.args.get('start_date')
if start_date is None:
start_date = '1900-01-01'
limit = request.args.get('limit')
offset = request.args.get('offset')
if limit is None:
limit = 10_000 # default limit
offset = 0
else:
if offset is None:
offset = 0
# TODO - think about optimising this more (indexes) - and caching
sql = """
select array_to_json(array_agg(row_to_json(t))) from
(
select
pc.margin_account,
pc.owner,
case when pc.name is null then '' else pc.name end as name,
pc.cumulative_pnl - case when pc2.cumulative_pnl is null then 0 else pc2.cumulative_pnl end as pnl,
row_number() over (order by pc.cumulative_pnl - case when pc2.cumulative_pnl is null then 0 else pc2.cumulative_pnl end desc) as rank
from pnl_cache pc
left join pnl_cache pc2
on pc2.margin_account = pc.margin_account
and pc2.price_date = (%(start_date)s ::date - interval '1 day')::date
where pc.price_date = (select max(price_date) from prices)
order by
pc.cumulative_pnl - case when pc2.cumulative_pnl is null then 0 else pc2.cumulative_pnl end desc
limit %(limit)s offset %(offset)s
) t
"""
cur.execute(sql, {'start_date': start_date, 'limit': limit, 'offset': offset})
data = cur.fetchone()[0]
if data is None:
return jsonify({})
else:
return jsonify(data)
except Exception as e:
print(e)
@app.route('/stats/pnl_leaderboard_rank/<margin_account>')
def pnl_leaderboard_rank(margin_account):
try:
db = get_trades_db()
cur = db.cursor()
start_date = request.args.get('start_date')
if start_date is None:
start_date = '1900-01-01'
sql = """
select row_to_json(t) from
(
select margin_account, owner, name, pnl, rank from
(
select
pc.margin_account,
pc.owner,
case when pc.name is null then '' else pc.name end as name,
pc.cumulative_pnl - case when pc2.cumulative_pnl is null then 0 else pc2.cumulative_pnl end as pnl,
row_number() over (order by pc.cumulative_pnl - case when pc2.cumulative_pnl is null then 0 else pc2.cumulative_pnl end desc) as rank
from pnl_cache pc
left join pnl_cache pc2
on pc2.margin_account = pc.margin_account
and pc2.price_date = (%(start_date)s ::date - interval '1 day')::date
where pc.price_date = (select max(price_date) from prices)
) t1
where margin_account = %(margin_account)s
) t
"""
cur.execute(sql, {'start_date': start_date, 'margin_account': margin_account})
data = cur.fetchone()
if data is None:
return jsonify({})
else:
return jsonify(data[0])
except Exception as e:
print(e)
@app.route('/stats/pnl_history/<margin_account>')
def pnl_history(margin_account):
try:
db = get_trades_db()
cur = db.cursor()
start_date = request.args.get('start_date')
limit = request.args.get('limit')
offset = request.args.get('offset')
if limit is None:
limit = 10_000 # default limit
offset = 0
else:
if offset is None:
offset = 0
sql = """
select array_to_json(array_agg(row_to_json(t))) from
(
select
margin_account,
owner,
case when pc.name is null then '' else pc.name end as name,
price_date as date,
cumulative_pnl
from pnl_cache pc
where margin_account = %(margin_account)s
order by price_date desc
limit %(limit)s offset %(offset)s
) t
"""
cur.execute(sql, {'margin_account': margin_account, 'limit': limit, 'offset': offset})
data = cur.fetchone()[0]
if data is None:
return jsonify({})
else:
if start_date is not None:
start_dt = datetime.strptime(start_date, '%Y-%m-%d').date()
last_entry = data[-1]
last_dt = datetime.strptime(last_entry['date'], '%Y-%m-%d').date()
# Pad PNL history with 0's if needed
if last_dt >= start_dt:
owner = last_entry['owner']
name = last_entry['name']
delta = timedelta(days=1)
dt_iter = last_dt - delta
while dt_iter >= start_dt:
data.append({
'margin_account': margin_account,
'owner': owner,
'name': name,
'date': dt_iter.strftime('%Y-%m-%d'),
'cumulative_pnl': 0
})
dt_iter -= delta
return jsonify(data)
except Exception as e:
print(e)
@app.route('/')
def index():
return "<h1>Welcome to mango transaction stats</h1>"