commit 492c03bfcf51dcf09b33aedcfcb8a97a79c782bd Author: Teck Meng Date: Wed Feb 28 10:33:15 2024 +0800 init diff --git a/.env b/.env new file mode 100644 index 0000000..197f782 --- /dev/null +++ b/.env @@ -0,0 +1,27 @@ +# Environment variables for docker-compose.yml + +LOG_LEVEL="DEBUG" +NETWORK=web +## dashboard configs +HOST="furyhawk.lol" +# subdomain for dashboard. +DASHBOARD_HOST="dashboard.furyhawk.lol" + +# The following are the environment variables for the streamlit app +FIN_LOCATION="/fin" +STREAMLIT_FIN_SERVER_PORT="8501" +BAI_LOCATION="/bai" +STREAMLIT_BAI_SERVER_PORT="8502" + +# user/pass +DASHBOARD_USER=admin +DASHBOARD_PASSWORD=pass + +OSRM_ALGORITHM="mld" +OSRM_THREADS=2 +OSRM_PORT=5000 +OSRM_PROFILE="/opt/car.lua" +OSRM_MAP_NAME=${OSRM_MAP_NAME} +OSRM_GEOFABRIK_PATH=${OSRM_GEOFABRIK_PATH} +# Notify OSRM Manager to restart without stopping container +OSRM_NOTIFY_FILEPATH="/data/osrm_notify.txt" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abff676 --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# ignores production code for traefik and production yml +# compose/traefik/ +# production.yml + diff --git a/README.md b/README.md new file mode 100644 index 0000000..57b7a7c --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Streamlit + Traefik + Docker +This simple project uses Traefik as a reverse proxy to a Streamlit application and handles SSL certs with Lets Encrypt. + +## Requirements +- Docker Compose +- Python 3.9 + +## Local Deployment +#### Python: +1. `cd src` +2. `pip install -r requirements.txt` +3. `streamlit run app.py` + +#### Docker: +1. `sudo docker-compose -f local.yml up --build` + +## Production Deployment +1. In `compose/traefik/traefik.yml`, change `example@test.com` to your email. +2. In `compose/traefik/traefik.yml`, change `example.com` to your domain. +3. `docker compose -f production.yml up --build -d --remove-orphans` + +### Notes: +Feel free to make a PR or get in contact with me on Discord at yoyojoe#5510. diff --git a/certs/.gitignore b/certs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/certs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/compose/.DS_Store b/compose/.DS_Store new file mode 100644 index 0000000..3b7f455 Binary files /dev/null and b/compose/.DS_Store differ diff --git a/compose/streamlit-fin/Dockerfile b/compose/streamlit-fin/Dockerfile new file mode 100644 index 0000000..f7ea590 --- /dev/null +++ b/compose/streamlit-fin/Dockerfile @@ -0,0 +1,29 @@ +# base image +FROM python:3.11-slim + +#basic build prep +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + software-properties-common \ + git \ + && rm -rf /var/lib/apt/lists/* + +# copy over and install packages +COPY ./src/requirements.txt ./requirements.txt +RUN pip3 install cython +RUN pip3 install -r requirements.txt + +# streamlit-specific commands +RUN mkdir -p /root/.streamlit +RUN bash -c 'echo -e "\ +[general]\n\ +email = \"\"\n\ +" > /root/.streamlit/credentials.toml' +# RUN bash -c 'echo -e "\ +# [server]\n\ +# baseUrlPath = \"/fin\"\n\ +# " > /root/.streamlit/config.toml' + +# copying everything over +COPY . . \ No newline at end of file diff --git a/compose/traefik/Dockerfile b/compose/traefik/Dockerfile new file mode 100644 index 0000000..ad3905e --- /dev/null +++ b/compose/traefik/Dockerfile @@ -0,0 +1,5 @@ +FROM traefik:v2.11 +RUN mkdir -p /etc/traefik/acme \ + && touch /etc/traefik/acme/acme.json \ + && chmod 600 /etc/traefik/acme/acme.json +COPY ./compose/traefik/traefik.yml /etc/traefik \ No newline at end of file diff --git a/compose/traefik/traefik.yml b/compose/traefik/traefik.yml new file mode 100644 index 0000000..82a81ef --- /dev/null +++ b/compose/traefik/traefik.yml @@ -0,0 +1,143 @@ +log: + level: DEBUG +api: + # Dashboard + dashboard: true + # https://docs.traefik.io/master/operations/api/#insecure + # insecure: true + + +entryPoints: + web: + # http + address: ":80" + http: + # https://docs.traefik.io/routing/entrypoints/#entrypoint + redirections: + entryPoint: + to: web-secure + + web-secure: + # https + address: ":443" + + # osrm: + # address: ":5000" + +certificatesResolvers: + letsencrypt: + # https://docs.traefik.io/master/https/acme/#lets-encrypt + acme: + email: "furyx@hotmail.com" + storage: /etc/traefik/acme/acme.json + # https://docs.traefik.io/master/https/acme/#httpchallenge + httpChallenge: + entryPoint: web + +http: + routers: + dashboard: + rule: "Host(`dashboard.furyhawk.lol`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" + service: api@internal + middlewares: + - auth + tls: + # https://docs.traefik.io/master/routing/routers/#certresolver + certResolver: letsencrypt + + web-secure-router: + rule: "Host(`furyhawk.lol`, `www.furyhawk.lol`, `bai.furyhawk.lol`) || PathPrefix(`/bai`)" + entryPoints: + - web-secure + middlewares: + - csrf + - add-bai + service: streamlit_bai_app + tls: + # https://docs.traefik.io/master/routing/routers/#certresolver + certResolver: letsencrypt + fin-router: + rule: "Host(`fin.furyhawk.lol`)" + entryPoints: + - web-secure + middlewares: + - csrf + - add-fin + service: streamlit_fin_app + tls: + # https://docs.traefik.io/master/routing/routers/#certresolver + certResolver: letsencrypt + blog-router: + rule: "Host(`blog.furyhawk.lol`)" + entryPoints: + - web-secure + # redirect to external blog + middlewares: + - redirect-blog + + service: blog + tls: + # https://docs.traefik.io/master/routing/routers/#certresolver + certResolver: letsencrypt + + osrm-router: + rule: "Host(`osrm.furyhawk.lol`)" + entryPoints: + - "web-secure" + # - "osrm" + middlewares: + - csrf + service: osrm_service + tls: + # https://docs.traefik.io/master/routing/routers/#certresolver + certResolver: letsencrypt + + middlewares: + auth: + basicAuth: + users: + - "test:$apr1$2E4PEW8M$/wEgFNKX71h.YYMywV7WZ/" + csrf: + # https://doc.traefik.io/traefik/middlewares/http/headers/#hostsproxyheaders + # https://docs.djangoproject.com/en/dev/ref/csrf/#ajax + headers: + hostsProxyHeaders: ["X-CSRFToken"] + + add-bai: + addPrefix: + prefix: "/bai" + + add-fin: + addPrefix: + prefix: "/fin" + + redirect-blog: + # https://docs.traefik.io/master/middlewares/redirectscheme/ + redirectregex: + regex: "^https://blog.furyhawk.lol/(.*)" + replacement: "https://furyhawk.github.io/124c41/${1}" + permanent: true + + services: + osrm_service: + loadBalancer: + servers: + - url: http://osrm_backend:{{env "OSRM_PORT"}} + streamlit_bai_app: + loadBalancer: + servers: + - url: http://streamlit_bai_app:8502/bai + streamlit_fin_app: + loadBalancer: + servers: + - url: http://streamlit_fin_app:{{env "STREAMLIT_FIN_SERVER_PORT"}}/{{env "FIN_LOCATION"}} + blog: + loadBalancer: + servers: + - url: https://furyhawk.github.io/124c41/ + +providers: + # https://docs.traefik.io/master/providers/file/ + file: + filename: /etc/traefik/traefik.yml + watch: true diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 0000000..e3c1139 --- /dev/null +++ b/config/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!certs.toml diff --git a/config/certs.toml b/config/certs.toml new file mode 100644 index 0000000..5c59a4e --- /dev/null +++ b/config/certs.toml @@ -0,0 +1,3 @@ +[tls.stores.default.defaultCertificate] + certFile = "/certs/cert.crt" + keyFile = "/certs/cert.key" diff --git a/local.yml b/local.yml new file mode 100644 index 0000000..34bdc94 --- /dev/null +++ b/local.yml @@ -0,0 +1,13 @@ +version: '3.8' + +services: + streamlit-fin: + build: + context: . + dockerfile: ./compose/streamlit-fin/Dockerfile + image: streamlit_fin_local + container_name: streamlit_fin_app + restart: always + ports: + - 8501:8501 + command: streamlit run src/app.py \ No newline at end of file diff --git a/logs/.gitignore b/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/production.yml b/production.yml new file mode 100644 index 0000000..4dcbe48 --- /dev/null +++ b/production.yml @@ -0,0 +1,68 @@ +version: '3.7' + +x-environment: &default-environment + LOG_LEVEL: "DEBUG" + DASHBOARD_USER: ${DASHBOARD_USER} + DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD} + FIN_LOCATION: "/fin" + STREAMLIT_FIN_SERVER_PORT: "8501" + BAI_LOCATION: "/bai" + STREAMLIT_BAI_SERVER_PORT: "8502" + +volumes: + production_traefik: {} + +services: + osrm-backend: + environment: + # OSRM manager setup + - OSRM_ALGORITHM=mld + - OSRM_THREADS=2 + - OSRM_PORT=5000 + - OSRM_PROFILE=/opt/car.lua + - OSRM_MAP_NAME=${OSRM_MAP_NAME} + - OSRM_GEOFABRIK_PATH=${OSRM_GEOFABRIK_PATH} + # Notify OSRM Manager to restart without stopping container + - OSRM_NOTIFY_FILEPATH=/data/osrm_notify.txt + image: furyhawk/osrm-backend:${OSRM_VERSION:-latest} + container_name: osrm_backend + restart: always + ports: + - ${OSRM_PORT}:${OSRM_PORT} + + streamlit-bai: + environment: + <<: *default-environment + image: furyhawk/beyondallinfo:latest + container_name: streamlit_bai_app + restart: always + expose: + - ${STREAMLIT_BAI_SERVER_PORT} + command: streamlit run --server.port=$STREAMLIT_BAI_SERVER_PORT --server.address=0.0.0.0 --server.baseUrlPath=$BAI_LOCATION src/app.py + + streamlit-fin: + environment: + <<: *default-environment + build: + context: . + dockerfile: ./compose/streamlit-fin/Dockerfile + image: streamlit_fin_production + container_name: streamlit_fin_app + restart: always + expose: + - ${STREAMLIT_FIN_SERVER_PORT} + command: streamlit run --server.port=$STREAMLIT_FIN_SERVER_PORT --server.address=0.0.0.0 --server.baseUrlPath=$FIN_LOCATION src/app.py + + traefik: + environment: + <<: *default-environment + build: + context: . + dockerfile: ./compose/traefik/Dockerfile + image: traefik_production + volumes: + - production_traefik:/etc/traefik/acme:z + - /var/run/docker.sock:/var/run/docker.sock:ro + ports: + - "0.0.0.0:80:80" + - "0.0.0.0:443:443" \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..1fc8ef9 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -ev + +docker-compose config +docker-compose pull +docker-compose up -d +docker-compose ps diff --git a/scripts/cert.sh b/scripts/cert.sh new file mode 100755 index 0000000..ad7dcb6 --- /dev/null +++ b/scripts/cert.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -e + +eval $(egrep '^HOST' .env | xargs) +eval $(egrep '^CERT_PATH' .env | xargs) + +echo "Domain: ${HOST}" +echo "Cert Path: ${CERT_PATH}" + +if [ -f certs/cert.crt ] || [ -f certs/cert.key ] || [ -f certs/cert.pem ]; then + echo -e "cert already exists in certs directory\nDo you want to overwrite the files? [y]es/[n]o" + read -r ANSWER + echo + if [[ "$ANSWER" =~ ^[Yy](es)?$ ]] ; then + echo "Creating Cert" + else + exit 1 + fi +fi + +./scripts/requests.sh + +openssl genrsa -out $CERT_PATH/cert.key +openssl req -new -key $CERT_PATH/cert.key -out $CERT_PATH/cert.csr -config $CERT_PATH/csr.conf +openssl x509 -req -days 365 -in $CERT_PATH/cert.csr -signkey $CERT_PATH/cert.key -out $CERT_PATH/cert.crt -extensions req_ext -extfile $CERT_PATH/csr.conf + +sudo cp $CERT_PATH/cert.crt /usr/local/share/ca-certificates/cert.crt +sudo rm -f /usr/local/share/ca-certificates/certificate.crt +# --fresh is needed to remove symlinks to no-longer-present certificates +sudo update-ca-certificates --fresh diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100755 index 0000000..b9fd2b3 --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +echo "Cleaning up..." +docker-compose down + +printf "Deleting network: " +eval $(egrep '^NETWORK' .env | xargs) +printf "$NETWORK\n" +docker network rm $NETWORK | echo diff --git a/scripts/color.sh b/scripts/color.sh new file mode 100755 index 0000000..7350ef0 --- /dev/null +++ b/scripts/color.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# color +RESET=$'\e[1;0m' +RED=$'\e[1;31m' +GREEN=$'\e[1;32m' +YELLOW=$'\e[1;33m' +RED_BACK=$'\e[101m' +GREEN_BACK=$'\e[102m' +YELLOW_BACK=$'\e[103m' diff --git a/scripts/host.sh b/scripts/host.sh new file mode 100755 index 0000000..9e37131 --- /dev/null +++ b/scripts/host.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -ev + +eval $(egrep '^HOST' .env | xargs) + +if [ "$HOST" != "localhost" ]; then + grep "127.0.0.1 ${HOST}" /etc/hosts || (echo "127.0.0.1 ${HOST}" | sudo tee -a /etc/hosts) +fi +grep "127.0.0.1 docker.${HOST}" /etc/hosts || (echo "127.0.0.1 docker.${HOST}" | sudo tee -a /etc/hosts) +grep "127.0.0.1 dashboard.${HOST}" /etc/hosts || (echo "127.0.0.1 dashboard.${HOST}" | sudo tee -a /etc/hosts) diff --git a/scripts/init.sh b/scripts/init.sh new file mode 100755 index 0000000..9dceaab --- /dev/null +++ b/scripts/init.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +echo "Copying env file" +# Create env from env.example if it doesn't exist +if [ -f ".env" ] +then + echo -e "env file exists" +else + echo -e "Copying env file" + cp env.example .env +fi + +echo "creating acme.json" +touch acme.json +chmod 600 acme.json + +echo "creating provider.key" +touch provider.key +echo "supersecretkey" | tee provider.key +chmod 600 provider.key + +printf "Creating network: " +eval $(egrep '^NETWORK' .env | xargs) +printf "$NETWORK\n" +docker network create $NETWORK | echo diff --git a/scripts/install-docker-compose.sh b/scripts/install-docker-compose.sh new file mode 100755 index 0000000..1490b24 --- /dev/null +++ b/scripts/install-docker-compose.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -ex + +if [ -z "$DOCKER_COMPOSE_VERSION" ]; then + DOCKER_COMPOSE_VERSION=1.25.4 +fi + +echo "Installing docker-compose version: $DOCKER_COMPOSE_VERSION" + +if [ -z "`sudo -l 2>/dev/null`" ]; then + + rm /usr/local/bin/docker-compose | echo + curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose + chmod +x docker-compose + mv docker-compose /usr/local/bin +else + sudo rm /usr/local/bin/docker-compose | echo + curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose + sudo chmod +x docker-compose + sudo mv docker-compose /usr/local/bin +fi diff --git a/scripts/requests.sh b/scripts/requests.sh new file mode 100755 index 0000000..b52e025 --- /dev/null +++ b/scripts/requests.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +eval $(egrep -v '^#' .env | xargs) + +echo " +[req] +default_bits = 2048 +distinguished_name = dn +prompt = no + +[dn] +C=\"US\" +ST=\"Florida\" +OU=\"Service\" +emailAddress=\"admin@${HOST}\" +CN=\"${HOST}\" + +[req_ext] +subjectAltName = @alt_names + +[alt_names] +DNS.0 = ${HOST} +DNS.1 = *.${HOST} +DNS.2 = *.docker.${HOST} +" > certs/csr.conf diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..182fbda --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +set -e + +export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt + +source ./scripts/color.sh + +shopt -s expand_aliases +alias curl="curl -ILsS -X GET" +alias grep="grep -C 100 --color=auto" +alias echo="echo -e \${RESET}" + + +eval $(egrep '^HOST' .env | xargs) +eval $(egrep '^DASHBOARD_HOST' .env | xargs) + +echo "\n\n${YELLOW_BACK}${RED}Testing Traefik........................${RESET}\n" +echo "\nHOST=${HOST}" +echo "\nDASHBOARD_HOST=${DASHBOARD_HOST}\n" + + +echo "\n\n${YELLOW}Rediection test........................${RESET}\n" +echo "\n${GREEN}http://${HOST}${RESET}\n" +curl http://${HOST} | grep 302 || exit 1 +echo "\n${GREEN}http://${HOST}${RESET}\n" +curl http://${DASHBOARD_HOST} | grep 302 || exit 1 + +# echo "\n\nAuthentication test....................\n" + +echo "\n\n${YELLOW}Authentication test....................${RESET}\n" +echo "\n${GREEN}https://user:pass@${DASHBOARD_HOST}${RESET}\n" +curl -f --anyauth -u user:pass https://${DASHBOARD_HOST} | grep 200 || exit 1 + +echo "\n${GREEN}https://user:pass@${DASHBOARD_HOST}/dashboard/${RESET}\n" +curl -f --anyauth -u user:pass https://${DASHBOARD_HOST}/dashboard/ | grep 200 || exit 1 +echo "\n\n${GREEN}.......................................${RESET}\n" diff --git a/scripts/travis.sh b/scripts/travis.sh new file mode 100755 index 0000000..540fc89 --- /dev/null +++ b/scripts/travis.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -ev + +./scripts/init.sh +./scripts/cert.sh +./scripts/host.sh +./scripts/build.sh +./scripts/wait.sh ${WAIT_FOR} +./scripts/test.sh diff --git a/scripts/wait.sh b/scripts/wait.sh new file mode 100755 index 0000000..63cadb2 --- /dev/null +++ b/scripts/wait.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +x="$1" +[[ -z "$x" ]] && x=5 + +printf "\n\nWaiting for things to start" +while [ $x -gt 0 ] +do + printf "." + sleep 1 + x=$(( $x - 1 )) +done +echo "." diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000..0340032 --- /dev/null +++ b/src/app.py @@ -0,0 +1,127 @@ +import streamlit as st +import yfinance as yf +from ta import volume, trend + + +st.set_page_config(page_title='Technical Analysis',page_icon='📈', layout='wide') +hide_streamlit_style = """ + + """ +st.markdown(hide_streamlit_style, unsafe_allow_html=True) + +stock = st.sidebar.text_input(label="Ticker",value='AAPL') + +def get_data(start): + ticker = yf.Ticker(stock) + try: + df = ticker.history(period='max', start=start) + except: + pass + return df + +list_of_indicator_types = ['Volume', 'Trend'] +volumn_types = ['Volume','Force Index'] +trend_types = ["Simple Moving Average", "Exponential Moving Average"] + +indicator_types = st.sidebar.selectbox(label='Indicator Type', options = list_of_indicator_types) + +st.sidebar.success('All charts are interactive!') + +if indicator_types == 'Volume': + indicator = st.selectbox(label='Indicator', options=volumn_types,key=0) + + start = st.text_input(label='Start Year', value = '2018') + start = f'{start}-01-01' + + + + df = get_data(start) + + if indicator == 'Volume': + df = df[['Close','Volume']] + + price_vs_time = st.line_chart(data= df['Close'], width=500, height=400) + volume_vs_time = st.bar_chart(data=df['Volume'], width=500, height=150) + + df = df.to_csv() + st.download_button(label=f'Download Data',data=df,file_name=f'{stock}.csv') + + if indicator == 'Force Index': + window_slider_expander = st.expander(label='Force Index Parameters') + window_slider = window_slider_expander.slider(label='Window', value=13, min_value=1, max_value=20) + + df[f'fi_{window_slider}'] = volume.force_index(close=df['Close'],volume=df['Volume'], window=window_slider) + + df = df[['Close','Volume',f'fi_{window_slider}']] + + price_vs_time = st.line_chart(data= df['Close'], width=500, height=400) + fi_vs_time = st.area_chart(data= df[f'fi_{window_slider}'],width=500, height=200) + + df = df.to_csv() + st.download_button(label=f'Download Data',data=df,file_name=f'{stock}.csv') + +if indicator_types == 'Trend': + indicator = st.selectbox(label='Indicator', options=trend_types, key=1) + + start = st.text_input(label='Start Year', value = '2018') + start = f'{start}-01-01' + + df = get_data(start) + if indicator == 'Simple Moving Average': + + window_slider_expander = st.expander(label='SMA Parameters') + window_slider_1 = window_slider_expander.slider(label='Window 1', value=30, min_value=1, max_value=200) + window_slider_2 = window_slider_expander.slider(label='Window 2', value=100, min_value=1, max_value=200) + + df[f'sma{window_slider_1}'] = trend.sma_indicator(close=df['Close'], window=window_slider_1) + df[f'sma{window_slider_2}'] = trend.sma_indicator(close=df['Close'], window=window_slider_2) + + df = df[['Close',f'sma{window_slider_1}',f'sma{window_slider_2}']] + + sma_vs_time = st.line_chart(data=df, width=500, height=550) + + df = df.to_csv() + st.download_button(label=f'Download Data',data=df,file_name=f'{stock}.csv') + + if indicator == "Exponential Moving Average": + + window_slider_expander = st.expander(label='EMA Parameters') + window_slider_1 = window_slider_expander.slider(label='Window 1', value=30, min_value=1, max_value=200) + window_slider_2 = window_slider_expander.slider(label='Window 2', value=100, min_value=1, max_value=200) + + df[f'ema{window_slider_1}'] = trend.ema_indicator(close=df['Close'], window=window_slider_1) + df[f'ema{window_slider_2}'] = trend.ema_indicator(close=df['Close'], window=window_slider_2) + + df = df[['Close',f'ema{window_slider_1}',f'ema{window_slider_2}']] + + sma_vs_time = st.line_chart(data=df, width=500, height=550) + + df = df.to_csv() + st.download_button(label=f'Download Data',data=df,file_name=f'{stock}.csv') + diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..2464d8e --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,6 @@ +pandas==2.1.3 +streamlit==1.28.2 +ta==0.11.0 +yfinance==0.2.32 +watchdog==3.0.0 +protobuf==4.25.1 \ No newline at end of file