Skip to content

Commit

Permalink
Push
Browse files Browse the repository at this point in the history
  • Loading branch information
spencer-sliffe committed Dec 7, 2024
1 parent c200502 commit 4efca21
Show file tree
Hide file tree
Showing 13 changed files with 956 additions and 418 deletions.
21 changes: 20 additions & 1 deletion Backend/kobrastocks/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class User(db.Model):
favorite_stocks = db.relationship('FavoriteStock', backref='user', lazy=True)
watched_stocks = db.relationship('WatchedStock', backref='user', lazy=True)
portfolio = db.relationship('Portfolio', back_populates='user', uselist=False)

favorite_cryptos = db.relationship('FavoriteCrypto', backref='user', lazy=True)
watched_cryptos = db.relationship('WatchedCrypto', backref='user', lazy=True)

def check_password(self, password):
return bcrypt.check_password_hash(self.password_hash, password)
Expand Down Expand Up @@ -84,3 +85,21 @@ class PortfolioStock(db.Model):

# Relationships
portfolio = db.relationship('Portfolio', back_populates='stocks')


class FavoriteCrypto(db.Model):
__tablename__ = 'favorite_crypto'

id = db.Column(db.Integer, primary_key=True)
ticker = db.Column(db.String(10), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)


class WatchedCrypto(db.Model):
__tablename__ = 'watched_crypto'

id = db.Column(db.Integer, primary_key=True)
ticker = db.Column(db.String(10), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)


65 changes: 63 additions & 2 deletions Backend/kobrastocks/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@
from flask_jwt_extended import jwt_required, get_jwt_identity
from .models import User

from .serializers import stock_data_schema, contact_form_schema
from .serializers import stock_data_schema, contact_form_schema, crypto_data_schema
from .services import (
get_stock_data,
send_contact_form,
get_predictions,
get_stock_chart,
get_stock_chart, get_crypto_data,
)
from .utils import convert_to_builtin_types

Expand Down Expand Up @@ -191,3 +191,64 @@ def get_stock_news_articles():
current_app.logger.error(f"Error fetching news articles: {e}")
return jsonify({'error': 'Failed to fetch news articles'}), 500


@main.route('/api/hot_crypto', methods=['GET'])
@jwt_required()
def get_hot_crypto_currencies():
try:
# Get the user's budget
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404

user_budget = user.budget

# Fetch cryptocurrency data from CoinGecko
url = "https://api.coingecko.com/api/v3/coins/markets"
query_params = {
"vs_currency": "usd", # Prices in USD
"order": "market_cap_desc", # Top cryptocurrencies by market cap
"per_page": 50, # Fetch top 50
"page": 1, # Page number
"sparkline": False # No sparkline data
}
response = requests.get(url, params=query_params)
if response.status_code != 200:
return jsonify({'error': 'Failed to fetch cryptocurrency data'}), 500

cryptos = response.json()
hot_crypto_data = []

# Filter cryptocurrencies within the user's budget
for crypto in cryptos:
price = crypto.get('current_price')
if price and (user_budget is None or price <= user_budget):
hot_crypto_data.append({
"ticker": crypto.get('symbol'),
"name": crypto.get('name'),
"price": price,
"market_cap": crypto.get('market_cap'),
"24h_change": crypto.get('price_change_percentage_24h')
})

# Limit to top 10
hot_crypto_data = hot_crypto_data[:10]

if not hot_crypto_data:
return jsonify({'message': 'No hot cryptocurrencies found within your budget. Showing top cryptocurrencies.'}), 200

return jsonify(hot_crypto_data), 200

except Exception as e:
current_app.logger.error(f"Error fetching hot cryptocurrencies: {e}")
return jsonify({'error': 'Failed to fetch hot cryptocurrencies'}), 500


@main.route('/api/crypto_data', methods=['GET'])
def crypto_data():
ticker = request.args.get('ticker', type=str)
crypto_data = get_crypto_data(ticker)
if crypto_data is None:
return jsonify({'error': f"No data found for ticker {ticker}"}), 404
return jsonify(crypto_data_schema.dump(crypto_data))
21 changes: 20 additions & 1 deletion Backend/kobrastocks/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,23 @@ class PortfolioRecommendationsSchema(Schema):
recommendations = fields.Dict(keys=fields.Str(), values=fields.Raw())


portfolio_recommendations_schema = PortfolioRecommendationsSchema()
portfolio_recommendations_schema = PortfolioRecommendationsSchema()


class CryptoDataSchema(Schema):
"""
Serializer for cryptocurrency data.
Ensures that the API response is formatted properly.
"""
ticker = fields.Str(required=True)
name = fields.Str(required=True)
price = fields.Float(required=True)
market_cap = fields.Float(required=False)
percentage_change_24h = fields.Float(required=False)
volume = fields.Float(required=False)
rank = fields.Int(required=False)
symbol = fields.Str(required=False)


crypto_data_schema = CryptoDataSchema()
crypto_data_list_schema = CryptoDataSchema(many=True)
40 changes: 40 additions & 0 deletions Backend/kobrastocks/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
---------------------------------------------
"""
import pytz
import requests
import yfinance as yf
from datetime import datetime, timedelta
import pandas as pd
Expand Down Expand Up @@ -588,3 +589,42 @@ def get_stock_price_at_date(ticker, purchase_date=None):
except Exception as e:
logger.error(f"Error getting stock price for {ticker} at date {purchase_date}: {e}")
return None


def get_crypto_data(ticker):
"""
Fetches data for a specific cryptocurrency using the CoinGecko API.
"""
try:
# CoinGecko API base URL
url = f"https://api.coingecko.com/api/v3/coins/{ticker}"

# Make the API request
response = requests.get(url)
if response.status_code != 200:
current_app.logger.error(f"Failed to fetch data for crypto ticker {ticker}")
return None

data = response.json()

# Extract relevant fields
crypto_data = {
"ticker": data.get("id"),
"name": data.get("name"),
"price": data.get("market_data", {}).get("current_price", {}).get("usd"),
"market_cap": data.get("market_data", {}).get("market_cap", {}).get("usd"),
"percentage_change_24h": data.get("market_data", {}).get("price_change_percentage_24h"),
"volume": data.get("market_data", {}).get("total_volume", {}).get("usd"),
"rank": data.get("market_cap_rank"),
"symbol": data.get("symbol")
}

# Ensure all required fields are present
if crypto_data["price"] is None:
raise ValueError("Missing required cryptocurrency data.")

return crypto_data

except Exception as e:
current_app.logger.error(f"Error fetching crypto data for {ticker}: {e}")
return None
90 changes: 88 additions & 2 deletions Backend/kobrastocks/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from kobrastocks.models import User, FavoriteStock, WatchedStock
from kobrastocks.models import User, FavoriteStock, WatchedStock, WatchedCrypto, FavoriteCrypto
from .extensions import db

user = Blueprint('user', __name__)
Expand Down Expand Up @@ -130,4 +130,90 @@ def update_budget():
db.session.commit()
return jsonify({'message': 'Budget updated successfully'}), 200
else:
return jsonify({'error': 'User not found'}), 404
return jsonify({'error': 'User not found'}), 404


@user.route('/api/crypto_favorites', methods=['GET'])
@jwt_required()
def get_crypto_favorites():
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
favorites = [fc.ticker for fc in user.favorite_crypto]
return jsonify({'favorites': favorites}), 200


@user.route('/api/crypto_favorites', methods=['POST'])
@jwt_required()
def add_crypto_favorite():
user_id = get_jwt_identity()
data = request.get_json()
ticker = data.get('ticker')
if not ticker:
return jsonify({'error': 'Ticker is required'}), 400

existing = FavoriteCrypto.query.filter_by(user_id=user_id, ticker=ticker).first()
if existing:
return jsonify({'message': 'Crypto already in favorites'}), 200

favorite = FavoriteCrypto(user_id=user_id, ticker=ticker)
db.session.add(favorite)
db.session.commit()
return jsonify({'message': 'Crypto added to favorites'}), 201


@user.route('/api/crypto_favorites/<string:ticker>', methods=['DELETE'])
@jwt_required()
def remove_crypto_favorite(ticker):
user_id = get_jwt_identity()
favorite = FavoriteCrypto.query.filter_by(user_id=user_id, ticker=ticker).first()
if favorite:
db.session.delete(favorite)
db.session.commit()
return jsonify({'message': 'Crypto removed from favorites'}), 200
else:
return jsonify({'error': 'Crypto not found in favorites'}), 404


@user.route('/api/crypto_watchlist', methods=['GET'])
@jwt_required()
def get_crypto_watchlist():
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
watchlist = [wc.ticker for wc in user.watched_crypto]
return jsonify({'watchlist': watchlist}), 200


@user.route('/api/crypto_watchlist', methods=['POST'])
@jwt_required()
def add_crypto_to_watchlist():
user_id = get_jwt_identity()
data = request.get_json()
ticker = data.get('ticker')
if not ticker:
return jsonify({'error': 'Ticker is required'}), 400

existing = WatchedCrypto.query.filter_by(user_id=user_id, ticker=ticker).first()
if existing:
return jsonify({'message': 'Crypto already in watchlist'}), 200

watch_stock = WatchedCrypto(user_id=user_id, ticker=ticker)
db.session.add(watch_stock)
db.session.commit()
return jsonify({'message': 'Crypto added to watchlist'}), 201


@user.route('/api/crypto_watchlist/<string:ticker>', methods=['DELETE'])
@jwt_required()
def remove_crypto_from_watchlist(ticker):
user_id = get_jwt_identity()
watch_stock = WatchedCrypto.query.filter_by(user_id=user_id, ticker=ticker).first()
if watch_stock:
db.session.delete(watch_stock)
db.session.commit()
return jsonify({'message': 'Crypto removed from watchlist'}), 200
else:
return jsonify({'error': 'Crypto not found in watchlist'}), 404
42 changes: 42 additions & 0 deletions Backend/migrations/versions/9c57f23e362e_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""empty message
Revision ID: 9c57f23e362e
Revises: 8bbece38a60b
Create Date: 2024-12-07 10:18:10.551376
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '9c57f23e362e'
down_revision = '8bbece38a60b'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('favorite_crypto',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('ticker', sa.String(length=10), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('watched_crypto',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('ticker', sa.String(length=10), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('watched_crypto')
op.drop_table('favorite_crypto')
# ### end Alembic commands ###
Loading

0 comments on commit 4efca21

Please sign in to comment.