Настройка логирования сделок крипто-бота
Большинство торговых ботов логируют либо слишком мало (только факт исполнения ордера), либо в неструктурированном виде (console.log в файл). Это создаёт проблемы при отладке просадок, расчёте реальной P&L с учётом fees, и налоговой отчётности. Правильное логирование — это структурированные события с достаточным контекстом для полного аудита.
Что нужно логировать
Минимальный набор событий:
type TradeEvent =
| { type: "order_placed"; orderId: string; symbol: string; side: "buy" | "sell"; quantity: number; price: number; orderType: "market" | "limit"; timestamp: number; }
| { type: "order_filled"; orderId: string; executedQty: number; executedPrice: number; fee: number; feeCurrency: string; timestamp: number; }
| { type: "order_cancelled"; orderId: string; reason: string; timestamp: number; }
| { type: "position_opened"; positionId: string; entryPrice: number; size: number; leverage: number; timestamp: number; }
| { type: "position_closed"; positionId: string; exitPrice: number; realizedPnl: number; timestamp: number; }
| { type: "signal_generated"; strategy: string; signal: string; params: Record<string, unknown>; timestamp: number; }
| { type: "error"; code: string; message: string; context: Record<string, unknown>; timestamp: number; };
Каждое событие должно содержать strategy (имя стратегии), exchange, sessionId (ID запуска бота) — это позволяет фильтровать логи по конкретному прогону.
Структурированное логирование: pino или winston
Не пишите в файл сырым текстом. JSON-логи парсятся инструментами, plain text — нет:
import pino from "pino";
const logger = pino({
level: process.env.LOG_LEVEL ?? "info",
base: {
strategy: process.env.STRATEGY_NAME,
exchange: process.env.EXCHANGE,
sessionId: process.env.SESSION_ID ?? Date.now().toString(36),
},
transport:
process.env.NODE_ENV === "development"
? { target: "pino-pretty" } // читаемый вывод в dev
: undefined, // JSON в production
});
// Использование
logger.info({ type: "order_filled", orderId, executedQty, executedPrice, fee }, "Order filled");
logger.error({ type: "error", code: "EXCHANGE_TIMEOUT", context: { orderId } }, "Exchange request timeout");
Хранение: PostgreSQL + TimescaleDB
Для серьёзного анализа логи нужно писать в базу, а не только в файл:
-- Основная таблица событий
CREATE TABLE trade_events (
id BIGSERIAL,
session_id TEXT NOT NULL,
strategy TEXT NOT NULL,
exchange TEXT NOT NULL,
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
ts TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (id, ts)
);
-- TimescaleDB гипертаблица для эффективных time-range запросов
SELECT create_hypertable('trade_events', 'ts');
CREATE INDEX ON trade_events (session_id, ts DESC);
CREATE INDEX ON trade_events USING GIN (payload);
С TimescaleDB запрос "все сделки стратегии X за прошлую неделю" выполняется за миллисекунды даже на миллионах строк.
P&L расчёт из логов
SELECT
strategy,
date_trunc('day', ts) AS day,
COUNT(*) FILTER (WHERE event_type = 'order_filled') AS trades,
SUM((payload->>'executedQty')::numeric * (payload->>'executedPrice')::numeric)
FILTER (WHERE payload->>'side' = 'sell') AS gross_revenue,
SUM((payload->>'fee')::numeric) FILTER (WHERE event_type = 'order_filled') AS total_fees,
SUM((payload->>'realizedPnl')::numeric) FILTER (WHERE event_type = 'position_closed') AS realized_pnl
FROM trade_events
WHERE ts > now() - interval '30 days'
GROUP BY strategy, day
ORDER BY day DESC;
Алерты на аномалии
Логирование бесполезно без реакции на проблемы. Простой мониторинг через периодический запрос:
// Запускать каждые 5 минут
async function checkAnomalies() {
// Много ошибок подряд — вероятно, проблема с exchange API
const recentErrors = await db.countEvents({
type: "error",
since: minutesAgo(5),
});
if (recentErrors > 10) await alertService.send("High error rate: " + recentErrors + " errors in 5m");
// Нет заполненных ордеров за долгое время — бот завис?
const lastFill = await db.lastEventTime({ type: "order_filled" });
if (minutesSince(lastFill) > 60 && isMarketHours()) {
await alertService.send("No fills in 60 minutes — bot may be stuck");
}
}
Что делаем за 1 день
- Замена console.log на pino/winston с JSON-форматом
- Добавление
sessionId,strategy,exchangeво все события - Таблица
trade_eventsв PostgreSQL с индексами - Async writer (буферизованная вставка батчами, не блокирует торговый loop)
- Базовый SQL-запрос P&L по дням
- Алерт в Telegram при >N ошибках за период







