Files
trade/app/legacy_bot.py
T

236 строки
9.8 KiB
Python

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', 'ALL_USDT'),
('Trend Follow', '5min', 'ALL_USDT'),
('Volatility Catcher', '1min', 'ALL_USDT'),
]
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
unrealized = sum(item.unrealized_pnl for item in db.query(DemoPosition).filter(DemoPosition.is_open.is_(True)).all())
open_value = sum(item.current_price * item.amount for item in db.query(DemoPosition).filter(DemoPosition.is_open.is_(True)).all())
equity = balance + open_value
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,
'equity': equity,
'realized_pnl': realized,
'unrealized_pnl': unrealized,
'total_pnl': realized + unrealized,
'open_positions_value': open_value,
'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]:
dash = self.dashboard(db)
point = ChartHistoryPoint(
balance=dash['kpi']['equity'],
pnl_total=dash['kpi']['total_pnl'],
open_trades=dash['kpi']['open_positions'],
closed_trades=dash['kpi']['closed_positions'],
)
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'],
'equity': dash['kpi']['equity'],
'pnl': dash['kpi']['total_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:]}'