Skip to main content
The Autonome trading system orchestrates AI-driven cryptocurrency trading through a modular architecture that handles decision-making, order execution, position tracking, and risk management.

Architecture

The trading system is built on a single source of truth pattern where the Orders table in PostgreSQL serves as the canonical state for all positions and trades.

Orders Table

  • OPEN status = active positions
  • CLOSED status = completed trades
  • Stores entry/exit prices, P&L, and exit plans

Dual Mode

  • Live Trading via Lighter exchange API
  • Simulation with order book matching engine
  • Toggle via IS_SIMULATION_ENABLED environment variable

Core Components

Trade Executor

Orchestrates the complete trading workflow for each AI model.
export async function runTradeWorkflow(account: Account) {
  const queryClient = new QueryClient();

  // Fetch initial data in parallel
  const [portfolio, openPositionsRaw, decisionIndex] = await Promise.all([
    queryClient.fetchQuery(portfolioQuery(account)),
    queryClient.fetchQuery(openPositionsQuery(account)),
    account.id
      ? fetchLatestDecisionIndex(account.id)
      : Promise.resolve(new Map<string, TradingDecisionWithContext>()),
  ]);

  const openPositions = enrichOpenPositions(openPositionsRaw, decisionIndex);
  const exposureSummary = summarizePositionRisk(openPositions);

  // Create invocation record and track it
  const modelInvocation = await createInvocationMutation(account.id);

  // Calculate performance metrics
  const currentPortfolioValue = parseFloat(portfolio.total);
  const performanceMetrics = await calculatePerformanceMetrics(
    account,
    currentPortfolioValue,
  );

  // Build prompts with fresh data
  const enrichedPrompt = buildTradingPrompts({
    account,
    portfolio,
    openPositions,
    exposureSummary,
    performanceMetrics,
    marketIntelligence,
    currentTime,
    variant: variantId,
    symbolActionCounts,
    competition: competitionSnapshot,
  });

  // Create and execute the agent
  const { agent } = createTradeAgent({
    account,
    systemPrompt: enrichedPrompt.systemPrompt,
    toolContext,
    onStepTelemetry: (telemetry) => capturedStepTelemetry.push(telemetry),
    rebuildUserPrompt,
  });

  const result = await executeWithRetry();

  // Update invocation with results
  await updateInvocationMutation({
    id: modelInvocation.id,
    response: responseText,
    responsePayload,
  });

  return responseText;
}
Location: src/server/features/trading/tradeExecutor.ts:82 Key Responsibilities:
  • Fetch portfolio, positions, and market data
  • Build AI prompts with current state
  • Execute agent with tool calls (create/close/hold positions)
  • Track telemetry and update database
  • Emit SSE events for real-time UI updates
Execution Flow:
  1. Query current portfolio and open positions
  2. Calculate performance metrics (Sharpe, win rate, drawdown)
  3. Fetch shared market intelligence (cached across models)
  4. Build variant-specific prompts
  5. Execute AI agent with retry logic (2 retries, exponential backoff)
  6. Process tool calls and capture results
  7. Persist invocation and emit events

Scheduler

Runs trading workflows on a 5-minute interval for all active models.
const TRADE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
const AGENT_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes per agent
const MODEL_RUN_TIMEOUT_MS = 9 * 60 * 1000; // 9 minutes per model
Location: src/server/features/trading/tradeExecutor.ts:376 Execution Strategy:
  • Models run in parallel (non-blocking)
  • Per-model timeout ensures no model blocks others
  • Stale detection clears models stuck >10 minutes
  • Market intelligence cache invalidated after batch completion

Database Schema

The Orders table is the single source of truth for all trading state.
/**
 * Orders table - single source of truth for positions
 *
 * OPEN orders = active positions (shown in Positions tab)
 * CLOSED orders = completed trades (shown in Trades tab)
 *
 * Unrealized P&L is calculated live from current prices, not stored.
 * When an order is closed, exitPrice and realizedPnl are populated.
 */
