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:
123
backend/app/api/endpoints/signals.py
Normal file
123
backend/app/api/endpoints/signals.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
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}
|
||||
Reference in New Issue
Block a user