UNPKG

skayn-trading-sdk

Version:

Professional Bitcoin trading strategy backtesting framework with institutional-grade architecture

371 lines (309 loc) 14.4 kB
#!/usr/bin/env node const { SDK } = require('../index'); const SmartDataProvider = require('./smart-data-provider'); /** * Real-world momentum strategy using actual trading logic * Based on RSI oversold conditions and price momentum */ class ProductionMomentumStrategy extends SDK.Strategy { async analyze(data, portfolio) { if (data.length < 50) { return { action: 'HOLD', confidence: 0, strategy: 'momentum', reasons: ['Insufficient data'] }; } const prices = data.map(bar => bar.close); const currentPrice = prices[prices.length - 1]; // Calculate momentum (10-period price change) const momentum = this.calculateMomentum(prices, 10); // Calculate volume trend const volumes = data.map(bar => bar.volume); const avgVolume = volumes.slice(-20).reduce((sum, vol) => sum + vol, 0) / 20; const currentVolume = volumes[volumes.length - 1]; const volumeRatio = currentVolume / avgVolume; // TRUE MOMENTUM confidence scoring (not swing trading) let confidence = 0; const reasons = []; // Base momentum confidence - strong upward price action if (momentum > 0.001) { // 0.1% momentum threshold (very sensitive) confidence += Math.min(momentum * 100, 0.6); // Strong momentum weight reasons.push(`Momentum: +${(momentum * 100).toFixed(2)}%`); } // Volume confirmation (supports momentum) if (volumeRatio > 1.1) { // Lower threshold, more sensitive confidence += Math.min((volumeRatio - 1) * 0.4, 0.3); reasons.push(`Volume: ${volumeRatio.toFixed(1)}x average`); } // Calculate SMAs first const sma20 = prices.slice(-20).reduce((sum, p) => sum + p, 0) / 20; const sma50 = prices.slice(-50).reduce((sum, p) => sum + p, 0) / 50; // Trend strength bonus (SMA alignment) if (sma20 > sma50) { const trendStrength = (sma20 - sma50) / sma50; confidence += Math.min(trendStrength * 20, 0.2); // Trend bonus reasons.push(`Trend strength: SMA20 > SMA50`); } // Show current prices like quick-start example console.log(`Current Price: $${currentPrice.toFixed(0)}, SMA20: $${sma20.toFixed(0)}, SMA50: $${sma50.toFixed(0)}`); // Enhanced signal quality filters const rsi = this.calculateRSI(prices, 14); const vwap = this.calculateVWAP(data.slice(-20)); // Multi-condition entry with better signal quality const signalQuality = { trend: sma20 > sma50 * 1.002, // Uptrend confirmed rsiNotOverbought: rsi < 70, // Not overbought volumeConfirm: currentVolume > avgVolume * 1.1, // Volume confirmation priceAboveVWAP: currentPrice > vwap, // Price strength confidence: confidence > 0.45 // Lower threshold (45% vs 60%) }; if (signalQuality.trend && signalQuality.rsiNotOverbought && signalQuality.volumeConfirm && signalQuality.priceAboveVWAP && signalQuality.confidence) { // Buy with enhanced signal quality return { action: 'BUY', confidence: Math.min(confidence, 0.95), // Max 95% confidence for swing trades quantity: this.calculatePositionSize(confidence), strategy: 'momentum', reasons: [`Enhanced momentum: Trend + RSI + Volume + VWAP`, ...reasons], metadata: { rsi, momentum, volumeRatio, priceLevel: currentPrice } }; } else if (sma20 < sma50 * 0.998 && confidence > 0.45 && currentPrice < vwap) { // Sell when trending down return { action: 'SELL', confidence: Math.min(confidence, 0.95), quantity: this.calculatePositionSize(confidence), strategy: 'momentum', reasons: [`Swing bearish: SMA20 < SMA50`, ...reasons], metadata: { rsi, momentum, volumeRatio, priceLevel: currentPrice } }; } // Debug: Show why no trade triggered const debugInfo = { conf: `${(confidence * 100).toFixed(1)}%`, trend: sma20 > sma50 * 1.002 ? '✅' : '❌', rsi: rsi < 70 ? '✅' : `❌${rsi.toFixed(0)}`, vol: currentVolume > avgVolume * 1.1 ? '✅' : '❌', vwap: currentPrice > vwap ? '✅' : '❌' }; console.log(` → HOLD: Conf:${debugInfo.conf} Trend:${debugInfo.trend} RSI:${debugInfo.rsi} Vol:${debugInfo.vol} VWAP:${debugInfo.vwap}`); return { action: 'HOLD', confidence: confidence, strategy: 'momentum', reasons: [`Below 60% confidence: ${(confidence * 100).toFixed(1)}%`, `Momentum: ${(momentum * 100).toFixed(2)}%`, ...reasons], metadata: { rsi, momentum, volumeRatio } }; } calculateRSI(prices, period = 14) { if (prices.length < period + 1) return 50; let gains = 0; let losses = 0; // Calculate initial average gain/loss for (let i = 1; i <= period; i++) { const change = prices[i] - prices[i - 1]; if (change > 0) { gains += change; } else { losses -= change; } } let avgGain = gains / period; let avgLoss = losses / period; // Calculate RSI using Wilder's smoothing for (let i = period + 1; i < prices.length; i++) { const change = prices[i] - prices[i - 1]; if (change > 0) { avgGain = (avgGain * (period - 1) + change) / period; avgLoss = (avgLoss * (period - 1)) / period; } else { avgGain = (avgGain * (period - 1)) / period; avgLoss = (avgLoss * (period - 1) - change) / period; } } if (avgLoss === 0) return 100; const rs = avgGain / avgLoss; return 100 - (100 / (1 + rs)); } calculateMomentum(prices, period = 10) { if (prices.length < period) return 0; const currentPrice = prices[prices.length - 1]; const pastPrice = prices[prices.length - 1 - period]; return (currentPrice - pastPrice) / pastPrice; } calculateVWAP(data) { if (!data || data.length === 0) return 0; let totalVolumePrice = 0; let totalVolume = 0; for (const bar of data) { const typicalPrice = (bar.high + bar.low + bar.close) / 3; totalVolumePrice += typicalPrice * bar.volume; totalVolume += bar.volume; } return totalVolume > 0 ? totalVolumePrice / totalVolume : 0; } calculatePositionSize(confidence) { // Dynamic position sizing based on confidence const baseSize = 0.001; // Base 0.001 BTC const maxSize = 0.01; // Max 0.01 BTC const confidenceMultiplier = confidence * 2; // 0.7 conf = 1.4x multiplier return Math.min(baseSize * confidenceMultiplier, maxSize); } getStrategyMetadata() { return { name: 'Swing Momentum Strategy', version: '2.0.0', description: 'High-confidence swing trading: 5% stop, 7% target, 72h max hold', riskProfile: 'conservative', timeframe: '4h-1d', maxPositions: 2, stopLoss: '5%', profitTarget: '7%', maxHoldTime: '72 hours', expectedTrades: '0.7-1.1 per day' }; } getRequiredWarmupPeriods() { // Need enough data for RSI (14) + momentum (10) + volume average (20) // Plus some buffer for accurate calculations return 50; } } // Example usage with real data async function runMomentumExample() { console.log('🚀 Production Momentum Strategy Example\n'); // Smart data provider - tries real market data, falls back to synthetic const smartDataProvider = new SmartDataProvider(); // Mock data provider with STRONG TRENDING movements for swing trading const mockDataProvider = SDK.dataProvider(async (startDate, endDate) => { const data = []; let price = 95000; // Start lower for better trend demonstration let trendDirection = 1; // Start with strong uptrend let trendDuration = 0; const hours = 720; // 30 days of hourly data for (let i = 0; i < hours; i++) { trendDuration++; // Create STRONG trending periods (200-300 hours) for swing opportunities if (trendDuration > 200 + Math.random() * 100) { trendDirection *= -1; trendDuration = 0; console.log(`📈 TREND CHANGE at hour ${i}: Now ${trendDirection > 0 ? 'BULLISH' : 'BEARISH'}`); } // MUCH STRONGER trending movements for swing trading let trendMove = trendDirection * (0.003 + Math.random() * 0.007); // 0.3-1.0% per hour const noise = (Math.random() - 0.5) * 0.002; // Less noise, clearer trends // Reverse trend direction when approaching bounds to avoid flat periods if (price > 135000 && trendDirection > 0) { trendDirection = -1; trendMove = trendDirection * (0.003 + Math.random() * 0.007); console.log(`📈 PRICE REVERSAL at $${price.toFixed(0)}: Now BEARISH`); } else if (price < 85000 && trendDirection < 0) { trendDirection = 1; trendMove = trendDirection * (0.003 + Math.random() * 0.007); console.log(`📈 PRICE REVERSAL at $${price.toFixed(0)}: Now BULLISH`); } price = price * (1 + trendMove + noise); // Wider price range with smoother bounds price = Math.max(80000, Math.min(140000, price)); const high = price * (1 + Math.random() * 0.015); const low = price * (1 - Math.random() * 0.015); // Variable volume with spikes during trends const volumeBase = 100 + Math.random() * 200; const volumeSpike = Math.abs(trendMove) > 0.005 ? 2 + Math.random() * 3 : 1; // Volume spikes on big moves const volume = volumeBase * volumeSpike; data.push({ timestamp: Date.now() - (hours - i) * 60 * 60 * 1000, open: price, high, low, close: price, volume }); } console.log(`🎯 Generated ${hours} hours of data with strong trends for swing trading`); return data; }); try { const results = await SDK.create() .strategy(new ProductionMomentumStrategy()) .dataProvider(smartDataProvider) .backtest({ startDate: '2023-10-01', // Better bull market period endDate: '2023-12-31', // Bitcoin rally to ~$44K initialBalance: 8860000, // 8.86M sats (~$10,000 at $112,900/BTC) symbol: 'BTCUSDT' }); console.log('📊 PRODUCTION MOMENTUM STRATEGY RESULTS'); console.log('═'.repeat(60)); console.log('⚠️ DISCLAIMER: Past performance does not guarantee future results.'); console.log(' This is a demonstration using historical data for educational purposes only.'); // Calculate P&L metrics const initialBal = results.initialBalance || 8860000; const finalBal = results.finalBalance || initialBal; const totalPnL = finalBal - initialBal; const totalReturn = results.totalReturn || ((finalBal - initialBal) / initialBal * 100); console.log(`💰 Initial Balance: ${initialBal.toLocaleString()} sats (~$${(initialBal/100000).toFixed(0)})`); console.log(`💰 Final Balance: ${finalBal.toLocaleString()} sats (~$${(finalBal/100000).toFixed(0)})`); console.log(`📈 Total P&L: ${totalPnL >= 0 ? '+' : ''}${totalPnL.toLocaleString()} sats (${totalReturn >= 0 ? '+' : ''}${totalReturn.toFixed(2)}%)`); console.log(`🔄 Total Trades: ${results.totalTrades || 0}`); console.log(`🎯 Win Rate: ${results.winRate?.toFixed(1) || 'N/A'}%`); console.log(`📉 Max Drawdown: ${results.maxDrawdown?.toFixed(2) || 'N/A'}%`); console.log(`⚡ Sharpe Ratio: ${results.sharpeRatio?.toFixed(2) || 'N/A'}`); if (results.trades && results.trades.length > 0) { console.log('\n🔄 TRADE EXECUTION SUMMARY:'); console.log('-'.repeat(60)); let totalPnLFromTrades = 0; let winningTrades = 0; let losingTrades = 0; // Sort trades to show both wins and losses const allTrades = results.trades || []; const wins = allTrades.filter(t => (t.realizedPnL || t.closedPosition?.realizedPnL || 0) > 0).slice(0, 3); const losses = allTrades.filter(t => (t.realizedPnL || t.closedPosition?.realizedPnL || 0) < 0).slice(0, 3); const displayTrades = [...wins, ...losses].slice(0, 8); displayTrades.forEach((trade, i) => { const action = trade.signal?.action || 'N/A'; const price = trade.position?.entryPrice || trade.closedPosition?.entryPrice || 0; const pnl = trade.realizedPnL || trade.closedPosition?.realizedPnL || 0; const confidence = trade.signal?.confidence || 0; const pnlDisplay = pnl >= 0 ? `+${pnl.toFixed(0)}` : `${pnl.toFixed(0)}`; const pnlIcon = pnl >= 0 ? '✅' : '❌'; totalPnLFromTrades += pnl; if (pnl > 0) winningTrades++; if (pnl < 0) losingTrades++; console.log(`${i + 1}. ${pnlIcon} ${action} at $${price.toFixed(0)} → P&L: ${pnlDisplay} sats (${(confidence * 100).toFixed(1)}%)`); }); if (allTrades.length > displayTrades.length) { console.log(`... and ${allTrades.length - displayTrades.length} more trades`); } console.log('\n📊 TRADE PERFORMANCE BREAKDOWN:'); console.log('-'.repeat(40)); console.log(`✅ Winning Trades: ${winningTrades} shown (${wins.length} total wins)`); console.log(`❌ Losing Trades: ${losingTrades} shown (${losses.length} total losses)`); console.log(`💎 Sample P&L: ${(totalPnLFromTrades / displayTrades.length).toFixed(0)} sats/trade`); console.log(`⚡ Trade Frequency: ${(results.totalTrades / 92).toFixed(1)} trades/day (Oct-Dec 2023)`); } } catch (error) { console.error('❌ Backtest failed:', error.message); } } // Export for use in other modules module.exports = { ProductionMomentumStrategy }; // Run example if executed directly if (require.main === module) { console.log('🏗️ Skayn Trading SDK Structure:'); console.log(' 📁 /src - Core trading framework'); console.log(' 📁 /examples - Demo strategies (this file)'); console.log(' 📁 /tests - Test suites'); console.log(''); console.log('Look at this structure - /src/backtesting, /src/interfaces, /src/core.'); console.log('This is how professional trading firms organize their systems.'); console.log('We\'ve just made it accessible to everyone.\n'); runMomentumExample(); }