- FastAPI service for market data synchronization from Polygon.io
- Async PostgreSQL integration with SQLAlchemy
- API endpoints for tickers, OHLCV data, and sync operations
- Rate-limited Polygon.io API client
- Background task support for historical data sync
- Support for 6 assets: XAUUSD, EURUSD, BTCUSD, GBPUSD, USDJPY, AUDUSD
Endpoints:
- GET /api/v1/tickers - List all tickers
- GET /api/v1/tickers/{symbol}/ohlcv - Get OHLCV data
- GET /api/v1/tickers/{symbol}/latest - Get latest price
- POST /api/v1/sync/historical - Start historical sync
- POST /api/v1/sync/ticker/{symbol} - Sync single ticker
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
134 lines
3.9 KiB
Python
134 lines
3.9 KiB
Python
"""
|
|
Sync API Router - Endpoints for data synchronization.
|
|
"""
|
|
from fastapi import APIRouter, BackgroundTasks, HTTPException
|
|
from datetime import datetime
|
|
from typing import List, Optional
|
|
|
|
from src.schemas import SyncRequest, SyncResponse, SyncStatus
|
|
from src.services.sync_service import SyncService
|
|
|
|
router = APIRouter(prefix="/api/v1/sync", tags=["sync"])
|
|
|
|
# Singleton sync service
|
|
sync_service = SyncService()
|
|
|
|
|
|
@router.get("/status", response_model=SyncResponse)
|
|
async def get_sync_status():
|
|
"""Get current synchronization status."""
|
|
status = sync_service.get_status()
|
|
return SyncResponse(
|
|
status=SyncStatus(status.get("status", "idle")),
|
|
message=status.get("message", ""),
|
|
symbols_processed=status.get("symbols_processed", []),
|
|
records_inserted=status.get("records_inserted", 0),
|
|
errors=status.get("errors", []),
|
|
started_at=status.get("started_at"),
|
|
completed_at=status.get("completed_at")
|
|
)
|
|
|
|
|
|
@router.post("/historical", response_model=SyncResponse)
|
|
async def sync_historical(
|
|
request: SyncRequest,
|
|
background_tasks: BackgroundTasks
|
|
):
|
|
"""
|
|
Start historical data synchronization.
|
|
|
|
This endpoint starts a background task to sync historical data
|
|
from Polygon.io to the database.
|
|
"""
|
|
# Check if sync is already running
|
|
current_status = sync_service.get_status()
|
|
if current_status.get("status") == "running":
|
|
raise HTTPException(
|
|
status_code=409,
|
|
detail="Sync already in progress"
|
|
)
|
|
|
|
# Start sync in background
|
|
background_tasks.add_task(
|
|
sync_service.sync_all,
|
|
symbols=request.symbols,
|
|
start_date=request.start_date,
|
|
end_date=request.end_date,
|
|
timeframe=request.timeframe
|
|
)
|
|
|
|
return SyncResponse(
|
|
status=SyncStatus.pending,
|
|
message="Historical sync started in background",
|
|
symbols_processed=[],
|
|
records_inserted=0,
|
|
errors=[],
|
|
started_at=datetime.now(),
|
|
completed_at=None
|
|
)
|
|
|
|
|
|
@router.post("/latest", response_model=SyncResponse)
|
|
async def sync_latest(
|
|
symbols: Optional[List[str]] = None,
|
|
background_tasks: BackgroundTasks = None
|
|
):
|
|
"""
|
|
Sync latest data for all or specified tickers.
|
|
|
|
This fetches the most recent data since the last sync.
|
|
"""
|
|
# Check if sync is already running
|
|
current_status = sync_service.get_status()
|
|
if current_status.get("status") == "running":
|
|
raise HTTPException(
|
|
status_code=409,
|
|
detail="Sync already in progress"
|
|
)
|
|
|
|
# Start sync in background
|
|
background_tasks.add_task(
|
|
sync_service.sync_all,
|
|
symbols=symbols,
|
|
start_date=None, # Will auto-detect from last timestamp
|
|
end_date=None,
|
|
timeframe="5m"
|
|
)
|
|
|
|
return SyncResponse(
|
|
status=SyncStatus.pending,
|
|
message="Latest data sync started in background",
|
|
symbols_processed=[],
|
|
records_inserted=0,
|
|
errors=[],
|
|
started_at=datetime.now(),
|
|
completed_at=None
|
|
)
|
|
|
|
|
|
@router.post("/ticker/{symbol}", response_model=SyncResponse)
|
|
async def sync_single_ticker(
|
|
symbol: str,
|
|
start_date: Optional[datetime] = None,
|
|
end_date: Optional[datetime] = None,
|
|
timeframe: str = "5m"
|
|
):
|
|
"""Sync data for a single ticker (synchronous)."""
|
|
# This runs synchronously for single ticker
|
|
result = await sync_service.sync_all(
|
|
symbols=[symbol.upper()],
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
timeframe=timeframe
|
|
)
|
|
|
|
return SyncResponse(
|
|
status=SyncStatus(result.get("status", "completed")),
|
|
message=result.get("message", ""),
|
|
symbols_processed=result.get("symbols_processed", []),
|
|
records_inserted=result.get("records_inserted", 0),
|
|
errors=result.get("errors", []),
|
|
started_at=result.get("started_at"),
|
|
completed_at=result.get("completed_at")
|
|
)
|