feat: API gateway trading endpoints, controls, backtest, WebSocket
This commit is contained in:
parent
e0d138c457
commit
6fe586f722
11 changed files with 1304 additions and 0 deletions
87
services/api_gateway/routes/news.py
Normal file
87
services/api_gateway/routes/news.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
"""News endpoints — recent scored articles with filtering."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, Request
|
||||
|
||||
from services.api_gateway.auth.middleware import get_current_user
|
||||
from sqlalchemy import select, desc, func
|
||||
|
||||
router = APIRouter(prefix="/api/news", tags=["news"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_news(
|
||||
request: Request,
|
||||
_user: dict = Depends(get_current_user),
|
||||
ticker: str | None = Query(default=None),
|
||||
source: str | None = Query(default=None),
|
||||
min_score: float | None = Query(default=None, ge=-1.0, le=1.0),
|
||||
max_score: float | None = Query(default=None, ge=-1.0, le=1.0),
|
||||
page: int = Query(default=1, ge=1),
|
||||
per_page: int = Query(default=20, ge=1, le=100),
|
||||
) -> dict:
|
||||
"""Recent scored articles with optional filters."""
|
||||
from shared.models.news import Article, ArticleSentiment
|
||||
|
||||
db = request.app.state.db_session_factory
|
||||
async with db() as session:
|
||||
# Base query joining articles with sentiments
|
||||
query = (
|
||||
select(Article, ArticleSentiment)
|
||||
.join(ArticleSentiment, Article.id == ArticleSentiment.article_id)
|
||||
.order_by(desc(Article.fetched_at))
|
||||
)
|
||||
count_query = (
|
||||
select(func.count())
|
||||
.select_from(Article)
|
||||
.join(ArticleSentiment, Article.id == ArticleSentiment.article_id)
|
||||
)
|
||||
|
||||
if ticker:
|
||||
query = query.where(ArticleSentiment.ticker == ticker.upper())
|
||||
count_query = count_query.where(
|
||||
ArticleSentiment.ticker == ticker.upper()
|
||||
)
|
||||
if source:
|
||||
query = query.where(Article.source == source)
|
||||
count_query = count_query.where(Article.source == source)
|
||||
if min_score is not None:
|
||||
query = query.where(ArticleSentiment.score >= min_score)
|
||||
count_query = count_query.where(ArticleSentiment.score >= min_score)
|
||||
if max_score is not None:
|
||||
query = query.where(ArticleSentiment.score <= max_score)
|
||||
count_query = count_query.where(ArticleSentiment.score <= max_score)
|
||||
|
||||
total = (await session.execute(count_query)).scalar() or 0
|
||||
offset = (page - 1) * per_page
|
||||
query = query.offset(offset).limit(per_page)
|
||||
|
||||
result = await session.execute(query)
|
||||
rows = result.all()
|
||||
|
||||
return {
|
||||
"articles": [
|
||||
{
|
||||
"id": str(article.id),
|
||||
"source": article.source,
|
||||
"url": article.url,
|
||||
"title": article.title,
|
||||
"published_at": (
|
||||
article.published_at.isoformat()
|
||||
if article.published_at
|
||||
else None
|
||||
),
|
||||
"fetched_at": article.fetched_at.isoformat(),
|
||||
"ticker": sentiment.ticker,
|
||||
"sentiment_score": sentiment.score,
|
||||
"confidence": sentiment.confidence,
|
||||
"model_used": sentiment.model_used,
|
||||
}
|
||||
for article, sentiment in rows
|
||||
],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"pages": (total + per_page - 1) // per_page if per_page else 0,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue