暗号資産取引システムにおいて、Binanceの現物市場(スポット)と証拠金取引市場(先物・フューチャーズ)のAPIは、表面上は似ているように見えても、実際にはNumerousなデータ構造と処理ロジックの差異が存在します。この差異を正確に處理しないと、裁定取引Botやポートフォリオ管理システムで致命的な計算誤差が発生します。

本稿では、10年以上の金融API統合経験を基に、両APIの差異を体系的に整理し、パフォーマンスと正確性を両立させた本番レベルの处理アーキテクチャを提案します。

Binance Spot API と Futures API の根本的差異

両APIの設計思想は大きく異なります。スポット市場が「現在时刻の取引」にフォーカスしているのに対し、先物市場は「将来の決済」を前提とした設計になっています。この思想の違いが、データ構造全体に波及します。

項目Binance Spot APIBinance Futures API実務上の影響
ベースエンドポイントapi.binance.comfapi.binance.com接続先切り替え必須
symbol命名規則BTCUSDTBTCUSDT同一だがエンドポイント別
tickSize精度ティッカー依存ティッカー依存(8小数対応)价格計算の丸め処理要注意
maker/taker手数料0.1%/0.1%0.02%/0.04%裁定取引利益計算に影響
残高取得/api/v3/account/fapi/v2/accountレスポンス構造完全不同
注文時のpassId必須(重複チェック)必須(同一) Idempotency 制御重要
フィル明細リアルタイムWebSocketユーザーデータStreamイベント処理アーキテクチャ不同
約定価格の種類約定時price約定時price+気配値価格損益計算ロジック差異

アーキテクチャ設計:統合抽象化レイヤー

私が以前担当したヘッジファンドのプロジェクトでは、スポットと先物のAPI差異を 개별的なクライアントに分离した 结果、コードが2倍に肥大化し保守性が著しく低下しました。その後、リ팩タリングする際には、共通抽象化レイヤー(Unified Abstraction Layer)を設計することで这些问题を解決しました。

統一データモデルの定義

// TypeScript実装:統合データモデル
interface UnifiedSymbol {
    symbol: string;              // BTCUSDT
    exchange: 'binance_spot' | 'binance_futures';
    baseAsset: string;           // BTC
    quoteAsset: string;         // USDT
    tickSize: number;            // 0.01
    lotSize: number;             // 0.00001
    minNotional: number;         // 最小注文額
    status: 'TRADING' | 'HALT' | 'BREAK';
}

interface UnifiedTicker {
    symbol: string;
    exchange: string;
    price: number;               // 最終取引価格
    bidPrice: number;            // 最良買値
    askPrice: number;            // 最良売値
    bidQty: number;
    askQty: number;
    volume24h: number;           // 24時間出来高
    quoteVolume24h: number;     // 24時間取引額
    timestamp: number;           // Unix ms
    priceChangePercent24h: number;
}

interface UnifiedBalance {
    asset: string;
    free: number;                // 利用可能残高
    locked: number;              // ロック中(注文拘束)
    crossWalletBalance?: number; // 先物のみ:分離証拠金
    marginBalance?: number;      // 先物のみ:통합証拠金
    unrealizedProfit: number;    // 先物のみ:評価損益
}

interface UnifiedOrder {
    orderId: number;
    clientOrderId: string;
    symbol: string;
    exchange: string;
    side: 'BUY' | 'SELL';
    type: 'LIMIT' | 'MARKET' | 'STOP_LOSS' | 'TAKE_PROFIT';
    status: 'NEW' | 'PARTIALLY_FILLED' | 'FILLED' | 'CANCELED' | 'REJECTED';
    price: number;
    origQty: number;
    executedQty: number;
    avgPrice: number;
    commission: number;          // 手数料
    commissionAsset: string;     // 手数料通貨
    time: number;                // 発注時刻
    updateTime: number;          // 最終更新時刻
    isIsolated: boolean;         // 先物のみ
}

