skills/sanctifiedops/solana-skills/trading-bot-architecture

trading-bot-architecture

SKILL.md

Trading Bot Architecture

Role framing: You are a trading systems architect building automated trading bots on Solana. Your goal is to design reliable, safe, and efficient trading systems with proper risk controls and operational monitoring.

Initial Assessment

  • What type of trading: market making, arbitrage, trend following, sniping?
  • Target assets: SOL pairs, memecoins, specific tokens?
  • Capital allocation: how much per trade, total capital at risk?
  • Latency requirements: milliseconds matter or seconds acceptable?
  • Risk tolerance: max drawdown, position limits, loss limits?
  • Infrastructure: where will bot run, what RPC access?
  • Manual oversight: 24/7 autonomous or supervised?

Core Principles

  • Never risk more than you can lose: Hard position limits, not just soft warnings.
  • Fail safe, not fail open: On error, close positions or stop trading, don't continue blindly.
  • Execution reliability > speed: A trade that lands beats a fast trade that fails.
  • Log everything: Every decision, every order, every fill. Debug tomorrow's disaster today.
  • Separate concerns: Price feeds, signals, execution, and risk are different systems.
  • Test with real money carefully: Paper trading hides latency and slippage reality.

Workflow

1. System Architecture

┌─────────────────────────────────────────────────────────────┐
│                      Trading Bot                             │
├──────────────┬──────────────┬──────────────┬────────────────┤
│  Data Feed   │   Strategy   │  Execution   │     Risk       │
│   Module     │    Engine    │   Engine     │   Manager      │
├──────────────┼──────────────┼──────────────┼────────────────┤
│ - Prices     │ - Signals    │ - Order mgmt │ - Position lim │
│ - Orderbook  │ - Entry/exit │ - Jito/RPC   │ - Loss limits  │
│ - On-chain   │ - Sizing     │ - Retries    │ - Drawdown     │
│ - Socials    │ - Filters    │ - Status     │ - Kill switch  │
└──────────────┴──────────────┴──────────────┴────────────────┘
        │               │              │              │
        └───────────────┴──────────────┴──────────────┘
                     ┌─────────┴─────────┐
                     │   Infrastructure  │
                     ├───────────────────┤
                     │ - RPC connections │
                     │ - Wallet mgmt     │
                     │ - Logging         │
                     │ - Alerting        │
                     └───────────────────┘

2. Core Components

// Main bot structure
interface TradingBot {
  // Configuration
  config: BotConfig;

  // Core modules
  dataFeed: DataFeedModule;
  strategy: StrategyEngine;
  execution: ExecutionEngine;
  risk: RiskManager;

  // State
  positions: Map<string, Position>;
  openOrders: Map<string, Order>;
  status: BotStatus;

  // Lifecycle
  start(): Promise<void>;
  stop(): Promise<void>;
  pause(): void;
  resume(): void;
}

interface BotConfig {
  // Trading parameters
  tradingPairs: string[];
  maxPositionSize: number;      // Per token
  maxTotalExposure: number;     // Total USD
  maxConcurrentPositions: number;

  // Risk limits
  maxLossPerTrade: number;      // USD
  maxDailyLoss: number;         // USD
  maxDrawdownPercent: number;   // % of capital

  // Execution
  defaultSlippageBps: number;
  maxSlippageBps: number;
  priorityFeeMicroLamports: number;
  useJito: boolean;

  // Infrastructure
  rpcEndpoints: string[];
  wsEndpoints: string[];
  jitoEndpoint?: string;
}

3. Data Feed Module

interface DataFeedModule {
  // Price data
  getPrice(mint: string): Promise<PriceData>;
  subscribePrices(mints: string[], callback: PriceCallback): Subscription;

  // On-chain data
  getTokenAccount(mint: string, owner: string): Promise<TokenAccountData>;
  getPoolState(poolAddress: string): Promise<PoolState>;

  // Health
  isHealthy(): boolean;
  getLatency(): number;
}

class JupiterPriceFeed implements DataFeedModule {
  private priceCache: Map<string, { price: number; timestamp: number }>;
  private cacheTTL = 1000; // 1 second

  async getPrice(mint: string): Promise<PriceData> {
    // Check cache
    const cached = this.priceCache.get(mint);
    if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
      return { price: cached.price, source: 'cache' };
    }

    // Fetch from Jupiter
    const response = await fetch(
      `https://price.jup.ag/v6/price?ids=${mint}`
    );
    const data = await response.json();

    // Update cache
    this.priceCache.set(mint, {
      price: data.data[mint].price,
      timestamp: Date.now(),
    });

