alpha-engine / api /trades.py
Dharambir Agrawal
HF Space server-only
fd48bc8
from __future__ import annotations
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy import desc, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from api.deps import get_current_user, get_portfolio_or_404
from api.utils import transaction_to_out
from core.database import get_db
from core.models import Transaction, User
from core.schemas import PaginatedTransactionsOut, TransactionOut
router = APIRouter(tags=["trades"])
def _parse_datetime(value: str | None) -> datetime | None:
if not value:
return None
raw = value.strip()
if raw.endswith("Z"):
raw = raw.replace("Z", "+00:00")
try:
return datetime.fromisoformat(raw)
except ValueError:
return None
@router.get(
"/portfolios/{portfolio_id}/transactions",
response_model=PaginatedTransactionsOut,
)
async def list_transactions(
portfolio_id: str,
request: Request,
ticker: str | None = None,
action: str | None = None,
limit: int = Query(default=20, ge=1, le=200),
offset: int = Query(default=0, ge=0),
from_date: str | None = Query(default=None, alias="from"),
to_date: str | None = Query(default=None, alias="to"),
search: str | None = None,
_: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
portfolio = await get_portfolio_or_404(portfolio_id, request, db)
stmt = select(Transaction).where(Transaction.portfolio_id == portfolio.id)
if ticker:
stmt = stmt.where(Transaction.ticker == ticker.upper().strip())
if action:
stmt = stmt.where(Transaction.action == action.upper().strip())
if search:
term = f"%{search.strip()}%"
stmt = stmt.where(
or_(
Transaction.ticker.ilike(term),
Transaction.llm_reasoning.ilike(term),
)
)
parsed_from = _parse_datetime(from_date)
if parsed_from:
stmt = stmt.where(Transaction.executed_at >= parsed_from)
parsed_to = _parse_datetime(to_date)
if parsed_to:
stmt = stmt.where(Transaction.executed_at <= parsed_to)
total_stmt = select(func.count()).select_from(stmt.subquery())
total = int((await db.scalar(total_stmt)) or 0)
page_stmt = stmt.order_by(desc(Transaction.executed_at)).limit(limit).offset(offset)
rows = (await db.scalars(page_stmt)).all()
return PaginatedTransactionsOut(
transactions=[transaction_to_out(row) for row in rows],
total=total,
limit=limit,
offset=offset,
)
@router.get(
"/portfolios/{portfolio_id}/transactions/{tx_id}",
response_model=TransactionOut,
)
async def get_transaction(
portfolio_id: str,
tx_id: str,
request: Request,
_: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
portfolio = await get_portfolio_or_404(portfolio_id, request, db)
stmt = select(Transaction).where(
Transaction.id == tx_id,
Transaction.portfolio_id == portfolio.id,
)
tx = await db.scalar(stmt)
if not tx:
raise HTTPException(status_code=404, detail="Transaction not found")
return transaction_to_out(tx)