Crypto Bot Trade Logging Setup

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Crypto Bot Trade Logging Setup
Simple
~1 business day
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1252
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    873
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1092
  • image_logo-advance_0.png
    B2B Advance company logo design
    563
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    830

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, exchange to all events
  • trade_events table 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