Skip to content

Commit

Permalink
Merge pull request #39 from JJtan2002/watchlist
Browse files Browse the repository at this point in the history
Base implementation of watchlist and resources
  • Loading branch information
JJtan2002 authored Jul 16, 2024
2 parents deb58a5 + 41d0cf0 commit 425102f
Show file tree
Hide file tree
Showing 26 changed files with 668 additions and 0 deletions.
Empty file added backend/__init__.py
Empty file.
1 change: 1 addition & 0 deletions backend/cashflow/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"rest_framework.authtoken",
"rest_framework_simplejwt",
"rest_framework_simplejwt.token_blacklist",
"watchlist",
]

REST_FRAMEWORK = {
Expand Down
1 change: 1 addition & 0 deletions backend/cashflow/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
path('admin/', admin.site.urls),
path('users/', include('users.urls')),
path('budget_tracking/', include('budget_tracking.urls')),
path('watchlist/', include('watchlist.urls')),
path('protected/', views.ProtectedView.as_view(), name='protected'),
]
16 changes: 16 additions & 0 deletions backend/celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from celery import Celery
from celery.schedules import crontab

app = Celery('tasks', broker='redis://localhost:6379/0', backend='redis://localhost:6379/0')

app.conf.beat_schedule = {
'fetch_stock_data': {
'task': 'watchlist.tasks.fetch_stock_data',
'schedule': crontab(hour=0, minute=0), # Adjust this based on your requirements
},
}

app.conf.timezone = 'UTC +8'

if __name__ == '__main__':
app.start()
31 changes: 31 additions & 0 deletions backend/insert_test_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import os
import django
from datetime import datetime

# Set up Django environment
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cashflow.settings')
django.setup()

# Import models after setting up Django

from watchlist.models import StockData # Adjusted import path

# Example data to insert
test_data = [
{'ticker': 'AAPL', 'close_price': 150.0, 'date': datetime(2023, 7, 7)},
{'ticker': 'MSFT', 'close_price': 280.0, 'date': datetime(2023, 7, 7)},
{'ticker': 'GOOGL', 'close_price': 2700.0, 'date': datetime(2023, 7, 7)},
]

# Insert test data
for data in test_data:
stock_data, created = StockData.objects.update_or_create(
id=f"{data['ticker']}_{data['date']}",
defaults={
'ticker': data['ticker'],
'close_price': data['close_price'],
'date': data['date'],
},
)

print("Test data inserted successfully!")
Binary file modified backend/requirements.txt
Binary file not shown.
33 changes: 33 additions & 0 deletions backend/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from celery import shared_task
import requests
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from datetime import datetime
from backend.models import StockData

API_KEY = 'your_api_key'
STOCKS = ['AAPL', 'MSFT', 'GOOGL'] # Top 25 by market cap in S&P 500

# PostgreSQL database configuration
DATABASE_URI = 'postgresql://username:password@localhost:5432/yourdatabase'
engine = create_engine(DATABASE_URI)
Session = sessionmaker(bind=engine)
session = Session()

@shared_task
def fetch_stock_data():
for ticker in STOCKS:
response = requests.get(f'https://api.example.com/stock/{ticker}/quote', params={'api_key': API_KEY})
data = response.json()
close_price = data['close']
date = datetime.strptime(data['date'], '%Y-%m-%dT%H:%M:%S')

stock_data = StockData(
id=f'{ticker}_{date}',
ticker=ticker,
close_price=close_price,
date=date
)

session.merge(stock_data)
session.commit()
Empty file added backend/watchlist/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions backend/watchlist/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.contrib import admin
from .models import Watchlist, StockData

