Files

311 lines
11 KiB
Python
Raw Permalink Normal View History

"""Strategy API endpoints."""
from typing import List
from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy import select
from ..core.dependencies import get_strategy_registry
from ..core.schemas import (
StrategyCreate, StrategyUpdate, StrategyResponse
)
from src.core.database import Strategy, get_database
from src.strategies.scheduler import get_scheduler as _get_scheduler
def get_strategy_scheduler():
"""Get strategy scheduler instance."""
return _get_scheduler()
router = APIRouter()
@router.get("/", response_model=List[StrategyResponse])
async def list_strategies():
"""List all strategies."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).order_by(Strategy.created_at.desc())
result = await session.execute(stmt)
strategies = result.scalars().all()
return [StrategyResponse.model_validate(s) for s in strategies]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/available")
async def list_available_strategies(
registry=Depends(get_strategy_registry)
):
"""List available strategy types."""
try:
return {"strategies": registry.list_available()}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/", response_model=StrategyResponse)
async def create_strategy(strategy_data: StrategyCreate):
"""Create a new strategy."""
try:
db = get_database()
async with db.get_session() as session:
strategy = Strategy(
name=strategy_data.name,
description=strategy_data.description,
strategy_type=strategy_data.strategy_type,
class_name=strategy_data.class_name,
parameters=strategy_data.parameters,
timeframes=strategy_data.timeframes,
paper_trading=strategy_data.paper_trading,
schedule=strategy_data.schedule,
enabled=False
)
session.add(strategy)
await session.commit()
await session.refresh(strategy)
return StrategyResponse.model_validate(strategy)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{strategy_id}", response_model=StrategyResponse)
async def get_strategy(strategy_id: int):
"""Get strategy by ID."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
return StrategyResponse.model_validate(strategy)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/{strategy_id}", response_model=StrategyResponse)
async def update_strategy(strategy_id: int, strategy_data: StrategyUpdate):
"""Update a strategy."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
if strategy_data.name is not None:
strategy.name = strategy_data.name
if strategy_data.description is not None:
strategy.description = strategy_data.description
if strategy_data.parameters is not None:
strategy.parameters = strategy_data.parameters
if strategy_data.timeframes is not None:
strategy.timeframes = strategy_data.timeframes
if strategy_data.enabled is not None:
strategy.enabled = strategy_data.enabled
if strategy_data.schedule is not None:
strategy.schedule = strategy_data.schedule
await session.commit()
await session.refresh(strategy)
return StrategyResponse.model_validate(strategy)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{strategy_id}")
async def delete_strategy(strategy_id: int):
"""Delete a strategy."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
await session.delete(strategy)
await session.commit()
return {"status": "deleted", "strategy_id": strategy_id}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{strategy_id}/start")
async def start_strategy(strategy_id: int):
"""Start a strategy manually (bypasses Autopilot)."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
# Start strategy via scheduler
scheduler = get_strategy_scheduler()
scheduler.start_strategy(strategy_id)
strategy.running = True # Only set running, not enabled
await session.commit()
return {"status": "started", "strategy_id": strategy_id}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{strategy_id}/stop")
async def stop_strategy(strategy_id: int):
"""Stop a manually running strategy."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
# Stop strategy via scheduler
scheduler = get_strategy_scheduler()
scheduler.stop_strategy(strategy_id)
strategy.running = False # Only set running, not enabled
await session.commit()
return {"status": "stopped", "strategy_id": strategy_id}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{strategy_type}/optimize")
async def optimize_strategy(
strategy_type: str,
symbol: str = "BTC/USD",
method: str = "genetic",
population_size: int = 50,
generations: int = 100
):
"""Optimize strategy parameters using genetic algorithm.
This endpoint queues an optimization task and returns immediately.
Use /api/tasks/{task_id} to monitor progress.
Args:
strategy_type: Type of strategy to optimize (e.g., 'rsi', 'macd')
symbol: Trading symbol for backtesting
method: Optimization method ('genetic', 'grid')
population_size: Population size for genetic algorithm
generations: Number of generations
Returns:
Task ID for monitoring
"""
try:
from src.worker.tasks import optimize_strategy_task
# Get parameter ranges for the strategy type
registry = get_strategy_registry()
strategy_class = registry.get(strategy_type)
if not strategy_class:
raise HTTPException(status_code=404, detail=f"Strategy type '{strategy_type}' not found")
# Default parameter ranges based on strategy type
param_ranges = {
"rsi": {"period": (5, 50), "overbought": (60, 90), "oversold": (10, 40)},
"macd": {"fast_period": (5, 20), "slow_period": (15, 40), "signal_period": (5, 15)},
"moving_average": {"short_period": (5, 30), "long_period": (20, 100)},
"bollinger_mean_reversion": {"period": (10, 50), "std_dev": (1.5, 3.0)},
}.get(strategy_type.lower(), {"period": (10, 50)})
# Queue the optimization task
task = optimize_strategy_task.delay(
strategy_type=strategy_type,
symbol=symbol,
param_ranges=param_ranges,
method=method,
population_size=population_size,
generations=generations
)
return {
"task_id": task.id,
"status": "queued",
"strategy_type": strategy_type,
"symbol": symbol,
"method": method
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{strategy_id}/status")
async def get_strategy_status(strategy_id: int):
"""Get real-time status of a running strategy.
Returns execution info including last tick time, last signal, and stats.
"""
try:
scheduler = get_strategy_scheduler()
status = scheduler.get_strategy_status(strategy_id)
if not status:
# Check if strategy exists but isn't running
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
return {
"strategy_id": strategy_id,
"name": strategy.name,
"type": strategy.strategy_type,
"symbol": strategy.parameters.get('symbol') if strategy.parameters else None,
"running": False,
"enabled": strategy.enabled,
}
return status
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/running/all")
async def get_all_running_strategies():
"""Get status of all currently running strategies."""
try:
scheduler = get_strategy_scheduler()
active = scheduler.get_all_active_strategies()
return {
"total_running": len(active),
"strategies": active
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))