Files
trading_bot_v3/lib/risk-reward-learner.js
mindesbunister 08f9a9b541 🤖 COMPLETE: Learning-Enhanced AI with HTTP Compatibility
LEARNING INTEGRATION:
- Enhanced AI analysis service feeds historical data into OpenAI prompts
- Symbol/timeframe specific learning optimization
- Pattern recognition from past trade outcomes
- Confidence adjustment based on success rates

 HTTP COMPATIBILITY SYSTEM:
- HttpUtil with automatic curl/no-curl detection
- Node.js fallback for Docker environments without curl
- Updated all automation systems to use HttpUtil
- Production-ready error handling

 AUTONOMOUS RISK MANAGEMENT:
- Enhanced risk manager with learning integration
- Simplified learners using existing AILearningData schema
- Real-time position monitoring every 30 seconds
- Smart stop-loss decisions with AI learning

 INFRASTRUCTURE:
- Database utility for shared Prisma connections
- Beach mode status display system
- Complete error handling and recovery
- Docker container compatibility tested

Historical performance flows into OpenAI prompts before every trade.
2025-07-25 13:38:24 +02:00

548 lines
19 KiB
JavaScript

#!/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');
}