Crypto Bot Trade Logging Setup
Most trading bots log either too little (just execution fact) or in unstructured way (console.log to file). This creates problems debugging drawdowns, calculating real P&L accounting for fees, and tax reporting. Correct logging — structured events with enough context for full audit.
What to Log
Minimal event set:
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; };
Each event should contain strategy (strategy name), exchange, sessionId (bot run ID) — this allows filtering logs by specific run.
Structured Logging: pino or winston
Don't write to file as raw text. JSON logs are parsed by tools, plain text — not:
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" } // readable output in dev
: undefined, // JSON in production
});
// Usage
logger.info({ type: "order_filled", orderId, executedQty, executedPrice, fee }, "Order filled");
logger.error({ type: "error", code: "EXCHANGE_TIMEOUT", context: { orderId } }, "Exchange request timeout");
Storage: PostgreSQL + TimescaleDB
For serious analysis logs should go to database, not just file:
-- Main events table
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 hypertable for efficient time-range queries
SELECT create_hypertable('trade_events', 'ts');
CREATE INDEX ON trade_events (session_id, ts DESC);
CREATE INDEX ON trade_events USING GIN (payload);
With TimescaleDB query "all trades of strategy X for last week" executes in milliseconds even on millions of rows.
P&L Calculation from Logs
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;
Anomaly Alerts
Logging useless without reacting to problems. Simple monitoring via periodic query:
// Run every 5 minutes
async function checkAnomalies() {
// Many errors in a row — probably exchange API problem
const recentErrors = await db.countEvents({
type: "error",
since: minutesAgo(5),
});
if (recentErrors > 10) await alertService.send("High error rate: " + recentErrors + " errors in 5m");
// No filled orders for long time — bot stuck?
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");
}
}
What We Do in 1 Day
- Replace console.log with pino/winston in JSON format
- Add
sessionId,strategy,exchangeto all events -
trade_eventstable in PostgreSQL with indexes - Async writer (buffered batched insert, doesn't block trading loop)
- Basic SQL query P&L by days
- Telegram alert on >N errors per period







