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
124 lines
3.6 KiB
Python
124 lines
3.6 KiB
Python
"""
|
|
Buy Signals API Endpoints
|
|
"""
|
|
|
|
from typing import List, Optional
|
|
from datetime import datetime, timedelta
|
|
from uuid import UUID
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, desc
|
|
|
|
from app.core.database import get_db
|
|
from app.models.signal import BuySignal
|
|
from app.schemas.signal import SignalResponse, SignalWithDetails
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/", response_model=List[SignalResponse])
|
|
async def list_signals(
|
|
db: AsyncSession = Depends(get_db),
|
|
status: Optional[str] = Query("active", description="Signal status: active, triggered, expired"),
|
|
min_confidence: float = Query(0.5, ge=0, le=1, description="Minimum confidence score"),
|
|
skip: int = Query(0, ge=0),
|
|
limit: int = Query(50, ge=1, le=100),
|
|
):
|
|
"""List buy signals ordered by confidence."""
|
|
query = select(BuySignal).where(BuySignal.confidence_score >= min_confidence)
|
|
|
|
if status:
|
|
query = query.where(BuySignal.status == status)
|
|
|
|
query = query.order_by(desc(BuySignal.confidence_score)).offset(skip).limit(limit)
|
|
result = await db.execute(query)
|
|
return result.scalars().all()
|
|
|
|
|
|
@router.get("/top")
|
|
async def get_top_signals(
|
|
db: AsyncSession = Depends(get_db),
|
|
limit: int = Query(10, ge=1, le=20),
|
|
):
|
|
"""Get top buy signals by confidence score."""
|
|
query = (
|
|
select(BuySignal)
|
|
.where(BuySignal.status == "active")
|
|
.order_by(desc(BuySignal.confidence_score))
|
|
.limit(limit)
|
|
)
|
|
result = await db.execute(query)
|
|
signals = result.scalars().all()
|
|
|
|
return {
|
|
"count": len(signals),
|
|
"signals": signals,
|
|
}
|
|
|
|
|
|
@router.get("/stock/{symbol}", response_model=List[SignalResponse])
|
|
async def get_signals_for_stock(
|
|
symbol: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
include_historical: bool = Query(False, description="Include past signals"),
|
|
):
|
|
"""Get buy signals for a specific stock."""
|
|
# We need to join with stocks table
|
|
# For now, placeholder
|
|
return []
|
|
|
|
|
|
@router.get("/{signal_id}", response_model=SignalWithDetails)
|
|
async def get_signal(
|
|
signal_id: UUID,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Get detailed information about a specific signal."""
|
|
query = select(BuySignal).where(BuySignal.id == signal_id)
|
|
result = await db.execute(query)
|
|
signal = result.scalar_one_or_none()
|
|
|
|
if not signal:
|
|
raise HTTPException(status_code=404, detail="Signal not found")
|
|
|
|
return signal
|
|
|
|
|
|
@router.post("/{signal_id}/trigger")
|
|
async def trigger_signal(
|
|
signal_id: UUID,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Mark a signal as triggered (you bought the stock)."""
|
|
query = select(BuySignal).where(BuySignal.id == signal_id)
|
|
result = await db.execute(query)
|
|
signal = result.scalar_one_or_none()
|
|
|
|
if not signal:
|
|
raise HTTPException(status_code=404, detail="Signal not found")
|
|
|
|
signal.status = "triggered"
|
|
signal.triggered_at = datetime.utcnow()
|
|
await db.commit()
|
|
|
|
return {"message": "Signal marked as triggered", "signal_id": signal_id}
|
|
|
|
|
|
@router.post("/{signal_id}/dismiss")
|
|
async def dismiss_signal(
|
|
signal_id: UUID,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Dismiss a signal (not interested)."""
|
|
query = select(BuySignal).where(BuySignal.id == signal_id)
|
|
result = await db.execute(query)
|
|
signal = result.scalar_one_or_none()
|
|
|
|
if not signal:
|
|
raise HTTPException(status_code=404, detail="Signal not found")
|
|
|
|
signal.status = "cancelled"
|
|
await db.commit()
|
|
|
|
return {"message": "Signal dismissed", "signal_id": signal_id}
|