skayn-trading-sdk
Version:
Professional Bitcoin trading strategy backtesting framework with institutional-grade architecture
371 lines (309 loc) • 14.4 kB
JavaScript
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();
}