Data aggregation and distribution service: - Market data collection - OHLCV aggregation - Real-time data feeds - Data API endpoints Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
104 lines
2.9 KiB
Python
104 lines
2.9 KiB
Python
"""
|
|
FastAPI Dependencies
|
|
OrbiQuant IA Trading Platform - Data Service
|
|
"""
|
|
|
|
from typing import Optional, AsyncGenerator
|
|
import asyncpg
|
|
from fastapi import Request, HTTPException, status
|
|
|
|
from config import Config
|
|
|
|
|
|
async def get_db_pool(request: Request) -> asyncpg.Pool:
|
|
"""Get database connection pool from app state."""
|
|
pool = request.app.state.db_pool
|
|
if not pool:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="Database connection not available"
|
|
)
|
|
return pool
|
|
|
|
|
|
async def get_db_connection(request: Request) -> AsyncGenerator[asyncpg.Connection, None]:
|
|
"""Get a database connection from pool."""
|
|
pool = await get_db_pool(request)
|
|
async with pool.acquire() as connection:
|
|
yield connection
|
|
|
|
|
|
def get_data_service(request: Request):
|
|
"""Get DataService instance from app state."""
|
|
service = request.app.state.data_service
|
|
if not service:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="Data service not initialized"
|
|
)
|
|
return service
|
|
|
|
|
|
def get_config(request: Request) -> Config:
|
|
"""Get configuration from app state."""
|
|
return request.app.state.config
|
|
|
|
|
|
def get_polygon_client(request: Request):
|
|
"""Get Polygon client from app state."""
|
|
client = request.app.state.polygon_client
|
|
if not client:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="Polygon client not configured"
|
|
)
|
|
return client
|
|
|
|
|
|
def get_mt4_client(request: Request):
|
|
"""Get MT4/MetaAPI client from app state."""
|
|
return request.app.state.mt4_client # May be None
|
|
|
|
|
|
class RateLimiter:
|
|
"""Simple in-memory rate limiter."""
|
|
|
|
def __init__(self, requests_per_minute: int = 60):
|
|
self.requests_per_minute = requests_per_minute
|
|
self._requests: dict[str, list[float]] = {}
|
|
|
|
async def check(self, client_id: str) -> bool:
|
|
"""Check if client can make a request."""
|
|
import time
|
|
now = time.time()
|
|
minute_ago = now - 60
|
|
|
|
if client_id not in self._requests:
|
|
self._requests[client_id] = []
|
|
|
|
# Clean old requests
|
|
self._requests[client_id] = [
|
|
ts for ts in self._requests[client_id] if ts > minute_ago
|
|
]
|
|
|
|
if len(self._requests[client_id]) >= self.requests_per_minute:
|
|
return False
|
|
|
|
self._requests[client_id].append(now)
|
|
return True
|
|
|
|
|
|
# Global rate limiter instance
|
|
rate_limiter = RateLimiter(requests_per_minute=60)
|
|
|
|
|
|
async def check_rate_limit(request: Request) -> None:
|
|
"""Rate limit dependency."""
|
|
client_ip = request.client.host if request.client else "unknown"
|
|
|
|
if not await rate_limiter.check(client_ip):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail="Too many requests. Please slow down."
|
|
)
|