Features: - FastAPI backend with stocks, news, signals, watchlist, analytics endpoints - React frontend with TailwindCSS dark mode trading dashboard - Celery workers for news fetching, sentiment analysis, pattern detection - TimescaleDB schema for time-series stock data - Docker Compose setup for all services - OpenAI integration for sentiment analysis
142 lines
4.2 KiB
Python
142 lines
4.2 KiB
Python
"""
|
|
Watchlist API Endpoints
|
|
"""
|
|
|
|
from typing import List, Optional
|
|
from uuid import UUID
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
|
|
from app.core.database import get_db
|
|
from app.models.watchlist import Watchlist
|
|
from app.models.stock import Stock
|
|
from app.schemas.watchlist import WatchlistResponse, WatchlistCreate, WatchlistUpdate
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/", response_model=List[WatchlistResponse])
|
|
async def list_watchlist(
|
|
db: AsyncSession = Depends(get_db),
|
|
priority: Optional[int] = Query(None, ge=1, le=3, description="Filter by priority"),
|
|
):
|
|
"""Get all items in watchlist."""
|
|
query = select(Watchlist).where(Watchlist.is_active == True)
|
|
|
|
if priority:
|
|
query = query.where(Watchlist.priority == priority)
|
|
|
|
query = query.order_by(Watchlist.priority)
|
|
result = await db.execute(query)
|
|
return result.scalars().all()
|
|
|
|
|
|
@router.post("/", response_model=WatchlistResponse)
|
|
async def add_to_watchlist(
|
|
item: WatchlistCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Add a stock to the watchlist."""
|
|
# Find the stock
|
|
stock_query = select(Stock).where(Stock.symbol == item.symbol.upper())
|
|
stock_result = await db.execute(stock_query)
|
|
stock = stock_result.scalar_one_or_none()
|
|
|
|
if not stock:
|
|
raise HTTPException(status_code=404, detail=f"Stock {item.symbol} not found")
|
|
|
|
# Check if already in watchlist
|
|
existing = await db.execute(
|
|
select(Watchlist).where(Watchlist.stock_id == stock.id)
|
|
)
|
|
if existing.scalar_one_or_none():
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Stock {item.symbol} is already in watchlist"
|
|
)
|
|
|
|
watchlist_item = Watchlist(
|
|
stock_id=stock.id,
|
|
panic_alert_threshold=item.panic_alert_threshold,
|
|
price_alert_low=item.price_alert_low,
|
|
price_alert_high=item.price_alert_high,
|
|
priority=item.priority,
|
|
notes=item.notes,
|
|
)
|
|
db.add(watchlist_item)
|
|
await db.commit()
|
|
await db.refresh(watchlist_item)
|
|
return watchlist_item
|
|
|
|
|
|
@router.put("/{watchlist_id}", response_model=WatchlistResponse)
|
|
async def update_watchlist_item(
|
|
watchlist_id: UUID,
|
|
update: WatchlistUpdate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Update a watchlist item."""
|
|
query = select(Watchlist).where(Watchlist.id == watchlist_id)
|
|
result = await db.execute(query)
|
|
item = result.scalar_one_or_none()
|
|
|
|
if not item:
|
|
raise HTTPException(status_code=404, detail="Watchlist item not found")
|
|
|
|
update_data = update.model_dump(exclude_unset=True)
|
|
for key, value in update_data.items():
|
|
setattr(item, key, value)
|
|
|
|
await db.commit()
|
|
await db.refresh(item)
|
|
return item
|
|
|
|
|
|
@router.delete("/{watchlist_id}")
|
|
async def remove_from_watchlist(
|
|
watchlist_id: UUID,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Remove a stock from the watchlist."""
|
|
query = select(Watchlist).where(Watchlist.id == watchlist_id)
|
|
result = await db.execute(query)
|
|
item = result.scalar_one_or_none()
|
|
|
|
if not item:
|
|
raise HTTPException(status_code=404, detail="Watchlist item not found")
|
|
|
|
await db.delete(item)
|
|
await db.commit()
|
|
return {"message": "Removed from watchlist"}
|
|
|
|
|
|
@router.delete("/symbol/{symbol}")
|
|
async def remove_symbol_from_watchlist(
|
|
symbol: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Remove a stock from watchlist by symbol."""
|
|
# Find the stock
|
|
stock_query = select(Stock).where(Stock.symbol == symbol.upper())
|
|
stock_result = await db.execute(stock_query)
|
|
stock = stock_result.scalar_one_or_none()
|
|
|
|
if not stock:
|
|
raise HTTPException(status_code=404, detail=f"Stock {symbol} not found")
|
|
|
|
# Find and remove watchlist item
|
|
watchlist_query = select(Watchlist).where(Watchlist.stock_id == stock.id)
|
|
watchlist_result = await db.execute(watchlist_query)
|
|
item = watchlist_result.scalar_one_or_none()
|
|
|
|
if not item:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"Stock {symbol} is not in watchlist"
|
|
)
|
|
|
|
await db.delete(item)
|
|
await db.commit()
|
|
return {"message": f"Removed {symbol} from watchlist"}
|