311 lines
11 KiB
Python
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))
|