Initial project structure: MarketScanner - Fear-to-Fortune Trading Intelligence
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
This commit is contained in:
141
backend/app/api/endpoints/watchlist.py
Normal file
141
backend/app/api/endpoints/watchlist.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
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"}
|
||||
Reference in New Issue
Block a user