from datetime import datetime, timezone from typing import Any from sqlalchemy.orm import Session from app.models import ( AiDecision, ChartHistoryPoint, CoinExSettings, DemoAccountState, DemoPosition, DemoTradeRecord, SystemStatus, TelegramSettings, WorkerRunLog, WorkerStrategy, ) DEFAULT_STRATEGIES = [ ('AI News Scalper', '1min', 'BTCUSDT,ETHUSDT,SOLUSDT'), ('Trend Follow', '5min', 'BTCUSDT,ETHUSDT'), ('Volatility Catcher', '1min', 'SOLUSDT,XRPUSDT,DOGEUSDT'), ] class LegacyBotService: def ensure_defaults(self, db: Session) -> None: if db.get(CoinExSettings, 1) is None: db.add(CoinExSettings(id=1)) if db.get(TelegramSettings, 1) is None: db.add(TelegramSettings(id=1)) for name, timeframe, markets in DEFAULT_STRATEGIES: exists = db.query(WorkerStrategy).filter(WorkerStrategy.name == name).first() if exists is None: db.add(WorkerStrategy(name=name, timeframe=timeframe, markets=markets, enabled=False, dry_run=True)) db.commit() def dashboard(self, db: Session) -> dict[str, Any]: self.ensure_defaults(db) account = db.get(DemoAccountState, 1) balance = account.balance if account else 0.0 realized = account.realized_pnl if account else 0.0 trades_total = db.query(DemoTradeRecord).count() open_positions = db.query(DemoPosition).filter(DemoPosition.is_open.is_(True)).count() closed_positions = db.query(DemoPosition).filter(DemoPosition.is_open.is_(False)).count() latest_decision = db.query(AiDecision).order_by(AiDecision.id.desc()).first() strategies = db.query(WorkerStrategy).order_by(WorkerStrategy.id.asc()).all() coinex = db.get(CoinExSettings, 1) telegram = db.get(TelegramSettings, 1) return { 'kpi': { 'balance': balance, 'realized_pnl': realized, 'trades_total': trades_total, 'open_positions': open_positions, 'closed_positions': closed_positions, 'active_strategies': sum(1 for s in strategies if s.enabled), }, 'latest_decision': self._decision(latest_decision), 'strategies': [self._strategy(s) for s in strategies], 'coinex': self._coinex(coinex), 'telegram': self._telegram(telegram), } def open_positions(self, db: Session) -> list[dict[str, Any]]: positions = db.query(DemoPosition).filter(DemoPosition.is_open.is_(True)).order_by(DemoPosition.id.desc()).all() return [self._position(item) for item in positions] def closed_positions(self, db: Session, limit: int = 50) -> list[dict[str, Any]]: positions = db.query(DemoPosition).filter(DemoPosition.is_open.is_(False)).order_by(DemoPosition.id.desc()).limit(limit).all() return [self._position(item) for item in positions] def chart_history(self, db: Session, limit: int = 100) -> list[dict[str, Any]]: rows = db.query(ChartHistoryPoint).order_by(ChartHistoryPoint.id.desc()).limit(limit).all() return [ { 'time': int(row.created_at.timestamp()), 'balance': row.balance, 'pnl_total': row.pnl_total, 'open_trades': row.open_trades, 'closed_trades': row.closed_trades, } for row in reversed(rows) ] def snapshot_history(self, db: Session) -> dict[str, Any]: account = db.get(DemoAccountState, 1) point = ChartHistoryPoint( balance=account.balance if account else 0.0, pnl_total=account.realized_pnl if account else 0.0, open_trades=db.query(DemoPosition).filter(DemoPosition.is_open.is_(True)).count(), closed_trades=db.query(DemoPosition).filter(DemoPosition.is_open.is_(False)).count(), ) db.add(point) db.commit() db.refresh(point) return {'id': point.id, 'balance': point.balance, 'pnl_total': point.pnl_total} def update_strategy(self, db: Session, strategy_id: int, enabled: bool | None = None, dry_run: bool | None = None) -> dict[str, Any]: self.ensure_defaults(db) strategy = db.get(WorkerStrategy, strategy_id) if strategy is None: raise ValueError('strategy not found') if enabled is not None: strategy.enabled = enabled if dry_run is not None: strategy.dry_run = dry_run strategy.last_signal = 'waiting' if strategy.enabled else 'stopped' strategy.last_message = 'Strategy settings updated from dashboard' db.add(strategy) db.add(WorkerRunLog(strategy_name=strategy.name, status='ok', message=strategy.last_message)) db.commit() db.refresh(strategy) return self._strategy(strategy) def update_coinex(self, db: Session, enabled: bool | None = None, access_id: str | None = None, secret_key: str | None = None, mode: str | None = None) -> dict[str, Any]: self.ensure_defaults(db) settings = db.get(CoinExSettings, 1) if enabled is not None: settings.enabled = enabled if access_id is not None: settings.access_id = access_id if secret_key is not None and secret_key: settings.secret_masked = self._mask(secret_key) if mode is not None: settings.mode = mode db.add(settings) db.commit() db.refresh(settings) return self._coinex(settings) def update_telegram(self, db: Session, enabled: bool | None = None, chat_id: str | None = None, message_format: str | None = None) -> dict[str, Any]: self.ensure_defaults(db) settings = db.get(TelegramSettings, 1) if enabled is not None: settings.enabled = enabled if chat_id is not None: settings.chat_id = chat_id if message_format is not None: settings.message_format = message_format db.add(settings) db.commit() db.refresh(settings) return self._telegram(settings) def updater_status(self, db: Session) -> dict[str, Any]: db.add(SystemStatus(component='updater', status='ok', message='Updater ready. Manual git pull is available on server.')) db.commit() return {'status': 'ready', 'message': 'Обновление готово: git pull + docker compose up -d --build'} def terminal_command(self, command: str) -> dict[str, Any]: allowed = {'status', 'health', 'version'} if command not in allowed: return {'status': 'blocked', 'output': 'Разрешены команды: status, health, version'} return {'status': 'ok', 'output': f'Команда {command} выполнена в безопасном режиме панели'} def android_status(self, db: Session) -> dict[str, Any]: dash = self.dashboard(db) return { 'ok': True, 'mode': dash['coinex']['mode'], 'balance': dash['kpi']['balance'], 'pnl': dash['kpi']['realized_pnl'], 'open_positions': dash['kpi']['open_positions'], 'active_strategies': dash['kpi']['active_strategies'], } def _position(self, position: DemoPosition) -> dict[str, Any]: return { 'id': position.id, 'market': position.market, 'side': position.side, 'amount': position.amount, 'avg_entry_price': position.avg_entry_price, 'current_price': position.current_price, 'take_profit': position.take_profit, 'stop_loss': position.stop_loss, 'unrealized_pnl': position.unrealized_pnl, 'unrealized_pnl_pct': position.unrealized_pnl_pct, 'realized_pnl': position.realized_pnl, 'is_open': position.is_open, } def _strategy(self, strategy: WorkerStrategy) -> dict[str, Any]: return { 'id': strategy.id, 'name': strategy.name, 'enabled': strategy.enabled, 'dry_run': strategy.dry_run, 'timeframe': strategy.timeframe, 'markets': strategy.markets, 'last_signal': strategy.last_signal, 'last_message': strategy.last_message, } def _decision(self, decision: AiDecision | None) -> dict[str, Any] | None: if decision is None: return None return { 'market': decision.market, 'action': decision.action, 'score': decision.score, 'confidence': decision.confidence, 'reason': decision.reason, } def _coinex(self, settings: CoinExSettings | None) -> dict[str, Any]: if settings is None: return {'enabled': False, 'mode': 'demo', 'access_id': '', 'secret_masked': ''} return { 'enabled': settings.enabled, 'mode': settings.mode, 'access_id': settings.access_id, 'secret_masked': settings.secret_masked, 'base_url': settings.base_url, 'account_type': settings.account_type, } def _telegram(self, settings: TelegramSettings | None) -> dict[str, Any]: if settings is None: return {'enabled': False, 'chat_id': ''} return {'enabled': settings.enabled, 'chat_id': settings.chat_id, 'message_format': settings.message_format} def _mask(self, value: str) -> str: if len(value) <= 8: return '***' return f'{value[:4]}***{value[-4:]}'