Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
hinedy committed Oct 20, 2022
0 parents commit 735ed44
Show file tree
Hide file tree
Showing 21 changed files with 579 additions and 0 deletions.
240 changes: 240 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import os

from cs50 import SQL
from flask import Flask, flash, redirect, render_template, request, session
from flask_session import Session
from tempfile import mkdtemp
from werkzeug.security import check_password_hash, generate_password_hash

from helpers import apology, login_required, lookup, usd

# Configure application
app = Flask(__name__)

# Ensure templates are auto-reloaded
app.config["TEMPLATES_AUTO_RELOAD"] = True

# 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")

# Make sure API key is set
if not os.environ.get("API_KEY"):
raise RuntimeError("API_KEY not set")


@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"""
userID = session["user_id"]
symbols = db.execute("SELECT DISTINCT symbol FROM transactions WHERE user_id == ?", userID)
assets = []
rows = db.execute("SELECT cash from users WHERE id = ?", userID )
current_cash = rows[0]["cash"]
total = current_cash
for symbol in symbols:

shares_bought = db.execute("SELECT SUM(shares) FROM transactions WHERE user_id == :userID AND symbol == :symbol AND type == 'buy'", userID = userID, symbol = symbol['symbol'])
shares_sold = db.execute("SELECT SUM(shares) FROM transactions WHERE user_id == :userID AND symbol == :symbol AND type == 'sell'", userID = userID, symbol = symbol['symbol'])
if shares_sold[0]['SUM(shares)'] is None:
shares_sold[0]['SUM(shares)'] = 0
net_shares = shares_bought[0]['SUM(shares)'] - shares_sold[0]['SUM(shares)']

if not net_shares == 0:
assets.append({"symbol": symbol["symbol"], "name" : lookup(symbol["symbol"])['name'] , "shares" : net_shares , "price" : lookup(symbol["symbol"])['price'] })
for asset in assets:
total = total + (asset['price'] * asset['shares'])

return render_template("index.html" , current_cash = current_cash, assets = assets , total = total)


@app.route("/buy", methods=["GET", "POST"])
@login_required
def buy():
"""Buy shares of stock"""
userID = session["user_id"]
if request.method == "POST":
symbol = request.form.get("symbol")
if not lookup(symbol):
return apology("invalid symbol")
if not request.form.get("shares") or not request.form.get("shares").isnumeric():
return apology("please specify shares", 400)
else:
price = lookup(symbol)["price"]
stock = lookup(symbol)["name"]

shares = int(request.form.get("shares"))
rows = db.execute("SELECT cash from users WHERE id = ?", userID )
current_cash = rows[0]["cash"]
if current_cash - (shares * price) < 0:
return apology("not enough funds")
else:
# make transaction
current_cash = current_cash - (shares * price)
db.execute("UPDATE users SET cash= :current_cash WHERE id= :userID" , current_cash = current_cash, userID= userID)
db.execute("CREATE TABLE IF NOT EXISTS transactions (transaction_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id TEXT NOT NULL, stock TEXT NOT NULL, symbol TEXT NOT NULL, price NUMERIC NOT NULL, shares INTEGER NOT NULL, type TEXT NOT NULL, time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(user_id) REFERENCES users(id));")
db.execute("INSERT INTO transactions (user_id, stock, symbol, price, shares, type) VALUES(?, ?, ?, ?, ?, ?)", userID, stock, symbol, usd(price), shares, 'buy')
return redirect("/")

else:
return render_template("buy.html")



@app.route("/history")
@login_required
def history():
"""Show history of transactions"""
userID = session["user_id"]
transactions = db.execute("SELECT symbol, shares, type, price, time FROM transactions WHERE user_id == ?", userID)


return render_template("history.html", transactions = transactions)


@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 lookup(symbol):
return apology("invalid symbol")
else:
return render_template("quoted.html", quote = lookup(symbol))
else:
return render_template("quote.html")


@app.route("/register", methods=["GET", "POST"])
def register():
"""Register user"""
# Forget any user_id
session.clear()

# User reached route via POST (as by submitting a form via POST)
if request.method == "POST":
name = request.form.get("username")
rows = db.execute("SELECT * FROM users WHERE username = ?", request.form.get("username"))
if not name or len(rows) > 0:
return apology("Invalid username", 400)
# Ensure password was submitted

elif not request.form.get("password"):
return apology("must provide password", 400)
elif not request.form.get("password") == request.form.get("confirmation"):
return apology("password doesn't match", 400)

hash = generate_password_hash(request.form.get("password"))
db.execute("INSERT INTO users (username, hash) VALUES(?, ?)", name , hash )

return redirect("/")

# User reached route via GET (as by clicking a link or via redirect)
else:
return render_template("register.html")





@app.route("/sell", methods=["GET", "POST"])
@login_required
def sell():

"""Sell shares of stock"""
userID = session["user_id"]


if request.method == "POST":
symbol = request.form.get("symbol")
shares_bought = db.execute("SELECT SUM(shares) FROM transactions WHERE user_id == :userID AND symbol == :symbol AND type == 'buy'", userID = userID, symbol = symbol)
shares_sold = db.execute("SELECT SUM(shares) FROM transactions WHERE user_id == :userID AND symbol == :symbol AND type == 'sell'", userID = userID, symbol = symbol)
# rows = db.execute("SELECT SUM(shares) - (SELECT SUM(shares) FROM transactions WHERE user_id == :userID AND symbol == :symbol AND type == 'sell') AS net_shares FROM transactions WHERE user_id == :userID AND symbol == :symbol AND type == 'buy'", userID = userID, symbol = symbol)
if shares_sold[0]['SUM(shares)'] is None:
shares_sold[0]['SUM(shares)'] = 0
net_shares = shares_bought[0]['SUM(shares)'] - shares_sold[0]['SUM(shares)']
shares_to_sell = int(request.form.get("shares"))
if not symbol:
return apology("INVALID SYMBOL")
elif shares_to_sell < 0 or (net_shares - shares_to_sell) < 0:
return apology("invalid number of shares")
else:
price = lookup(symbol)["price"]
stock = lookup(symbol)["name"]
difference = int(shares_to_sell) * price
cash_list = db.execute("SELECT cash from users WHERE id = ?", userID )
current_cash = cash_list[0]["cash"]
current_cash = current_cash + difference
db.execute("UPDATE users SET cash= :current_cash WHERE id= :userID" , current_cash = current_cash, userID= userID)
db.execute("INSERT INTO transactions (user_id, stock, symbol, price, shares, type) VALUES(?, ?, ?, ?, ?, ?)", userID, stock, symbol, usd(price), shares_to_sell, 'sell')
return redirect("/")

else:

user_stocks = db.execute("SELECT DISTINCT symbol FROM transactions WHERE user_id == ?", userID )
return render_template("sell.html", options = user_stocks)
Binary file added finance.db
Binary file not shown.
Binary file added finance.zip
Binary file not shown.
Binary file added flask_session/2029240f6d1128be89ddc32729463129
Binary file not shown.
Binary file added flask_session/b40c21b292afe27c0b3163683abd2fb4
Binary file not shown.
64 changes: 64 additions & 0 deletions helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
import requests
import urllib.parse

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/1.1.x/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."""

