Настройка автоматического перезапуска торгового бота
Торговый бот должен работать 24/7. Процесс падает — из-за сетевой ошибки, OOM, необработанного исключения или просто системного перезапуска. Без автоматического перезапуска бот лежит до тех пор, пока кто-то не заметит. Правильная настройка — это не просто "restart always", это ещё и проверка состояния, graceful shutdown и алерты.
systemd: production-стандарт на Linux
systemd — правильный инструмент для управления долгоживущими сервисами на Linux. Поддерживает автоматический перезапуск, зависимости между сервисами, лимиты ресурсов, ротацию логов.
# /etc/systemd/system/trading-bot.service
[Unit]
Description=Trading Bot
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=botuser
WorkingDirectory=/opt/trading-bot
ExecStart=/opt/trading-bot/venv/bin/python -u bot.py
Restart=always
# Ждать 10 секунд перед перезапуском (не спамить при быстрых падениях)
RestartSec=10
# Если бот падает быстрее 5 раз за 60 секунд — остановить (что-то серьёзно не так)
StartLimitIntervalSec=60
StartLimitBurst=5
# Переменные окружения
EnvironmentFile=/opt/trading-bot/.env
# Лимиты ресурсов
MemoryLimit=2G
CPUQuota=80%
# Логирование через journald
StandardOutput=journal
StandardError=journal
SyslogIdentifier=trading-bot
[Install]
WantedBy=multi-user.target
Активация и управление:
systemctl daemon-reload
systemctl enable trading-bot # автозапуск при старте системы
systemctl start trading-bot
systemctl status trading-bot
journalctl -u trading-bot -f # live логи
StartLimitBurst=5 + StartLimitIntervalSec=60 — важная защита. Без неё бот в crash loop будет непрерывно перезапускаться, накапливая ошибки (открытые позиции, дублирующиеся ордера). После 5 быстрых падений systemd остановит службу и отправит алерт (если настроен).
Graceful shutdown: корректное завершение
Бот нельзя убивать SIGKILL — можно оставить открытые ордера, незафиксированные позиции, неотправленные алерты. Обрабатываем SIGTERM:
import signal
import asyncio
class TradingBot:
def __init__(self):
self.running = True
self.open_orders: list = []
async def shutdown(self):
self.running = False
# Отменяем все открытые лимитные ордера
for order_id in self.open_orders:
try:
await self.exchange.cancel_order(order_id)
except Exception as e:
logger.error(f"Failed to cancel order {order_id}: {e}")
logger.info("Graceful shutdown complete")
async def run(self):
loop = asyncio.get_event_loop()
loop.add_signal_handler(
signal.SIGTERM,
lambda: asyncio.create_task(self.shutdown())
)
while self.running:
try:
await self.main_loop()
except Exception as e:
logger.exception(f"Error in main loop: {e}")
await asyncio.sleep(5) # backoff перед следующей итерацией
systemd при systemctl stop шлёт SIGTERM, затем через TimeoutStopSec (default 90 сек) — SIGKILL. Для бота с позициями 90 секунд обычно достаточно.
Docker: альтернатива для контейнеризированных ботов
Если бот запускается в Docker:
# docker-compose.yml
services:
trading-bot:
image: trading-bot:latest
restart: unless-stopped # перезапускать всегда, кроме явной остановки
env_file: .env
volumes:
- ./data:/app/data # персистентное хранение состояния
- ./logs:/app/logs
mem_limit: 2g
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
healthcheck:
test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8080/health', timeout=5)"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
restart: unless-stopped — перезапускает при падении и при перезагрузке хоста, но не перезапускает если остановлен вручную через docker-compose stop.
Health check — критически важен. Docker перезапускает контейнер только если он полностью упал. Если бот завис (hanging без ошибки) — Docker этого не заметит. Health check endpoint должен проверять не просто "процесс жив", а реальное состояние:
from aiohttp import web
async def health_check(request):
# Проверяем что последний цикл был не больше 5 минут назад
last_loop_age = time.time() - bot.last_loop_time
if last_loop_age > 300: # 5 минут
return web.Response(status=503, text=f"Bot stuck: last loop {last_loop_age:.0f}s ago")
# Проверяем подключение к бирже
if not bot.exchange_connected:
return web.Response(status=503, text="Exchange disconnected")
return web.Response(status=200, text="OK")
app = web.Application()
app.router.add_get('/health', health_check)
Алерты при падении
Сам факт перезапуска должен генерировать уведомление — даже если бот восстановился автоматически.
systemd → Telegram:
# /etc/systemd/system/trading-bot-notify.service
[Unit]
Description=Notify on trading bot failure
[Service]
Type=oneshot
ExecStart=/opt/scripts/notify-telegram.sh "Trading bot restarted on $(hostname)"
# В trading-bot.service добавить:
OnFailure=trading-bot-notify.service
Prometheus Alertmanager для более сложных сценариев:
# alert rule
- alert: TradingBotRestarted
expr: changes(process_start_time_seconds{job="trading-bot"}[5m]) > 0
annotations:
summary: "Trading bot restarted {{ $value }} times in last 5 minutes"
Разумный минимум: Telegram-уведомление при каждом (пере)запуске с именем хоста и временем. Достаточно для 95% случаев.







