From 7490381924bf22fabc27f857f9a1abdb4acd70fc Mon Sep 17 00:00:00 2001 From: bot50 Date: Fri, 26 Apr 2024 05:39:00 +0000 Subject: [PATCH] kukemuna-cs50/problems/2024/x/finance@20240426T053900.161304594Z --- app.py | 273 ++++++++++++++++++ finance.db | Bin 0 -> 20480 bytes .../16fa13c5890feb947b5d5c907cfedf5b | Bin 0 -> 46 bytes .../2029240f6d1128be89ddc32729463129 | Bin 0 -> 9 bytes .../39b31c290f4b506fd545e82536ad3484 | Bin 0 -> 46 bytes helpers.py | 88 ++++++ requirements.txt | 5 + static/I_heart_validator.png | Bin 0 -> 345 bytes static/favicon.ico | Bin 0 -> 15406 bytes static/styles.css | 23 ++ templates/apology.html | 11 + templates/buy.html | 17 ++ templates/history.html | 30 ++ templates/home.html | 40 +++ templates/layout.html | 91 ++++++ templates/login.html | 17 ++ templates/quote.html | 23 ++ templates/register.html | 20 ++ templates/sell.html | 22 ++ 19 files changed, 660 insertions(+) create mode 100644 app.py create mode 100644 finance.db create mode 100644 flask_session/16fa13c5890feb947b5d5c907cfedf5b create mode 100644 flask_session/2029240f6d1128be89ddc32729463129 create mode 100644 flask_session/39b31c290f4b506fd545e82536ad3484 create mode 100644 helpers.py create mode 100644 requirements.txt create mode 100644 static/I_heart_validator.png create mode 100644 static/favicon.ico create mode 100644 static/styles.css create mode 100644 templates/apology.html create mode 100644 templates/buy.html create mode 100644 templates/history.html create mode 100644 templates/home.html create mode 100644 templates/layout.html create mode 100644 templates/login.html create mode 100644 templates/quote.html create mode 100644 templates/register.html create mode 100644 templates/sell.html diff --git a/app.py b/app.py new file mode 100644 index 0000000..cd55ffa --- /dev/null +++ b/app.py @@ -0,0 +1,273 @@ +import os +import datetime + +from cs50 import SQL +from flask import Flask, flash, redirect, render_template, request, session +from flask_session import Session +from werkzeug.security import check_password_hash, generate_password_hash + +from helpers import apology, login_required, lookup, usd + +# Configure application +app = Flask(__name__) + +# Custom filter +app.jinja_env.filters["usd"] = usd + +# Configure session to use filesystem (instead of signed cookies) +app.config["SESSION_PERMANENT"] = False +app.config["SESSION_TYPE"] = "filesystem" +Session(app) + +# Configure CS50 Library to use SQLite database +db = SQL("sqlite:///finance.db") + + +@app.after_request +def after_request(response): + """Ensure responses aren't cached""" + response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" + response.headers["Expires"] = 0 + response.headers["Pragma"] = "no-cache" + return response + + +@app.route("/") +@login_required +def index(): + """Show portfolio of stocks""" + user_id = session["user_id"] + + cash = db.execute("SELECT * FROM users where id = ?", user_id) + + stocks = db.execute( + "SELECT symbol, sum(shares) FROM transactions WHERE user_id = ? GROUP BY symbol HAVING sum(shares) > 0", user_id) + + stocks_total = 0 + + for stock in stocks: + + symbol = stock["symbol"] + stock_price = lookup(symbol.upper()) + stock["shares"] = stock["sum(shares)"] + stock["price"] = stock_price["price"] + stock["total"] = usd(stock_price["price"] * stock["sum(shares)"]) + stocks_total = stocks_total + (stock_price["price"] * stock["sum(shares)"]) + + total = usd(cash[0]["cash"] + stocks_total) + + return render_template("home.html", cash=usd(cash[0]["cash"]), stocks=stocks, total=total) + # return apology("MOFO") + + +@app.route("/buy", methods=["GET", "POST"]) +@login_required +def buy(): + """Buy shares of stock""" + if request.method == "GET": + return render_template("buy.html") + else: + symbol = request.form.get("symbol") + shares = request.form.get("shares") + + if not symbol: + return apology("Not Symbol") + + stock = lookup(symbol.upper()) + + if stock == None: + return apology("Symbol not found") + + if not shares or shares.isalpha() or not float(shares).is_integer(): + return apology("invalid shares") + + else: + if float(shares) > 0: + transaction_value = float(shares) * stock["price"] + + user_id = session["user_id"] + user_cash_db = db.execute("SELECT cash FROM users WHERE id = ?", user_id) + user_cash = user_cash_db[0]["cash"] + + if user_cash < transaction_value: + return apology("U broke, m8!") + + free_cash = user_cash - transaction_value + + db.execute("UPDATE users SET cash = ? WHERE id = ?", free_cash, user_id) + + date = datetime.datetime.now() + + db.execute("INSERT INTO transactions (user_id, symbol, shares, price, date) VALUES (?, ?, ?, ?, ?)", + user_id, stock["symbol"], shares, stock["price"], date) + + flash("Bought!") + else: + return apology("Missing shares", 400) + + return redirect("/") + # return apology("TODO") + + +@app.route("/history") +@login_required +def history(): + """Show history of transactions""" + stocks = db.execute("SELECT * FROM transactions") + + return render_template("history.html", stocks=stocks) + # return apology("TODO") + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + """Log user in""" + + # Forget any user_id + session.clear() + + # User reached route via POST (as by submitting a form via POST) + if request.method == "POST": + # Ensure username was submitted + if not request.form.get("username"): + return apology("must provide username", 403) + + # Ensure password was submitted + elif not request.form.get("password"): + return apology("must provide password", 403) + + # Query database for username + rows = db.execute( + "SELECT * FROM users WHERE username = ?", request.form.get("username") + ) + + # Ensure username exists and password is correct + if len(rows) != 1 or not check_password_hash( + rows[0]["hash"], request.form.get("password") + ): + return apology("invalid username and/or password", 403) + + # Remember which user has logged in + session["user_id"] = rows[0]["id"] + + # Redirect user to home page + return redirect("/") + + # User reached route via GET (as by clicking a link or via redirect) + else: + return render_template("login.html") + + +@app.route("/logout") +def logout(): + """Log user out""" + + # Forget any user_id + session.clear() + + # Redirect user to login form + return redirect("/") + + +@app.route("/quote", methods=["GET", "POST"]) +@login_required +def quote(): + """Get stock quote.""" + if request.method == "POST": + + symbol = request.form.get("symbol") + if not symbol: + return apology("Not Symbol") + + stock = lookup(symbol.upper()) + + if stock == None: + return apology("Symbol not found") + + stock["price"] = usd(stock["price"]) + + return render_template("quote.html", stock=stock) + else: + return render_template("quote.html") + # return apology("TODO") + + +@app.route("/register", methods=["GET", "POST"]) +def register(): + """Register user""" + if request.method == "POST": + # Ensure username was submitted + if not request.form.get("username"): + return apology("must provide username", 400) + + # Ensure password was submitted + elif not request.form.get("password"): + return apology("must provide password", 400) + + # Ensure password repeat matches + if not request.form.get("password") == request.form.get("confirmation"): + return apology("passwords don't match", 400) + + username = request.form.get("username") + password = request.form.get("password") + + username_exists = db.execute("SELECT * FROM users WHERE username = ?", username) + + if username_exists: + return apology("username already exists!", 400) + + db.execute("INSERT INTO users(username, hash) VALUES(?, ?)", + username, generate_password_hash(password)) + return redirect("/") + else: + return render_template("register.html") + # return apology("MOFO") + + +@app.route("/sell", methods=["GET", "POST"]) +@login_required +def sell(): + """Sell shares of stock""" + user_id = session["user_id"] + + if request.method == "POST": + symbol = request.form.get("symbol") + shares = int(request.form.get("shares")) + + if not symbol: + return apology("Not Symbol") + + stock = lookup(symbol.upper()) + + if stock == None: + return apology("Symbol not found") + + stocks = db.execute( + "SELECT symbol, sum(shares) FROM transactions WHERE user_id = ? AND symbol = ?", user_id, symbol) + + if stocks[0]["sum(shares)"] < shares: + return apology("Not enough shares") + + transaction_value = shares * stock["price"] + + user_cash_db = db.execute("SELECT cash FROM users WHERE id = ?", user_id) + user_cash = user_cash_db[0]["cash"] + + new_cash = transaction_value + user_cash + + db.execute("UPDATE users SET cash = ? WHERE id = ?", new_cash, user_id) + + date = datetime.datetime.now() + + db.execute("INSERT INTO transactions (user_id, symbol, shares, price, date) VALUES (?, ?, ?, ?, ?)", + user_id, stock["symbol"], -abs(shares), stock["price"], date) + + flash("Sold!") + + return redirect("/") + + else: + stocks = db.execute( + "SELECT symbol FROM transactions WHERE user_id = ? GROUP BY symbol HAVING sum(shares) > 0", user_id) + return render_template("sell.html", stocks=stocks) + # return apology("TODO") diff --git a/finance.db b/finance.db new file mode 100644 index 0000000000000000000000000000000000000000..82ffe12280571445902f1e353acbde825b3c40ff GIT binary patch literal 20480 zcmeI3TZkJ~7=S0)OPXYptlBl+x{kWLlXctm%(-MPw3yhY%hv2Y(XQ5($>fY(*n76v z3qI^(--P;F&Nn~hRr=pQ z6cbyUW7m|#X#7F^lITDK17H9QfB`T72EYIq00UqE41j^hVPN^A=1{V`JFxQUl2e-I zON-8I#c`LW=4LB$zh(b;E?dZ{h3q@|oGNcoQ<15%IyhX&4dlkv(ec5d?D(YmZf;V| z77HVT!(zvw+;Bl19uZ41pWhuhQQ?cnqG&6fE||H!k>4EMh)&1)qUhX==sIT8Lq z?y$N}s3XJGenm~Kuj(BQwkG@f0+Urym4#_>D}1cN7f$e5mv8ymv`N>NjVV8c-D1z) zHZ(shWwTE8xKla4MX~Do>xPR%x$(jM8!puY zxr5nazM$$zw0?woV~G(Z7`v&&ZpOcQ9Fq@XfB`T72EYIq00UqE41fVJ00zLolQghQ z1FcHO%2;S(YHFt9E}oiSvI#cQrfu4K*Mj*8nIa41gOAxv>Alf`F2{6`>lv64>{6%f z@-lav66Qu4F^82rmwAj6PP{Tt6Neeha7~Uld0=je`3J#1i?lnrDc&$B$N>(uf} zC6{vU5sQ|P&WX-AA|~?ECgWH{rk?9jY!aUN`HRW(qAkA_Y*msgW5GP<({nX>JGsf? z*uWuI6ZmrEkBF2jzEZv<_qJ0X4sEc&K*hJ#R$_qi0;8@6`3|M#9#lLJ{VdB-(GuI z(~!re)++@LHUODT%$}4FhF1eO;Ha&eJ(|z$_)iYmy3y0~NDiAe!#&-P81O0-T1{JTC##B{N=T@u*b5X6@D${Lu>j^^)Y_}wv88^F0u*|Z64;;^ z0g|-#yaYCms2f^`lw(W791+%bZUaO%quO)Qp+%0i@B57GQA~NuAHruPP~^xuFOS_i zyBVZnW*XWv(xLBX*td7Y6{NQYq=wcmfub0H1n0l00mY<9NQZuq;iTHOfN7ghEiOeA zL-@6*<@AjjP)}CKhOtq*KQRKL{4oIji=3_*PDD+&a zQw$MjEYiXfD2l1)Q5PqdsqK(J5%Fxz$cV{^{TxFQxFf^Fg0hW|Fn7b;Oe!9h-;eGEJciD=shuaIxlZTTm`XBjeh|*{m%pd literal 0 HcmV?d00001 diff --git a/flask_session/16fa13c5890feb947b5d5c907cfedf5b b/flask_session/16fa13c5890feb947b5d5c907cfedf5b new file mode 100644 index 0000000000000000000000000000000000000000..4a8ac5194f51b9b0ee348c4769349067a730ba3b GIT binary patch literal 46 xcmZ>h2}x^Uohr`&0ku;!dbr{XQj2mE^HTFlrgZkOmlmfM#b>5W@n$U50{|+i4r~Ab literal 0 HcmV?d00001 diff --git a/flask_session/2029240f6d1128be89ddc32729463129 b/flask_session/2029240f6d1128be89ddc32729463129 new file mode 100644 index 0000000000000000000000000000000000000000..7f5741f13017ee705ea34021d222a06a6ee2a6c9 GIT binary patch literal 9 QcmZQzU|?uq^=8ro00XcA0RR91 literal 0 HcmV?d00001 diff --git a/flask_session/39b31c290f4b506fd545e82536ad3484 b/flask_session/39b31c290f4b506fd545e82536ad3484 new file mode 100644 index 0000000000000000000000000000000000000000..2f7fa3ad5df5db38fa0fd157f36f2033d36ae944 GIT binary patch literal 46 xcmZ=>7@XF?I#r$l0&1sd^l-%&q!#5S=B4J9OzG@lFD*_jiqA}$;>}p92LM4-4($K{ literal 0 HcmV?d00001 diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..43349ca --- /dev/null +++ b/helpers.py @@ -0,0 +1,88 @@ +import csv +import datetime +import pytz +import requests +import urllib +import uuid + +from flask import redirect, render_template, request, session +from functools import wraps + + +def apology(message, code=400): + """Render message as an apology to user.""" + + def escape(s): + """ + Escape special characters. + + https://github.com/jacebrowning/memegen#special-characters + """ + for old, new in [ + ("-", "--"), + (" ", "-"), + ("_", "__"), + ("?", "~q"), + ("%", "~p"), + ("#", "~h"), + ("/", "~s"), + ('"', "''"), + ]: + s = s.replace(old, new) + return s + + return render_template("apology.html", top=code, bottom=escape(message)), code + + +def login_required(f): + """ + Decorate routes to require login. + + https://flask.palletsprojects.com/en/latest/patterns/viewdecorators/ + """ + + @wraps(f) + def decorated_function(*args, **kwargs): + if session.get("user_id") is None: + return redirect("/login") + return f(*args, **kwargs) + + return decorated_function + + +def lookup(symbol): + """Look up quote for symbol.""" + + # Prepare API request + symbol = symbol.upper() + end = datetime.datetime.now(pytz.timezone("US/Eastern")) + start = end - datetime.timedelta(days=7) + + # Yahoo Finance API + url = ( + f"https://query1.finance.yahoo.com/v7/finance/download/{urllib.parse.quote_plus(symbol)}" + f"?period1={int(start.timestamp())}" + f"&period2={int(end.timestamp())}" + f"&interval=1d&events=history&includeAdjustedClose=true" + ) + + # Query API + try: + response = requests.get( + url, + cookies={"session": str(uuid.uuid4())}, + headers={"Accept": "*/*", "User-Agent": request.headers.get("User-Agent")}, + ) + response.raise_for_status() + + # CSV header: Date,Open,High,Low,Close,Adj Close,Volume + quotes = list(csv.DictReader(response.content.decode("utf-8").splitlines())) + price = round(float(quotes[-1]["Adj Close"]), 2) + return {"price": price, "symbol": symbol} + except (KeyError, IndexError, requests.RequestException, ValueError): + return None + + +def usd(value): + """Format value as USD.""" + return f"${value:,.2f}" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..743fadd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +cs50 +Flask +Flask-Session +pytz +requests diff --git a/static/I_heart_validator.png b/static/I_heart_validator.png new file mode 100644 index 0000000000000000000000000000000000000000..c7db3d2bd567deed30dafc9f831d1e4d3aa56c10 GIT binary patch literal 345 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 + + {{ top }} +{% endblock %} diff --git a/templates/buy.html b/templates/buy.html new file mode 100644 index 0000000..b34ebb5 --- /dev/null +++ b/templates/buy.html @@ -0,0 +1,17 @@ +{% extends "layout.html" %} + +{% block title %} + Buy +{% endblock %} + +{% block main %} +
+
+ +
+
+ +
+ +
+{% endblock %} diff --git a/templates/history.html b/templates/history.html new file mode 100644 index 0000000..7384232 --- /dev/null +++ b/templates/history.html @@ -0,0 +1,30 @@ +{% extends "layout.html" %} + +{% block title %} + History +{% endblock %} + +{% block main %} + + + + + + + + + + + + {% for stock in stocks%} + + + + + + + {% endfor%} + +
SymbolSharesPriceTransacted
{{ stock.symbol }}{{ stock.shares }}{{ stock.price }}{{ stock.date }}
+ +{% endblock %} diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..e7fe1bb --- /dev/null +++ b/templates/home.html @@ -0,0 +1,40 @@ +{% extends "layout.html" %} + +{% block title %} + Home +{% endblock %} + +{% block main %} + + + + + + + + + + + + {% for stock in stocks%} + + + + + + + {% endfor%} + + + + + + + + + + + + +
SymbolSharesPriceTOTAL
{{ stock.symbol }}{{ stock.shares }}{{ stock.price }}{{ stock.total }}
Cash{{ cash }}
TOTAL{{ total }}
+{% endblock %} diff --git a/templates/layout.html b/templates/layout.html new file mode 100644 index 0000000..14d3053 --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + C$50 Finance: {% block title %}{% endblock %} + + + + + + + + {% if get_flashed_messages() %} +
+ +
+ {% endif %} + +
+ {% block main %}{% endblock %} +
+ +
+ +

+ Data provided by Yahoo +

+ +
+ + + +
+ +
+ + + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..0fe2d8e --- /dev/null +++ b/templates/login.html @@ -0,0 +1,17 @@ +{% extends "layout.html" %} + +{% block title %} + Log In +{% endblock %} + +{% block main %} +
+
+ +
+
+ +
+ +
+{% endblock %} diff --git a/templates/quote.html b/templates/quote.html new file mode 100644 index 0000000..aebde0e --- /dev/null +++ b/templates/quote.html @@ -0,0 +1,23 @@ +{% extends "layout.html" %} + +{% block title %} + Quote +{% endblock %} + +{% block main %} + + {% if stock %} +

A share of {{ stock.symbol }} costs {{ stock.price }}

+ + {% else%} +
+
+ +
+ +
+ {% endif %} + + + +{% endblock %} diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..c0bddda --- /dev/null +++ b/templates/register.html @@ -0,0 +1,20 @@ +{% extends "layout.html" %} + +{% block title %} + Register +{% endblock %} + +{% block main %} +
+
+ +
+
+ +
+
+ +
+ +
+{% endblock %} diff --git a/templates/sell.html b/templates/sell.html new file mode 100644 index 0000000..68f5186 --- /dev/null +++ b/templates/sell.html @@ -0,0 +1,22 @@ +{% extends "layout.html" %} + +{% block title %} + Sell +{% endblock %} + +{% block main %} +
+
+ +
+
+ +
+ +
+{% endblock %}