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
125
services/api_gateway/routes/portfolio.py
Normal file
125
services/api_gateway/routes/portfolio.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
"""Portfolio endpoints — current value, positions, equity curve."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from enum import Enum
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, Request
|
||||
|
||||
from services.api_gateway.auth.middleware import get_current_user
|
||||
from sqlalchemy import select, desc
|
||||
|
||||
router = APIRouter(prefix="/api/portfolio", tags=["portfolio"])
|
||||
|
||||
|
||||
class HistoryPeriod(str, Enum):
|
||||
ONE_DAY = "1d"
|
||||
ONE_WEEK = "1w"
|
||||
ONE_MONTH = "1m"
|
||||
THREE_MONTHS = "3m"
|
||||
ONE_YEAR = "1y"
|
||||
|
||||
|
||||
def _period_to_timedelta(period: HistoryPeriod) -> timedelta:
|
||||
"""Convert a period enum value to a timedelta."""
|
||||
mapping = {
|
||||
HistoryPeriod.ONE_DAY: timedelta(days=1),
|
||||
HistoryPeriod.ONE_WEEK: timedelta(weeks=1),
|
||||
HistoryPeriod.ONE_MONTH: timedelta(days=30),
|
||||
HistoryPeriod.THREE_MONTHS: timedelta(days=90),
|
||||
HistoryPeriod.ONE_YEAR: timedelta(days=365),
|
||||
}
|
||||
return mapping[period]
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def get_portfolio(
|
||||
request: Request,
|
||||
_user: dict = Depends(get_current_user),
|
||||
) -> dict:
|
||||
"""Current portfolio summary — value, cash, buying power, daily P&L."""
|
||||
from shared.models.timeseries import PortfolioSnapshot
|
||||
|
||||
db = request.app.state.db_session_factory
|
||||
async with db() as session:
|
||||
latest = (
|
||||
await session.execute(
|
||||
select(PortfolioSnapshot)
|
||||
.order_by(desc(PortfolioSnapshot.timestamp))
|
||||
.limit(1)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if latest is None:
|
||||
return {
|
||||
"total_value": 0.0,
|
||||
"cash": 0.0,
|
||||
"buying_power": 0.0,
|
||||
"daily_pnl": 0.0,
|
||||
}
|
||||
|
||||
return {
|
||||
"total_value": latest.total_value,
|
||||
"cash": latest.cash,
|
||||
"buying_power": latest.cash,
|
||||
"daily_pnl": latest.daily_pnl,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/positions")
|
||||
async def get_positions(
|
||||
request: Request,
|
||||
_user: dict = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
"""All open positions with unrealized P&L."""
|
||||
from shared.models.trading import Position
|
||||
|
||||
db = request.app.state.db_session_factory
|
||||
async with db() as session:
|
||||
result = await session.execute(select(Position))
|
||||
positions = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": str(p.id),
|
||||
"ticker": p.ticker,
|
||||
"qty": p.qty,
|
||||
"avg_entry": p.avg_entry,
|
||||
"unrealized_pnl": p.unrealized_pnl or 0.0,
|
||||
"stop_loss": p.stop_loss,
|
||||
"take_profit": p.take_profit,
|
||||
}
|
||||
for p in positions
|
||||
]
|
||||
|
||||
|
||||
@router.get("/history")
|
||||
async def get_portfolio_history(
|
||||
request: Request,
|
||||
_user: dict = Depends(get_current_user),
|
||||
period: HistoryPeriod = Query(default=HistoryPeriod.ONE_MONTH),
|
||||
) -> list[dict]:
|
||||
"""Equity curve from portfolio_snapshots over a given period."""
|
||||
from shared.models.timeseries import PortfolioSnapshot
|
||||
|
||||
since = datetime.now(timezone.utc) - _period_to_timedelta(period)
|
||||
db = request.app.state.db_session_factory
|
||||
async with db() as session:
|
||||
result = await session.execute(
|
||||
select(PortfolioSnapshot)
|
||||
.where(PortfolioSnapshot.timestamp >= since)
|
||||
.order_by(PortfolioSnapshot.timestamp)
|
||||
)
|
||||
snapshots = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"timestamp": s.timestamp.isoformat(),
|
||||
"total_value": s.total_value,
|
||||
"cash": s.cash,
|
||||
"positions_value": s.positions_value,
|
||||
"daily_pnl": s.daily_pnl,
|
||||
}
|
||||
for s in snapshots
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue