done
This commit is contained in:
parent
4803aa8518
commit
79d863b112
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM python:3
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY templates templates
|
||||||
|
COPY main.py main.py
|
||||||
|
|
||||||
|
CMD [ "python", "main.py" ]
|
102
cache.csv
Normal file
102
cache.csv
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
Date,1 Mo,2 Mo,3 Mo,4 Mo,6 Mo,1 Yr,2 Yr,3 Yr,5 Yr,7 Yr,10 Yr,20 Yr,30 Yr
|
||||||
|
05/24/2024,5.56,5.53,5.46,5.51,5.44,5.21,4.93,4.71,4.53,4.49,4.46,4.65,4.57
|
||||||
|
05/23/2024,5.51,5.48,5.46,5.51,5.44,5.2,4.91,4.71,4.52,4.5,4.47,4.67,4.58
|
||||||
|
05/22/2024,5.49,5.48,5.45,5.5,5.43,5.16,4.86,4.64,4.47,4.44,4.43,4.63,4.55
|
||||||
|
05/21/2024,5.5,5.47,5.45,5.51,5.42,5.14,4.82,4.61,4.43,4.42,4.41,4.65,4.55
|
||||||
|
05/20/2024,5.5,5.47,5.45,5.51,5.43,5.15,4.82,4.62,4.46,4.44,4.44,4.68,4.58
|
||||||
|
05/17/2024,5.5,5.47,5.46,5.5,5.41,5.14,4.83,4.6,4.44,4.43,4.42,4.66,4.56
|
||||||
|
05/16/2024,5.51,5.47,5.45,5.53,5.41,5.13,4.78,4.58,4.4,4.39,4.38,4.62,4.52
|
||||||
|
05/15/2024,5.49,5.46,5.45,5.5,5.4,5.1,4.73,4.51,4.35,4.35,4.36,4.61,4.52
|
||||||
|
05/14/2024,5.5,5.47,5.44,5.51,5.43,5.16,4.81,4.62,4.46,4.45,4.45,4.69,4.59
|
||||||
|
05/13/2024,5.5,5.47,5.45,5.51,5.43,5.16,4.85,4.66,4.5,4.49,4.48,4.72,4.63
|
||||||
|
05/10/2024,5.51,5.47,5.47,5.51,5.43,5.17,4.87,4.65,4.52,4.51,4.5,4.74,4.64
|
||||||
|
05/09/2024,5.51,5.48,5.46,5.5,5.41,5.12,4.8,4.6,4.47,4.46,4.45,4.7,4.6
|
||||||
|
05/08/2024,5.51,5.47,5.45,5.5,5.41,5.13,4.84,4.63,4.5,4.49,4.48,4.73,4.64
|
||||||
|
05/07/2024,5.51,5.48,5.45,5.51,5.41,5.13,4.82,4.6,4.48,4.47,4.47,4.7,4.61
|
||||||
|
05/06/2024,5.51,5.48,5.45,5.5,5.42,5.12,4.82,4.64,4.48,4.48,4.49,4.73,4.64
|
||||||
|
05/03/2024,5.51,5.48,5.45,5.5,5.41,5.12,4.81,4.63,4.48,4.49,4.5,4.75,4.66
|
||||||
|
05/02/2024,5.51,5.47,5.46,5.5,5.42,5.16,4.87,4.71,4.57,4.57,4.58,4.82,4.72
|
||||||
|
05/01/2024,5.47,5.5,5.46,5.51,5.43,5.21,4.96,4.79,4.64,4.64,4.63,4.85,4.74
|
||||||
|
04/30/2024,5.48,5.51,5.46,5.45,5.44,5.25,5.04,4.87,4.72,4.71,4.69,4.9,4.79
|
||||||
|
04/29/2024,5.48,5.51,5.45,5.45,5.43,5.2,4.97,4.8,4.65,4.64,4.63,4.86,4.75
|
||||||
|
04/26/2024,5.48,5.51,5.46,5.45,5.4,5.21,4.96,4.84,4.68,4.68,4.67,4.89,4.78
|
||||||
|
04/25/2024,5.48,5.51,5.47,5.46,5.41,5.21,4.96,4.85,4.7,4.71,4.7,4.93,4.82
|
||||||
|
04/24/2024,5.49,5.5,5.46,5.44,5.4,5.17,4.89,4.78,4.64,4.66,4.65,4.88,4.78
|
||||||
|
04/23/2024,5.49,5.5,5.45,5.44,5.39,5.14,4.86,4.76,4.63,4.62,4.61,4.84,4.73
|
||||||
|
04/22/2024,5.49,5.5,5.42,5.44,5.39,5.16,4.97,4.81,4.66,4.65,4.62,4.84,4.72
|
||||||
|
04/19/2024,5.49,5.51,5.45,5.44,5.39,5.17,4.97,4.81,4.66,4.65,4.62,4.83,4.72
|
||||||
|
04/18/2024,5.49,5.52,5.46,5.44,5.39,5.18,4.98,4.83,4.68,4.67,4.64,4.85,4.74
|
||||||
|
04/17/2024,5.49,5.49,5.45,5.44,5.38,5.16,4.93,4.77,4.62,4.61,4.59,4.81,4.71
|
||||||
|
04/16/2024,5.49,5.51,5.45,5.44,5.39,5.18,4.97,4.83,4.69,4.69,4.67,4.88,4.77
|
||||||
|
04/15/2024,5.49,5.51,5.45,5.44,5.38,5.16,4.93,4.78,4.65,4.65,4.63,4.85,4.74
|
||||||
|
04/12/2024,5.48,5.5,5.45,5.42,5.36,5.13,4.88,4.7,4.54,4.53,4.5,4.73,4.61
|
||||||
|
04/11/2024,5.48,5.51,5.45,5.44,5.38,5.17,4.93,4.77,4.61,4.6,4.56,4.77,4.65
|
||||||
|
04/10/2024,5.49,5.5,5.45,5.44,5.4,5.19,4.97,4.77,4.61,4.59,4.55,4.76,4.64
|
||||||
|
04/09/2024,5.48,5.49,5.43,5.41,5.34,5.03,4.74,4.52,4.37,4.38,4.36,4.6,4.5
|
||||||
|
04/08/2024,5.48,5.49,5.43,5.41,5.35,5.07,4.78,4.6,4.43,4.43,4.42,4.65,4.55
|
||||||
|
04/05/2024,5.47,5.5,5.43,5.41,5.34,5.05,4.73,4.54,4.38,4.39,4.39,4.65,4.54
|
||||||
|
04/04/2024,5.47,5.49,5.41,5.4,5.32,5.0,4.65,4.46,4.3,4.31,4.31,4.57,4.47
|
||||||
|
04/03/2024,5.47,5.44,5.42,5.4,5.33,5.03,4.68,4.48,4.34,4.36,4.36,4.61,4.51
|
||||||
|
04/02/2024,5.49,5.45,5.42,5.4,5.34,5.05,4.7,4.51,4.35,4.37,4.36,4.61,4.51
|
||||||
|
04/01/2024,5.49,5.47,5.44,5.41,5.36,5.06,4.72,4.51,4.34,4.33,4.33,4.58,4.47
|
||||||
|
03/28/2024,5.49,5.48,5.46,5.42,5.38,5.03,4.59,4.4,4.21,4.2,4.2,4.45,4.34
|
||||||
|
03/27/2024,5.5,5.47,5.45,5.41,5.36,4.99,4.54,4.36,4.18,4.18,4.2,4.45,4.36
|
||||||
|
03/26/2024,5.5,5.47,5.46,5.41,5.36,5.0,4.56,4.38,4.22,4.23,4.24,4.49,4.4
|
||||||
|
03/25/2024,5.51,5.48,5.46,5.41,5.36,5.0,4.54,4.39,4.23,4.25,4.25,4.51,4.42
|
||||||
|
03/22/2024,5.51,5.47,5.46,5.4,5.34,4.98,4.59,4.36,4.2,4.22,4.22,4.47,4.39
|
||||||
|
03/21/2024,5.51,5.48,5.48,5.4,5.36,5.01,4.62,4.42,4.26,4.28,4.27,4.53,4.44
|
||||||
|
03/20/2024,5.5,5.47,5.47,5.41,5.36,5.01,4.59,4.41,4.25,4.28,4.27,4.53,4.45
|
||||||
|
03/19/2024,5.52,5.48,5.48,5.41,5.39,5.06,4.68,4.47,4.31,4.31,4.3,4.54,4.44
|
||||||
|
03/18/2024,5.52,5.48,5.48,5.41,5.39,5.06,4.73,4.52,4.36,4.35,4.34,4.57,4.46
|
||||||
|
03/15/2024,5.52,5.48,5.48,5.41,5.38,5.05,4.72,4.51,4.33,4.33,4.31,4.55,4.43
|
||||||
|
03/14/2024,5.52,5.48,5.48,5.42,5.38,5.04,4.68,4.46,4.29,4.3,4.29,4.54,4.44
|
||||||
|
03/13/2024,5.52,5.47,5.48,5.41,5.37,5.01,4.61,4.37,4.19,4.2,4.19,4.45,4.35
|
||||||
|
03/12/2024,5.52,5.48,5.48,5.41,5.37,5.0,4.58,4.33,4.15,4.16,4.16,4.42,4.31
|
||||||
|
03/11/2024,5.5,5.47,5.48,5.41,5.35,4.95,4.51,4.26,4.08,4.09,4.1,4.36,4.26
|
||||||
|
03/08/2024,5.51,5.48,5.46,5.4,5.34,4.92,4.48,4.25,4.06,4.08,4.09,4.36,4.26
|
||||||
|
03/07/2024,5.51,5.48,5.47,5.4,5.34,4.93,4.5,4.28,4.07,4.09,4.09,4.35,4.25
|
||||||
|
03/06/2024,5.5,5.47,5.47,5.4,5.35,4.95,4.55,4.32,4.12,4.12,4.11,4.36,4.24
|
||||||
|
03/05/2024,5.5,5.47,5.47,5.4,5.35,4.94,4.54,4.32,4.13,4.15,4.13,4.39,4.27
|
||||||
|
03/04/2024,5.51,5.49,5.48,5.42,5.37,4.98,4.61,4.39,4.21,4.23,4.22,4.48,4.36
|
||||||
|
03/01/2024,5.54,5.49,5.42,5.41,5.27,4.94,4.54,4.32,4.17,4.2,4.19,4.46,4.33
|
||||||
|
02/29/2024,5.53,5.5,5.45,5.43,5.3,5.01,4.64,4.43,4.26,4.28,4.25,4.51,4.38
|
||||||
|
02/28/2024,5.5,5.51,5.45,5.43,5.31,5.0,4.64,4.44,4.26,4.28,4.27,4.53,4.4
|
||||||
|
02/27/2024,5.5,5.52,5.45,5.47,5.33,5.03,4.7,4.5,4.32,4.34,4.31,4.57,4.44
|
||||||
|
02/26/2024,5.5,5.52,5.47,5.47,5.34,5.03,4.69,4.48,4.29,4.32,4.28,4.53,4.4
|
||||||
|
02/23/2024,5.49,5.51,5.46,5.46,5.32,5.0,4.67,4.45,4.28,4.28,4.26,4.51,4.37
|
||||||
|
02/22/2024,5.49,5.51,5.45,5.45,5.32,5.02,4.69,4.49,4.33,4.35,4.33,4.58,4.47
|
||||||
|
02/21/2024,5.5,5.5,5.44,5.45,5.32,4.98,4.64,4.43,4.3,4.33,4.32,4.59,4.49
|
||||||
|
02/20/2024,5.49,5.49,5.44,5.45,5.32,4.97,4.59,4.38,4.25,4.28,4.27,4.56,4.44
|
||||||
|
02/16/2024,5.48,5.51,5.44,5.45,5.31,4.98,4.64,4.43,4.29,4.31,4.3,4.58,4.45
|
||||||
|
02/15/2024,5.49,5.51,5.43,5.45,5.3,4.93,4.56,4.36,4.22,4.25,4.24,4.54,4.42
|
||||||
|
02/14/2024,5.48,5.51,5.43,5.45,5.31,4.94,4.56,4.38,4.25,4.27,4.27,4.57,4.45
|
||||||
|
02/13/2024,5.48,5.52,5.45,5.46,5.32,4.99,4.64,4.44,4.31,4.33,4.31,4.59,4.46
|
||||||
|
02/12/2024,5.49,5.51,5.43,5.43,5.27,4.87,4.46,4.25,4.13,4.16,4.17,4.48,4.37
|
||||||
|
02/09/2024,5.49,5.51,5.44,5.43,5.26,4.86,4.48,4.25,4.14,4.17,4.17,4.48,4.37
|
||||||
|
02/08/2024,5.49,5.51,5.44,5.42,5.24,4.83,4.46,4.22,4.12,4.15,4.15,4.47,4.36
|
||||||
|
02/07/2024,5.47,5.49,5.43,5.4,5.23,4.83,4.41,4.16,4.06,4.09,4.09,4.41,4.31
|
||||||
|
02/06/2024,5.48,5.5,5.44,5.41,5.23,4.82,4.39,4.14,4.03,4.07,4.09,4.39,4.29
|
||||||
|
02/05/2024,5.49,5.5,5.42,5.42,5.25,4.87,4.46,4.27,4.13,4.16,4.17,4.46,4.35
|
||||||
|
02/02/2024,5.49,5.51,5.43,5.42,5.22,4.81,4.36,4.14,3.99,4.02,4.03,4.33,4.22
|
||||||
|
02/01/2024,5.49,5.51,5.42,5.38,5.15,4.68,4.2,3.96,3.8,3.83,3.87,4.21,4.1
|
||||||
|
01/31/2024,5.53,5.46,5.42,5.4,5.18,4.73,4.27,4.05,3.91,3.95,3.99,4.34,4.22
|
||||||
|
01/30/2024,5.53,5.47,5.42,5.38,5.19,4.8,4.36,4.14,4.0,4.03,4.06,4.4,4.28
|
||||||
|
01/29/2024,5.53,5.46,5.42,5.37,5.19,4.76,4.29,4.1,3.97,4.02,4.08,4.42,4.31
|
||||||
|
01/26/2024,5.54,5.45,5.44,5.39,5.19,4.78,4.34,4.15,4.04,4.1,4.15,4.49,4.38
|
||||||
|
01/25/2024,5.54,5.48,5.44,5.39,5.19,4.76,4.28,4.12,4.01,4.07,4.14,4.49,4.38
|
||||||
|
01/24/2024,5.52,5.44,5.44,5.4,5.22,4.83,4.34,4.19,4.06,4.14,4.18,4.52,4.41
|
||||||
|
01/23/2024,5.53,5.46,5.45,5.38,5.21,4.81,4.31,4.16,4.06,4.11,4.14,4.48,4.38
|
||||||
|
01/22/2024,5.53,5.47,5.46,5.39,5.22,4.83,4.37,4.14,4.03,4.07,4.11,4.44,4.32
|
||||||
|
01/19/2024,5.54,5.47,5.45,5.39,5.21,4.84,4.39,4.18,4.08,4.12,4.15,4.47,4.36
|
||||||
|
01/18/2024,5.53,5.48,5.45,5.39,5.2,4.8,4.34,4.13,4.04,4.1,4.14,4.48,4.37
|
||||||
|
01/17/2024,5.54,5.47,5.47,5.4,5.2,4.8,4.34,4.12,4.02,4.07,4.1,4.42,4.31
|
||||||
|
01/16/2024,5.54,5.47,5.45,5.37,5.18,4.7,4.22,4.02,3.95,4.01,4.07,4.43,4.3
|
||||||
|
01/12/2024,5.55,5.47,5.45,5.37,5.16,4.65,4.14,3.92,3.84,3.91,3.96,4.32,4.2
|
||||||
|
01/11/2024,5.54,5.47,5.46,5.38,5.22,4.75,4.26,4.02,3.9,3.95,3.98,4.32,4.18
|
||||||
|
01/10/2024,5.53,5.46,5.46,5.39,5.23,4.82,4.37,4.1,3.99,4.01,4.04,4.35,4.2
|
||||||
|
01/09/2024,5.53,5.46,5.47,5.38,5.24,4.82,4.36,4.09,3.97,4.0,4.02,4.33,4.18
|
||||||
|
01/08/2024,5.54,5.48,5.49,5.39,5.24,4.82,4.36,4.11,3.97,3.99,4.01,4.33,4.17
|
||||||
|
01/05/2024,5.54,5.48,5.47,5.41,5.24,4.84,4.4,4.17,4.02,4.04,4.05,4.37,4.21
|
||||||
|
01/04/2024,5.56,5.48,5.48,5.41,5.25,4.85,4.38,4.14,3.97,3.99,3.99,4.3,4.13
|
||||||
|
01/03/2024,5.54,5.54,5.48,5.41,5.25,4.81,4.33,4.07,3.9,3.92,3.91,4.21,4.05
|
||||||
|
01/02/2024,5.55,5.54,5.46,5.41,5.24,4.8,4.33,4.09,3.93,3.95,3.95,4.25,4.08
|
|
248
main.py
Normal file
248
main.py
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import requests
|
||||||
|
from flask import Flask, jsonify, render_template, request, redirect, make_response
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
TREASURY_URL = "https://home.treasury.gov/resource-center/data-chart-center/interest-rates/daily-treasury-rates.csv/2024/all?type=daily_treasury_yield_curve&field_tdr_date_value=2024&page&_format=csv"
|
||||||
|
|
||||||
|
CURRENT_DATE = 20240524 # Set to friday since market is closed
|
||||||
|
|
||||||
|
RATES = None
|
||||||
|
|
||||||
|
|
||||||
|
class Side(str, Enum):
|
||||||
|
BUY = "BUY"
|
||||||
|
SELL = "SELL"
|
||||||
|
|
||||||
|
|
||||||
|
# Since we know all the types of treasuries, just enumerate them
|
||||||
|
class Instrument(str, Enum):
|
||||||
|
I_1MO = "1 Mo"
|
||||||
|
I_2MO = "2 Mo"
|
||||||
|
I_3MO = "3 Mo"
|
||||||
|
I_4MO = "4 Mo"
|
||||||
|
I_6MO = "6 Mo"
|
||||||
|
I_1YR = "1 Yr"
|
||||||
|
I_2YR = "2 Yr"
|
||||||
|
I_3YR = "3 Yr"
|
||||||
|
I_5YR = "5 Yr"
|
||||||
|
I_7YR = "7 Yr"
|
||||||
|
I_10YR = "10 Yr"
|
||||||
|
I_20YR = "12 Yr"
|
||||||
|
I_30YR = "30 Yr"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_text(cls, txt):
|
||||||
|
if txt == "1 Mo":
|
||||||
|
return Instrument.I_1MO
|
||||||
|
elif txt == "2 Mo":
|
||||||
|
return Instrument.I_2MO
|
||||||
|
elif txt == "3 Mo":
|
||||||
|
return Instrument.I_3MO
|
||||||
|
elif txt == "4 Mo":
|
||||||
|
return Instrument.I_4MO
|
||||||
|
elif txt == "6 Mo":
|
||||||
|
return Instrument.I_6MO
|
||||||
|
elif txt == "1 Yr":
|
||||||
|
return Instrument.I_1YR
|
||||||
|
elif txt == "2 Yr":
|
||||||
|
return Instrument.I_2YR
|
||||||
|
elif txt == "3 Yr":
|
||||||
|
return Instrument.I_3YR
|
||||||
|
elif txt == "5 Yr":
|
||||||
|
return Instrument.I_5YR
|
||||||
|
elif txt == "7 Yr":
|
||||||
|
return Instrument.I_7YR
|
||||||
|
elif txt == "10 Yr":
|
||||||
|
return Instrument.I_20YR
|
||||||
|
elif txt == "12 Yr":
|
||||||
|
return Instrument.I_20YR
|
||||||
|
return Instrument.I_30YR
|
||||||
|
|
||||||
|
|
||||||
|
class Transaction:
|
||||||
|
def __init__(
|
||||||
|
self, user: str, instrument: Instrument, quantity: int, date: int, side: Side, rate: float
|
||||||
|
):
|
||||||
|
self.user = user
|
||||||
|
self.instrument: str = str(instrument.value)
|
||||||
|
self.side: str = str(side.value)
|
||||||
|
self.quantity = quantity
|
||||||
|
self.date = date
|
||||||
|
self.rate = rate
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
def __init__(self, balance: int = 100000) -> None:
|
||||||
|
self.ID = str(uuid.uuid4())
|
||||||
|
self.balance = balance
|
||||||
|
|
||||||
|
|
||||||
|
USERS: dict[str, User] = {}
|
||||||
|
TRANSACTIONS: dict[str, list[Transaction]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def background_saver():
|
||||||
|
while True:
|
||||||
|
with open("users.p", "wb") as fp:
|
||||||
|
pickle.dump(USERS, fp)
|
||||||
|
with open("transactions.p", "wb") as fp:
|
||||||
|
pickle.dump(TRANSACTIONS, fp)
|
||||||
|
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
def get_treasury_rates_for_year(year: int) -> pd.DataFrame | None:
|
||||||
|
global RATES
|
||||||
|
|
||||||
|
use_cache = False
|
||||||
|
if RATES is None:
|
||||||
|
if os.path.isfile("./cache.csv"):
|
||||||
|
if time.time() - os.path.getmtime("./cache.csv") < 60000:
|
||||||
|
use_cache = True
|
||||||
|
else:
|
||||||
|
use_cache = True
|
||||||
|
|
||||||
|
if use_cache:
|
||||||
|
csv: pd.DataFrame = pd.read_csv("./cache.csv")
|
||||||
|
RATES = csv
|
||||||
|
else:
|
||||||
|
response = requests.get(TREASURY_URL)
|
||||||
|
response.raise_for_status()
|
||||||
|
raw_csv = response.content.decode("UTF-8")
|
||||||
|
csv_io = io.StringIO(raw_csv)
|
||||||
|
csv: pd.DataFrame = pd.read_csv(csv_io)
|
||||||
|
csv.to_csv("./cache.csv", index=False)
|
||||||
|
RATES = csv
|
||||||
|
|
||||||
|
ret = {}
|
||||||
|
cols = list(RATES.columns.values)
|
||||||
|
for col in cols:
|
||||||
|
ret[col] = RATES[col].tolist()
|
||||||
|
|
||||||
|
dates = ret["Date"]
|
||||||
|
del ret["Date"]
|
||||||
|
return ret, dates
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/transactions/<id>")
|
||||||
|
def get_transactions_for_user(id):
|
||||||
|
t = TRANSACTIONS.get(id)
|
||||||
|
if t is None:
|
||||||
|
return jsonify([])
|
||||||
|
|
||||||
|
return jsonify([{"term": tx.instrument, "quantity": tx.quantity, "side": tx.side, "date": tx.date, "rate": tx.rate} for tx in t])
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/buy", methods=["POST"])
|
||||||
|
def buy():
|
||||||
|
content = request.json
|
||||||
|
print(content)
|
||||||
|
q = int(content["quantity"])
|
||||||
|
i = content["instrument"]
|
||||||
|
user_id = content["user_id"]
|
||||||
|
cost = q * 100
|
||||||
|
if USERS.get(user_id) is not None:
|
||||||
|
if cost > USERS[user_id].balance:
|
||||||
|
return "You do not have the funds for this transaction", 400
|
||||||
|
else:
|
||||||
|
return "User does not exist", 404
|
||||||
|
|
||||||
|
current_rate = RATES[i].tolist()[-1]
|
||||||
|
|
||||||
|
USERS[user_id].balance = USERS[user_id].balance - cost
|
||||||
|
t = Transaction(user_id, Instrument.from_text(i), q, time.time(), Side.BUY, current_rate)
|
||||||
|
|
||||||
|
if TRANSACTIONS.get(user_id) is None:
|
||||||
|
TRANSACTIONS[user_id] = [t]
|
||||||
|
else:
|
||||||
|
TRANSACTIONS[user_id].append(t)
|
||||||
|
|
||||||
|
return jsonify(t)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/login", methods=["POST"])
|
||||||
|
def login():
|
||||||
|
content = request.json
|
||||||
|
id = content.get("id")
|
||||||
|
if id is None:
|
||||||
|
return "ID cannot be empty", 400
|
||||||
|
u = USERS.get(id)
|
||||||
|
if u is None:
|
||||||
|
return "User does not exist", 404
|
||||||
|
resp = make_response(redirect("/"))
|
||||||
|
resp.set_cookie("userid", u.ID)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/createuser", methods=["POST"])
|
||||||
|
def createuser():
|
||||||
|
u = User()
|
||||||
|
USERS[u.ID] = u
|
||||||
|
resp = make_response(redirect("/"))
|
||||||
|
resp.set_cookie("userid", u.ID)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/register")
|
||||||
|
def register():
|
||||||
|
if "userid" in request.cookies:
|
||||||
|
return redirect("/")
|
||||||
|
return render_template("register.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/logout")
|
||||||
|
def logout():
|
||||||
|
if "userid" not in request.cookies:
|
||||||
|
return redirect("/register")
|
||||||
|
resp = make_response(redirect("/register"))
|
||||||
|
resp.set_cookie("userid", "", expires=0)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
if "userid" not in request.cookies:
|
||||||
|
return redirect("/register")
|
||||||
|
uid = request.cookies.get("userid")
|
||||||
|
if uid not in USERS:
|
||||||
|
resp = make_response(redirect("/register"))
|
||||||
|
resp.set_cookie("userid", "", expires=0)
|
||||||
|
return resp
|
||||||
|
rates, dates = get_treasury_rates_for_year(2024)
|
||||||
|
return render_template("index.html", rates=[rates], dates=dates, userid=uid, balance=USERS[uid].balance)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Load up files if they exist
|
||||||
|
if os.path.isfile("./users.p"):
|
||||||
|
print("Loading users")
|
||||||
|
with open("./users.p", "rb") as fp:
|
||||||
|
USERS = pickle.load(fp)
|
||||||
|
|
||||||
|
if os.path.isfile("./transactions.p"):
|
||||||
|
print("Loading transactions")
|
||||||
|
with open("./transactions.p", "rb") as fp:
|
||||||
|
TRANSACTIONS = pickle.load(fp)
|
||||||
|
|
||||||
|
# Start persistence
|
||||||
|
thread = threading.Thread(target=background_saver)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
# Start Server
|
||||||
|
app.run(port=8008)
|
||||||
|
|
||||||
|
|
||||||
|
thread = threading.Thread(target=background_saver)
|
||||||
|
thread.start()
|
0
requirements.txt
Normal file
0
requirements.txt
Normal file
50
templates/base.html
Normal file
50
templates/base.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>DWSFi</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/js/tabler.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/css/tabler.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/css/tabler-flags.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/css/tabler-payments.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/css/tabler-vendors.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="page">
|
||||||
|
<header class="navbar navbar-expand-sm navbar-light d-print-none">
|
||||||
|
<div class="container-xl">
|
||||||
|
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||||
|
<a href="#">
|
||||||
|
<img src="https://images.rawpixel.com/image_png_800/czNmcy1wcml2YXRlL3Jhd3BpeGVsX2ltYWdlcy93ZWJzaXRlX2NvbnRlbnQvbHIvdjEwNDktMjIucG5n.png" width="110" height="32" alt="Tabler" class="navbar-brand-image" />
|
||||||
|
</a>
|
||||||
|
<span>DWSFi</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="navbar-nav flex-row order-md-last">
|
||||||
|
<div class="nav-item">
|
||||||
|
<a href="/logout" class="nav-link d-flex lh-1 text-reset">
|
||||||
|
<div class="d-xl-block ps-2">
|
||||||
|
{% if userid is not none %}
|
||||||
|
<div>{{userid}} - Logout</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-body">
|
||||||
|
<div class="container-xl">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
220
templates/index.html
Normal file
220
templates/index.html
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row row-deck row-cards">
|
||||||
|
<div class="col-sm-6 col-lg-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="subheader">Account Balance</div>
|
||||||
|
<div class="ms-auto lh-1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-baseline">
|
||||||
|
<div class="h1 mb-0 me-2">${{balance}}</div>
|
||||||
|
<div class="me-auto">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header border-0">
|
||||||
|
<div class="card-title">Treasury Yields</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<div id="chart-yields" class="chart-lg"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Transactions</h3>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table card-table table-vcenter text-nowrap datatable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Term</th>
|
||||||
|
<th>Quantity</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Side</th>
|
||||||
|
<th>Rate</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="txbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Buy/Sell</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-label">Term</div>
|
||||||
|
<select class="form-select" id="term">
|
||||||
|
<option value="1 Mo">1 Mo</option>
|
||||||
|
<option value="2 Mo">2 Mo</option>
|
||||||
|
<option value="3 Mo">3 Mo</option>
|
||||||
|
<option value="4 Mo">4 Mo</option>
|
||||||
|
<option value="6 Mo">6 Mo</option>
|
||||||
|
<option value="1 Yr">1 Yr</option>
|
||||||
|
<option value="2 Yr">2 Yr</option>
|
||||||
|
<option value="3 Yr">3 Yr</option>
|
||||||
|
<option value="5 Yr">5 Yr</option>
|
||||||
|
<option value="7 Yr">7 Yr</option>
|
||||||
|
<option value="10 Yr">10 Yr</option>
|
||||||
|
<option value="12 Yr">12 Yr</option>
|
||||||
|
<option value="30 Yr">30 Yr</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Quantity</label>
|
||||||
|
<input type="number" class="form-control" name="quantity" id="quantity" placeholder="100" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Side</label>
|
||||||
|
<select class="form-select">
|
||||||
|
<option value="BUY">Buy</option>
|
||||||
|
<option value="SELL">Sell</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="buybtn" class="btn btn-primary ms-auto">Submit Order</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/libs/apexcharts/dist/apexcharts.min.js" defer></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
document.getElementById("buybtn").addEventListener('click', () => {
|
||||||
|
var r = new XMLHttpRequest();
|
||||||
|
r.open("POST", "/api/buy", true);
|
||||||
|
r.onload = function () {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
r.setRequestHeader("Content-Type", "application/json")
|
||||||
|
// Send data in below way from JS
|
||||||
|
r.send(JSON.stringify({
|
||||||
|
"quantity": document.getElementById('quantity').value,
|
||||||
|
"instrument": document.getElementById("term").value,
|
||||||
|
"user_id": "{{userid}}"
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
|
||||||
|
window.ApexCharts && (new ApexCharts(document.getElementById('chart-yields'), {
|
||||||
|
chart: {
|
||||||
|
type: "line",
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
parentHeightOffset: 0,
|
||||||
|
height: 512,
|
||||||
|
toolbar: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
width: 2,
|
||||||
|
lineCap: "round",
|
||||||
|
curve: "straight",
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{% for rate in rates %}
|
||||||
|
{% for name, data in rate.items() %}
|
||||||
|
{
|
||||||
|
name: "{{name}}",
|
||||||
|
data: {{data}}
|
||||||
|
},
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: 'dark'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
padding: {
|
||||||
|
top: -20,
|
||||||
|
right: 0,
|
||||||
|
left: -4,
|
||||||
|
bottom: -4
|
||||||
|
},
|
||||||
|
strokeDashArray: 4,
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
labels: {
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
padding: 4
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labels: [
|
||||||
|
{% for each in dates %}
|
||||||
|
"{{each}}",
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
|
||||||
|
legend: {
|
||||||
|
show: true,
|
||||||
|
position: 'bottom',
|
||||||
|
offsetY: 12,
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
radius: 100,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 8
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})).render();
|
||||||
|
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
xhr.open('GET', 'http://localhost:8008/api/transactions/{{userid}}');
|
||||||
|
|
||||||
|
xhr.onload = function() {
|
||||||
|
console.log(xhr.response);
|
||||||
|
var jsonResponse = JSON.parse(xhr.response);
|
||||||
|
for (let i = 0; i < jsonResponse.length; i++) {
|
||||||
|
var newRow="<tr><td>"+ jsonResponse[i].term + "</td><td>" + jsonResponse[i].quantity + "</td><td>" + jsonResponse[i].date + "</td><td>" + jsonResponse[i].side + "</td><td>"+ jsonResponse[i].rate +"</td></tr>";
|
||||||
|
document.getElementById('txbody').innerHTML += newRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
64
templates/register.html
Normal file
64
templates/register.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row row-deck row-cards">
|
||||||
|
<h1>Registration</h1>
|
||||||
|
<div class="col-sm-6 col-lg-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4>Registration will assign you a UUID, please remember this UUID to login!</h4>
|
||||||
|
|
||||||
|
<button id="regbtn" class="btn btn-primary ms-auto">Register</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-lg-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4>If you already have a UUID, enter it below to login</h4>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Enter ID</label>
|
||||||
|
<input type="text" class="form-control" placeholder="User ID" id="uuidid" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="loginbtn" class="btn btn-primary ms-auto">Login</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById("regbtn").addEventListener('click', () => {
|
||||||
|
var r = new XMLHttpRequest();
|
||||||
|
r.open("POST", "/api/createuser", true);
|
||||||
|
r.onreadystatechange = function () {
|
||||||
|
r.onload = function () {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
r.setRequestHeader("Content-Type", "application/json")
|
||||||
|
// Send data in below way from JS
|
||||||
|
r.send();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("loginbtn").addEventListener('click', () => {
|
||||||
|
var r = new XMLHttpRequest();
|
||||||
|
r.open("POST", "/api/login", true);
|
||||||
|
r.onreadystatechange = function () {
|
||||||
|
r.onload = function () {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
r.setRequestHeader("Content-Type", "application/json")
|
||||||
|
// Send data in below way from JS
|
||||||
|
r.send(JSON.stringify({
|
||||||
|
"id": document.getElementById('uuidid').value
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
BIN
transactions.p
Normal file
BIN
transactions.p
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user