UNPKG

skayn-trading-sdk

Version:

Professional Bitcoin trading strategy backtesting framework with institutional-grade architecture

521 lines (458 loc) • 18.4 kB
const logger = require('../../utils/logger'); /** * Simple Position Manager for Backtesting * Handles position sizing, leverage, and risk management */ class SimplePositionManager { constructor(config = {}) { this.config = { adaptiveMinimum: 85000, // $100 minimum for premium adaptive strategy maxPositions: config.maxPositions || 5, ...config }; // Strategy-specific configuration with tiers this.strategyTiers = { conservative: { tier: 'FREE', leverage: { max: 1.0, default: 1.0 }, minBalance: 0, description: 'Basic trading - any balance, no leverage' }, enhanced: { tier: 'FREE', leverage: { max: 2.0, default: 2.0 }, minBalance: 0, description: 'Advanced indicators - any balance, 2x leverage' }, momentum: { tier: 'FREE', leverage: { max: 5.0, default: 4.0 }, minBalance: 0, description: 'Trend-following momentum - any balance, up to 5x leverage' }, adaptive: { tier: 'PREMIUM', leverage: { max: 10.0, default: 2.0 }, minBalance: 85000, // $100+ minimum for serious traders description: 'AI-powered Claude analysis - $100+ minimum, up to 10x leverage' } }; this.positions = []; this.totalExposure = 0; this.tradingFee = config.tradingFee || 0.001; // Default 0.1% per trade } /** * Calculate position size and leverage profile */ calculatePosition(signal, portfolioValue, currentPrice) { try { // Ensure strategy is defined (safety check for zero-config) if (!signal.strategy || signal.strategy === undefined) { signal.strategy = 'conservative'; logger.debug('šŸ”§ Position manager applied default strategy: conservative'); } // Validate strategy tier requirements this.validateStrategyAccess(signal.strategy, portfolioValue); // Get leverage profile with reasoning const leverageProfile = this.calculateLeverageProfile(signal); // Calculate base position size (percentage of portfolio) const basePositionPercent = this.getPositionSizePercent(signal); const basePositionValue = portfolioValue * basePositionPercent; // Apply strategy-specific minimums let positionValue = basePositionValue; if (signal.strategy === 'adaptive') { // Premium strategy enforces $100 minimum per position positionValue = Math.max(basePositionValue, this.config.adaptiveMinimum); } // Calculate leveraged position size const leveragedPositionValue = positionValue * leverageProfile.leverage; const quantity = leveragedPositionValue / currentPrice; // USD position / USD price = BTC quantity // Calculate margin requirement in sats (actual money at risk) const marginRequired = Math.round(positionValue / currentPrice * 100000000); // Convert USD to sats // Calculate stop loss and take profit const stopLossPrice = this.calculateStopLoss(currentPrice, signal, leverageProfile); const takeProfitPrice = this.calculateTakeProfit(currentPrice, signal, leverageProfile); return { quantity: quantity, positionValue: leveragedPositionValue, marginRequired: Math.round(marginRequired), leverage: leverageProfile.leverage, leverageReason: leverageProfile.reason, riskLevel: leverageProfile.riskLevel, stopLossPrice: Math.round(stopLossPrice), takeProfitPrice: Math.round(takeProfitPrice), entryPrice: currentPrice, maxLoss: Math.round(marginRequired * 0.8), // Max 80% of margin potentialProfit: Math.round((takeProfitPrice - currentPrice) * quantity) }; } catch (error) { logger.error('āŒ Position calculation failed', { error: error.message, signal }); throw error; } } /** * Validate strategy access based on tier requirements */ validateStrategyAccess(strategy, portfolioValue) { const tier = this.strategyTiers[strategy]; if (!tier) { // Default unknown strategies to conservative tier for zero-config experience logger.warn(`āš ļø Unknown strategy "${strategy}" - defaulting to conservative tier (1x leverage, basic risk management)`); return this.validateStrategyAccess('conservative', portfolioValue); } if (strategy === 'adaptive' && portfolioValue < tier.minBalance) { throw new Error( `šŸ”’ ADAPTIVE QUANT STRATEGY - PREMIUM TIER REQUIRED\n\n` + `🧠 UNLOCK ADAPTIVE INTELLIGENCE:\n` + ` • Real-Time Market Sentiment Analysis\n` + ` • Multi-Factor Signal Processing\n` + ` • Dynamic Risk-Adjusted Position Sizing\n` + ` • Advanced Leverage Optimization (up to 10x)\n` + ` • Adaptive Confidence-Based Execution\n` + ` • Systematic Alpha Generation\n\n` + `šŸ’° Minimum Balance: ${tier.minBalance.toLocaleString()} sats ($100)\n` + `⚔ Built for serious traders seeking edge\n` + `šŸŽÆ Precision execution with intelligent risk management\n\n` + `šŸ“Š Your Balance: ${portfolioValue.toLocaleString()} sats\n` + `šŸ’” Try Enhanced (2x leverage) or Conservative strategies instead` ); } } /** * Calculate leverage and generate reasoning profile */ calculateLeverageProfile(signal) { const strategyTier = this.strategyTiers[signal.strategy] || this.strategyTiers.conservative; const strategyLeverage = strategyTier.leverage; if (signal.strategy === 'adaptive') { // AGGRESSIVE: Claude confidence-based leverage if (signal.confidence >= 0.95) { return { leverage: 10.0, reason: 'MAXIMUM CONVICTION - Claude extremely confident', riskLevel: 'AGGRESSIVE' }; } if (signal.confidence >= 0.90) { return { leverage: 8.0, reason: 'VERY HIGH confidence - Strong market signals', riskLevel: 'HIGH' }; } if (signal.confidence >= 0.85) { return { leverage: 6.0, reason: 'HIGH confidence - Multiple indicators aligned', riskLevel: 'ELEVATED' }; } if (signal.confidence >= 0.80) { return { leverage: 4.0, reason: 'GOOD confidence - Solid technical setup', riskLevel: 'MODERATE' }; } if (signal.confidence >= 0.70) { return { leverage: 3.0, reason: 'MODERATE confidence - Some uncertainty', riskLevel: 'CONSERVATIVE' }; } return { leverage: strategyLeverage.default, reason: 'LOW confidence - Playing it safe', riskLevel: 'MINIMAL' }; } // CONSERVATIVE leverage for momentum strategy if (signal.strategy === 'momentum') { // Moderate leverage based on confidence for momentum trades if (signal.confidence >= 0.85) { return { leverage: 4.0, // 4x for very high confidence (was 8x) reason: 'HIGH CONFIDENCE momentum - Moderate leverage', riskLevel: 'MODERATE' }; } else if (signal.confidence >= 0.75) { return { leverage: 3.0, // 3x for high confidence (was 6x) reason: 'GOOD CONFIDENCE momentum - Conservative leverage', riskLevel: 'MODERATE' }; } else { return { leverage: 2.0, // 2x for standard momentum (was 5x) reason: 'STANDARD momentum - Conservative leverage', riskLevel: 'CONSERVATIVE' }; } } // Fixed leverage for other strategies const tier = this.strategyTiers[signal.strategy] || this.strategyTiers.conservative; return { leverage: strategyLeverage.max, reason: `${tier.tier} ${signal.strategy.toUpperCase()} - ${tier.description}`, riskLevel: signal.strategy === 'enhanced' ? 'MODERATE' : 'CONSERVATIVE' }; } /** * Get position size as percentage of portfolio */ getPositionSizePercent(signal) { // CONSERVATIVE position sizing to prevent over-leverage const baseSizes = { conservative: 0.10, // 10% per position enhanced: 0.15, // 15% per position momentum: 0.20, // 20% per position (REDUCED from 40%) adaptive: 0.25 // 25% per position }; let baseSize = baseSizes[signal.strategy] || baseSizes.conservative; // More conservative confidence-based sizing for momentum if (signal.strategy === 'momentum' && signal.confidence) { if (signal.confidence >= 0.85) { baseSize = 0.25; // 25% for very high confidence (was 50%) } else if (signal.confidence >= 0.75) { baseSize = 0.22; // 22% for high confidence (was 45%) } else if (signal.confidence >= 0.65) { baseSize = 0.20; // 20% for good confidence (was 40%) } else { baseSize = 0.15; // 15% for lower confidence (was 30%) } } // Conservative adaptive strategy sizing if (signal.strategy === 'adaptive' && signal.confidence) { const confidenceBonus = (signal.confidence - 0.5) * 0.10; // Up to 5% bonus (was 7.5%) baseSize = Math.min(baseSize + confidenceBonus, 0.30); // Max 30% (was 45%) } return baseSize; } /** * Calculate dynamic stop loss based on leverage */ calculateStopLoss(entryPrice, signal, leverageProfile) { // TIGHTER STOP LOSSES - Better risk management const baseStopLoss = { conservative: 0.025, // 2.5% (was 4.5%) enhanced: 0.03, // 3.0% (was 4.5%) momentum: 0.04, // 4.0% (was 8.0%) adaptive: 0.025 // 2.5% (was 4.5%) }; let stopLossPercent = baseStopLoss[signal.strategy] || baseStopLoss.conservative; // Slightly wider stops for higher confidence only if (signal.confidence && signal.confidence > 0.85) { stopLossPercent *= 1.2; // 20% wider for very high confidence } return (signal.action === 'LONG' || signal.action === 'BUY') ? entryPrice * (1 - stopLossPercent) : entryPrice * (1 + stopLossPercent); } /** * Calculate dynamic take profit based on leverage and confidence */ calculateTakeProfit(entryPrice, signal, leverageProfile) { // AGGRESSIVE TAKE PROFIT - Bigger targets for better returns const baseTakeProfit = { conservative: 0.04, // 4% enhanced: 0.05, // 5% momentum: 0.15, // 15% MUCH HIGHER targets (was 7%) adaptive: 0.12 // 12% higher targets }; let takeProfitPercent = baseTakeProfit[signal.strategy] || baseTakeProfit.conservative; // MUCH higher targets for higher confidence if (signal.confidence && signal.confidence > 0.85) { takeProfitPercent *= 1.5; // 50% higher target (was 30%) } else if (signal.confidence && signal.confidence > 0.75) { takeProfitPercent *= 1.3; // 30% higher target (was 15%) } // More aggressive targets for higher leverage if (leverageProfile.leverage > 7) { takeProfitPercent *= 1.4; // 40% higher } else if (leverageProfile.leverage > 5) { takeProfitPercent *= 1.25; // 25% higher } return (signal.action === 'LONG' || signal.action === 'BUY') ? entryPrice * (1 + takeProfitPercent) : entryPrice * (1 - takeProfitPercent); } /** * Open a new position */ openPosition(positionData, signal) { const position = { id: `pos_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, ...positionData, action: signal.action, strategy: signal.strategy, openTime: Date.now(), // Will be overridden by backtest engine with historical timestamp status: 'open', signalReason: signal.reason, maxHoldTime: signal.exitRules?.maxHoldTime || null // Add time stop from signal }; this.positions.push(position); this.totalExposure += positionData.positionValue; logger.info('šŸ“ˆ Position opened in backtest', { id: position.id, strategy: position.strategy, action: position.action, leverage: `${position.leverage}x`, reason: position.leverageReason, riskLevel: position.riskLevel, quantity: position.quantity, marginRequired: `${position.marginRequired} sats`, stopLoss: position.stopLossPrice, takeProfit: position.takeProfitPrice }); return position; } /** * Close a position */ closePosition(positionId, exitPrice, reason = 'Manual close') { const position = this.positions.find(p => p.id === positionId); if (!position) { throw new Error(`Position ${positionId} not found`); } // Calculate P&L const priceDiff = (position.action === 'LONG' || position.action === 'BUY') ? exitPrice - position.entryPrice : position.entryPrice - exitPrice; // CRITICAL FIX: P&L is in USD, convert to sats const rawPnLUSD = priceDiff * position.quantity; const rawPnL = Math.round(rawPnLUSD / exitPrice * 100000000); // Convert USD to sats // Calculate trading fees in sats const tradingFeesUSD = position.positionValue * this.tradingFee * 2; const tradingFees = Math.round(tradingFeesUSD / exitPrice * 100000000); // Convert USD to sats const realizedPnL = rawPnL - tradingFees; position.exitPrice = exitPrice; position.closeTime = Date.now(); position.status = 'closed'; position.realizedPnL = realizedPnL; position.tradingFees = tradingFees; position.rawPnL = rawPnL; position.rawPnLUSD = rawPnLUSD; // Store USD amount for debugging position.closeReason = reason; this.totalExposure -= position.positionValue; logger.info('šŸ“‰ Position closed in backtest', { id: position.id, strategy: position.strategy, entryPrice: position.entryPrice, exitPrice: exitPrice, quantity: position.quantity, rawPnL: rawPnL, fees: tradingFees, pnl: `${realizedPnL > 0 ? '+' : ''}${realizedPnL.toLocaleString()} sats`, reason: reason, holdTime: `${((position.closeTime - position.openTime) / 1000 / 60).toFixed(1)}m` }); return position; } /** * Get current open positions */ getOpenPositions() { return this.positions.filter(p => p.status === 'open'); } /** * Get closed positions */ getClosedPositions() { return this.positions.filter(p => p.status === 'closed'); } /** * Check if position limits allow new position */ canOpenPosition(strategy, portfolioSizeUSD = null) { const openPositions = this.getOpenPositions(); const strategyPositions = openPositions.filter(p => p.strategy === strategy); // Base strategy-specific limits const baseLimits = { conservative: 1, // Single position - truly conservative enhanced: 5, // Multiple positions - active trading momentum: 5, // Multiple positions - trend following adaptive: 5 // Multiple positions - premium tier }; let limit = baseLimits[strategy] || baseLimits.conservative; // Scale position limits based on portfolio size for momentum strategy if (strategy === 'momentum' && portfolioSizeUSD) { if (portfolioSizeUSD >= 5000) { // $5K+ portfolio limit = 8; // 60% more positions } else if (portfolioSizeUSD >= 3000) { // $3K+ portfolio limit = 6; // 20% more positions } else if (portfolioSizeUSD >= 1500) { // $1.5K+ portfolio limit = 5; // Standard 5 positions } else { // Small portfolios limit = 3; // Fewer positions for risk management } } return strategyPositions.length < limit; } /** * Get portfolio summary */ getPortfolioSummary() { const openPositions = this.getOpenPositions(); const closedPositions = this.getClosedPositions(); const totalPnL = closedPositions.reduce((sum, pos) => sum + (pos.realizedPnL || 0), 0); const totalMarginUsed = openPositions.reduce((sum, pos) => sum + pos.marginRequired, 0); return { openPositions: openPositions.length, closedPositions: closedPositions.length, totalPnL: totalPnL, totalMarginUsed: totalMarginUsed, totalExposure: this.totalExposure, winRate: closedPositions.length > 0 ? (closedPositions.filter(p => (p.realizedPnL || 0) > 0).length / closedPositions.length) * 100 : 0 }; } /** * Get available strategy tiers for current balance */ getAvailableStrategies(portfolioValue) { const available = []; for (const [strategy, tier] of Object.entries(this.strategyTiers)) { if (portfolioValue >= tier.minBalance) { available.push({ strategy, tier: tier.tier, description: tier.description, maxLeverage: `${tier.leverage.max}x`, minBalance: tier.minBalance === 0 ? 'Any amount' : `${tier.minBalance.toLocaleString()} sats` }); } } return available; } /** * Display strategy tier information */ displayStrategyTiers(portfolioValue) { const available = this.getAvailableStrategies(portfolioValue); console.log('\nšŸ“Š AVAILABLE TRADING STRATEGIES:'); console.log('='.repeat(50)); available.forEach(strategy => { const tierBadge = strategy.tier === 'PREMIUM' ? 'šŸ”’ PREMIUM' : 'šŸ†“ FREE'; console.log(`${tierBadge} ${strategy.strategy.toUpperCase()}`); console.log(` ${strategy.description}`); console.log(` Max Leverage: ${strategy.maxLeverage}`); console.log(` Minimum: ${strategy.minBalance}\n`); }); // Show unavailable premium strategies if (portfolioValue < this.strategyTiers.adaptive.minBalance) { console.log('šŸ”’ PREMIUM STRATEGY (Upgrade Required):'); console.log(` ADAPTIVE - ${this.strategyTiers.adaptive.description}`); console.log(` Requires: ${this.strategyTiers.adaptive.minBalance.toLocaleString()} sats ($100+)\n`); } } /** * Reset position manager */ reset() { this.positions = []; this.totalExposure = 0; } } module.exports = SimplePositionManager;