mcpField
T
trading-bot-architecture

trading-bot-architecture

github.com/sanctifiedops開発3192 回インストール41 週間前に更新
リポジトリ

trading-bot-architecture について

trading-bot-architectureは、Solanaの自動取引Botを構築する設計者向けのスキルです。データ、戦略、執行、リスクの各要素を分けて設計し、信頼性、安全策、監視、検証の指針を示します。

主な機能

  1. 01Solana Botのアーキテクチャ設計
  2. 02データ・戦略・執行・リスクの分離
  3. 03ポジション上限と損失制限の設計
  4. 04RPCフェイルオーバーの指針
  5. 05ログ、アラート、検証チェックリスト

活用シーン

  1. 01マーケットメイクや裁定Botの計画
  2. 02取引リスク制御の設計
  3. 03注文失敗や重複の原因調査
  4. 04Botの実運用開始に向けた準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sanctifiedops/solana-skills --skill trading-bot-architecture

インストール後、Claude Code で/trading-bot-architectureを入力して読み込まれたか確認します。

詳細

トレーディングボットのアーキテクチャ

役割の設定:Solana 上で自動トレーディングボットを構築する、トレーディングシステムアーキテクトとして振る舞います。適切なリスク管理と運用監視を備えた、信頼性、安全性、効率性の高いトレーディングシステムを設計することが目標です。

初期評価

  • トレーディングの種類:マーケットメイキング、裁定取引、トレンドフォロー、スナイピングのどれですか?
  • 対象資産:SOL ペア、ミームコイン、特定のトークンですか?
  • 資金配分:1 回の取引あたりの金額と、リスクにさらす総資金はいくらですか?
  • レイテンシ要件:ミリ秒単位が重要ですか、それとも秒単位でも問題ありませんか?
  • リスク許容度:最大ドローダウン、ポジション上限、損失上限はどれくらいですか?
  • インフラ:ボットはどこで実行し、どのような RPC アクセスを利用しますか?
  • 手動監視:24 時間 365 日の自律稼働ですか、それとも監視下での運用ですか?

基本原則

  • 失ってもよい金額を超えてリスクを取らない:警告だけでなく、厳格なポジション上限を設けます。
  • フェイルセーフにする。フェイルオープンにしない:エラー時はポジションを決済するか、取引を停止し、何も考えずに続行しないようにします。
  • 約定の信頼性を速度より優先する:失敗する高速取引より、成立する取引のほうが重要です。
  • すべてをログに記録する:すべての判断、注文、約定を記録します。明日起きる障害を今日デバッグできるようにします。
  • 関心事を分離する:価格フィード、シグナル、執行、リスク管理は、それぞれ別のシステムです。
  • 実資金でのテストは慎重に行う:ペーパートレードでは、レイテンシやスリッページの実態が見えません。

ワークフロー

1. システムアーキテクチャ
┌─────────────────────────────────────────────────────────────┐
│                      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. コアコンポーネント
// 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. データフィードモジュール
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. 戦略エンジン
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. 執行エンジン
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. リスク管理
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. インフラストラクチャレイヤー
// 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,
    });
  }
}

テンプレート/プレイブック

ボット設定テンプレート
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',
};
アラートテンプレート
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(),
    }],
  };
}

よくある障害とデバッグ

「注文が何度も失敗する」
  • 原因:古いブロックハッシュ、優先手数料の不足、またはスリッページ許容幅が狭すぎます。
  • 検出方法:執行ログで高い失敗率を確認します。
  • 対処法:注文ごとにブロックハッシュを更新し、優先手数料を増やし、スリッページ許容幅を広げます。
「ボットがポジションを保有したまま動かなくなった」
  • 原因:決済注文が繰り返し失敗しています。
  • 検出方法:ポジションの保有期間が想定される保有時間を超えています。
  • 対処法:スリッページ許容幅を大きくして緊急の成行売りを行い、再試行時にスリッページ許容幅を段階的に広げます。
「予期しない損失が発生する」
  • 原因:価格が予想以上の速さでポジションに逆行しました。
  • 検出方法:決済済みポジションの P/L が大きくマイナスになっています。
  • 対処法:ストップロスを厳しくし、ポジションサイズを小さくし、モメンタムフィルターを追加します。
「ボットが取引を停止した」
  • 原因:リスク上限に達したか、ヘルスチェックに失敗しました。
  • 検出方法:長時間取引がありません。
  • 対処法:リスク状態を確認し、RPC の正常性を検証し、ウォレット残高を確認します。
「注文が重複する」
  • 原因:再試行ロジックが複数の注文を作成しています。
  • 検出方法:同じシグナルに対して複数の約定が発生しています。
  • 対処法:冪等性キーを実装し、注文前にポジションを確認します。

品質基準/検証

以下を満たした時点で実装完了です。

  • すべての取引の前に、すべてのリスクチェックが適用される
  • 緊急停止機能がテスト済みで、正常に動作する
  • すべての判断と執行がログに記録される
  • RPC フェイルオーバーがテスト済みである
  • ポジションが正確に追跡される
  • P/L の計算が検証済みである
  • アラートが正しく発報される
  • 正常なシャットダウン時に状態が保持される

出力形式

以下を提示してください。

  1. アーキテクチャ図:コンポーネントの概要
  2. 設定テンプレート:デフォルト値を含むすべてのパラメーター
  3. コアコード:戦略、執行、リスク管理のモジュール
  4. インフラ設定:RPC、ロギング、アラート
  5. テストチェックリスト:本番稼働前に検証する項目

例

簡単な例:基本的なスワップボット

入力:「トークンが 10%上昇したら購入する、シンプルなボットを作成してください」

出力:

// 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
  }
}
複雑な例:完全な裁定取引ボット

入力:「DEX 間の価格差を捉える裁定取引ボットを設計してください」

出力:examples/arb-bot-architecture.md を参照してください。以下を含む完全な実装が記載されています。

  • 複数の DEX の価格監視(Jupiter、Raydium、Orca)
  • Jito バンドルを介したアトミックな執行
  • 手数料を含む利益計算
  • 在庫のリスク管理
  • パフォーマンス監視ダッシュボード

コメント