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