import asyncio import logging from app.models.schemas import StrategyConfig, StrategyStatus from app.services.market_data import market_data_service from app.services.order_manager import order_manager from app.strategies.base import BaseStrategy from app.strategies.bollinger_bands import BollingerBandsStrategy from app.strategies.dual_thrust import DualThrustStrategy from app.strategies.ma_crossover import MACrossoverStrategy logger = logging.getLogger(__name__) STRATEGY_CLASSES: dict[str, type[BaseStrategy]] = { "ma_crossover": MACrossoverStrategy, "bollinger_bands": BollingerBandsStrategy, "dual_thrust": DualThrustStrategy, } class StrategyEngine: def __init__(self): self._strategies: dict[str, BaseStrategy] = {} self._configs: dict[str, StrategyConfig] = {} self._tasks: dict[str, asyncio.Task] = {} def get_available_strategies(self) -> list[dict]: result = [] for key, cls in STRATEGY_CLASSES.items(): instance = cls(strategy_id="temp", symbol="") result.append({ "type": key, "name": instance.name, "description": instance.description, "default_params": instance.default_params, }) return result def get_running_strategies(self) -> list[StrategyConfig]: return list(self._configs.values()) def get_strategy_signals(self, strategy_id: str) -> list[dict]: strategy = self._strategies.get(strategy_id) if strategy: return strategy.signals return [] def get_strategy_performance(self, strategy_id: str): strategy = self._strategies.get(strategy_id) if strategy: return strategy.performance return None def add_strategy(self, config: StrategyConfig) -> StrategyConfig: cls = STRATEGY_CLASSES.get(config.strategy_type) if cls is None: raise ValueError(f"Unknown strategy type: {config.strategy_type}") strategy = cls( strategy_id=config.strategy_id, symbol=config.symbol, params=config.params, ) self._strategies[config.strategy_id] = strategy self._configs[config.strategy_id] = config return config def start_strategy(self, strategy_id: str) -> StrategyConfig | None: config = self._configs.get(strategy_id) if config is None: return None config.status = StrategyStatus.RUNNING task = asyncio.create_task(self._run_strategy(strategy_id)) self._tasks[strategy_id] = task return config def stop_strategy(self, strategy_id: str) -> StrategyConfig | None: config = self._configs.get(strategy_id) if config is None: return None config.status = StrategyStatus.STOPPED task = self._tasks.pop(strategy_id, None) if task: task.cancel() return config def remove_strategy(self, strategy_id: str) -> bool: self.stop_strategy(strategy_id) self._strategies.pop(strategy_id, None) self._configs.pop(strategy_id, None) return True async def _run_strategy(self, strategy_id: str): strategy = self._strategies.get(strategy_id) config = self._configs.get(strategy_id) if not strategy or not config: return try: while config.status == StrategyStatus.RUNNING: klines = market_data_service.get_kline_history(strategy.symbol) if klines: signal = strategy.calculate_signal(klines) if signal: order = order_manager.place_order(signal) logger.info( "Strategy %s generated %s signal for %s, order: %s", strategy_id, signal.side, signal.symbol, order.order_id ) await asyncio.sleep(2) except asyncio.CancelledError: pass except Exception as e: logger.error("Strategy %s error: %s", strategy_id, e) config.status = StrategyStatus.ERROR strategy_engine = StrategyEngine()