trading-bot-architecture について
trading-bot-architectureは、Solanaの自動取引Botを構築する設計者向けのスキルです。データ、戦略、執行、リスクの各要素を分けて設計し、信頼性、安全策、監視、検証の指針を示します。
主な機能
- 01Solana Botのアーキテクチャ設計
- 02データ・戦略・執行・リスクの分離
- 03ポジション上限と損失制限の設計
- 04RPCフェイルオーバーの指針
- 05ログ、アラート、検証チェックリスト
活用シーン
- 01マーケットメイクや裁定Botの計画
- 02取引リスク制御の設計
- 03注文失敗や重複の原因調査
- 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 の計算が検証済みである
- アラートが正しく発報される
- 正常なシャットダウン時に状態が保持される
出力形式
以下を提示してください。
- アーキテクチャ図:コンポーネントの概要
- 設定テンプレート:デフォルト値を含むすべてのパラメーター
- コアコード:戦略、執行、リスク管理のモジュール
- インフラ設定:RPC、ロギング、アラート
- テストチェックリスト:本番稼働前に検証する項目
例
簡単な例:基本的なスワップボット
入力:「トークンが 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 バンドルを介したアトミックな執行
- 手数料を含む利益計算
- 在庫のリスク管理
- パフォーマンス監視ダッシュボード
コメント
Markdown 対応