Настройка логирования сделок крипто-бота
Більшість торгових ботів логирует або занадто мало (тільки факт виконання ордера), або в неструктурованому вигляді (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 помилках за період







