- Fixed network connectivity and live trading mode - Updated Drift SDK integration with proper API methods - Fixed BN type conversions and minimum order size - Fixed stop loss & take profit conditional orders - Complete risk management system now functional
421 lines
16 KiB
JavaScript
421 lines
16 KiB
JavaScript
// Simple automation service for basic start/stop functionality
|
||
class SimpleAutomation {
|
||
constructor() {
|
||
this.isRunning = false;
|
||
this.config = null;
|
||
this.intervalId = null;
|
||
this.stats = {
|
||
totalCycles: 0,
|
||
totalTrades: 0,
|
||
startTime: null,
|
||
lastActivity: null,
|
||
status: 'Stopped',
|
||
networkErrors: 0,
|
||
consecutiveErrors: 0
|
||
};
|
||
}
|
||
|
||
async start(config) {
|
||
try {
|
||
if (this.isRunning) {
|
||
return { success: false, message: 'Automation already running' };
|
||
}
|
||
|
||
// Validate basic config
|
||
if (!config.selectedTimeframes || config.selectedTimeframes.length === 0) {
|
||
return { success: false, message: 'At least one timeframe required' };
|
||
}
|
||
|
||
this.config = config;
|
||
this.isRunning = true;
|
||
this.stats.startTime = new Date().toISOString();
|
||
this.stats.status = 'Running';
|
||
this.stats.totalCycles = 0;
|
||
|
||
// Auto-enable trading when in LIVE mode
|
||
if (config.mode === 'LIVE') {
|
||
this.config.enableTrading = true;
|
||
console.log('🔥 LIVE TRADING ENABLED: Real trades will be executed');
|
||
} else {
|
||
this.config.enableTrading = false;
|
||
console.log('📊 SIMULATION MODE: Trades will be simulated only');
|
||
}
|
||
|
||
// Detect strategy
|
||
const timeframes = config.selectedTimeframes;
|
||
let strategy = 'General';
|
||
|
||
const isScalping = timeframes.includes('5') || timeframes.includes('15') || timeframes.includes('30');
|
||
const isDayTrading = timeframes.includes('60') || timeframes.includes('120');
|
||
const isSwingTrading = timeframes.includes('240') || timeframes.includes('D');
|
||
|
||
if (isScalping) strategy = 'Scalping';
|
||
else if (isDayTrading) strategy = 'Day Trading';
|
||
else if (isSwingTrading) strategy = 'Swing Trading';
|
||
|
||
console.log('SIMPLE AUTOMATION: Starting ' + strategy + ' strategy');
|
||
console.log('TIMEFRAMES: [' + timeframes.join(', ') + ']');
|
||
console.log('MODE: ' + (config.mode || 'SIMULATION'));
|
||
|
||
// Start simple monitoring cycle (10 minutes for safety)
|
||
this.intervalId = setInterval(() => {
|
||
this.runCycle();
|
||
}, 10 * 60 * 1000); // 10 minutes
|
||
|
||
// First cycle after 30 seconds
|
||
setTimeout(() => {
|
||
this.runCycle();
|
||
}, 30000);
|
||
|
||
return {
|
||
success: true,
|
||
message: strategy + ' automation started successfully',
|
||
strategy: strategy,
|
||
timeframes: timeframes
|
||
};
|
||
|
||
} catch (error) {
|
||
console.error('Failed to start automation:', error);
|
||
return { success: false, message: 'Failed to start automation: ' + error.message };
|
||
}
|
||
}
|
||
|
||
async stop() {
|
||
try {
|
||
this.isRunning = false;
|
||
this.stats.status = 'Stopped';
|
||
|
||
if (this.intervalId) {
|
||
clearInterval(this.intervalId);
|
||
this.intervalId = null;
|
||
}
|
||
|
||
console.log('SIMPLE AUTOMATION: Stopped');
|
||
|
||
return { success: true, message: 'Automation stopped successfully' };
|
||
} catch (error) {
|
||
console.error('Failed to stop automation:', error);
|
||
return { success: false, message: 'Failed to stop automation: ' + error.message };
|
||
}
|
||
}
|
||
|
||
async runCycle() {
|
||
try {
|
||
this.stats.totalCycles++;
|
||
this.stats.lastActivity = new Date().toISOString();
|
||
|
||
console.log('🔄 AUTOMATION CYCLE ' + this.stats.totalCycles + ': ' + (this.config?.symbol || 'SOLUSD'));
|
||
console.log('⏰ TIME: ' + new Date().toLocaleTimeString());
|
||
console.log('📊 STRATEGY: ' + (this.config.selectedTimeframes?.join(', ') || 'N/A') + ' timeframes');
|
||
|
||
if (this.config) {
|
||
// Perform actual analysis using enhanced screenshot API
|
||
await this.performAnalysis();
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Error in automation cycle:', error);
|
||
}
|
||
}
|
||
|
||
async performAnalysis() {
|
||
console.log(`📊 TRUE PARALLEL ANALYSIS: Starting simultaneous analysis for ${this.config.selectedTimeframes.length} timeframes...`);
|
||
console.log(`🚀 This will capture ${this.config.selectedTimeframes.length * 2} screenshots in parallel (${this.config.selectedTimeframes.length} timeframes × 2 layouts)`);
|
||
|
||
try {
|
||
// Use internal container port for server-side API calls
|
||
const baseUrl = process.env.INTERNAL_API_URL || 'http://localhost:3000';
|
||
const response = await fetch(`${baseUrl}/api/batch-analysis`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
symbol: this.config.symbol,
|
||
timeframes: this.config.selectedTimeframes, // All timeframes at once!
|
||
layouts: ['ai', 'diy'],
|
||
analyze: true
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
console.warn(`⚠️ BATCH API ERROR: ${response.status}, falling back to sequential...`);
|
||
return this.performSequentialAnalysis();
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success && result.analysis) {
|
||
// Reset consecutive error counter on success
|
||
this.stats.consecutiveErrors = 0;
|
||
|
||
console.log(`✅ PARALLEL ANALYSIS COMPLETE: ${result.totalScreenshots} screenshots captured`);
|
||
console.log(`📊 COMBINED Recommendation: ${result.analysis.recommendation}`);
|
||
console.log(`🎯 COMBINED Confidence: ${result.analysis.confidence}%`);
|
||
console.log(`⏰ Timeframes analyzed: ${result.timeframes.join(', ')}`);
|
||
|
||
// Check if we should execute a trade based on combined analysis
|
||
if (this.shouldExecuteTrade(result.analysis)) {
|
||
console.log('💰 TRADE SIGNAL: Executing trade...');
|
||
await this.executeTrade(result.analysis);
|
||
} else {
|
||
console.log('📈 SIGNAL: Combined analysis complete, no trade executed');
|
||
}
|
||
|
||
return;
|
||
} else {
|
||
console.warn('⚠️ BATCH ANALYSIS: No valid data, falling back to sequential...');
|
||
return this.performSequentialAnalysis();
|
||
}
|
||
} catch (error) {
|
||
// Track network errors
|
||
this.stats.networkErrors++;
|
||
this.stats.consecutiveErrors++;
|
||
|
||
console.error(`❌ PARALLEL ANALYSIS FAILED (Network Error #${this.stats.networkErrors}):`, error.message);
|
||
|
||
// If too many consecutive errors, slow down
|
||
if (this.stats.consecutiveErrors >= 3) {
|
||
console.warn(`⚠️ HIGH NETWORK ERROR COUNT: ${this.stats.consecutiveErrors} consecutive failures. System will continue but may slow down.`);
|
||
}
|
||
|
||
console.log(`🔄 FALLBACK: Using sequential analysis...`);
|
||
return this.performSequentialAnalysis();
|
||
}
|
||
}
|
||
|
||
// Fallback sequential analysis method
|
||
async performSequentialAnalysis() {
|
||
try {
|
||
console.log('📸 SEQUENTIAL ANALYSIS: Starting...');
|
||
|
||
const allResults = [];
|
||
|
||
// Analyze each timeframe separately
|
||
for (const timeframe of this.config.selectedTimeframes) {
|
||
console.log(`📊 ANALYZING: ${timeframe} timeframe...`);
|
||
|
||
// Use the enhanced screenshot API for each timeframe
|
||
const baseUrl = process.env.INTERNAL_API_URL || 'http://localhost:3000';
|
||
const response = await fetch(`${baseUrl}/api/enhanced-screenshot`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
symbol: this.config.symbol,
|
||
timeframe: timeframe, // Send one timeframe at a time
|
||
layouts: ['ai', 'diy'],
|
||
analyze: true
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
console.log(`⚠️ TIMEFRAME ${timeframe}: API error ${response.status}`);
|
||
continue;
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.analysis) {
|
||
console.log(`✅ TIMEFRAME ${timeframe}: ${result.analysis.recommendation} (${result.analysis.confidence}%)`);
|
||
allResults.push({
|
||
timeframe: timeframe,
|
||
analysis: result.analysis,
|
||
screenshots: result.screenshots
|
||
});
|
||
} else {
|
||
console.log(`⚠️ TIMEFRAME ${timeframe}: No analysis data`);
|
||
}
|
||
}
|
||
|
||
if (allResults.length > 0) {
|
||
console.log('✅ MULTI-TIMEFRAME ANALYSIS COMPLETE:');
|
||
|
||
// Combine results for overall decision
|
||
const combinedAnalysis = this.combineTimeframeAnalysis(allResults);
|
||
|
||
console.log('📊 COMBINED Recommendation: ' + combinedAnalysis.recommendation);
|
||
console.log('🎯 COMBINED Confidence: ' + combinedAnalysis.confidence + '%');
|
||
console.log('⏰ Timeframes analyzed: ' + allResults.map(r => r.timeframe).join(', '));
|
||
|
||
// Check if we should execute a trade based on combined analysis
|
||
if (this.shouldExecuteTrade(combinedAnalysis)) {
|
||
console.log('💰 TRADE SIGNAL: Executing trade...');
|
||
await this.executeTrade(combinedAnalysis);
|
||
} else {
|
||
console.log('📈 SIGNAL: Combined analysis complete, no trade executed');
|
||
}
|
||
} else {
|
||
console.log('⚠️ ANALYSIS: No valid analysis data from any timeframe');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ ANALYSIS ERROR:', error.message);
|
||
}
|
||
}
|
||
|
||
combineTimeframeAnalysis(results) {
|
||
if (results.length === 1) {
|
||
return results[0].analysis;
|
||
}
|
||
|
||
// Simple combination logic - can be enhanced
|
||
const recommendations = results.map(r => r.analysis.recommendation?.toLowerCase() || 'hold');
|
||
const confidences = results.map(r => r.analysis.confidence || 0);
|
||
|
||
// Count votes for each recommendation
|
||
const votes = {};
|
||
recommendations.forEach(rec => {
|
||
votes[rec] = (votes[rec] || 0) + 1;
|
||
});
|
||
|
||
// Get majority recommendation
|
||
const majorityRec = Object.keys(votes).reduce((a, b) => votes[a] > votes[b] ? a : b);
|
||
|
||
// Average confidence, but boost if multiple timeframes agree
|
||
const avgConfidence = confidences.reduce((sum, conf) => sum + conf, 0) / confidences.length;
|
||
const agreementBonus = votes[majorityRec] > 1 ? 10 : 0; // +10% if multiple agree
|
||
const finalConfidence = Math.min(95, avgConfidence + agreementBonus);
|
||
|
||
console.log(`🔄 CONSENSUS: ${majorityRec.toUpperCase()} from ${votes[majorityRec]}/${results.length} timeframes`);
|
||
|
||
return {
|
||
recommendation: majorityRec,
|
||
confidence: Math.round(finalConfidence),
|
||
reasoning: `Multi-timeframe consensus from ${results.length} timeframes (${results.map(r => r.timeframe).join(', ')})`,
|
||
timeframeResults: results
|
||
};
|
||
}
|
||
|
||
shouldExecuteTrade(analysis) {
|
||
// Always allow trade execution - the useRealDEX flag determines if it's real or simulated
|
||
console.log(`<EFBFBD> TRADE MODE: ${this.config.mode || 'SIMULATION'} - Trading ${this.config.enableTrading ? 'ENABLED' : 'DISABLED'}`);
|
||
|
||
const recommendation = analysis.recommendation?.toLowerCase() || '';
|
||
const confidence = analysis.confidence || 0;
|
||
|
||
// Strategy-specific confidence requirements
|
||
let minConfidence = 75;
|
||
if (this.config.selectedTimeframes?.includes('5') || this.config.selectedTimeframes?.includes('15')) {
|
||
minConfidence = 80; // Higher confidence for scalping
|
||
}
|
||
|
||
const isHighConfidence = confidence >= minConfidence;
|
||
const isClearDirection = recommendation.includes('buy') || recommendation.includes('sell');
|
||
|
||
console.log('🎯 TRADE DECISION: ' + recommendation + ' (' + confidence + '%) - Min: ' + minConfidence + '%');
|
||
|
||
return isHighConfidence && isClearDirection;
|
||
}
|
||
|
||
async executeTrade(analysis) {
|
||
try {
|
||
console.log('💰 EXECUTING TRADE...');
|
||
console.log('📊 Analysis data:', JSON.stringify(analysis, null, 2));
|
||
|
||
// Map analysis recommendation to trading side
|
||
const recommendation = analysis.recommendation?.toLowerCase() || '';
|
||
let side = '';
|
||
|
||
if (recommendation.includes('buy')) {
|
||
side = 'BUY';
|
||
} else if (recommendation.includes('sell')) {
|
||
side = 'SELL';
|
||
} else {
|
||
console.log('❌ TRADE SKIP: Invalid recommendation - ' + recommendation);
|
||
return { success: false, error: 'Invalid recommendation: ' + recommendation };
|
||
}
|
||
|
||
// Extract stop loss and take profit from analysis
|
||
let stopLoss = null;
|
||
let takeProfit = null;
|
||
|
||
// Try to extract from the structured analysis format
|
||
if (analysis.stopLoss && typeof analysis.stopLoss === 'object') {
|
||
stopLoss = analysis.stopLoss.price;
|
||
} else if (analysis.stopLoss && typeof analysis.stopLoss === 'number') {
|
||
stopLoss = analysis.stopLoss;
|
||
}
|
||
|
||
// Extract take profit - prefer tp1 if available
|
||
if (analysis.takeProfits && typeof analysis.takeProfits === 'object') {
|
||
if (analysis.takeProfits.tp1 && analysis.takeProfits.tp1.price) {
|
||
takeProfit = analysis.takeProfits.tp1.price;
|
||
} else if (analysis.takeProfits.tp2 && analysis.takeProfits.tp2.price) {
|
||
takeProfit = analysis.takeProfits.tp2.price;
|
||
}
|
||
} else if (analysis.takeProfit && typeof analysis.takeProfit === 'number') {
|
||
takeProfit = analysis.takeProfit;
|
||
}
|
||
|
||
// Fallback: try to extract from nested levels object
|
||
if (!stopLoss && analysis.levels) {
|
||
stopLoss = analysis.levels.stopLoss || analysis.levels.stop;
|
||
}
|
||
if (!takeProfit && analysis.levels) {
|
||
takeProfit = analysis.levels.takeProfit || analysis.levels.target;
|
||
}
|
||
|
||
// Parse numeric values if they're strings
|
||
if (stopLoss && typeof stopLoss === 'string') {
|
||
stopLoss = parseFloat(stopLoss.replace(/[^0-9.]/g, ''));
|
||
}
|
||
if (takeProfit && typeof takeProfit === 'string') {
|
||
takeProfit = parseFloat(takeProfit.replace(/[^0-9.]/g, ''));
|
||
}
|
||
|
||
console.log(`🎯 Trade levels - SL: ${stopLoss}, TP: ${takeProfit}`);
|
||
|
||
// Use the trading API with proper fields for Drift
|
||
const tradePayload = {
|
||
symbol: this.config.symbol,
|
||
side: side,
|
||
amount: this.config.tradingAmount || 49, // Use available balance
|
||
leverage: 1, // Default leverage
|
||
stopLoss: stopLoss,
|
||
takeProfit: takeProfit,
|
||
useRealDEX: this.config.enableTrading === true, // Use real DEX when live trading enabled
|
||
analysis: analysis // Include analysis for reference
|
||
};
|
||
|
||
console.log('📊 TRADE PAYLOAD:', tradePayload);
|
||
|
||
const baseUrl = process.env.INTERNAL_API_URL || 'http://localhost:3000';
|
||
const response = await fetch(`${baseUrl}/api/trading/execute-drift`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(tradePayload)
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
console.log('✅ TRADE EXECUTED: ' + result.message);
|
||
this.stats.totalTrades = (this.stats.totalTrades || 0) + 1;
|
||
this.stats.successfulTrades = (this.stats.successfulTrades || 0) + 1;
|
||
} else {
|
||
console.log('❌ TRADE FAILED: ' + result.error);
|
||
}
|
||
|
||
return result;
|
||
} catch (error) {
|
||
console.error('❌ TRADE ERROR:', error.message);
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
getStatus() {
|
||
return {
|
||
isActive: this.isRunning,
|
||
mode: this.config?.mode || 'SIMULATION',
|
||
enableTrading: this.config?.enableTrading || false,
|
||
tradingStatus: this.config?.enableTrading ? 'REAL TRADES' : 'SIMULATED ONLY',
|
||
symbol: this.config?.symbol || 'SOLUSD',
|
||
timeframes: this.config?.selectedTimeframes || [],
|
||
automationType: 'SIMPLE',
|
||
...this.stats
|
||
};
|
||
}
|
||
}
|
||
|
||
// Export singleton instance
|
||
const simpleAutomation = new SimpleAutomation();
|
||
export { simpleAutomation };
|