#!/usr/bin/env node /** * Complete Risk/Reward Learning System * * This enhanced system learns from BOTH stop losses AND take profits to optimize * the AI's risk/reward settings and position management decisions. */ const { getDB } = require('./db'); class RiskRewardLearner { constructor() { this.setupHistory = []; this.patterns = { optimal_rr_ratios: {}, market_condition_adjustments: {}, symbol_specific_patterns: {} }; } async getPrisma() { return await getDB(); } async log(message) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] 🎯 RR Learner: ${message}`); } /** * Record a complete risk/reward setup for learning */ async recordRiskRewardSetup(setupData) { try { const setup = { id: `rr_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, tradeId: setupData.tradeId, symbol: setupData.symbol, entryPrice: setupData.entryPrice, stopLoss: setupData.stopLoss, takeProfit: setupData.takeProfit, leverage: setupData.leverage, side: setupData.side, // Calculate initial risk/reward metrics stopLossDistance: this.calculateDistance(setupData.entryPrice, setupData.stopLoss, setupData.side), takeProfitDistance: this.calculateDistance(setupData.entryPrice, setupData.takeProfit, setupData.side), riskRewardRatio: this.calculateRiskRewardRatio(setupData), // Market context when setup was made marketConditions: { volatility: await this.calculateVolatility(setupData.symbol), trend: await this.analyzeMarketTrend(setupData.symbol), timeOfDay: new Date().getHours(), dayOfWeek: new Date().getDay(), aiConfidence: setupData.aiConfidence || 0.7 }, // AI reasoning for the setup aiReasoning: setupData.aiReasoning || 'Standard risk/reward setup', expectedOutcome: setupData.expectedOutcome || 'REACH_TAKE_PROFIT', setupTimestamp: new Date(), status: 'ACTIVE' }; // Store in database const prisma = await this.getPrisma(); await prisma.riskRewardSetup.create({ data: { id: setup.id, tradeId: setup.tradeId, symbol: setup.symbol, entryPrice: setup.entryPrice, stopLoss: setup.stopLoss, takeProfit: setup.takeProfit, leverage: setup.leverage, side: setup.side, stopLossDistance: setup.stopLossDistance, takeProfitDistance: setup.takeProfitDistance, riskRewardRatio: setup.riskRewardRatio, marketConditions: JSON.stringify(setup.marketConditions), aiReasoning: setup.aiReasoning, expectedOutcome: setup.expectedOutcome, setupTimestamp: setup.setupTimestamp, status: setup.status } }); this.learningHistory.push(setup); await this.log(`📝 Recorded R/R setup: ${setup.symbol} SL=${setup.stopLossDistance.toFixed(2)}% TP=${setup.takeProfitDistance.toFixed(2)}% Ratio=1:${setup.riskRewardRatio.toFixed(2)}`); return setup.id; } catch (error) { await this.log(`❌ Error recording R/R setup: ${error.message}`); return null; } } /** * Record the final outcome when position closes */ async recordTradeOutcome(outcomeData) { try { const { setupId, exitPrice, exitReason, actualPnL, timeToExit } = outcomeData; // Determine outcome quality const outcomeAnalysis = this.analyzeOutcomeQuality(outcomeData); // Update setup record with outcome // Update setup in database const prisma = await this.getPrisma(); await prisma.riskRewardSetup.update({ where: { id: setupId }, data: { exitPrice, exitReason, // 'STOP_LOSS', 'TAKE_PROFIT', 'MANUAL_EXIT', 'LIQUIDATION' actualPnL, timeToExit, outcomeQuality: outcomeAnalysis.quality, learningScore: outcomeAnalysis.score, actualRiskReward: outcomeAnalysis.actualRR, exitTimestamp: new Date(), status: 'COMPLETED', learningData: JSON.stringify({ wasOptimal: outcomeAnalysis.wasOptimal, improvements: outcomeAnalysis.suggestedImprovements, marketBehavior: outcomeAnalysis.marketBehavior }) } }); await this.log(`✅ Recorded outcome: ${exitReason} - Quality: ${outcomeAnalysis.quality} (Score: ${outcomeAnalysis.score.toFixed(2)})`); // Trigger learning update await this.updateRiskRewardLearning(); return outcomeAnalysis; } catch (error) { await this.log(`❌ Error recording trade outcome: ${error.message}`); return null; } } /** * Analyze the quality of a risk/reward setup outcome */ analyzeOutcomeQuality(outcomeData) { const { exitReason, actualPnL, setupData } = outcomeData; let quality = 'UNKNOWN'; let score = 0.5; let wasOptimal = false; let suggestedImprovements = []; let actualRR = 0; if (setupData) { actualRR = Math.abs(actualPnL) / Math.abs(setupData.stopLossDistance * setupData.entryPrice / 100); } switch (exitReason) { case 'TAKE_PROFIT': // Excellent - AI's take profit was hit quality = 'EXCELLENT'; score = 0.9; wasOptimal = true; if (actualRR > setupData?.riskRewardRatio * 1.2) { suggestedImprovements.push('Consider setting take profit even higher in similar conditions'); } break; case 'STOP_LOSS': // Stop loss hit - analyze if it was appropriate if (actualPnL > -(setupData?.stopLossDistance * setupData?.entryPrice / 100) * 0.8) { quality = 'GOOD'; // Stop loss worked as intended score = 0.6; wasOptimal = true; } else { quality = 'POOR'; // Stop loss was too tight or poorly placed score = 0.3; suggestedImprovements.push('Consider wider stop loss in similar market conditions'); } break; case 'MANUAL_EXIT': // Manual exit - analyze timing and P&L if (actualPnL > 0) { if (actualPnL >= setupData?.takeProfitDistance * setupData?.entryPrice / 100 * 0.8) { quality = 'GOOD'; // Took profit manually near target score = 0.7; } else { quality = 'FAIR'; // Took profit early score = 0.5; suggestedImprovements.push('Consider holding longer to reach full take profit'); } } else { quality = 'POOR'; // Manual exit at loss score = 0.2; suggestedImprovements.push('Consider trusting stop loss instead of manual exit'); } break; case 'LIQUIDATION': // Liquidation - very poor outcome quality = 'TERRIBLE'; score = 0.1; suggestedImprovements.push('Reduce leverage significantly', 'Use wider stop loss', 'Better position sizing'); break; default: quality = 'UNKNOWN'; score = 0.3; } return { quality, score, wasOptimal, suggestedImprovements, actualRR, marketBehavior: this.analyzeMarketBehaviorDuringTrade(outcomeData) }; } /** * Learn from risk/reward patterns and optimize future setups */ async updateRiskRewardLearning() { try { const prisma = await this.getPrisma(); const recentSetups = await prisma.riskRewardSetup.findMany({ where: { status: 'COMPLETED' }, orderBy: { setupTimestamp: 'desc' }, take: 100 }); if (recentSetups.length < 5) { await this.log('📊 Insufficient data for learning (need at least 5 completed trades)'); return; } // Analyze patterns const patterns = { stopLossPatterns: this.analyzeStopLossPatterns(recentSetups), takeProfitPatterns: this.analyzeTakeProfitPatterns(recentSetups), optimalRatios: this.findOptimalRiskRewardRatios(recentSetups), timeBasedPatterns: this.analyzeTimeBasedPatterns(recentSetups), volatilityPatterns: this.analyzeVolatilityPatterns(recentSetups) }; // Update learning patterns this.riskRewardPatterns = patterns; await this.log(`🧠 Updated R/R learning: ${patterns.stopLossPatterns.length} SL patterns, ${patterns.takeProfitPatterns.length} TP patterns`); return patterns; } catch (error) { await this.log(`❌ Error updating R/R learning: ${error.message}`); return null; } } /** * Analyze stop loss effectiveness patterns */ analyzeStopLossPatterns(setups) { const patterns = []; // Group by stop loss distance ranges const slRanges = [ { min: 0, max: 1, label: 'Tight (0-1%)' }, { min: 1, max: 3, label: 'Normal (1-3%)' }, { min: 3, max: 5, label: 'Wide (3-5%)' }, { min: 5, max: 100, label: 'Very Wide (>5%)' } ]; for (const range of slRanges) { const rangeSetups = setups.filter(s => s.stopLossDistance >= range.min && s.stopLossDistance < range.max ); if (rangeSetups.length >= 3) { const stopLossHits = rangeSetups.filter(s => s.exitReason === 'STOP_LOSS'); const takeProfitHits = rangeSetups.filter(s => s.exitReason === 'TAKE_PROFIT'); const avgScore = rangeSetups.reduce((sum, s) => sum + (s.learningScore || 0), 0) / rangeSetups.length; patterns.push({ range: range.label, distanceRange: [range.min, range.max], totalSetups: rangeSetups.length, stopLossHitRate: (stopLossHits.length / rangeSetups.length) * 100, takeProfitHitRate: (takeProfitHits.length / rangeSetups.length) * 100, avgLearningScore: avgScore, effectiveness: avgScore > 0.6 ? 'HIGH' : avgScore > 0.4 ? 'MEDIUM' : 'LOW', recommendation: this.generateStopLossRecommendation(rangeSetups, avgScore) }); } } return patterns.sort((a, b) => b.avgLearningScore - a.avgLearningScore); } /** * Analyze take profit effectiveness patterns */ analyzeTakeProfitPatterns(setups) { const patterns = []; // Group by risk/reward ratios const rrRanges = [ { min: 0, max: 1, label: 'Conservative (1:0-1)' }, { min: 1, max: 2, label: 'Balanced (1:1-2)' }, { min: 2, max: 3, label: 'Aggressive (1:2-3)' }, { min: 3, max: 100, label: 'Very Aggressive (1:3+)' } ]; for (const range of rrRanges) { const rangeSetups = setups.filter(s => s.riskRewardRatio >= range.min && s.riskRewardRatio < range.max ); if (rangeSetups.length >= 3) { const takeProfitHits = rangeSetups.filter(s => s.exitReason === 'TAKE_PROFIT'); const avgPnL = rangeSetups.reduce((sum, s) => sum + (s.actualPnL || 0), 0) / rangeSetups.length; const avgScore = rangeSetups.reduce((sum, s) => sum + (s.learningScore || 0), 0) / rangeSetups.length; patterns.push({ range: range.label, rrRange: [range.min, range.max], totalSetups: rangeSetups.length, takeProfitHitRate: (takeProfitHits.length / rangeSetups.length) * 100, avgPnL, avgLearningScore: avgScore, profitability: avgPnL > 0 ? 'PROFITABLE' : 'UNPROFITABLE', recommendation: this.generateTakeProfitRecommendation(rangeSetups, avgScore, avgPnL) }); } } return patterns.sort((a, b) => b.avgLearningScore - a.avgLearningScore); } /** * Find optimal risk/reward ratios for different market conditions */ findOptimalRiskRewardRatios(setups) { const optimalRatios = []; // Group by market conditions const conditionGroups = { 'High Volatility': setups.filter(s => this.getVolatility(s) > 0.08), 'Medium Volatility': setups.filter(s => this.getVolatility(s) >= 0.04 && this.getVolatility(s) <= 0.08), 'Low Volatility': setups.filter(s => this.getVolatility(s) < 0.04), 'Bullish Trend': setups.filter(s => this.getTrend(s) === 'BULLISH'), 'Bearish Trend': setups.filter(s => this.getTrend(s) === 'BEARISH'), 'Sideways Market': setups.filter(s => this.getTrend(s) === 'SIDEWAYS') }; for (const [condition, conditionSetups] of Object.entries(conditionGroups)) { if (conditionSetups.length >= 5) { const excellentSetups = conditionSetups.filter(s => s.outcomeQuality === 'EXCELLENT'); if (excellentSetups.length >= 2) { const avgOptimalRR = excellentSetups.reduce((sum, s) => sum + s.riskRewardRatio, 0) / excellentSetups.length; const avgOptimalSL = excellentSetups.reduce((sum, s) => sum + s.stopLossDistance, 0) / excellentSetups.length; optimalRatios.push({ condition, sampleSize: conditionSetups.length, excellentSamples: excellentSetups.length, optimalRiskReward: avgOptimalRR, optimalStopLoss: avgOptimalSL, successRate: (excellentSetups.length / conditionSetups.length) * 100, confidence: Math.min(0.95, excellentSetups.length / 10) // Max 95% confidence }); } } } return optimalRatios.sort((a, b) => b.confidence - a.confidence); } /** * Get smart risk/reward recommendation for current setup */ async getSmartRiskRewardRecommendation(requestData) { try { const { symbol, entryPrice, side, marketConditions } = requestData; // Get current market context const currentVolatility = marketConditions?.volatility || await this.calculateVolatility(symbol); const currentTrend = marketConditions?.trend || await this.analyzeMarketTrend(symbol); // Find best matching patterns const matchingPatterns = this.riskRewardPatterns.optimalRatios.filter(pattern => { if (currentVolatility > 0.08 && pattern.condition.includes('High Volatility')) return true; if (currentVolatility < 0.04 && pattern.condition.includes('Low Volatility')) return true; if (currentTrend === 'BULLISH' && pattern.condition.includes('Bullish')) return true; if (currentTrend === 'BEARISH' && pattern.condition.includes('Bearish')) return true; return false; }); let recommendation = { stopLossDistance: 2.5, // Default 2.5% riskRewardRatio: 2.0, // Default 1:2 confidence: 0.3, reasoning: 'Using default values - insufficient learning data', learningBased: false }; if (matchingPatterns.length > 0) { const bestPattern = matchingPatterns.reduce((best, current) => current.confidence > best.confidence ? current : best ); const stopLoss = side === 'LONG' ? entryPrice * (1 - bestPattern.optimalStopLoss / 100) : entryPrice * (1 + bestPattern.optimalStopLoss / 100); const takeProfitDistance = bestPattern.optimalStopLoss * bestPattern.optimalRiskReward; const takeProfit = side === 'LONG' ? entryPrice * (1 + takeProfitDistance / 100) : entryPrice * (1 - takeProfitDistance / 100); recommendation = { stopLoss, takeProfit, stopLossDistance: bestPattern.optimalStopLoss, takeProfitDistance, riskRewardRatio: bestPattern.optimalRiskReward, confidence: bestPattern.confidence, reasoning: `Based on ${bestPattern.excellentSamples} excellent outcomes in ${bestPattern.condition}`, learningBased: true, patternMatch: bestPattern.condition, historicalSuccessRate: bestPattern.successRate }; } await this.log(`🎯 R/R Recommendation: SL=${recommendation.stopLossDistance?.toFixed(2)}% RR=1:${recommendation.riskRewardRatio.toFixed(2)} (${(recommendation.confidence * 100).toFixed(1)}% confidence)`); return recommendation; } catch (error) { await this.log(`❌ Error generating R/R recommendation: ${error.message}`); return { stopLossDistance: 2.5, riskRewardRatio: 2.0, confidence: 0.1, reasoning: `Error in recommendation system: ${error.message}`, learningBased: false }; } } // Helper methods calculateDistance(entryPrice, targetPrice, side) { if (side === 'LONG') { return Math.abs((entryPrice - targetPrice) / entryPrice) * 100; } else { return Math.abs((targetPrice - entryPrice) / entryPrice) * 100; } } calculateRiskRewardRatio(setupData) { if (!setupData.stopLoss || !setupData.takeProfit) return 1.0; const riskDistance = this.calculateDistance(setupData.entryPrice, setupData.stopLoss, setupData.side); const rewardDistance = this.calculateDistance(setupData.entryPrice, setupData.takeProfit, setupData.side); return rewardDistance / riskDistance; } getVolatility(setup) { try { const conditions = JSON.parse(setup.marketConditions || '{}'); return conditions.volatility || 0.05; } catch { return 0.05; } } getTrend(setup) { try { const conditions = JSON.parse(setup.marketConditions || '{}'); return conditions.trend || 'SIDEWAYS'; } catch { return 'SIDEWAYS'; } } generateStopLossRecommendation(setups, avgScore) { if (avgScore > 0.7) return 'Optimal range - continue using'; if (avgScore > 0.5) return 'Good range with room for improvement'; return 'Consider adjusting - poor performance'; } generateTakeProfitRecommendation(setups, avgScore, avgPnL) { if (avgScore > 0.7 && avgPnL > 0) return 'Excellent - optimal risk/reward ratio'; if (avgPnL > 0) return 'Profitable but could be optimized'; return 'Needs adjustment - consider different ratio'; } analyzeMarketBehaviorDuringTrade(outcomeData) { // Simplified market behavior analysis if (outcomeData.exitReason === 'TAKE_PROFIT') return 'FAVORABLE'; if (outcomeData.exitReason === 'STOP_LOSS') return 'UNFAVORABLE'; return 'MIXED'; } async calculateVolatility(symbol) { // Mock volatility calculation - implement with real price data return Math.random() * 0.1; } async analyzeMarketTrend(symbol) { // Mock trend analysis - implement with real market data const trends = ['BULLISH', 'BEARISH', 'SIDEWAYS']; return trends[Math.floor(Math.random() * trends.length)]; } analyzeTimeBasedPatterns(setups) { // Analyze performance by time of day, day of week const timePatterns = {}; // Implementation for time-based analysis return timePatterns; } analyzeVolatilityPatterns(setups) { // Analyze performance in different volatility conditions const volPatterns = {}; // Implementation for volatility-based analysis return volPatterns; } } // Export for use in other modules module.exports = RiskRewardLearner; // Direct execution for testing if (require.main === module) { const learner = new RiskRewardLearner(); console.log('🎯 Risk/Reward Learning System'); console.log('📊 Learning from BOTH stop losses AND take profits!'); console.log('🧠 Optimizing risk/reward ratios based on real outcomes'); }