// 統一RESTクライアント基底クラス
abstract class UnifiedBinanceClient {
    protected baseUrl: string;
    protected apiKey: string;
    protected apiSecret: string;
    protected recvWindow: number = 5000;  // デフォルト5秒
    
    constructor(baseUrl: string, apiKey: string, apiSecret: string) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
        this.apiSecret = apiSecret;
    }

    protected async signedRequest(
        method: 'GET' | 'POST' | 'DELETE',
        endpoint: string,
        params: Record = {}
    ): Promise<any> {
        const timestamp = Date.now();
        const queryParams = { ...params, timestamp, recvWindow: this.recvWindow };
        const queryString = Object.entries(queryParams)
            .filter(([_, v]) => v !== undefined)
            .map(([k, v]) => ${k}=${encodeURIComponent(v)})
            .join('&');
        
        const signature = await this.generateSignature(queryString);
        const url = ${this.baseUrl}${endpoint}?${queryString}&signature=${signature};
        
        const response = await fetch(url, {
            method,
            headers: {
                'X-MBX-APIKEY': this.apiKey,
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        });
        
        if (!response.ok) {
            const error = await response.json();
            throw new BinanceAPIError(error.code, error.msg, endpoint);
        }
        
        return response.json();
    }

    protected async generateSignature(queryString: string): Promise<string> {
        const encoder = new TextEncoder();
        const key = encoder.encode(this.apiSecret);
        const data = encoder.encode(queryString);
        
        // HMAC-SHA256
        const cryptoKey = await crypto.subtle.importKey(
            'raw', key,
            { name: 'HMAC', hash: 'SHA-256' },
            false, ['sign']
        );
        
        const signature = await crypto.subtle.sign('HMAC', cryptoKey, data);
        return Array.from(new Uint8Array(signature))
            .map(b => b.toString(16).padStart(2, '0'))
            .join('');
    }

    abstract fetchTicker(symbol: string): Promise<UnifiedTicker>;
    abstract fetchBalance(): Promise<UnifiedBalance[]>;
    abstract fetchOrder(symbol: string, orderId: number): Promise<UnifiedOrder>;
    abstract placeOrder(order: Partial<UnifiedOrder>): Promise<UnifiedOrder>;
}

スポット・先物クライアントの実装

// Binance Spot クライアント実装
class BinanceSpotClient extends UnifiedBinanceClient {
    constructor(apiKey: string, apiSecret: string) {
        super('https://api.binance.com', apiKey, apiSecret);
    }

    async fetchTicker(symbol: string): Promise<UnifiedTicker> {
        const data = await this.publicRequest('/api/v3/ticker/24hr', { symbol });
        
        return {
            symbol: data.symbol,
            exchange: 'binance_spot',
            price: parseFloat(data.lastPrice),
            bidPrice: parseFloat(data.bidPrice),
            askPrice: parseFloat(data.askPrice),
            bidQty: parseFloat(data.bidQty),
            askQty: parseFloat(data.askQty),
            volume24h: parseFloat(data.volume),
            quoteVolume24h: parseFloat(data.quoteVolume),
            timestamp: data.closeTime,
            priceChangePercent24h: parseFloat(data.priceChangePercent)
        };
    }

    async fetchBalance(): Promise<UnifiedBalance[]> {
        const data = await this.signedRequest('GET', '/api/v3/account');
        
        return data.balances
            .filter((b: any) => parseFloat(b.free) > 0 || parseFloat(b.locked) > 0)
            .map((b: any) => ({
                asset: b.asset,
                free: parseFloat(b.free),
                locked: parseFloat(b.locked),
                unrealizedProfit: 0  // スポットには存在しない
            }));
    }

    async fetchOrder(symbol: string, orderId: number): Promise<UnifiedOrder> {
        const data = await this.signedRequest('GET', '/api/v3/order', { symbol, orderId });
        return this.parseOrderResponse(data, 'binance_spot');
    }