# Register your models here.
admin.site.register(Watchlist)
admin.site.register(StockData)
# Register your models here.
6 changes: 6 additions & 0 deletions backend/watchlist/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class WatchlistConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'watchlist'
35 changes: 35 additions & 0 deletions backend/watchlist/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 5.0.6 on 2024-07-10 15:26

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='StockData',
fields=[
('id', models.CharField(max_length=100, primary_key=True, serialize=False)),
('ticker', models.CharField(max_length=10)),
('close_price', models.FloatField()),
('date', models.DateTimeField()),
],
),
migrations.CreateModel(
name='Watchlist',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('symbol', models.CharField(max_length=10)),
('entry_price', models.DecimalField(decimal_places=2, max_digits=15)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
Empty file.
40 changes: 40 additions & 0 deletions backend/watchlist/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import users.models
from django.db import models

class Watchlist(models.Model):
user = models.ForeignKey(users.models.User, on_delete=models.CASCADE)
symbol = models.CharField(max_length=10)
entry_price = models.DecimalField(decimal_places=2, max_digits=15)

def __str__(self):
return f'{self.user.username} - {self.symbol}'

@staticmethod
def create_from_json(data: dict, user_pk: int):
try:
user: users.models.User = users.models.User.objects.get(pk=user_pk)
except users.models.User.DoesNotExist:
raise PermissionError()

watchlist = Watchlist()
watchlist.user = user

if not data.get("ticker"):
raise Exception("Ticker is required.")
watchlist.symbol = data.get("ticker")

if data.get("entry") is not None:
watchlist.entry_price = data.get("entry")

watchlist.save()

return watchlist

class StockData(models.Model):
id = models.CharField(max_length=100, primary_key=True)
ticker = models.CharField(max_length=10)
close_price = models.FloatField()
date = models.DateTimeField()

def __str__(self):
return f"{self.ticker} - {self.date}"
12 changes: 12 additions & 0 deletions backend/watchlist/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from rest_framework import serializers
from .models import Watchlist, StockData

class WatchlistSerializer(serializers.ModelSerializer):
class Meta:
model = Watchlist
fields = ['id', 'symbol', 'entry_price']

class StockDataSerializer(serializers.ModelSerializer):
class Meta:
model = StockData
fields = ['id', 'ticker', 'close_price', 'date']
24 changes: 24 additions & 0 deletions backend/watchlist/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from celery import shared_task
import requests
from datetime import datetime
from .models import StockData

API_KEY = 'your_api_key'
STOCKS = ['AAPL', 'MSFT', 'GOOGL'] # Top 25 by market cap in S&P 500

@shared_task
def fetch_stock_data():
for ticker in STOCKS:
response = requests.get(f'https://api.example.com/stock/{ticker}/quote', params={'api_key': API_KEY})
data = response.json()
close_price = data['close']
date = datetime.strptime(data['date'], '%Y-%m-%dT%H:%M:%S')

stock_data, created = StockData.objects.update_or_create(
id=f'{ticker}_{date}',
defaults={
'ticker': ticker,
'close_price': close_price,
'date': date,
},
)
3 changes: 3 additions & 0 deletions backend/watchlist/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
9 changes: 9 additions & 0 deletions backend/watchlist/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path
from .views import WatchlistAPIView, StockDataAPIView

urlpatterns = [
path('watchlist/', WatchlistAPIView.as_view(), name='watchlist'),
path('watchlist/<int:watchlist_pk>/', WatchlistAPIView.as_view(), name='watchlist'),
path('stockdata/', StockDataAPIView.as_view(), name='stockdata'),
path('stockdata/<int:stock_data_pk>/', StockDataAPIView.as_view(), name='stockdata-detail'),
]
22 changes: 22 additions & 0 deletions backend/watchlist/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Union
from django.http import JsonResponse


def custom_server_error_response(error_message: str = '', status_code=500):
if 500 <= status_code < 600:
return JsonResponse({'success': False, 'message': error_message}, status=status_code)
raise Exception # TODO: implement custom exceptions


def custom_user_error_response(error_message: str = '', status_code=400):
if 400 <= status_code < 500:
return JsonResponse({'success': False, 'message': error_message}, status=status_code)
raise Exception # TODO: implement custom exceptions


def custom_success_response(success_message: Union[str, dict] = '', status_code=200):
if 200 <= status_code < 300:
response = {'success': True, 'message': success_message}
return JsonResponse(response, status=status_code)

raise Exception # TODO: implement custom exceptions
50 changes: 50 additions & 0 deletions backend/watchlist/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from django.db.models import QuerySet, Sum
from users.models import User
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from .serializers import WatchlistSerializer, StockDataSerializer
from datetime import date
from watchlist.models import Watchlist, StockData
from .utils import custom_success_response, custom_server_error_response

class WatchlistAPIView(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = WatchlistSerializer

def get(self, request):
user: User = request.user
watchlists = Watchlist.objects.filter(user=self.request.user)


serializer = self.serializer_class(watchlists, many=True)
return Response(serializer.data)

def post(self, request):
user: User = request.user

data = request.data
Watchlist.create_from_json(data, user_pk=user.pk)

return custom_success_response("Ticker added with success!")

def delete(self, request, watchlist_pk):
user: User = request.user

if not watchlist_pk:
return custom_server_error_response("No watchlist id was given. Please try again.")

watchlist = Watchlist.objects.get(pk=watchlist_pk)

watchlist.delete()
return custom_success_response("Ticker deleted with success!")

class StockDataAPIView(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = StockDataSerializer

def get(self, request):
stock_data = StockData.objects.all()
serializer = self.serializer_class(stock_data, many=True)
return Response(serializer.data)
Loading

0 comments on commit 425102f

Please sign in to comment.