🤖 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.
This commit is contained in:
46
lib/db.js
Normal file
46
lib/db.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Database utility for Prisma client
|
||||
*
|
||||
* Provides a global Prisma instance to avoid connection issues
|
||||
*/
|
||||
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
|
||||
// Global Prisma instance
|
||||
let prisma = null;
|
||||
|
||||
/**
|
||||
* Get the global Prisma database instance
|
||||
*/
|
||||
async function getDB() {
|
||||
if (!prisma) {
|
||||
prisma = new PrismaClient();
|
||||
|
||||
// Test the connection
|
||||
try {
|
||||
await prisma.$connect();
|
||||
console.log('✅ Database connection established');
|
||||
} catch (error) {
|
||||
console.error('❌ Database connection failed:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return prisma;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection
|
||||
*/
|
||||
async function closeDB() {
|
||||
if (prisma) {
|
||||
await prisma.$disconnect();
|
||||
prisma = null;
|
||||
console.log('✅ Database connection closed');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getDB,
|
||||
closeDB
|
||||
};
|
||||
@@ -5,8 +5,9 @@
|
||||
* risk/reward setups and make smarter position management decisions.
|
||||
*/
|
||||
|
||||
const StopLossDecisionLearner = require('./stop-loss-decision-learner');
|
||||
const RiskRewardLearner = require('./risk-reward-learner');
|
||||
const SimplifiedStopLossLearner = require('./simplified-stop-loss-learner');
|
||||
const SimplifiedRiskRewardLearner = require('./simplified-risk-reward-learner');
|
||||
const HttpUtil = require('./http-util');
|
||||
const { exec } = require('child_process');
|
||||
const util = require('util');
|
||||
const execAsync = util.promisify(exec);
|
||||
@@ -14,8 +15,8 @@ const execAsync = util.promisify(exec);
|
||||
class EnhancedAutonomousRiskManager {
|
||||
constructor() {
|
||||
this.isActive = false;
|
||||
this.learner = new StopLossDecisionLearner();
|
||||
this.rrLearner = new RiskRewardLearner(); // NEW: Complete R/R learning
|
||||
this.learner = new SimplifiedStopLossLearner();
|
||||
this.rrLearner = new SimplifiedRiskRewardLearner(); // NEW: Complete R/R learning
|
||||
this.emergencyThreshold = 1.0; // Will be updated by learning system
|
||||
this.riskThreshold = 2.0;
|
||||
this.mediumRiskThreshold = 5.0;
|
||||
@@ -477,8 +478,7 @@ class EnhancedAutonomousRiskManager {
|
||||
async checkPositionStatus(symbol) {
|
||||
// Check if position is still active
|
||||
try {
|
||||
const { stdout } = await execAsync('curl -s http://localhost:9001/api/automation/position-monitor');
|
||||
const data = JSON.parse(stdout);
|
||||
const data = await HttpUtil.get('http://localhost:9001/api/automation/position-monitor');
|
||||
|
||||
if (data.success && data.monitor?.hasPosition && data.monitor.position?.symbol === symbol) {
|
||||
return data.monitor;
|
||||
@@ -547,8 +547,7 @@ class EnhancedAutonomousRiskManager {
|
||||
|
||||
async getCurrentPositionStatus(symbol) {
|
||||
try {
|
||||
const { stdout } = await execAsync('curl -s http://localhost:9001/api/automation/position-monitor');
|
||||
const data = JSON.parse(stdout);
|
||||
const data = await HttpUtil.get('http://localhost:9001/api/automation/position-monitor');
|
||||
|
||||
if (data.success && data.monitor?.hasPosition) {
|
||||
return {
|
||||
@@ -605,8 +604,7 @@ class EnhancedAutonomousRiskManager {
|
||||
async analyzeMarketConditions(symbol) {
|
||||
// Enhanced market analysis for better decision making
|
||||
try {
|
||||
const { stdout } = await execAsync('curl -s http://localhost:9001/api/automation/position-monitor');
|
||||
const data = JSON.parse(stdout);
|
||||
const data = await HttpUtil.get('http://localhost:9001/api/automation/position-monitor');
|
||||
|
||||
if (data.success && data.monitor?.position) {
|
||||
const pnl = data.monitor.position.unrealizedPnl;
|
||||
@@ -653,8 +651,7 @@ class EnhancedAutonomousRiskManager {
|
||||
|
||||
try {
|
||||
// Check current positions
|
||||
const { stdout } = await execAsync('curl -s http://localhost:9001/api/automation/position-monitor');
|
||||
const data = JSON.parse(stdout);
|
||||
const data = await HttpUtil.get('http://localhost:9001/api/automation/position-monitor');
|
||||
|
||||
if (data.success) {
|
||||
const decision = await this.analyzePosition(data.monitor);
|
||||
@@ -665,7 +662,7 @@ class EnhancedAutonomousRiskManager {
|
||||
await this.assessDecisionOutcomes();
|
||||
|
||||
} catch (error) {
|
||||
await this.log(`Error in beach mode cycle: ${error.message}`);
|
||||
await this.log(`Error in beach mode: ${error.message}`);
|
||||
}
|
||||
|
||||
// Schedule next check
|
||||
|
||||
155
lib/http-util.js
Normal file
155
lib/http-util.js
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* HTTP Utility for Node.js environments
|
||||
*
|
||||
* Handles HTTP requests with fallback from curl to built-in http module
|
||||
*/
|
||||
|
||||
const { exec } = require('child_process');
|
||||
const util = require('util');
|
||||
const http = require('http');
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
class HttpUtil {
|
||||
static curlAvailable = null;
|
||||
|
||||
static async checkCurlAvailability() {
|
||||
if (this.curlAvailable !== null) {
|
||||
return this.curlAvailable;
|
||||
}
|
||||
|
||||
try {
|
||||
await execAsync('which curl');
|
||||
this.curlAvailable = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.curlAvailable = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static async get(url) {
|
||||
const isCurlAvailable = await this.checkCurlAvailability();
|
||||
|
||||
if (isCurlAvailable) {
|
||||
return this.getWithCurl(url);
|
||||
} else {
|
||||
return this.getWithHttp(url);
|
||||
}
|
||||
}
|
||||
|
||||
static async getWithCurl(url) {
|
||||
try {
|
||||
const { stdout } = await execAsync(`curl -s ${url}`);
|
||||
return JSON.parse(stdout);
|
||||
} catch (error) {
|
||||
throw new Error(`curl request failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
static async getWithHttp(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const urlObj = new URL(url);
|
||||
|
||||
const req = http.get({
|
||||
hostname: urlObj.hostname,
|
||||
port: urlObj.port || 80,
|
||||
path: urlObj.pathname + urlObj.search,
|
||||
timeout: 5000
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const parsedData = JSON.parse(data);
|
||||
resolve(parsedData);
|
||||
} catch (parseError) {
|
||||
reject(new Error(`JSON parse error: ${parseError.message}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(new Error(`HTTP request error: ${error.message}`));
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error('Request timeout'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static async post(url, data) {
|
||||
const isCurlAvailable = await this.checkCurlAvailability();
|
||||
|
||||
if (isCurlAvailable) {
|
||||
return this.postWithCurl(url, data);
|
||||
} else {
|
||||
return this.postWithHttp(url, data);
|
||||
}
|
||||
}
|
||||
|
||||
static async postWithCurl(url, data) {
|
||||
try {
|
||||
const jsonData = JSON.stringify(data);
|
||||
const { stdout } = await execAsync(`curl -s -X POST -H "Content-Type: application/json" -d '${jsonData}' ${url}`);
|
||||
return JSON.parse(stdout);
|
||||
} catch (error) {
|
||||
throw new Error(`curl POST request failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
static async postWithHttp(url, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const urlObj = new URL(url);
|
||||
const postData = JSON.stringify(data);
|
||||
|
||||
const options = {
|
||||
hostname: urlObj.hostname,
|
||||
port: urlObj.port || 80,
|
||||
path: urlObj.pathname + urlObj.search,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(postData)
|
||||
},
|
||||
timeout: 5000
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let responseData = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
responseData += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const parsedData = JSON.parse(responseData);
|
||||
resolve(parsedData);
|
||||
} catch (parseError) {
|
||||
reject(new Error(`JSON parse error: ${parseError.message}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(new Error(`HTTP request error: ${error.message}`));
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error('Request timeout'));
|
||||
});
|
||||
|
||||
req.write(postData);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HttpUtil;
|
||||
@@ -7,19 +7,22 @@
|
||||
* the AI's risk/reward settings and position management decisions.
|
||||
*/
|
||||
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const { getDB } = require('./db');
|
||||
|
||||
class RiskRewardLearner {
|
||||
constructor() {
|
||||
this.prisma = new PrismaClient();
|
||||
this.learningHistory = [];
|
||||
this.riskRewardPatterns = {
|
||||
stopLossPatterns: [],
|
||||
takeProfitPatterns: [],
|
||||
optimalRatios: []
|
||||
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}`);
|
||||
@@ -63,7 +66,8 @@ class RiskRewardLearner {
|
||||
};
|
||||
|
||||
// Store in database
|
||||
await this.prisma.riskRewardSetup.create({
|
||||
const prisma = await this.getPrisma();
|
||||
await prisma.riskRewardSetup.create({
|
||||
data: {
|
||||
id: setup.id,
|
||||
tradeId: setup.tradeId,
|
||||
@@ -106,7 +110,9 @@ class RiskRewardLearner {
|
||||
const outcomeAnalysis = this.analyzeOutcomeQuality(outcomeData);
|
||||
|
||||
// Update setup record with outcome
|
||||
await this.prisma.riskRewardSetup.update({
|
||||
// Update setup in database
|
||||
const prisma = await this.getPrisma();
|
||||
await prisma.riskRewardSetup.update({
|
||||
where: { id: setupId },
|
||||
data: {
|
||||
exitPrice,
|
||||
@@ -223,7 +229,8 @@ class RiskRewardLearner {
|
||||
*/
|
||||
async updateRiskRewardLearning() {
|
||||
try {
|
||||
const recentSetups = await this.prisma.riskRewardSetup.findMany({
|
||||
const prisma = await this.getPrisma();
|
||||
const recentSetups = await prisma.riskRewardSetup.findMany({
|
||||
where: { status: 'COMPLETED' },
|
||||
orderBy: { setupTimestamp: 'desc' },
|
||||
take: 100
|
||||
|
||||
276
lib/simplified-risk-reward-learner.js
Normal file
276
lib/simplified-risk-reward-learner.js
Normal file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Simplified Risk Reward Learning System
|
||||
*
|
||||
* Uses existing AILearningData schema for R/R learning integration
|
||||
*/
|
||||
|
||||
const { getDB } = require('./db');
|
||||
|
||||
class SimplifiedRiskRewardLearner {
|
||||
constructor() {
|
||||
this.setupHistory = [];
|
||||
this.patterns = {
|
||||
optimal_rr_ratios: {},
|
||||
market_condition_adjustments: {},
|
||||
symbol_specific_patterns: {}
|
||||
};
|
||||
}
|
||||
|
||||
async log(message) {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[${timestamp}] 🧠 RR Learner: ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a risk/reward setup for learning
|
||||
*/
|
||||
async recordSetup(setupData) {
|
||||
try {
|
||||
const setup = {
|
||||
userId: 'system',
|
||||
analysisData: {
|
||||
type: 'RISK_REWARD_SETUP',
|
||||
entryPrice: setupData.entryPrice,
|
||||
stopLoss: setupData.stopLoss,
|
||||
takeProfit: setupData.takeProfit,
|
||||
riskRewardRatio: setupData.riskRewardRatio,
|
||||
confidence: setupData.confidence,
|
||||
marketConditions: setupData.marketConditions || {},
|
||||
reasoning: setupData.reasoning,
|
||||
timestamp: new Date().toISOString()
|
||||
},
|
||||
marketConditions: setupData.marketConditions || {},
|
||||
timeframe: setupData.timeframe || '1h',
|
||||
symbol: setupData.symbol || 'SOLUSD'
|
||||
};
|
||||
|
||||
const prisma = await getDB();
|
||||
const record = await prisma.aILearningData.create({
|
||||
data: setup
|
||||
});
|
||||
|
||||
await this.log(`📝 Recorded R/R setup ${record.id}: ${setupData.riskRewardRatio}:1 ratio`);
|
||||
this.setupHistory.push(setup);
|
||||
return record.id;
|
||||
|
||||
} catch (error) {
|
||||
await this.log(`❌ Error recording R/R setup: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update setup outcome for learning
|
||||
*/
|
||||
async updateSetupOutcome(setupId, outcomeData) {
|
||||
try {
|
||||
const prisma = await getDB();
|
||||
await prisma.aILearningData.update({
|
||||
where: { id: setupId },
|
||||
data: {
|
||||
outcome: outcomeData.outcome,
|
||||
actualPrice: outcomeData.finalPrice,
|
||||
feedbackData: {
|
||||
outcome: outcomeData.outcome,
|
||||
actualRR: outcomeData.actualRR,
|
||||
pnlPercent: outcomeData.pnlPercent,
|
||||
duration: outcomeData.duration,
|
||||
hitTarget: outcomeData.hitTarget,
|
||||
hitStopLoss: outcomeData.hitStopLoss,
|
||||
marketConditions: outcomeData.marketConditions
|
||||
},
|
||||
updatedAt: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
await this.log(`✅ Updated R/R setup ${setupId} with outcome: ${outcomeData.outcome}`);
|
||||
} catch (error) {
|
||||
await this.log(`❌ Error updating setup outcome: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze historical setups for optimal R/R ratios
|
||||
*/
|
||||
async analyzeOptimalRatios(symbol = 'SOLUSD', timeframe = '1h') {
|
||||
try {
|
||||
const prisma = await getDB();
|
||||
const setups = await prisma.aILearningData.findMany({
|
||||
where: {
|
||||
symbol,
|
||||
timeframe,
|
||||
analysisData: {
|
||||
string_contains: '"type":"RISK_REWARD_SETUP"'
|
||||
},
|
||||
outcome: {
|
||||
not: null
|
||||
}
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 100
|
||||
});
|
||||
|
||||
if (setups.length === 0) {
|
||||
await this.log(`📊 No R/R setups found for ${symbol} ${timeframe}`);
|
||||
return {
|
||||
optimalRatio: 3.0,
|
||||
confidence: 0.5,
|
||||
sampleSize: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Analyze successful setups
|
||||
const successfulSetups = setups.filter(s =>
|
||||
s.outcome === 'PROFIT' || s.feedbackData?.hitTarget
|
||||
);
|
||||
|
||||
const failedSetups = setups.filter(s =>
|
||||
s.outcome === 'LOSS' || s.feedbackData?.hitStopLoss
|
||||
);
|
||||
|
||||
if (successfulSetups.length === 0) {
|
||||
await this.log(`📊 No successful setups found for analysis`);
|
||||
return {
|
||||
optimalRatio: 3.0,
|
||||
confidence: 0.3,
|
||||
sampleSize: setups.length
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate average successful R/R ratio
|
||||
const successfulRatios = successfulSetups
|
||||
.map(s => s.analysisData?.riskRewardRatio || 3.0)
|
||||
.filter(ratio => ratio > 0 && ratio < 20); // Filter outliers
|
||||
|
||||
const avgSuccessfulRatio = successfulRatios.reduce((a, b) => a + b, 0) / successfulRatios.length;
|
||||
|
||||
// Calculate win rate
|
||||
const winRate = successfulSetups.length / setups.length;
|
||||
|
||||
// Adjust optimal ratio based on win rate
|
||||
let optimalRatio = avgSuccessfulRatio;
|
||||
if (winRate < 0.4) {
|
||||
optimalRatio = Math.max(avgSuccessfulRatio, 3.5); // Need higher R/R if win rate is low
|
||||
} else if (winRate > 0.7) {
|
||||
optimalRatio = Math.max(avgSuccessfulRatio * 0.8, 2.0); // Can use lower R/R if win rate is high
|
||||
}
|
||||
|
||||
const confidence = Math.min(0.9, 0.3 + (setups.length * 0.01) + (winRate * 0.3));
|
||||
|
||||
await this.log(`📊 Analyzed ${setups.length} setups. Win rate: ${Math.round(winRate * 100)}%, Optimal R/R: ${optimalRatio.toFixed(1)}:1`);
|
||||
|
||||
return {
|
||||
optimalRatio: Number(optimalRatio.toFixed(1)),
|
||||
confidence,
|
||||
sampleSize: setups.length,
|
||||
winRate,
|
||||
avgSuccessfulRatio: Number(avgSuccessfulRatio.toFixed(1))
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
await this.log(`❌ Error analyzing optimal ratios: ${error.message}`);
|
||||
return {
|
||||
optimalRatio: 3.0,
|
||||
confidence: 0.5,
|
||||
sampleSize: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate smart R/R recommendation
|
||||
*/
|
||||
async generateRRRecommendation(marketData) {
|
||||
try {
|
||||
const { symbol, timeframe, currentPrice, marketConditions } = marketData;
|
||||
|
||||
const analysis = await this.analyzeOptimalRatios(symbol, timeframe);
|
||||
|
||||
// Adjust based on market conditions
|
||||
let adjustedRatio = analysis.optimalRatio;
|
||||
|
||||
if (marketConditions?.volatility === 'HIGH') {
|
||||
adjustedRatio *= 1.2; // Increase R/R in high volatility
|
||||
} else if (marketConditions?.volatility === 'LOW') {
|
||||
adjustedRatio *= 0.9; // Decrease R/R in low volatility
|
||||
}
|
||||
|
||||
if (marketConditions?.trend === 'STRONG_BULLISH' || marketConditions?.trend === 'STRONG_BEARISH') {
|
||||
adjustedRatio *= 0.8; // Lower R/R in strong trends (higher probability)
|
||||
}
|
||||
|
||||
// Suggest levels based on the ratio
|
||||
const defaultStopLossPercent = 2.0; // 2% default stop loss
|
||||
const takeProfitPercent = defaultStopLossPercent * adjustedRatio;
|
||||
|
||||
await this.log(`🎯 R/R Recommendation: ${adjustedRatio.toFixed(1)}:1 ratio (${analysis.confidence * 100}% confidence)`);
|
||||
|
||||
return {
|
||||
riskRewardRatio: Number(adjustedRatio.toFixed(1)),
|
||||
stopLossPercent: defaultStopLossPercent,
|
||||
takeProfitPercent: Number(takeProfitPercent.toFixed(1)),
|
||||
confidence: analysis.confidence,
|
||||
reasoning: `Based on ${analysis.sampleSize} historical setups with ${Math.round((analysis.winRate || 0.5) * 100)}% win rate`,
|
||||
marketAdjustment: adjustedRatio !== analysis.optimalRatio
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
await this.log(`❌ Error generating R/R recommendation: ${error.message}`);
|
||||
return {
|
||||
riskRewardRatio: 3.0,
|
||||
stopLossPercent: 2.0,
|
||||
takeProfitPercent: 6.0,
|
||||
confidence: 0.5,
|
||||
reasoning: `Default R/R setup - learning system error: ${error.message}`,
|
||||
marketAdjustment: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get learning status
|
||||
*/
|
||||
async getLearningStatus() {
|
||||
try {
|
||||
const prisma = await getDB();
|
||||
const totalSetups = await prisma.aILearningData.count({
|
||||
where: {
|
||||
analysisData: {
|
||||
string_contains: '"type":"RISK_REWARD_SETUP"'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const recentSetups = await prisma.aILearningData.count({
|
||||
where: {
|
||||
analysisData: {
|
||||
string_contains: '"type":"RISK_REWARD_SETUP"'
|
||||
},
|
||||
createdAt: {
|
||||
gte: new Date(Date.now() - 24 * 60 * 60 * 1000)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
totalSetups,
|
||||
recentSetups,
|
||||
patterns: this.patterns,
|
||||
isActive: totalSetups > 0
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
await this.log(`❌ Error getting R/R learning status: ${error.message}`);
|
||||
return {
|
||||
totalSetups: 0,
|
||||
recentSetups: 0,
|
||||
patterns: this.patterns,
|
||||
isActive: false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SimplifiedRiskRewardLearner;
|
||||
278
lib/simplified-stop-loss-learner.js
Normal file
278
lib/simplified-stop-loss-learner.js
Normal file
@@ -0,0 +1,278 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Simplified Stop Loss Decision Learning System
|
||||
*
|
||||
* Uses existing AILearningData schema for learning integration
|
||||
*/
|
||||
|
||||
const { getDB } = require('./db');
|
||||
|
||||
class SimplifiedStopLossLearner {
|
||||
constructor() {
|
||||
this.decisionHistory = [];
|
||||
this.learningThresholds = {
|
||||
emergencyDistance: 1.0,
|
||||
highRiskDistance: 2.0,
|
||||
mediumRiskDistance: 5.0
|
||||
};
|
||||
}
|
||||
|
||||
async log(message) {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[${timestamp}] 🧠 SL Learner: ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an AI decision for learning (using existing schema)
|
||||
*/
|
||||
async recordDecision(decisionData) {
|
||||
try {
|
||||
const decision = {
|
||||
userId: 'system', // System decisions
|
||||
analysisData: {
|
||||
type: 'STOP_LOSS_DECISION',
|
||||
decision: decisionData.decision,
|
||||
reasoning: decisionData.reasoning,
|
||||
confidence: decisionData.confidence,
|
||||
distanceFromSL: decisionData.distanceFromSL,
|
||||
marketConditions: decisionData.marketConditions || {},
|
||||
timestamp: new Date().toISOString()
|
||||
},
|
||||
marketConditions: decisionData.marketConditions || {},
|
||||
timeframe: decisionData.timeframe || '1h',
|
||||
symbol: decisionData.symbol || 'SOLUSD'
|
||||
};
|
||||
|
||||
const prisma = await getDB();
|
||||
const record = await prisma.aILearningData.create({
|
||||
data: decision
|
||||
});
|
||||
|
||||
await this.log(`📝 Recorded decision ${record.id} for learning: ${decisionData.decision}`);
|
||||
this.decisionHistory.push(decision);
|
||||
return record.id;
|
||||
|
||||
} catch (error) {
|
||||
await this.log(`❌ Error recording decision: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update decision outcome for learning
|
||||
*/
|
||||
async updateDecisionOutcome(decisionId, outcomeData) {
|
||||
try {
|
||||
const prisma = await getDB();
|
||||
await prisma.aILearningData.update({
|
||||
where: { id: decisionId },
|
||||
data: {
|
||||
outcome: outcomeData.outcome,
|
||||
actualPrice: outcomeData.price,
|
||||
feedbackData: {
|
||||
outcome: outcomeData.outcome,
|
||||
pnlImpact: outcomeData.pnlImpact,
|
||||
timeToOutcome: outcomeData.timeToOutcome,
|
||||
wasCorrect: outcomeData.wasCorrect,
|
||||
learningScore: outcomeData.learningScore
|
||||
},
|
||||
updatedAt: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
await this.log(`✅ Updated decision ${decisionId} with outcome: ${outcomeData.outcome}`);
|
||||
} catch (error) {
|
||||
await this.log(`❌ Error updating decision outcome: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze historical decisions for patterns
|
||||
*/
|
||||
async analyzeDecisionPatterns() {
|
||||
try {
|
||||
const prisma = await getDB();
|
||||
const decisions = await prisma.aILearningData.findMany({
|
||||
where: {
|
||||
analysisData: {
|
||||
string_contains: '"type":"STOP_LOSS_DECISION"'
|
||||
}
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 50
|
||||
});
|
||||
|
||||
if (decisions.length === 0) {
|
||||
await this.log(`📊 No stop loss decisions found for pattern analysis`);
|
||||
return this.learningThresholds;
|
||||
}
|
||||
|
||||
// Basic pattern analysis
|
||||
const patterns = {
|
||||
emergencyDecisions: decisions.filter(d =>
|
||||
d.analysisData?.distanceFromSL < 1.0
|
||||
),
|
||||
highRiskDecisions: decisions.filter(d =>
|
||||
d.analysisData?.distanceFromSL >= 1.0 &&
|
||||
d.analysisData?.distanceFromSL < 2.0
|
||||
),
|
||||
successfulExits: decisions.filter(d =>
|
||||
d.outcome === 'PROFIT' || d.outcome === 'BREAK_EVEN'
|
||||
)
|
||||
};
|
||||
|
||||
await this.log(`📊 Analyzed ${decisions.length} decisions. Emergency: ${patterns.emergencyDecisions.length}, High Risk: ${patterns.highRiskDecisions.length}, Successful: ${patterns.successfulExits.length}`);
|
||||
|
||||
// Update thresholds based on success rates
|
||||
if (patterns.successfulExits.length > 5) {
|
||||
const avgSuccessDistance = patterns.successfulExits
|
||||
.map(d => d.analysisData?.distanceFromSL || 2.0)
|
||||
.reduce((a, b) => a + b, 0) / patterns.successfulExits.length;
|
||||
|
||||
this.learningThresholds.emergencyDistance = Math.max(0.5, avgSuccessDistance - 1.0);
|
||||
this.learningThresholds.highRiskDistance = Math.max(1.0, avgSuccessDistance);
|
||||
}
|
||||
|
||||
return this.learningThresholds;
|
||||
|
||||
} catch (error) {
|
||||
await this.log(`❌ Error analyzing decision patterns: ${error.message}`);
|
||||
return this.learningThresholds;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate smart recommendation based on learning (alias for compatibility)
|
||||
*/
|
||||
async getSmartRecommendation(currentSituation) {
|
||||
return await this.generateSmartRecommendation(currentSituation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate smart recommendation based on learning
|
||||
*/
|
||||
async generateSmartRecommendation(currentSituation) {
|
||||
try {
|
||||
const patterns = await this.analyzeDecisionPatterns();
|
||||
const { distanceFromSL, marketConditions, position } = currentSituation;
|
||||
|
||||
// Find similar situations
|
||||
const prisma = await getDB();
|
||||
const similarDecisions = await prisma.aILearningData.findMany({
|
||||
where: {
|
||||
analysisData: {
|
||||
string_contains: '"type":"STOP_LOSS_DECISION"'
|
||||
},
|
||||
symbol: position?.symbol || 'SOLUSD'
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 20
|
||||
});
|
||||
|
||||
let recommendation = 'HOLD';
|
||||
let confidence = 0.5;
|
||||
let reasoning = 'Default decision based on distance thresholds';
|
||||
|
||||
if (distanceFromSL < patterns.emergencyDistance) {
|
||||
recommendation = 'EMERGENCY_EXIT';
|
||||
confidence = 0.9;
|
||||
reasoning = `Critical proximity (${distanceFromSL}%) to stop loss requires immediate action`;
|
||||
} else if (distanceFromSL < patterns.highRiskDistance) {
|
||||
recommendation = 'ENHANCED_MONITORING';
|
||||
confidence = 0.7;
|
||||
reasoning = `High risk zone (${distanceFromSL}%) - increased monitoring and preparation for exit`;
|
||||
} else if (distanceFromSL < patterns.mediumRiskDistance) {
|
||||
recommendation = 'MONITOR';
|
||||
confidence = 0.6;
|
||||
reasoning = `Medium risk zone (${distanceFromSL}%) - standard monitoring`;
|
||||
}
|
||||
|
||||
// Adjust based on similar situations
|
||||
const successfulSimilar = similarDecisions.filter(d =>
|
||||
d.outcome === 'PROFIT' || d.outcome === 'BREAK_EVEN'
|
||||
);
|
||||
|
||||
if (successfulSimilar.length > 0) {
|
||||
const avgSuccessAction = successfulSimilar
|
||||
.map(d => d.analysisData?.decision)
|
||||
.filter(Boolean);
|
||||
|
||||
if (avgSuccessAction.length > 0) {
|
||||
const mostSuccessfulAction = avgSuccessAction
|
||||
.reduce((a, b, _, arr) =>
|
||||
arr.filter(v => v === a).length >= arr.filter(v => v === b).length ? a : b
|
||||
);
|
||||
|
||||
if (mostSuccessfulAction !== recommendation) {
|
||||
reasoning += `. Learning suggests ${mostSuccessfulAction} based on ${successfulSimilar.length} similar situations`;
|
||||
confidence = Math.min(0.95, confidence + 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.log(`🎯 Smart recommendation: ${recommendation} (${Math.round(confidence * 100)}% confidence)`);
|
||||
|
||||
return {
|
||||
recommendation,
|
||||
confidence,
|
||||
reasoning,
|
||||
learnedThresholds: patterns
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
await this.log(`❌ Error generating smart recommendation: ${error.message}`);
|
||||
return {
|
||||
recommendation: 'HOLD',
|
||||
confidence: 0.5,
|
||||
reasoning: `Default decision - learning system error: ${error.message}`,
|
||||
learnedThresholds: this.learningThresholds
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get learning status
|
||||
*/
|
||||
async getLearningStatus() {
|
||||
try {
|
||||
const prisma = await getDB();
|
||||
const totalDecisions = await prisma.aILearningData.count({
|
||||
where: {
|
||||
analysisData: {
|
||||
string_contains: '"type":"STOP_LOSS_DECISION"'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const recentDecisions = await prisma.aILearningData.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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SimplifiedStopLossLearner;
|
||||
@@ -6,9 +6,7 @@
|
||||
* A lightweight version that monitors positions without complex fetch operations
|
||||
*/
|
||||
|
||||
const { exec } = require('child_process');
|
||||
const util = require('util');
|
||||
const execAsync = util.promisify(exec);
|
||||
const HttpUtil = require('./http-util');
|
||||
|
||||
class StableRiskMonitor {
|
||||
constructor() {
|
||||
@@ -23,9 +21,7 @@ class StableRiskMonitor {
|
||||
|
||||
async checkPosition() {
|
||||
try {
|
||||
// Use curl instead of fetch for better Node.js compatibility
|
||||
const { stdout } = await execAsync('curl -s http://localhost:9001/api/automation/position-monitor');
|
||||
const data = JSON.parse(stdout);
|
||||
const data = await HttpUtil.get('http://localhost:9001/api/automation/position-monitor');
|
||||
|
||||
if (data.success && data.monitor) {
|
||||
return this.analyzeRisk(data.monitor);
|
||||
|
||||
@@ -7,11 +7,10 @@
|
||||
* It records every decision, tracks outcomes, and continuously improves decision-making.
|
||||
*/
|
||||
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const { getDB } = require('./db');
|
||||
|
||||
class StopLossDecisionLearner {
|
||||
constructor() {
|
||||
this.prisma = new PrismaClient();
|
||||
this.decisionHistory = [];
|
||||
this.learningThresholds = {
|
||||
emergencyDistance: 1.0,
|
||||
@@ -20,6 +19,10 @@ class StopLossDecisionLearner {
|
||||
};
|
||||
}
|
||||
|
||||
async getPrisma() {
|
||||
return await getDB();
|
||||
}
|
||||
|
||||
async log(message) {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[${timestamp}] 🧠 SL Learner: ${message}`);
|
||||
@@ -52,7 +55,8 @@ class StopLossDecisionLearner {
|
||||
};
|
||||
|
||||
// Store in database
|
||||
await this.prisma.sLDecision.create({
|
||||
const prisma = await this.getPrisma();
|
||||
await prisma.sLDecision.create({
|
||||
data: {
|
||||
id: decision.id,
|
||||
tradeId: decision.tradeId,
|
||||
@@ -92,7 +96,8 @@ class StopLossDecisionLearner {
|
||||
const learningScore = this.calculateLearningScore(wasCorrect, pnlImpact, timeToOutcome);
|
||||
|
||||
// Update decision record
|
||||
await this.prisma.sLDecision.update({
|
||||
const prisma = await this.getPrisma();
|
||||
await prisma.sLDecision.update({
|
||||
where: { id: decisionId },
|
||||
data: {
|
||||
outcome: actualOutcome,
|
||||
@@ -135,7 +140,8 @@ class StopLossDecisionLearner {
|
||||
*/
|
||||
async analyzeDecisionPatterns() {
|
||||
try {
|
||||
const decisions = await this.prisma.sLDecision.findMany({
|
||||
const prisma = await this.getPrisma();
|
||||
const decisions = await prisma.sLDecision.findMany({
|
||||
where: { status: 'ASSESSED' },
|
||||
orderBy: { decisionTimestamp: 'desc' },
|
||||
take: 100 // Analyze last 100 decisions
|
||||
@@ -438,7 +444,8 @@ class StopLossDecisionLearner {
|
||||
const { distanceFromSL, marketConditions } = currentSituation;
|
||||
const tolerance = 0.5; // 0.5% tolerance for distance matching
|
||||
|
||||
const decisions = await this.prisma.sLDecision.findMany({
|
||||
const prisma = await this.getPrisma();
|
||||
const decisions = await prisma.sLDecision.findMany({
|
||||
where: {
|
||||
status: 'ASSESSED',
|
||||
distanceFromSL: {
|
||||
|
||||
Reference in New Issue
Block a user