    async placeOrder(order: Partial<UnifiedOrder>): Promise<UnifiedOrder> {
        const params: Record<string, any> = {
            symbol: order.symbol,
            side: order.side,
            type: order.type,
            quantity: order.origQty
        };
        
        if (order.type === 'LIMIT') {
            params.timeInForce = 'GTC';
            params.price = order.price;
        }
        
        if (order.clientOrderId) {
            params.newClientOrderId = order.clientOrderId;
        }
        
        const data = await this.signedRequest('POST', '/api/v3/order', params);
        return this.parseOrderResponse(data, 'binance_spot');
    }

    private parseOrderResponse(data: any, exchange: string): UnifiedOrder {
        return {
            orderId: data.orderId,
            clientOrderId: data.clientOrderId,
            symbol: data.symbol,
            exchange,
            side: data.side,
            type: data.type,
            status: data.status,
            price: parseFloat(data.price),
            origQty: parseFloat(data.origQty),
            executedQty: parseFloat(data.executedQty),
            avgPrice: parseFloat(data.avgPrice || data.price),
            commission: parseFloat(data.commission || '0'),
            commissionAsset: data.commissionAsset || data.symbol.slice(-4),
            time: data.transactTime || data.time,
            updateTime: data.updateTime || data.transactTime,
            isIsolated: false
        };
    }

    private async publicRequest(endpoint: string, params: Record<string, any> = {}): Promise<any> {
        const queryString = Object.entries(params)
            .map(([k, v]) => ${k}=${v})
            .join('&');
        
        const response = await fetch(${this.baseUrl}${endpoint}?${queryString});
        return response.json();
    }
}

// Binance Futures クライアント実装
class BinanceFuturesClient extends UnifiedBinanceClient {
    constructor(apiKey: string, apiSecret: string) {
        super('https://fapi.binance.com', apiKey, apiSecret);
    }

    async fetchTicker(symbol: string): Promise<UnifiedTicker> {
        // 先物は24hrティッカーではなくpremiumIndexを使用
        const data = await this.publicRequest('/fapi/v1/ticker/24hr', { symbol });
        
        return {
            symbol: data.symbol,
            exchange: 'binance_futures',
            price: parseFloat(data.lastPrice),
            bidPrice: parseFloat(data.bidPrice),
            askPrice: parseFloat(data.askPrice),
            bidQty: parseFloat(data.bidQty),
            askQty: parseFloat(data.askQty),
            volume24h: parseFloat(data.volume),
            quoteVolume24h: parseFloat(data.quoteVolume),
            timestamp: data.closeTime,
            priceChangePercent24h: parseFloat(data.priceChangePercent)
        };
    }

    async fetchBalance(): Promise<UnifiedBalance[]> {
        // 先物の残高取得はfapi/v2を使用
        const data = await this.signedRequest('GET', '/fapi/v2/account');
        
        const assets = data.assets || [];
        return assets
            .filter((a: any) => parseFloat(a.walletBalance) > 0)
            .map((a: any) => ({
                asset: a.asset,
                free: parseFloat(a.walletBalance),
                locked: 0,
                crossWalletBalance: parseFloat(a.walletBalance),
                marginBalance: parseFloat(a.marginBalance),
                unrealizedProfit: parseFloat(a.unrealizedProfit)
            }));
    }

    async fetchOrder(symbol: string, orderId: number): Promise<UnifiedOrder> {
        const data = await this.signedRequest('GET', '/fapi/v1/order', { symbol, orderId });
        return this.parseOrderResponse(data, 'binance_futures');
    }

    async placeOrder(order: Partial<UnifiedOrder>): Promise<UnifiedOrder> {
        const params: Record<string, any> = {
            symbol: order.symbol,
            side: order.side,
            type: order.type,
            quantity: this.normalizeQuantity(order.symbol!, order.origQty!)
        };
        
        if (order.type === 'LIMIT') {
            params.timeInForce = 'GTX';  // 先物はGTX(ポストのみ)
            params.price = this.normalizePrice(order.symbol!, order.price!);
        }
        
        if (order.clientOrderId) {
            params.newClientOrderId = order.clientOrderId;
        }
        
        // 先物特有のショート注文
        if ((order as any).reduceOnly !== undefined) {
            params.reduceOnly = (order as any).reduceOnly;
        }
        
        const data = await this.signedRequest('POST', '/fapi/v1/order', params);
        return this.parseOrderResponse(data, 'binance_futures');
    }