export const orders = pgTable(
  "Orders",
  {
    id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
    modelId: text("modelId")
      .notNull()
      .references(() => models.id),

    // Position details
    symbol: text("symbol").notNull(),
    side: orderSideEnum("side").notNull(), // LONG | SHORT
    quantity: numeric("quantity", { precision: 18, scale: 8 }).notNull(),
    leverage: numeric("leverage", { precision: 10, scale: 2 }),

    // Entry details
    entryPrice: numeric("entryPrice", { precision: 18, scale: 8 }).notNull(),

    // Exit plan (stop-loss, take-profit, confidence in the plan)
    exitPlan: jsonb("exitPlan").$type<{
      stop: number | null;
      target: number | null;
      invalidation: string | null;
      invalidationPrice: number | null;
      confidence: number | null;
      timeExit: string | null;
      cooldownUntil: string | null;
    }>(),

    // Status: OPEN = active position, CLOSED = completed trade
    status: orderStatusEnum("status").notNull().default("OPEN"),

    // Exit details (populated when closed)
    exitPrice: numeric("exitPrice", { precision: 18, scale: 8 }),
    realizedPnl: numeric("realizedPnl", { precision: 18, scale: 2 }),
    closeTrigger: text("closeTrigger"), // null | "STOP" | "TARGET"

    // Lighter exchange order indices for real SL/TP orders
    slOrderIndex: text("slOrderIndex"),
    tpOrderIndex: text("tpOrderIndex"),
    slTriggerPrice: numeric("slTriggerPrice", { precision: 18, scale: 8 }),
    tpTriggerPrice: numeric("tpTriggerPrice", { precision: 18, scale: 8 }),

    // Timestamps
    openedAt: timestamp("openedAt").defaultNow().notNull(),
    closedAt: timestamp("closedAt"),
    updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  },
);
Location: src/db/schema.ts:125 Key Fields:
  • status: OPEN (active position) or CLOSED (completed trade)
  • exitPlan: JSONB containing stop-loss, take-profit, invalidation conditions
  • slOrderIndex/tpOrderIndex: References to live SL/TP orders on exchange
  • closeTrigger: Tracks whether position was closed manually or via SL/TP
Design Principles:
  • Derived values not stored: entryNotional and exitNotional calculated on-the-fly
  • Unrealized P&L computed live: Uses current market prices
  • Confidence stored in exitPlan: Represents AI’s confidence in the exit strategy

Data Flow

Execution Modes

Live Trading

Directly interacts with the Lighter exchange via SignerClient.
1

Order Placement

Create market order with IOC (Immediate-Or-Cancel) time-in-force
2

Fill Tracking

Poll waitForTransaction() and checkOrderStatus() to verify execution
3

SL/TP Placement

Place real stop-loss and take-profit orders on the exchange
4

Database Sync

Persist position to Orders table with fill details

Simulation

Uses an in-memory order book matching engine.
1

Order Matching

Match orders against cached order book snapshots
2

Position Tracking

Maintain position state in AccountState (in-memory)
3

Auto-Close Triggers

Poll positions every refresh interval and trigger SL/TP
4

Database Sync

Persist simulated trades to Orders table for consistency
Bootstrap Process: The simulator restores OPEN positions from the database on startup, ensuring auto-close triggers work across server restarts.
private async restorePositionsFromDb() {
  const openOrders = await getAllOpenOrders();

  for (const order of openOrders) {
    const account = this.getOrCreateAccount(order.modelId);
    const symbol = normalizeSymbol(order.symbol);
    const market = this.markets.get(symbol);
    const markPrice = market?.getMidPrice() ?? parseFloat(order.entryPrice);

    // Restore the position via a synthetic execution
    const quantity = parseFloat(order.quantity);
    const entryPrice = parseFloat(order.entryPrice);
    const leverage = order.leverage ? parseFloat(order.leverage) : 1;
    const side = order.side === "LONG" ? "buy" : "sell";

    account.applyExecution(
      symbol,
      side,
      {
        fills: [{ quantity, price: entryPrice }],
        averagePrice: entryPrice,
        totalQuantity: quantity,
        totalFees: 0,
        status: "filled",
      },
      leverage,
    );

    // Set exit plan if present
    if (order.exitPlan) {
      account.setExitPlan(symbol, order.exitPlan);
    }

    // Update mark price
    account.updateMarkPrice(symbol, markPrice);
  }
}
Location: src/server/features/simulator/exchangeSimulator.ts:203

Key Files

FilePurpose
tradeExecutor.tsMain workflow orchestration and scheduler
order.tsOrder creation and fill tracking
fillTracker.tsTransaction polling and fill verification
slTpOrderManager.tsSL/TP order placement on exchange
createPosition.tsPosition opening logic (live + sim)
closePosition.tsPosition closure logic (live + sim)
exchangeSimulator.tsSimulation engine and order matching
orderMatching.tsOrder book matching algorithm
accountState.tsIn-memory position and P&L tracking

Environment Variables

# Trading mode
IS_SIMULATION_ENABLED=true  # Toggle simulation vs live trading

# Lighter API
BASE_URL=https://lighter.xyz/api
API_KEY_INDEX=0

# Simulator settings
DEFAULT_SIMULATOR_OPTIONS={
  "initialCapital": 10000,
  "takerFeeRate": 0.001,
  "makerFeeRate": 0.0005,
  "refreshIntervalMs": 5000
}

Next Steps

Order Execution

Learn how orders are placed and filled

Position Management

Understand position tracking and exit plans

Risk Controls

Explore risk management mechanisms

AI Strategies

Explore the AI trading strategy variants

Build docs developers (and LLMs) love