Files
marketscanner/backend/app/api/endpoints/signals.py
mindesbunister 074787f067 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
2026-01-08 14:15:51 +01:00

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}