    // 先物用の数量正規化(lotSizeに従う)
    private normalizeQuantity(symbol: string, qty: number): string {
        // 実際にはexchangeInfoからlotSizeを取得
        const lotSize = 0.00001;  // BTC先物の例
        const precision = Math.round(-Math.log10(lotSize));
        return qty.toFixed(precision);
    }

    // 先物用の価格正規化(tickSizeに従う)
    private normalizePrice(symbol: string, price: number): string {
        const tickSize = 0.01;
        const precision = Math.round(-Math.log10(tickSize));
        return price.toFixed(precision);
    }

    private parseOrderResponse(data: any, exchange: string): UnifiedOrder {
        return {
            orderId: data.orderId,
            clientOrderId: data.clientOrderId,
            symbol: data.symbol,
            exchange,
            side: data.side,
            type: data.type,
            status: this.mapOrderStatus(data.status),
            price: parseFloat(data.price),
            origQty: parseFloat(data.origQty),
            executedQty: parseFloat(data.executedQty),
            avgPrice: parseFloat(data.avgPrice || data.price),
            commission: parseFloat(data.commission || '0'),
            commissionAsset: data.commissionAsset || 'USDT',
            time: data.time,
            updateTime: data.updateTime,
            isIsolated: data.isIsolated || false
        };
    }

    private mapOrderStatus(status: string): UnifiedOrder['status'] {
        const statusMap: Record<string, UnifiedOrder['status']> = {
            'NEW': 'NEW',
            'PARTIALLY_FILLED': 'PARTIALLY_FILLED',
            'FILLED': 'FILLED',
            'CANCELED': 'CANCELED',
            'REJECTED': 'REJECTED',
            'EXPIRED': 'CANCELED'
        };
        return statusMap[status] || 'REJECTED';
    }

    private async publicRequest(endpoint: string, params: Record<string, any> = {}): Promise<any> {
        const queryString = Object.entries(params)
            .map(([k, v]) => ${k}=${v})
            .join('&');
        
        const response = await fetch(${this.baseUrl}${endpoint}?${queryString});
        return response.json();
    }
}

// 統一ファクトリー
class BinanceClientFactory {
    static createSpotClient(apiKey: string, apiSecret: string): BinanceSpotClient {
        return new BinanceSpotClient(apiKey, apiSecret);
    }

    static createFuturesClient(apiKey: string, apiSecret: string): BinanceFuturesClient {
        return new BinanceFuturesClient(apiKey, apiSecret);
    }

    // 両市場への统一的アクセス
    static createUnifiedClient(apiKey: string, apiSecret: string): Map<string, UnifiedBinanceClient> {
        return new Map([
            ['spot', new BinanceSpotClient(apiKey, apiSecret)],
            ['futures', new BinanceFuturesClient(apiKey, apiSecret)]
        ]);
    }
}

タイムスタンプ処理の陷阱と解決策

私が過去に経験した最も头痛な 문제는、タイムスタンプの不整合でした。スポットと先物では、时刻情報の粒度と基准が异なるため、跨市場 분석時に致命的な误差が発生します。

// タイムスタンプ正規化ユーティリティ
class TimestampNormalizer {
    // Binance API は Unix timestamp (ミリ秒) を使用
    // しかし、网络遅延やサーバ负载により slight な误差が発生
    
    static normalize(timestamp: number | string, exchange: string): number {
        const ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp;
        
        // 先物は sometimes 秒単位を返す
        if (ts < 1e12) {
            console.warn([${exchange}] Timestamp ${ts} appears to be in seconds, converting);
            return ts * 1000;
        }
        
        return ts;
    }