    return { price: data.data[mint].price, source: 'jupiter' };
  }
}

4. Strategy Engine

interface StrategyEngine {
  // Generate signals
  evaluate(data: MarketData): Signal[];

  // Position sizing
  calculateSize(signal: Signal, risk: RiskState): number;

  // Entry/exit logic
  shouldEnter(signal: Signal): boolean;
  shouldExit(position: Position, data: MarketData): boolean;
}

interface Signal {
  type: 'LONG' | 'SHORT' | 'EXIT';
  token: string;
  confidence: number;   // 0-1
  reason: string;
  metadata: any;
}

// Example: Simple momentum strategy
class MomentumStrategy implements StrategyEngine {
  private readonly minConfidence = 0.6;
  private readonly lookbackPeriod = 60; // seconds

  evaluate(data: MarketData): Signal[] {
    const signals: Signal[] = [];

    for (const token of data.watchlist) {
      const priceChange = this.calculatePriceChange(token, this.lookbackPeriod);
      const volumeSpike = this.detectVolumeSpike(token);

      if (priceChange > 5 && volumeSpike) {
        signals.push({
          type: 'LONG',
          token,
          confidence: Math.min(priceChange / 10, 1),
          reason: `${priceChange.toFixed(1)}% gain with volume spike`,
          metadata: { priceChange, volumeSpike },
        });
      }
    }

    return signals;
  }

  calculateSize(signal: Signal, risk: RiskState): number {
    // Base size from config
    let size = risk.maxPositionSize;

    // Scale by confidence
    size *= signal.confidence;

    // Reduce if approaching limits
    const utilizationRatio = risk.currentExposure / risk.maxExposure;
    if (utilizationRatio > 0.8) {
      size *= (1 - utilizationRatio);
    }

    return Math.floor(size);
  }
}

5. Execution Engine

interface ExecutionEngine {
  // Order management
  submitOrder(order: Order): Promise<OrderResult>;
  cancelOrder(orderId: string): Promise<boolean>;
  getOrderStatus(orderId: string): Promise<OrderStatus>;

  // Position management
  openPosition(params: OpenPositionParams): Promise<Position>;
  closePosition(positionId: string): Promise<CloseResult>;
}

class JupiterExecutionEngine implements ExecutionEngine {
  private connection: Connection;
  private wallet: Keypair;

  async submitOrder(order: Order): Promise<OrderResult> {
    const startTime = Date.now();

    try {
      // Get quote
      const quote = await this.getQuote(order);

      // Validate quote
      if (quote.priceImpactPct > order.maxPriceImpact) {
        return {
          success: false,
          error: 'Price impact too high',
          priceImpact: quote.priceImpactPct,
        };
      }

      // Get swap transaction
      const swapTx = await this.getSwapTransaction(quote, order);

      // Execute with retry
      const result = await this.executeWithRetry(swapTx, order.maxRetries);

      return {
        success: result.success,
        signature: result.signature,
        executionTime: Date.now() - startTime,
        fillPrice: this.calculateFillPrice(result),
        slippage: this.calculateSlippage(quote, result),
      };

    } catch (error) {
      return {
        success: false,
        error: error.message,
        executionTime: Date.now() - startTime,
      };
    }
  }

  private async executeWithRetry(
    swapTx: VersionedTransaction,
    maxRetries: number
  ): Promise<ExecutionResult> {
    let lastError: Error;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        // Increase priority fee on retry
        const priorityFee = this.config.basePriorityFee * Math.pow(1.5, attempt - 1);

        const signature = await this.sendTransaction(swapTx, priorityFee);
        const confirmation = await this.confirmTransaction(signature);

        if (confirmation.success) {
          return { success: true, signature, attempts: attempt };
        }

      } catch (error) {
        lastError = error;

        // Don't retry on certain errors
        if (this.isFatalError(error)) {
          break;
        }

        await this.sleep(100 * attempt);
      }
    }

    return { success: false, error: lastError, attempts: maxRetries };
  }
}

6. Risk Manager

interface RiskManager {
  // Pre-trade checks
  canTrade(order: Order): RiskCheckResult;

  // Position monitoring
  checkPositions(): RiskAlert[];

  // Kill switch
  emergencyStop(reason: string): Promise<void>;

  // State
  getRiskState(): RiskState;
}

class DefaultRiskManager implements RiskManager {
  private state: RiskState;
  private alerts: RiskAlert[] = [];

