Files
crypto_trader/backend/api/strategies.py

311 lines
11 KiB
Python

"""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))