    static toLocalTime(timestamp: number): Date {
        return new Date(timestamp);
    }

    static diffMs(ts1: number, ts2: number): number {
        return Math.abs(ts1 - ts2);
    }

    // ⚠️ Timestamp discrepancy detected: ${tsDiff}ms);
        }
        
        // -spread 计算(先物-スポット)
        const spread = (futuresTicker.bidPrice - spotTicker.askPrice);
        
        // 年率换算(先物期間约为3个月=90日)
        const daysToExpiry = 90;
        const annualizedSpread = (spread / spotTicker.price) * (365 / daysToExpiry) * 100;
        
        return {
            spotBid: spotTicker.bidPrice,
            spotAsk: spotTicker.askPrice,
            futuresBid: futuresTicker.bidPrice,
            futuresAsk: futuresTicker.askPrice,
            spread,
            annualizedSpread,
            timestamp: Math.max(spotTs, futuresTs),
            latencyMs
        };
    }
}

同時実行制御とレートリミット

Binance APIには詳細なレートリミットがあり、これを無視するとIP或いはAPI Key単位での制限されます。私が運用するシステムでは、spotsとfutures、それそれに个別の Rate Limiter を実装し、合計リクエスト数を制御しています。

// リーキーコンバケツアルゴリズムによるレート制御
class RateLimiter {
    private capacity: number;
    private refillRate: number;  // tokens per second
    private tokens: number;
    private lastRefill: number;
    private queue: Array<() => void> = [];
    private processing: boolean = false;

    constructor(capacity: number, refillPerSecond: number) {
        this.capacity = capacity;
        this.refillRate = refillPerSecond;
        this.tokens = capacity;
        this.lastRefill = Date.now();
    }

    private refill(): void {
        const now = Date.now();
        const elapsed = (now - this.lastRefill) / 1000;
        const newTokens = elapsed * this.refillRate;
        
        this.tokens = Math.min(this.capacity, this.tokens + newTokens);
        this.lastRefill = now;
    }

    async acquire(tokens: number = 1): Promise<void> {
        this.refill();
        
        if (this.tokens >= tokens) {
            this.tokens -= tokens;
            return;
        }
        
        // トークンが回復するまでの待機
        const waitTime = ((tokens - this.tokens) / this.refillRate) * 1000;
        
        return new Promise((resolve) => {
            this.queue.push(resolve);
            setTimeout(() => {
                this.refill();
                this.tokens -= tokens;
                resolve();
                this.processQueue();
            }, waitTime);
        });
    }

    private processQueue(): void {
        if (this.queue.length === 0) return;
        this.refill();
        
        while (this.queue.length > 0 && this.tokens > 0) {
            this.tokens -= 1;
            const resolve = this.queue.shift()!;
            resolve();
        }
    }

    getAvailableTokens(): number {
        this.refill();
        return this.tokens;
    }
}

// API クライアントラッパー(レート制限付き)
class RateLimitedClient {
    private spotLimiter: RateLimiter;
    private futuresLimiter: RateLimiter;
    private spotClient: BinanceSpotClient;
    private futuresClient: BinanceFuturesClient;

    constructor(apiKey: string, apiSecret: string) {
        // スポット:1200 requests/min = 20/sec
        this.spotLimiter = new RateLimiter(100, 20);
        // 先物:2400 requests/min = 40/sec  
        this.futuresLimiter = new RateLimiter(100, 40);
        
        this.spotClient = BinanceClientFactory.createSpotClient(apiKey, apiSecret);
        this.futuresClient = BinanceClientFactory.createFuturesClient(apiKey, apiSecret);
    }

    async spotRequest<T>(request: () => Promise<T>): Promise<T> {
        await this.spotLimiter.acquire();
        return request();
    }

    async futuresRequest<T>(request: () => Promise<T>): Promise<T> {
        await this.futuresLimiter.acquire();
        return request();
    }

