diff --git a/README.md b/README.md index f59525d1..6511ace0 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,5 @@ The themes for Buffer 6.0 are - 3. Next-Gen Academic Solutions 4. Custom Data Structure +# The Video Link of Demo @Codey Bunch: +https://drive.google.com/drive/folders/1pKZZKV0SGWigoP7-4Z2B54g3YGBT_xd8?usp=sharing* diff --git a/Stock.rar b/Stock.rar new file mode 100644 index 00000000..a43967da Binary files /dev/null and b/Stock.rar differ diff --git a/Stock_Final.rar b/Stock_Final.rar new file mode 100644 index 00000000..a09cfb1d Binary files /dev/null and b/Stock_Final.rar differ diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/asgi.py b/asgi.py new file mode 100644 index 00000000..0d287d04 --- /dev/null +++ b/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for stock project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'stock.settings') + +application = get_asgi_application() diff --git a/manage.py b/manage.py new file mode 100644 index 00000000..fbe51723 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'stock.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..284abda0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,26 @@ +asgiref==3.8.1 +contourpy==1.3.1 +cycler==0.12.1 +distlib==0.3.8 +Django==5.1.1 +et_xmlfile==2.0.0 +filelock==3.16.1 +fonttools==4.56.0 +kiwisolver==1.4.8 +matplotlib==3.10.1 +mysql-connector-python==9.0.0 +mysqlclient==2.2.4 +numpy==2.2.4 +openpyxl==3.1.5 +packaging==24.2 +pandas==2.2.3 +pillow==11.2.0 +platformdirs==4.3.6 +pyparsing==3.2.3 +python-dateutil==2.9.0.post0 +pytz==2024.2 +pywin32==306 +six==1.17.0 +sqlparse==0.5.1 +tzdata==2024.2 +virtualenv==20.26.6 diff --git a/settings.py b/settings.py new file mode 100644 index 00000000..5eac9ae1 --- /dev/null +++ b/settings.py @@ -0,0 +1,130 @@ +""" +Django settings for stock project. + +Generated by 'django-admin startproject' using Django 5.1.1. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ + +SESSION_ENGINE = 'django.contrib.sessions.backends.db' +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-#mbujguse5qev2irpi08%@()^9llf3bl1cqdpfxz07b9ou=7kp' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'stockapp', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'stock.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'stock.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'stock', + 'USER': 'root', + 'PASSWORD': 'S1tty1$great', + 'HOST': 'localhost', + 'PORT': '3306', + + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/stock/__init__.py b/stock/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/stock/__pycache__/__init__.cpython-312.pyc b/stock/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..399f346a Binary files /dev/null and b/stock/__pycache__/__init__.cpython-312.pyc differ diff --git a/stock/__pycache__/settings.cpython-312.pyc b/stock/__pycache__/settings.cpython-312.pyc new file mode 100644 index 00000000..553fcd25 Binary files /dev/null and b/stock/__pycache__/settings.cpython-312.pyc differ diff --git a/stock/__pycache__/urls.cpython-312.pyc b/stock/__pycache__/urls.cpython-312.pyc new file mode 100644 index 00000000..c7005f19 Binary files /dev/null and b/stock/__pycache__/urls.cpython-312.pyc differ diff --git a/stock/__pycache__/wsgi.cpython-312.pyc b/stock/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 00000000..ee70a35c Binary files /dev/null and b/stock/__pycache__/wsgi.cpython-312.pyc differ diff --git a/stock/asgi.py b/stock/asgi.py new file mode 100644 index 00000000..40f0a6e0 --- /dev/null +++ b/stock/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for stock project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'stock.settings') + +application = get_asgi_application() diff --git a/stock/settings.py b/stock/settings.py new file mode 100644 index 00000000..5eac9ae1 --- /dev/null +++ b/stock/settings.py @@ -0,0 +1,130 @@ +""" +Django settings for stock project. + +Generated by 'django-admin startproject' using Django 5.1.1. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ + +SESSION_ENGINE = 'django.contrib.sessions.backends.db' +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-#mbujguse5qev2irpi08%@()^9llf3bl1cqdpfxz07b9ou=7kp' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'stockapp', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'stock.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'stock.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'stock', + 'USER': 'root', + 'PASSWORD': 'S1tty1$great', + 'HOST': 'localhost', + 'PORT': '3306', + + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/stock/urls.py b/stock/urls.py new file mode 100644 index 00000000..a4201b37 --- /dev/null +++ b/stock/urls.py @@ -0,0 +1,23 @@ +""" +URL configuration for stock project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('stockapp.urls')), +] diff --git a/stock/wsgi.py b/stock/wsgi.py new file mode 100644 index 00000000..b4c4a41a --- /dev/null +++ b/stock/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for stock project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'stock.settings') + +application = get_wsgi_application() diff --git a/stockapp/__init__.py b/stockapp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/stockapp/admin.py b/stockapp/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/stockapp/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/stockapp/apps.py b/stockapp/apps.py new file mode 100644 index 00000000..59bcf172 --- /dev/null +++ b/stockapp/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class StockappConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'stockapp' diff --git a/stockapp/db.py b/stockapp/db.py new file mode 100644 index 00000000..ec7ae120 --- /dev/null +++ b/stockapp/db.py @@ -0,0 +1,12 @@ + +import mysql.connector +from django.conf import settings + +def get_db_connection(): + return mysql.connector.connect( + host=settings.DATABASES['default']['HOST'], + user=settings.DATABASES['default']['USER'], + password=settings.DATABASES['default']['PASSWORD'], + database=settings.DATABASES['default']['NAME'], + port=settings.DATABASES['default'].get('PORT', '3306') + ) \ No newline at end of file diff --git a/stockapp/forms.py b/stockapp/forms.py new file mode 100644 index 00000000..b8a2a7f5 --- /dev/null +++ b/stockapp/forms.py @@ -0,0 +1,12 @@ +from django import forms +from .models import CompanyStock + +class PortfolioForm(forms.Form): + def __init__(self, *args, **kwargs): + super(PortfolioForm, self).__init__(*args, **kwargs) + for stock in CompanyStock.objects.all(): + self.fields[f'stock_{stock.company_id}'] = forms.IntegerField( + label=f"{stock.cname} ({stock.company_id})", + required=False, + min_value=0 + ) diff --git a/stockapp/models.py b/stockapp/models.py new file mode 100644 index 00000000..196945a1 --- /dev/null +++ b/stockapp/models.py @@ -0,0 +1,19 @@ +from django.db import models + +# Create your models here. + + +class CompanyStock(models.Model): + company_id = models.CharField(max_length=20, primary_key=True) + cname = models.CharField(max_length=100) + current_price = models.FloatField() + +class UserPortfolio(models.Model): + user_id = models.IntegerField() + company_id = models.IntegerField() + total_shares = models.IntegerField() + average_price = models.DecimalField(max_digits=10, decimal_places=2) + total_value = models.DecimalField(max_digits=15, decimal_places=2) + + def __str__(self): + return f"Portfolio for User {self.user_id}" diff --git a/stockapp/tests.py b/stockapp/tests.py new file mode 100644 index 00000000..de8bdc00 --- /dev/null +++ b/stockapp/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/stockapp/urls.py b/stockapp/urls.py new file mode 100644 index 00000000..f83e387a --- /dev/null +++ b/stockapp/urls.py @@ -0,0 +1,14 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.home, name='home'), + path('login/', views.login_user, name='login'), + path('register/', views.register, name='register'), + path('main_menu/', views.main_menu, name='main_menu'), + path('add_stock/', views.add_stock, name='add_stock'), + path('user_portfolio/', views.user_portfolio, name='user_portfolio'), + path('create_portfolio/', views.create_portfolio, name='create_portfolio'), + path('portfolio_result/', views.portfolio_result, name='portfolio_result'), + path('visit_portfolio/', views.portfolio_result, name='visit_portfolio'), +] \ No newline at end of file diff --git a/stockapp/views.py b/stockapp/views.py new file mode 100644 index 00000000..d575ce50 --- /dev/null +++ b/stockapp/views.py @@ -0,0 +1,381 @@ + + +# Create your views here. +from django.shortcuts import render, redirect + +from .db import get_db_connection +from django.contrib import messages +import numpy as np +import matplotlib.pyplot as plt +import io, base64 +import sqlite3 +from django.db import connection +import mysql.connector +import numpy as np +import matplotlib.pyplot as plt +import base64 +import io +from django.shortcuts import render, redirect +from django.db import connection +from scipy.optimize import minimize +from django.contrib.auth.decorators import login_required +from django.contrib.auth import authenticate, login + + + +def get_db_connection(): + return mysql.connector.connect( + host="localhost", # Adjust host if necessary + user="root", # Replace with your DB username + password="S1tty1$great", # Replace with your DB password + database="stock" # Replace with your database name + ) +def home(request): + return render(request, 'home.html') + + +def login_user(request): + if request.method == 'POST': + uname = request.POST['uname'] + password = request.POST['password'] + + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("SELECT * FROM USER WHERE UNAME=%s AND PASSWORD=%s", (uname, password)) + user = cursor.fetchone() + conn.close() + + if user: + request.session['user_id'] = user[0] # Assuming user_id is in column 0 + request.session['uname'] = user[1] + print(request.session['user_id']) # Assuming uname is in column 1 + return redirect('main_menu') + else: + messages.error(request, "Invalid username or password") + + return render(request, 'login.html') + +# def login_user(request): +# if request.method == 'POST': +# uname = request.POST['uname'] +# password = request.POST['password'] + +# conn = get_db_connection() +# cursor = conn.cursor() + +# # Only fetch user_id and uname for clarity and safety +# cursor.execute("SELECT user_id, uname FROM USER WHERE UNAME=%s AND PASSWORD=%s", (uname, password)) +# user = cursor.fetchone() +# conn.close() + +# if user: +# user_id, username = user # unpacking values + +# # Store user info in session +# request.session['user_id'] = user_id +# request.session['uname'] = username + +# print(f"Logged in: user_id={user_id}, username={username}") # Debugging + +# return redirect('main_menu') +# else: +# messages.error(request, "Invalid username or password") + +# return render(request, 'login.html') + +def register(request): + if request.method == 'POST': + uname = request.POST['uname'] + email = request.POST['email'] + phone = request.POST['phone'] + password = request.POST['password'] + dob = request.POST['dob'] + + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("INSERT INTO USER(UNAME, EMAILID, PHONENUMBER, PASSWORD, DOB) VALUES (%s, %s, %s, %s, %s)", + (uname, email, phone, password, dob)) + conn.commit() + conn.close() + return redirect('login') + + return render(request, 'register.html') + +def main_menu(request): + return render(request, 'main_menu.html') +def add_stock(request): + if request.method == 'POST': + company_id = request.POST['company_id'] + cname = request.POST['cname'] + currdate = request.POST['currdate'] + openrate = request.POST['openrate'] + closerate = request.POST['closerate'] + dayhigh = request.POST['dayhigh'] + daylow = request.POST['daylow'] + + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute(""" + INSERT INTO COMPANY_STOCK (COMPANY_ID, CNAME, CURRDATE, OPENRATE, CLOSERATE, DAYHIGH, DAYLOW) + VALUES (%s, %s, %s, %s, %s, %s, %s) + """, (company_id, cname, currdate, openrate, closerate, dayhigh, daylow)) + conn.commit() + conn.close() + + return redirect('main_menu') + + return render(request, 'add_stock.html') + +def user_portfolio(request): + conn = get_db_connection() + cursor = conn.cursor() + + cursor.execute("SELECT COMPANY_ID, CNAME FROM COMPANY_STOCK GROUP BY COMPANY_ID") + companies = cursor.fetchall() + + if request.method == 'POST': + user_id = request.session.get('user_id') + company_id = request.POST['company_id'] + tot_shares = request.POST['tot_shares'] + avg_price = request.POST['avg_price'] + total_price = float(tot_shares) * float(avg_price) + + cursor.execute(""" + INSERT INTO USER_SHARES (USER_ID, COMPANY_ID, TOT_SHARES, AVERAGE_PRICE, TOTAL_PRICE) + VALUES (%s, %s, %s, %s, %s) + """, (user_id, company_id, tot_shares, avg_price, total_price)) + conn.commit() + conn.close() + return redirect('main_menu') + + conn.close() + return render(request, 'user_portfolio.html', {'companies': companies}) + +# def edit_user(request, user_id): +# conn = get_db_connection() +# cursor = conn.cursor() + +# if request.method == 'POST': +# uname = request.POST['uname'] +# email = request.POST['email'] +# phone = request.POST['phone'] +# dob = request.POST['dob'] + +# cursor.execute(""" +# UPDATE USER +# SET UNAME=%s, EMAILID=%s, PHONENUMBER=%s, DOB=%s +# WHERE USER_ID=%s +# """, (uname, email, phone, dob, user_id)) +# conn.commit() +# conn.close() +# return redirect('main_menu') + +# cursor.execute("SELECT UNAME, EMAILID, PHONENUMBER, DOB FROM USER WHERE USER_ID = %s", (user_id,)) +# user = cursor.fetchone() +# conn.close() + +# return render(request, 'register.html', {'edit': True, 'user': user, 'user_id': user_id}) + +def mean_variance_optimization(returns, cov_matrix, risk_free_rate=0.01): + num_assets = len(returns) + + def portfolio_performance(weights): + ret = np.dot(weights, returns) + vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) + + if vol == 0: + return float('inf') # Avoid division by zero + + sharpe = (ret - risk_free_rate) / vol + return -sharpe # We minimize the negative Sharpe Ratio + + if num_assets == 0 or np.all(returns == 0) or np.all(cov_matrix == 0): + # Sanity check to avoid nonsense optimization + return np.array([]) + + constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}) + bounds = tuple((0, 1) for _ in range(num_assets)) + initial_guess = num_assets * [1. / num_assets] + + result = minimize(portfolio_performance, initial_guess, method='SLSQP', bounds=bounds, constraints=constraints) + + if not result.success: + return np.array([]) + + return result.x + + +def create_portfolio(request): + #print("User:", request.user) + #user_id = request.user.id + + #print("User ID:", user_id) + user_id = request.session.get('user_id') + print("Session User:", request.session.get('uname')) + print("Session User ID:", user_id) + conn = get_db_connection() + cursor = conn.cursor() + + if request.method == 'POST': + print("POST data:", request.POST) + + inserted_any = False + error_messages = [] + stocks = fetch_company_stocks() + + for stock in stocks: + company_id = stock['company_id'] + shares = request.POST.get(f'shares_{company_id}') + print(f"Shares for company {company_id}: {shares}") + + if shares and shares.isdigit(): + shares = int(shares) + + cursor.execute("SELECT price FROM company_stock WHERE company_id = %s", (company_id,)) + data = cursor.fetchone() + + if data: + price = data[0] + total_price = shares * price + average_price = price + + try: + print("Inserting into user_shares:", user_id, company_id, shares, average_price, total_price) + cursor.execute(""" + INSERT INTO user_shares (USER_ID, COMPANY_ID, TOT_SHARES, AVERAGE_PRICE, TOTAL_PRICE) + VALUES (%s, %s, %s, %s, %s) + """, (user_id, company_id, shares, average_price, total_price)) + inserted_any = True + except Exception as e: + print(f"Error inserting data: {e}") + error_messages.append(f"Error with stock ID {company_id}: {str(e)}") + else: + error_messages.append(f"No stock found for ID: {company_id}") + elif shares: + error_messages.append(f"Invalid number of shares for stock ID {company_id}") + + conn.commit() + conn.close() + + if inserted_any: + return redirect('portfolio_result') + + return render(request, 'create_portfolio.html', { + 'error': "No valid entries added. " + "; ".join(error_messages), + 'stocks': stocks + }) + + # GET request + stocks = fetch_company_stocks() + return render(request, 'create_portfolio.html', {'stocks': stocks}) + + + + +def fetch_company_stocks(): + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("SELECT company_id, cname FROM company_stock") + stocks = [{'company_id': row[0], 'company_name': row[1]} for row in cursor.fetchall()] + conn.close() + return stocks + + +def portfolio_result(request): + user_id = request.session.get('user_id') + print("Session User:", request.session.get('uname')) + print("Session User ID:", user_id) + conn = get_db_connection() + cursor = conn.cursor() + print(">>> 1 achieved") + + # Get current portfolio from DB + cursor.execute(""" + SELECT cs.company_id, cs.cname, cs.price, us.tot_shares + FROM user_shares us + JOIN company_stock cs ON us.company_id = cs.company_id + WHERE us.user_id = %s + """, (user_id,)) + rows = cursor.fetchall() + print(rows) + conn.close() + + if not rows: + return render(request, 'portfolio_result.html', { + 'error': 'No portfolio data found. Please add stocks first.' + }) + + company_ids = [row[0] for row in rows] + company_names = [row[1] for row in rows] + prices = np.array([row[2] for row in rows]) + shares = np.array([row[3] for row in rows]) + portfolio_value = float(np.sum(prices * shares)) + + # Simulate returns (replace with real historical returns if needed) + returns = (np.random.normal(0.02, 0.01, len(prices))) + cov_matrix = np.diag(np.random.uniform(0.01, 0.05, len(prices))) + + try: + weights = mean_variance_optimization(returns, cov_matrix) + + if weights.size == 0: + return render(request, 'portfolio_result.html', { + 'error': 'Portfolio optimization failed. Try with different stock combinations.' + }) + + allocation = { + name: round(w * 100, 2) + for name, w in zip(company_names, weights) + } + + portfolio_summary = [] + for i in range(len(company_names)): + value = prices[i] * shares[i] + portfolio_summary.append({ + 'name': company_names[i], + 'shares': int(shares[i]), + 'price': round(prices[i], 2), + 'value': round(value, 2), + 'allocation': allocation[company_names[i]] + }) + + # Generate pie chart + fig, ax = plt.subplots() + wedges, texts, autotexts = ax.pie( + weights, + labels=company_names, + autopct='%1.1f%%', + startangle=90, + textprops=dict(color="black") # <-- black text + ) + + # Add a white circle in the center to make it a donut + centre_circle = plt.Circle((0, 0), 0.80, fc='white') + fig.gca().add_artist(centre_circle) + + # Make sure it stays circular + ax.axis('equal') + + # Improve visibility + plt.setp(autotexts, size=10, weight="bold") + plt.setp(texts, size=9) + + # Save to buffer + buf = io.BytesIO() + plt.savefig(buf, format='png', bbox_inches='tight') + plt.close(fig) + buf.seek(0) + pie_img = base64.b64encode(buf.read()).decode('utf-8') + + + # ✅ Return outside the loop so it runs after all companies are processed + return render(request, 'portfolio_result.html', { + 'portfolio_summary': portfolio_summary, + 'total': round(portfolio_value, 2), + 'pie_img': pie_img + }) + + except ZeroDivisionError: + return render(request, 'portfolio_result.html', { + 'error': 'Optimization failed due to zero variance or invalid data.' + }) diff --git a/urls.py b/urls.py new file mode 100644 index 00000000..a4201b37 --- /dev/null +++ b/urls.py @@ -0,0 +1,23 @@ +""" +URL configuration for stock project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('stockapp.urls')), +] diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 00000000..b4c4a41a --- /dev/null +++ b/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for stock project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'stock.settings') + +application = get_wsgi_application() diff --git a/your_database.db b/your_database.db new file mode 100644 index 00000000..e69de29b