Skip to content

Commit

Permalink
updated 10:06 am 20.5.2024
Browse files Browse the repository at this point in the history
  • Loading branch information
atef7534 committed May 20, 2024
1 parent 040eeb1 commit b9da108
Show file tree
Hide file tree
Showing 19 changed files with 617 additions and 0 deletions.
242 changes: 242 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import os
from datetime 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"""
trades = db.execute("SELECT * FROM trades WHERE id = ?", session["user_id"])
user = db.execute("SELECT * FROM users WHERE id = ?", session["user_id"])
return render_template("index.html", trades=trades, cash=int(user[0]["cash"]))


@app.route("/buy", methods=["GET", "POST"])
@login_required
def buy():
"""Buy shares of stock"""
cash = db.execute("SELECT * FROM users WHERE id = ?", session["user_id"])
cash = cash[0]["cash"]

if request.method == "GET":
return render_template("buy.html", cash=int(cash))

symbol = request.form.get("symbol")
if not symbol:
return apology("Missing Symbol!")


shares = request.form.get("shares")
if not shares:
return apology("Missing Shares!")

try:
shares = int(shares)
if shares < 0:
return apology("Positive my friend!")
except:
return apology("Number my friend!")

try:
symbol_price = lookup(symbol)["price"]
except:
return apology("Valid stock my friend!")

if int(shares) * symbol_price > int(cash):
return apology("You don't have enough money!")

trades = db.execute("SELECT * FROM trades WHERE symbol = ?", symbol.upper())
if not trades:
db.execute("INSERT INTO trades (id, symbol, shares, price) VALUES (?, ?, ?, ?)",
session["user_id"], symbol.upper(), int(shares), symbol_price)
else:
db.execute("UPDATE trades SET shares = ? WHERE symbol = ?",
int(trades[0]["shares"]) + int(shares), symbol.upper())
db.execute("UPDATE users SET cash = ? WHERE id = ?", float(
cash) - (float(shares) * float(symbol_price)), session["user_id"])
now = datetime.now()
now = now.strftime('%Y-%m-%d %H:%M:%S')
db.execute("INSERT INTO transactions (id, symbol, shares, price, transacted) VALUES (?, ?, ?, ?, ?)",
session["user_id"], symbol, int(shares), symbol_price, now)

return redirect("/")


@app.route("/history")
@login_required
def history():
"""Show history of transactions"""
transactions = db.execute("SELECT * FROM transactions WHERE id = ?", session["user_id"])
if request.method == "GET":
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 == "GET":
return render_template("quote.html")

symbol = request.form.get("symbol")
if not symbol:
return apology("Missing Symbol")

stock_quote = lookup(symbol)
if not stock_quote:
return apology("Missing Symbol")

return render_template("quoted.html", symbol=stock_quote)


@app.route("/register", methods=["GET", "POST"])
def register():
"""Register user"""
# if the user enters the registeration page using get method
if request.method == "GET":
return render_template("register.html")

# check mistakes in username [textfield]
username = request.form.get("username")
userExist = db.execute("SELECT * FROM users WHERE username = ?", username)
if not username or len(userExist):
return apology("Register with another name!")

# check mistakes in password and cofirmed password
password = request.form.get("password")
samepass = request.form.get("confirmation")
if not password or not samepass or password != samepass:
return apology("Register with another password or check confirmation password!")

# insertion : insert user with username, and hash
db.execute("INSERT INTO users (username, hash) VALUES (?, ?)",
username, generate_password_hash(password))

# get the user row from the data base
rows = db.execute("SELECT * FROM users WHERE username = ?", username)

# log the user in
session["user_id"] = rows[0]["id"]

flash("Registered!")
return redirect("/")


@app.route("/sell", methods=["GET", "POST"])
@login_required
def sell():
"""Sell shares of stock"""
symbols = db.execute("SELECT * FROM trades")
if request.method == "GET":
return render_template("sell.html", symbols=symbols)

symbol = request.form.get("symbol")
if not symbol:
return apology("Missing Symbol")

shares = request.form.get("shares")
if not shares:
return apology("Missing Shares")

data = db.execute("SELECT * FROM trades WHERE symbol = ? AND id = ?",
symbol, session["user_id"])
if int(data[0]["shares"]) < int(shares):
return apology("Many Shares!")

db.execute("UPDATE trades SET shares = ? WHERE symbol = ?",
int(data[0]["shares"]) - int(shares), symbol)
db.execute("DELETE FROM trades WHERE shares = 0")

user = db.execute("SELECT * FROM users WHERE id = ?", session["user_id"])
db.execute("UPDATE users SET cash = ? WHERE id = ?", int(
user[0]["cash"]) + int(shares) * float(data[0]["price"]), session["user_id"])

now = datetime.now()
now = now.strftime('%Y-%m-%d %H:%M:%S')
db.execute("INSERT INTO transactions (id, symbol, shares, price, transacted) VALUES (?, ?, ?, ?, ?)",
session["user_id"], symbol, (-1.00 * int(shares)), data[0]["price"], now)

flash("Sold!")
return redirect("/")
Binary file added finance.db
Binary file not shown.
Binary file added flask_session/0653299aba2b711566df92e037c4fe63
Binary file not shown.
Binary file added flask_session/2029240f6d1128be89ddc32729463129
Binary file not shown.
88 changes: 88 additions & 0 deletions helpers.py
Original file line number Diff line number Diff line change
@@ -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}"
5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cs50
Flask
Flask-Session
pytz
requests
Binary file added static/I_heart_validator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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;
}
11 changes: 11 additions & 0 deletions templates/apology.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% extends "layout.html" %}

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

{% block main %}
<!-- https://memegen.link/ -->
<!-- https://knowyourmeme.com/memes/grumpy-cat -->
<img alt="{{ top }}" class="border img-fluid" src="https://api.memegen.link/images/custom/{{ top | urlencode }}/{{ bottom | urlencode }}.jpg?background=https://i.imgur.com/CsCgN7Ll.png&width=400" title="{{ top }}">
{% endblock %}
18 changes: 18 additions & 0 deletions templates/buy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{% extends "layout.html" %}
{% block title %}
Buy
{% endblock %}
{% block main %}
<div class="cash mb-5 text-primary-emphasis bg-primary-subtle w-50 m-auto p-4 rounded-3">
<span>Your Balance </span>{{ cash | usd }}
</div>
<form action="/buy" method="post">
<div class="w-50 m-auto mb-3">
<input type="text" class="form-control" placeholder="Symbol" name="symbol" autofocus autocomplete="off"/>
</div>
<div class="w-50 m-auto mb-3">
<input type="number" value="" min="1" class="form-control" placeholder="Shares" name="shares" autofocus autocomplete="off"/>
</div>
<button type="submit" class="btn btn-primary">Buy</button>
</form>
{% endblock %}
Loading

0 comments on commit b9da108

Please sign in to comment.