# Contact API
try:
api_key = os.environ.get("API_KEY")
url = f"https://cloud.iexapis.com/stable/stock/{urllib.parse.quote_plus(symbol)}/quote?token={api_key}"
response = requests.get(url)
response.raise_for_status()
except requests.RequestException:
return None

# Parse response
try:
quote = response.json()
return {
"name": quote["companyName"],
"price": float(quote["latestPrice"]),
"symbol": quote["symbol"]
}
except (KeyError, TypeError, ValueError):
return None


def usd(value):
"""Format value as USD."""
return f"${value:,.2f}"
Binary file added images/hfinance-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions key.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pk_5824394ae4834a1abfb912ed8ba1cbbb
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cs50
Flask
Flask-Session
gunicorn
psycopg2
requests
Binary file added static/favicon.ico
Binary file not shown.
23 changes: 23 additions & 0 deletions static/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* Size for brand */
nav .navbar-brand
{
font-size: xx-large;
}

/* Colors for brand */
nav .navbar-brand .blue
{
color: #537fbe;
}
nav .navbar-brand .red
{
color: #ea433b;
}
nav .navbar-brand .yellow
{
color: #f5b82e;
}
nav .navbar-brand .green
{
color: #2e944b;
}
10 changes: 10 additions & 0 deletions templates/apology.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends "layout.html" %}

{% block title %}
Apology
{% endblock %}

{% block main %}
<!-- https://memegen.link/ -->
<img alt="{{ top }}" class="border img-fluid" src="http://memegen.link/custom/{{ top | urlencode }}/{{ bottom | urlencode }}.jpg?alt=https://i.imgur.com/WkyZxDD.jpg&width=400" title="{{ top }}">
{% endblock %}
14 changes: 14 additions & 0 deletions templates/buy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends "layout.html" %}

{% block title %}
Buy
{% endblock %}

{% block main %}
<form action="/buy" method="post">
<input class="form-control mx-auto w-auto mb-3" id="symbol" type="text" name="symbol" placeholder="Symbol">
<input class="form-control mx-auto w-auto mb-3" id="shares" type="number" name="shares" placeholder="shares" min="1" >
<button class="btn btn-primary" type="submit">Buy</button>
</form>

{% endblock %}
29 changes: 29 additions & 0 deletions templates/history.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{% extends "layout.html" %}

{% block title %}
History
{% endblock %}

{% block main %}
<table class="table table-striped" >
<thead>
<tr >
<td class="fw-bold">Symbol</td>

<td class="fw-bold">Shares</td>
<td class="fw-bold">Type</td>
<td class="fw-bold">Price</td>
<td class="fw-bold">Time</td>
</tr>
</thead>
<tbody>
{% for transaction in transactions %}
<tr>
<td>{{ transaction.symbol | upper() }}</td>
<td>{{ transaction.shares }}</td>
<td>{{ transaction.type }}</td>
<td>{{ transaction.price }}</td>
<td>{{ transaction.time }}</td>
</tr>
{% endfor %}
{% endblock %}
Loading

0 comments on commit 735ed44

Please sign in to comment.