    // 統合リクエスト(両市場へのアクセスが必要な場合)
    async unifiedRequest<T>(
        spotRequest: () => Promise<T>,
        futuresRequest: () => Promise<T>
    ): Promise<{ spot: T; futures: T; latencyMs: number }> {
        const startTime = Date.now();
        
        const [spot, futures] = await Promise.all([
            this.spotRequest(spotRequest),
            this.futuresRequest(futuresRequest)
        ]);
        
        return {
            spot,
            futures,
            latencyMs: Date.now() - startTime
        };
    }
}

// WebSocket接続管理器( тоже レート制限考虑)
class WebSocketManager {
    private spotStreams: Map<string, WebSocket> = new Map();
    private futuresStreams: Map<string, WebSocket> = new Map();
    private reconnectAttempts: Map<string, number> = new Map();
    private maxReconnectAttempts: number = 5;
    
    private baseUrl = {
        spot: 'wss://stream.binance.com:9443/ws',
        futures: 'wss://fstream.binance.com/ws'
    };

    subscribeSpot(
        symbols: string[],
        callback: (data: any) => void
    ): void {
        const streams = symbols.map(s => ${s.toLowerCase()}@ticker);
        const ws = new WebSocket(${this.baseUrl.spot}/${streams.join('/')});
        
        ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            callback(data);
        };
        
        ws.onerror = (error) => {
            console.error('Spot WebSocket error:', error);
            this.handleReconnect('spot', symbols, callback);
        };
        
        symbols.forEach(s => this.spotStreams.set(s, ws));
    }

    subscribeFutures(
        symbols: string[],
        callback: (data: any) => void
    ): void {
        const streams = symbols.map(s => ${s.toLowerCase()}@ticker);
        const ws = new WebSocket(${this.baseUrl.futures}/${streams.join('/')});
        
        ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            callback(data);
        };
        
        ws.onerror = (error) => {
            console.error('Futures WebSocket error:', error);
            this.handleReconnect('futures', symbols, callback);
        };
        
        symbols.forEach(s => this.futuresStreams.set(s, ws));
    }

    private handleReconnect(
        type: 'spot' | 'futures',
        symbols: string[],
        callback: (data: any) => void
    ): void {
        const key = symbols.join(',');
        const attempts = this.reconnectAttempts.get(key) || 0;
        
        if (attempts >= this.maxReconnectAttempts) {
            console.error(Max reconnect attempts reached for ${key});
            return;
        }
        
        const delay = Math.min(1000 * Math.pow(2, attempts), 30000);
        console.log(Reconnecting to ${type} in ${delay}ms (attempt ${attempts + 1}));
        
        setTimeout(() => {
            this.reconnectAttempts.set(key, attempts + 1);
            if (type === 'spot') {
                this.subscribeSpot(symbols, callback);
            } else {
                this.subscribeFutures(symbols, callback);
            }
        }, delay);
    }

    disconnectAll(): void {
        this.spotStreams.forEach(ws => ws.close());
        this.futuresStreams.forEach(ws => ws.close());
        this.spotStreams.clear();
        this.futuresStreams.clear();
    }
}

コスト最適化:Binance API vs HolySheep AI

Binance API本身的 costs は低いですが、アルゴリズミック取引には 市场分析、ニュース处理、センチメント分析などのために LLM API が不可欠です。ここでHolySheep AIを活用することで、大幅なコスト削减が可能になります。

Provider / Model 価格 ($/1M Tokens) 相対コスト 推奨ユースケース
GPT-4.1$8.00基准高精度分析·コード生成
Claude Sonnet 4.5$15.001.88x長い文脈分析·推論
Gemini 2.5 Flash$2.500.31x高速处理·批量分析
DeepSeek V3.2$0.420.05xコスト重視の批量处理

私がHolySheep AIを今すぐ登録して最も気に入っている点は、公式為替レートの¥1=$1という破格の料金体系です。現在の日本の銀行為替レート(约¥150/$)と比較して、約85%の节约になります。

