Files
trading_bot_v3/lib/simplified-stop-loss-learner-fixed.js
mindesbunister d5bf485e72 fix: Complete automation v2 page runtime error fixes
- Fixed ES modules error by converting automation-with-learning-v2.js to pure ES6
- Fixed singleton pattern in automation-singleton.js for proper async handling
- Fixed EnhancedAILearningPanel to handle recommendation objects correctly
- Updated API routes to use correct import paths (../../../../lib/)
- Created proper db.js utility with ES6 exports
- Fixed simplified-stop-loss-learner imports and exports

 Automation v2 page now loads without errors
 AI learning system fully integrated and operational
 Learning status API working with detailed reports
 Recommendation rendering fixed for object structure
2025-07-27 12:38:32 +02:00

531 lines
17 KiB
JavaScript

/**
* Simplified Stop Loss Learning System
*
* Simplified approach focusing on essential learning patterns
* without complex statistical analysis.
*/
import { PrismaClient } from '@prisma/client';
import { getDB } from './db.js';
class SimplifiedStopLossLearner {
constructor() {
this.learningThresholds = {
emergency: 1.0, // Emergency exit at 1% from SL
risk: 2.0, // High risk at 2% from SL
mediumRisk: 5.0 // Medium risk at 5% from SL
};
}
async log(message) {
console.log(`[${new Date().toISOString()}] 🧠 SL Learner: ${message}`);
}
/**
* Record a stop loss related decision for learning
*/
async recordDecision(decisionData) {
try {
const learningRecord = {
type: 'STOP_LOSS_DECISION',
tradeId: decisionData.tradeId,
symbol: decisionData.symbol,
decision: decisionData.decision,
distanceFromSL: decisionData.distanceFromSL,
reasoning: decisionData.reasoning,
marketConditions: decisionData.marketConditions,
expectedOutcome: decisionData.expectedOutcome,
timestamp: new Date().toISOString()
};
const prisma = await getDB();
const record = await prisma.ai_learning_data.create({
data: {
id: `sl_decision_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
userId: 'default-user',
symbol: decisionData.symbol,
timeframe: 'DECISION',
analysisData: JSON.stringify(learningRecord),
marketConditions: JSON.stringify(decisionData.marketConditions || {}),
confidenceScore: 50 // Neutral starting confidence
}
});
await this.log(`📝 Decision recorded: ${decisionData.decision} for ${decisionData.symbol} at ${decisionData.distanceFromSL}%`);
return record.id;
} catch (error) {
await this.log(`❌ Error recording decision: ${error.message}`);
return null;
}
}
/**
* Update the outcome of a previously recorded decision
*/
async assessDecisionOutcome(outcomeData) {
try {
const prisma = await getDB();
// Find the original decision record
const originalRecord = await prisma.ai_learning_data.findUnique({
where: { id: outcomeData.decisionId }
});
if (!originalRecord) {
await this.log(`⚠️ Original decision ${outcomeData.decisionId} not found`);
return false;
}
// Parse the original decision data
const originalDecision = JSON.parse(originalRecord.analysisData);
// Create outcome record with learning data
const outcomeRecord = {
type: 'STOP_LOSS_OUTCOME',
originalDecisionId: outcomeData.decisionId,
actualOutcome: outcomeData.actualOutcome,
timeToOutcome: outcomeData.timeToOutcome,
pnlImpact: outcomeData.pnlImpact,
wasCorrect: this.evaluateDecisionCorrectness(originalDecision, outcomeData),
learningData: {
originalDecision: originalDecision.decision,
distanceFromSL: originalDecision.distanceFromSL,
outcome: outcomeData.actualOutcome,
profitability: outcomeData.pnlImpact > 0 ? 'PROFITABLE' : 'LOSS'
},
timestamp: new Date().toISOString()
};
await prisma.ai_learning_data.create({
data: {
id: `sl_outcome_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
userId: 'default-user',
symbol: originalDecision.symbol,
timeframe: 'OUTCOME',
analysisData: JSON.stringify(outcomeRecord),
marketConditions: originalRecord.marketConditions,
confidenceScore: outcomeRecord.wasCorrect ? 75 : 25
}
});
await this.log(`✅ Outcome assessed for ${outcomeData.decisionId}: ${outcomeData.actualOutcome} (${outcomeRecord.wasCorrect ? 'CORRECT' : 'INCORRECT'})`);
// Update learning thresholds based on outcomes
await this.updateThresholdsFromOutcome(originalDecision, outcomeRecord);
return true;
} catch (error) {
await this.log(`❌ Error assessing outcome: ${error.message}`);
return false;
}
}
/**
* Evaluate if the original decision was correct based on outcome
*/
evaluateDecisionCorrectness(originalDecision, outcome) {
const decision = originalDecision.decision;
const actualOutcome = outcome.actualOutcome;
const pnlImpact = outcome.pnlImpact;
// Define what constitutes a "correct" decision
if (decision === 'EMERGENCY_EXIT' && (actualOutcome === 'STOPPED_OUT' || pnlImpact < -50)) {
return true; // Correctly identified emergency
}
if (decision === 'HOLD_POSITION' && pnlImpact > 0) {
return true; // Correctly held profitable position
}
if (decision === 'ADJUST_STOP_LOSS' && actualOutcome === 'TAKE_PROFIT') {
return true; // Adjustment led to profitable exit
}
return false;
}
/**
* Get smart recommendation based on learned patterns
*/
async getSmartRecommendation(requestData) {
try {
const { distanceFromSL, symbol, marketConditions } = requestData;
// Get historical data for similar situations
const prisma = await getDB();
const similarDecisions = await prisma.ai_learning_data.findMany({
where: {
symbol: symbol,
analysisData: {
string_contains: '"type":"STOP_LOSS_DECISION"'
}
},
orderBy: { createdAt: 'desc' },
take: 20
});
// Analyze patterns from similar situations
let recommendation = this.getBaseRecommendation(distanceFromSL);
if (similarDecisions.length >= 3) {
const learnedRecommendation = await this.analyzePatterns(similarDecisions, distanceFromSL);
if (learnedRecommendation) {
recommendation = learnedRecommendation;
}
}
await this.log(`🎯 Smart recommendation for ${symbol} at ${distanceFromSL}%: ${recommendation.action}`);
return recommendation;
} catch (error) {
await this.log(`❌ Error getting smart recommendation: ${error.message}`);
return this.getBaseRecommendation(distanceFromSL);
}
}
/**
* Get base recommendation using current thresholds
*/
getBaseRecommendation(distanceFromSL) {
if (distanceFromSL <= this.learningThresholds.emergency) {
return {
action: 'EMERGENCY_EXIT',
confidence: 0.8,
reasoning: `Very close to SL (${distanceFromSL}%), emergency exit recommended`
};
} else if (distanceFromSL <= this.learningThresholds.risk) {
return {
action: 'HIGH_ALERT',
confidence: 0.7,
reasoning: `Close to SL (${distanceFromSL}%), monitor closely`
};
} else if (distanceFromSL <= this.learningThresholds.mediumRisk) {
return {
action: 'MONITOR',
confidence: 0.6,
reasoning: `Moderate distance from SL (${distanceFromSL}%), continue monitoring`
};
} else {
return {
action: 'HOLD_POSITION',
confidence: 0.5,
reasoning: `Safe distance from SL (${distanceFromSL}%), maintain position`
};
}
}
/**
* Analyze historical patterns to improve recommendations
*/
async analyzePatterns(decisions, currentDistance) {
const outcomes = await this.getOutcomesForDecisions(decisions);
// Find decisions made at similar distances
const similarDistanceDecisions = decisions.filter(d => {
const data = JSON.parse(d.analysisData);
const distance = data.distanceFromSL;
return Math.abs(distance - currentDistance) <= 1.0; // Within 1%
});
if (similarDistanceDecisions.length < 2) {
return null; // Not enough similar data
}
// Analyze success rate of different actions at this distance
const actionSuccess = {};
for (const decision of similarDistanceDecisions) {
const decisionData = JSON.parse(decision.analysisData);
const action = decisionData.decision;
const outcome = outcomes.find(o => o.originalDecisionId === decision.id);
if (outcome) {
if (!actionSuccess[action]) {
actionSuccess[action] = { total: 0, successful: 0 };
}
actionSuccess[action].total++;
if (outcome.wasCorrect) {
actionSuccess[action].successful++;
}
}
}
// Find the action with highest success rate
let bestAction = null;
let bestSuccessRate = 0;
for (const [action, stats] of Object.entries(actionSuccess)) {
if (stats.total >= 2) { // Need at least 2 samples
const successRate = stats.successful / stats.total;
if (successRate > bestSuccessRate) {
bestSuccessRate = successRate;
bestAction = action;
}
}
}
if (bestAction && bestSuccessRate > 0.6) {
return {
action: bestAction,
confidence: bestSuccessRate,
reasoning: `Learned pattern: ${bestAction} successful ${Math.round(bestSuccessRate * 100)}% of time at this distance`
};
}
return null;
}
/**
* Get outcomes for a set of decisions
*/
async getOutcomesForDecisions(decisions) {
const prisma = await getDB();
const decisionIds = decisions.map(d => d.id);
const outcomes = await prisma.ai_learning_data.findMany({
where: {
analysisData: {
string_contains: '"type":"STOP_LOSS_OUTCOME"'
}
}
});
return outcomes.map(o => JSON.parse(o.analysisData))
.filter(outcome => decisionIds.includes(outcome.originalDecisionId));
}
/**
* Update learning thresholds based on outcome data
*/
async updateThresholdsFromOutcome(originalDecision, outcome) {
// Simple threshold adjustment based on outcomes
const distance = originalDecision.distanceFromSL;
const wasCorrect = outcome.wasCorrect;
if (!wasCorrect) {
// If decision was wrong, adjust thresholds slightly
if (originalDecision.decision === 'HOLD_POSITION' && outcome.actualOutcome === 'STOPPED_OUT') {
// We should have exited earlier - make thresholds more conservative
this.learningThresholds.emergency = Math.min(2.0, this.learningThresholds.emergency + 0.1);
this.learningThresholds.risk = Math.min(3.0, this.learningThresholds.risk + 0.1);
}
}
await this.log(`🔧 Thresholds updated: emergency=${this.learningThresholds.emergency}, risk=${this.learningThresholds.risk}`);
}
/**
* Get current learning status and statistics
*/
async analyzeDecisionPatterns() {
try {
const prisma = await getDB();
// Get recent decisions and outcomes
const decisions = await prisma.ai_learning_data.findMany({
where: {
analysisData: {
string_contains: '"type":"STOP_LOSS_DECISION"'
},
createdAt: {
gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) // Last 7 days
}
},
orderBy: { createdAt: 'desc' }
});
const outcomes = await prisma.ai_learning_data.findMany({
where: {
analysisData: {
string_contains: '"type":"STOP_LOSS_OUTCOME"'
},
createdAt: {
gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) // Last 7 days
}
}
});
// Analyze patterns
const patterns = {
totalDecisions: decisions.length,
totalOutcomes: outcomes.length,
successfulDecisions: outcomes.filter(o => JSON.parse(o.analysisData).wasCorrect).length,
successRate: outcomes.length > 0 ? outcomes.filter(o => JSON.parse(o.analysisData).wasCorrect).length / outcomes.length : 0,
learnedThresholds: this.learningThresholds
};
return patterns;
} catch (error) {
await this.log(`❌ Error analyzing patterns: ${error.message}`);
return {
totalDecisions: 0,
totalOutcomes: 0,
successfulDecisions: 0,
successRate: 0,
learnedThresholds: this.learningThresholds
};
}
}
/**
* Get learning status
*/
async getLearningStatus() {
try {
const prisma = await getDB();
const totalDecisions = await prisma.ai_learning_data.count({
where: {
analysisData: {
string_contains: '"type":"STOP_LOSS_DECISION"'
}
}
});
const recentDecisions = await prisma.ai_learning_data.count({
where: {
analysisData: {
string_contains: '"type":"STOP_LOSS_DECISION"'
},
createdAt: {
gte: new Date(Date.now() - 24 * 60 * 60 * 1000) // Last 24 hours
}
}
});
return {
totalDecisions,
recentDecisions,
thresholds: this.learningThresholds,
isActive: totalDecisions > 0
};
} catch (error) {
await this.log(`❌ Error getting learning status: ${error.message}`);
return {
totalDecisions: 0,
recentDecisions: 0,
thresholds: this.learningThresholds,
isActive: false
};
}
}
/**
* Generate comprehensive learning report
* Compatible implementation for enhanced-autonomous-risk-manager
*/
async generateLearningReport() {
try {
const status = await this.getLearningStatus();
const patterns = await this.analyzeDecisionPatterns();
// Calculate system confidence based on decisions made
const systemConfidence = this.calculateSystemConfidence(status.totalDecisions, status.recentDecisions, patterns.successRate);
const report = {
timestamp: new Date().toISOString(),
summary: {
totalDecisions: status.totalDecisions,
recentDecisions: status.recentDecisions,
successfulPatterns: patterns.successfulDecisions,
learningThresholds: this.learningThresholds,
systemConfidence: systemConfidence,
isActive: status.isActive,
successRate: patterns.successRate
},
insights: {
emergencyThreshold: this.learningThresholds.emergency,
riskThreshold: this.learningThresholds.risk,
mediumRiskThreshold: this.learningThresholds.mediumRisk,
confidenceLevel: systemConfidence > 0.7 ? 'HIGH' : systemConfidence > 0.4 ? 'MEDIUM' : 'LOW',
totalOutcomes: patterns.totalOutcomes,
decisionAccuracy: patterns.successRate
},
recommendations: this.generateSystemRecommendations(status, patterns)
};
await this.log(`📊 Learning report generated: ${report.summary.totalDecisions} decisions, ${(systemConfidence * 100).toFixed(1)}% confidence, ${(patterns.successRate * 100).toFixed(1)}% success rate`);
return report;
} catch (error) {
await this.log(`❌ Error generating learning report: ${error.message}`);
return {
timestamp: new Date().toISOString(),
summary: {
totalDecisions: 0,
recentDecisions: 0,
systemConfidence: 0.0,
isActive: false
},
error: error.message
};
}
}
/**
* Calculate system confidence based on learning data
*/
calculateSystemConfidence(totalDecisions, recentDecisions, successRate = 0) {
if (totalDecisions < 5) return 0.3; // Low confidence with insufficient data
if (totalDecisions < 20) return 0.4 + (successRate * 0.2); // Medium-low confidence boosted by success
if (totalDecisions < 50) return 0.6 + (successRate * 0.2); // Medium confidence boosted by success
// High confidence with lots of data, scaled by recent activity and success rate
const recentActivityFactor = Math.min(1.0, recentDecisions / 10);
const successFactor = successRate || 0.5; // Default to neutral if no success data
return Math.min(0.95, 0.7 + (recentActivityFactor * 0.1) + (successFactor * 0.15)); // Cap at 95%
}
/**
* Generate system recommendations based on learning status
*/
generateSystemRecommendations(status, patterns) {
const recommendations = [];
if (status.totalDecisions < 10) {
recommendations.push({
type: 'DATA_COLLECTION',
message: 'Need more decision data for reliable learning',
priority: 'HIGH'
});
}
if (status.recentDecisions < 3) {
recommendations.push({
type: 'ACTIVITY_LOW',
message: 'Recent trading activity is low - learning may be stale',
priority: 'MEDIUM'
});
}
if (patterns && patterns.successRate < 0.4 && patterns.totalOutcomes >= 5) {
recommendations.push({
type: 'THRESHOLD_ADJUSTMENT',
message: 'Low success rate detected - consider adjusting decision thresholds',
priority: 'HIGH'
});
}
if (status.totalDecisions >= 20 && patterns && patterns.successRate > 0.6) {
recommendations.push({
type: 'SYSTEM_PERFORMING',
message: 'System learning effectively with good success rate',
priority: 'LOW'
});
}
if (status.totalDecisions >= 50) {
recommendations.push({
type: 'OPTIMIZATION_READY',
message: 'Sufficient data available for advanced threshold optimization',
priority: 'LOW'
});
}
return recommendations;
}
}
export { SimplifiedStopLossLearner };