  canTrade(order: Order): RiskCheckResult {
    const checks: RiskCheck[] = [];

    // Check 1: Position size limit
    checks.push({
      name: 'positionSize',
      passed: order.size <= this.config.maxPositionSize,
      message: `Size ${order.size} vs limit ${this.config.maxPositionSize}`,
    });

    // Check 2: Total exposure limit
    const newExposure = this.state.currentExposure + order.size * order.price;
    checks.push({
      name: 'totalExposure',
      passed: newExposure <= this.config.maxTotalExposure,
      message: `New exposure ${newExposure} vs limit ${this.config.maxTotalExposure}`,
    });

    // Check 3: Concurrent positions
    checks.push({
      name: 'concurrentPositions',
      passed: this.state.openPositions < this.config.maxConcurrentPositions,
      message: `Positions ${this.state.openPositions} vs limit ${this.config.maxConcurrentPositions}`,
    });

    // Check 4: Daily loss limit
    checks.push({
      name: 'dailyLoss',
      passed: Math.abs(this.state.dailyPnL) < this.config.maxDailyLoss,
      message: `Daily P/L ${this.state.dailyPnL} vs limit ${this.config.maxDailyLoss}`,
    });

    // Check 5: Drawdown limit
    const currentDrawdown = (this.state.peakCapital - this.state.currentCapital) / this.state.peakCapital;
    checks.push({
      name: 'drawdown',
      passed: currentDrawdown < this.config.maxDrawdownPercent,
      message: `Drawdown ${(currentDrawdown * 100).toFixed(1)}% vs limit ${this.config.maxDrawdownPercent * 100}%`,
    });

    const allPassed = checks.every(c => c.passed);

    return {
      allowed: allPassed,
      checks,
      reason: allPassed ? null : checks.find(c => !c.passed)?.message,
    };
  }

  async emergencyStop(reason: string): Promise<void> {
    console.error(`🚨 EMERGENCY STOP: ${reason}`);

    // Stop all new orders
    this.state.tradingEnabled = false;

    // Cancel all open orders
    await this.cancelAllOrders();

    // Close all positions (market orders)
    await this.closeAllPositions();

    // Alert
    await this.sendAlert({
      level: 'CRITICAL',
      message: `Bot emergency stopped: ${reason}`,
      timestamp: new Date(),
    });
  }
}

7. Infrastructure Layer

// Multi-RPC with failover
class RPCManager {
  private endpoints: RPCEndpoint[];
  private activeIndex: number = 0;
  private healthChecks: Map<string, HealthStatus> = new Map();

  async getConnection(): Promise<Connection> {
    // Try active endpoint
    const active = this.endpoints[this.activeIndex];
    if (this.isHealthy(active)) {
      return new Connection(active.url);
    }

    // Failover to next healthy endpoint
    for (let i = 0; i < this.endpoints.length; i++) {
      const endpoint = this.endpoints[i];
      if (this.isHealthy(endpoint)) {
        this.activeIndex = i;
        return new Connection(endpoint.url);
      }
    }

    throw new Error('No healthy RPC endpoints available');
  }

  private async checkHealth(endpoint: RPCEndpoint): Promise<boolean> {
    try {
      const conn = new Connection(endpoint.url);
      const start = Date.now();
      await conn.getSlot();
      const latency = Date.now() - start;

      return latency < endpoint.maxLatency;
    } catch {
      return false;
    }
  }
}

// Structured logging
class BotLogger {
  log(level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR', event: string, data: any) {
    const entry = {
      timestamp: new Date().toISOString(),
      level,
      event,
      ...data,
    };

    console.log(JSON.stringify(entry));

    // Also send to external logging service
    this.sendToLoggingService(entry);
  }

  trade(action: 'OPEN' | 'CLOSE', position: Position, result: TradeResult) {
    this.log('INFO', 'TRADE', {
      action,
      token: position.token,
      side: position.side,
      size: position.size,
      entryPrice: position.entryPrice,
      exitPrice: result.price,
      pnl: result.pnl,
      pnlPercent: result.pnlPercent,
      executionTime: result.executionTime,
      signature: result.signature,
    });
  }
}

Templates / Playbooks

Bot Configuration Template

const botConfig: BotConfig = {
  // Identity
  name: 'solana-momentum-bot',
  version: '1.0.0',

  // Trading parameters
  tradingPairs: ['SOL/USDC', 'BONK/SOL'],
  maxPositionSize: 1000,        // $1000 per position
  maxTotalExposure: 5000,       // $5000 total
  maxConcurrentPositions: 3,

  // Risk limits
  maxLossPerTrade: 100,         // $100 max loss per trade
  maxDailyLoss: 500,            // $500 max daily loss
  maxDrawdownPercent: 0.15,     // 15% max drawdown

  // Execution
  defaultSlippageBps: 100,      // 1% default slippage
  maxSlippageBps: 300,          // 3% max slippage
  priorityFeeMicroLamports: 10000,
  useJito: true,
  jitoTipLamports: 50000,       // 0.00005 SOL

  // Infrastructure
  rpcEndpoints: [
    'https://api.mainnet-beta.solana.com',
    'https://your-helius-endpoint.com',
  ],
  jitoEndpoint: 'https://mainnet.block-engine.jito.wtf',

  // Monitoring
  healthCheckInterval: 10000,   // 10 seconds
  alertWebhook: 'https://your-discord-webhook',
};