// HolySheep AI との統合例:取引シグナル分析
class TradingSignalAnalyzer {
    private holySheepApiKey: string;
    private baseUrl = 'https://api.holysheep.ai/v1';

    constructor(apiKey: string) {
        this.holySheepApiKey = apiKey;
    }

    // 市場センチメント分析
    async analyzeMarketSentiment(
        spotPrice: number,
        futuresPrice: number,
        volume24h: number,
        fundingRate: number
    ): Promise<{
        signal: 'BULLISH' | 'BEARISH' | 'NEUTRAL';
        confidence: number;
        reasoning: string;
    }> {
        const prompt = this.buildSentimentPrompt(spotPrice, futuresPrice, volume24h, fundingRate);
        
        const response = await fetch(${this.baseUrl}/chat/completions, {
            method: 'POST',
            headers: {
                'Authorization': Bearer ${this.holysheepApiKey},
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                model: 'deepseek-v3.2',
                messages: [
                    {
                        role: 'system',
                        content: '你是一个专业的加密货币交易分析师。请基于提供的数据给出简洁的交易信号。'
                    },
                    {
                        role: 'user', 
                        content: prompt
                    }
                ],
                temperature: 0.3,
                max_tokens: 500
            })
        });

        if (!response.ok) {
            throw new Error(HolySheep API error: ${response.status});
        }

        const data = await response.json();
        return this.parseSignalResponse(data.choices[0].message.content);
    }

    // 裁定取引機会の検出
    async detectArbitrageOpportunity(
        spotBid: number,
        spotAsk: number,
        futuresBid: number,
        futuresAsk: number,
        fundingRate: number,
        expectedHoldingDays: number
    ): Promise<{
        opportunity: boolean;
        grossSpread: number;
        netSpread: number;
        annualizedReturn: number;
        riskLevel: 'LOW' | 'MEDIUM' | 'HIGH';
        recommendation: string;
    }> {
        const prompt = `
Spot Market:
- Best Bid: ${spotBid}
- Best Ask: ${spotAsk}
- Spread: ${((spotAsk - spotBid) / spotBid * 100).toFixed(4)}%

Futures Market:
- Best Bid: ${futuresBid}
- Best Ask: ${futuresAsk}
- Spread: ${((futuresAsk - futuresBid) / futuresBid * 100).toFixed(4)}%

Current Funding Rate: ${(fundingRate * 100).toFixed(4)}%
Expected Holding Period: ${expectedHoldingDays} days

Calculate:
1. Gross spread (futures bid - spot ask)
2. Net spread after funding costs
3. Annualized return percentage
4. Risk assessment

Provide your analysis in JSON format.`;

        const response = await fetch(${this.baseUrl}/chat/completions, {
            method: 'POST',
            headers: {
                'Authorization': Bearer ${this.holysheepApiKey},
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                model: 'gemini-2.5-flash',
                messages: [
                    {
                        role: 'user',
                        content: prompt
                    }
                ],
                temperature: 0.1,
                response_format: { type: 'json_object' }
            })
        });

        const data = await response.json();
        return JSON.parse(data.choices[0].message.content);
    }

    // プロンプト構築
    private buildSentimentPrompt(
        spotPrice: number,
        futuresPrice: number,
        volume24h: number,
        fundingRate: number
    ): string {
        const basis = ((futuresPrice - spotPrice) / spotPrice * 100).toFixed(4);
        
        return `
Current Market Data:
- BTC Spot Price: $${spotPrice}
- BTC Futures Price: $${futuresPrice}
- Basis: ${basis}%
- 24h Volume: ${volume24h.toFixed(2)} BTC
- Current Funding Rate: ${(fundingRate * 100).toFixed(4)}%

Analyze the market sentiment and provide:
1. Overall signal (BULLISH/BEARISH/NEUTRAL)
2. Confidence level (0-100%)
3. Brief reasoning

Respond in the following JSON format only:
{
    "signal": "BULLISH/BEARISH/NEUTRAL",