Alert Template

interface Alert {
  level: 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL';
  event: string;
  message: string;
  data?: any;
}

// Discord webhook format
function formatDiscordAlert(alert: Alert): object {
  const colors = {
    INFO: 0x00ff00,
    WARN: 0xffff00,
    ERROR: 0xff8800,
    CRITICAL: 0xff0000,
  };

  return {
    embeds: [{
      title: `[${alert.level}] ${alert.event}`,
      description: alert.message,
      color: colors[alert.level],
      fields: alert.data ? Object.entries(alert.data).map(([k, v]) => ({
        name: k,
        value: String(v),
        inline: true,
      })) : [],
      timestamp: new Date().toISOString(),
    }],
  };
}

Common Failure Modes + Debugging

"Orders keep failing"

  • Cause: Stale blockhash, low priority fee, or slippage too tight
  • Detection: High failure rate in execution logs
  • Fix: Refresh blockhash per order; increase priority fee; widen slippage

"Bot got stuck in position"

  • Cause: Exit order failing repeatedly
  • Detection: Position age exceeds expected hold time
  • Fix: Emergency market sell with high slippage; add escalating slippage on retry

"Unexpected losses"

  • Cause: Price moved against position faster than expected
  • Detection: Large negative P/L on closed positions
  • Fix: Tighter stop-losses; reduce position size; add momentum filters

"Bot stopped trading"

  • Cause: Risk limit hit or health check failed
  • Detection: No trades for extended period
  • Fix: Check risk state; verify RPC health; check wallet balance

"Duplicate orders"

  • Cause: Retry logic creating multiple orders
  • Detection: Multiple fills for same signal
  • Fix: Implement idempotency keys; check position before ordering

Quality Bar / Validation

Implementation is complete when:

  • All risk checks enforced before every trade
  • Emergency stop tested and working
  • Logging captures every decision and execution
  • RPC failover tested
  • Position tracking accurate
  • P/L calculations verified
  • Alerts firing correctly
  • Graceful shutdown preserves state

Output Format

Provide:

  1. Architecture diagram: Component overview
  2. Configuration template: All parameters with defaults
  3. Core code: Strategy, execution, and risk modules
  4. Infrastructure setup: RPC, logging, alerting
  5. Testing checklist: What to verify before going live

Examples

Simple Example: Basic Swap Bot

Input: "Create a simple bot that buys tokens when they pump 10%"

Output:

// Minimal viable trading bot
class SimplePumpBot {
  private positions: Map<string, Position> = new Map();
  private maxPositionUsd = 100;
  private minPumpPercent = 10;

  async run() {
    while (true) {
      // Check watchlist
      for (const token of this.watchlist) {
        const priceChange = await this.getPriceChange(token, '5m');

        // Entry signal
        if (priceChange >= this.minPumpPercent && !this.positions.has(token)) {
          await this.buy(token, this.maxPositionUsd);
        }

        // Exit signal (take profit or stop loss)
        if (this.positions.has(token)) {
          const position = this.positions.get(token);
          const pnl = await this.calculatePnL(position);

          if (pnl >= 20 || pnl <= -10) { // 20% TP or 10% SL
            await this.sell(token);
          }
        }
      }

      await this.sleep(5000); // Check every 5 seconds
    }
  }

  private async buy(token: string, usdAmount: number) {
    console.log(`BUY ${token} for $${usdAmount}`);
    // ... Jupiter swap implementation
  }

  private async sell(token: string) {
    console.log(`SELL ${token}`);
    // ... Jupiter swap implementation
  }
}

Complex Example: Full Arbitrage Bot

Input: "Design an arb bot that captures price discrepancies across DEXs"

Output: See examples/arb-bot-architecture.md for complete implementation including:

  • Multi-DEX price monitoring (Jupiter, Raydium, Orca)
  • Atomic execution via Jito bundles
  • Profit calculation with fees
  • Risk management for inventory
  • Performance monitoring dashboard
Weekly Installs
3
First Seen
2 days ago
Installed on
opencode3
claude-code3
cursor2
codex2
antigravity2
gemini-cli2