- Fixed Prisma schema: Added @default(cuid()) to ai_learning_data.id field - Fixed all updatedAt fields: Added @updatedAt decorators across all models - Enhanced position-aware automation with intelligent DCA/doubling down logic - Added safe automation starter script with position awareness - Resolved 'Argument id is missing' database creation errors - All AI learning data can now be created without Prisma errors Database schema now properly auto-generates IDs and timestamps for: - ai_learning_data records - All model updatedAt fields - Prevents Enhanced Risk Manager database failures
542 lines
18 KiB
JavaScript
542 lines
18 KiB
JavaScript
/**
|
|
* Position-Aware Smart Automation
|
|
*
|
|
* This addresses the user concerns:
|
|
* 1. Clear visibility of what's happening
|
|
* 2. Specific stop loss monitoring
|
|
* 3. Transparent decision making
|
|
* 4. No black box behavior
|
|
*/
|
|
|
|
// Enhanced simple automation with position awareness
|
|
class PositionAwareAutomation {
|
|
constructor() {
|
|
this.isRunning = false;
|
|
this.config = null;
|
|
this.intervalId = null;
|
|
this.positionCheckInterval = null;
|
|
this.stats = {
|
|
totalCycles: 0,
|
|
totalTrades: 0,
|
|
startTime: null,
|
|
lastActivity: null,
|
|
status: 'Stopped',
|
|
networkErrors: 0,
|
|
consecutiveErrors: 0,
|
|
lastPositionCheck: null,
|
|
positionMonitoringActive: false
|
|
};
|
|
}
|
|
|
|
async start(config) {
|
|
try {
|
|
if (this.isRunning) {
|
|
return { success: false, message: 'Automation already running' };
|
|
}
|
|
|
|
this.config = config;
|
|
this.isRunning = true;
|
|
this.stats.startTime = new Date().toISOString();
|
|
this.stats.status = 'Running';
|
|
|
|
console.log('🚀 POSITION-AWARE AUTOMATION STARTING...');
|
|
console.log('✅ AUTOMATION STATUS: isRunning =', this.isRunning);
|
|
|
|
// Check for existing positions first
|
|
const positionMonitor = await this.checkPositions();
|
|
|
|
if (positionMonitor.hasPosition) {
|
|
console.log('📊 EXISTING POSITION DETECTED:', positionMonitor.position.symbol, positionMonitor.position.side);
|
|
console.log('🎯 RISK LEVEL:', positionMonitor.riskLevel);
|
|
console.log('📍 NEXT ACTION:', positionMonitor.nextAction);
|
|
|
|
// Start position monitoring
|
|
await this.startPositionMonitoring(positionMonitor);
|
|
} else {
|
|
console.log('💰 NO POSITIONS - Starting opportunity scanning...');
|
|
|
|
// Start opportunity scanning
|
|
await this.startOpportunityScanning();
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Position-aware automation started',
|
|
hasPosition: positionMonitor.hasPosition,
|
|
positionDetails: positionMonitor.position,
|
|
riskLevel: positionMonitor.riskLevel,
|
|
nextAction: positionMonitor.nextAction
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error('Failed to start position-aware automation:', error);
|
|
return { success: false, message: 'Failed to start automation: ' + error.message };
|
|
}
|
|
}
|
|
|
|
async checkPositions() {
|
|
try {
|
|
const baseUrl = process.env.INTERNAL_API_URL || 'http://localhost:3000';
|
|
const response = await fetch(`${baseUrl}/api/automation/position-monitor`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
this.stats.lastPositionCheck = new Date().toISOString();
|
|
return data.monitor;
|
|
} else {
|
|
throw new Error('Failed to get position data');
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ Position check failed:', error);
|
|
return {
|
|
hasPosition: false,
|
|
position: null,
|
|
riskLevel: 'UNKNOWN',
|
|
nextAction: 'Retry position check',
|
|
recommendation: 'ERROR'
|
|
};
|
|
}
|
|
}
|
|
|
|
async startPositionMonitoring(positionData) {
|
|
console.log('🛡️ STARTING POSITION MONITORING MODE');
|
|
console.log(`⏰ Risk Level: ${positionData.riskLevel}`);
|
|
|
|
this.stats.positionMonitoringActive = true;
|
|
|
|
// Set monitoring frequency based on risk
|
|
let checkInterval;
|
|
switch (positionData.riskLevel) {
|
|
case 'CRITICAL':
|
|
checkInterval = 30 * 1000; // 30 seconds
|
|
break;
|
|
case 'HIGH':
|
|
checkInterval = 2 * 60 * 1000; // 2 minutes
|
|
break;
|
|
case 'MEDIUM':
|
|
checkInterval = 5 * 60 * 1000; // 5 minutes
|
|
break;
|
|
default:
|
|
checkInterval = 10 * 60 * 1000; // 10 minutes
|
|
}
|
|
|
|
console.log(`⏰ Position monitoring every ${checkInterval/1000} seconds`);
|
|
|
|
// Start position monitoring loop
|
|
this.positionCheckInterval = setInterval(async () => {
|
|
await this.runPositionCheck();
|
|
}, checkInterval);
|
|
|
|
// Run first check immediately
|
|
setTimeout(() => this.runPositionCheck(), 5000);
|
|
}
|
|
|
|
async startOpportunityScanning() {
|
|
console.log('🔍 STARTING OPPORTUNITY SCANNING MODE');
|
|
console.log('⏰ Scanning for entries every 10 minutes');
|
|
|
|
this.stats.positionMonitoringActive = false;
|
|
|
|
// Start opportunity scanning (less frequent when no position)
|
|
this.intervalId = setInterval(async () => {
|
|
await this.runOpportunityCheck();
|
|
}, 10 * 60 * 1000); // 10 minutes
|
|
|
|
// Run first check after 30 seconds
|
|
setTimeout(() => this.runOpportunityCheck(), 30000);
|
|
}
|
|
|
|
async runPositionCheck() {
|
|
try {
|
|
if (!this.isRunning) {
|
|
console.log('⏹️ POSITION CHECK STOPPED: Automation not running');
|
|
return;
|
|
}
|
|
|
|
console.log('🔍 POSITION CHECK: Monitoring stop loss proximity...');
|
|
|
|
const positionData = await this.checkPositions();
|
|
|
|
if (!positionData.hasPosition) {
|
|
console.log('📤 POSITION CLOSED: Switching to opportunity scanning...');
|
|
|
|
// Clear position monitoring
|
|
if (this.positionCheckInterval) {
|
|
clearInterval(this.positionCheckInterval);
|
|
this.positionCheckInterval = null;
|
|
}
|
|
|
|
// Start opportunity scanning
|
|
await this.startOpportunityScanning();
|
|
return;
|
|
}
|
|
|
|
console.log(`📊 Position Status: ${positionData.position.symbol} ${positionData.position.side}`);
|
|
console.log(`⚠️ Risk Level: ${positionData.riskLevel}`);
|
|
console.log(`📍 Distance to SL: ${positionData.stopLossProximity?.distancePercent}%`);
|
|
|
|
// Take action based on proximity
|
|
if (positionData.riskLevel === 'CRITICAL' || positionData.riskLevel === 'HIGH') {
|
|
console.log('🚨 RUNNING EMERGENCY ANALYSIS...');
|
|
await this.runEmergencyAnalysis(positionData);
|
|
}
|
|
|
|
this.stats.totalCycles++;
|
|
this.stats.lastActivity = new Date().toISOString();
|
|
|
|
} catch (error) {
|
|
console.error('❌ Position check error:', error);
|
|
this.stats.consecutiveErrors++;
|
|
}
|
|
}
|
|
|
|
async runOpportunityCheck() {
|
|
try {
|
|
if (!this.isRunning) {
|
|
console.log('⏹️ OPPORTUNITY CHECK STOPPED: Automation not running');
|
|
return;
|
|
}
|
|
|
|
console.log('💰 OPPORTUNITY CHECK: Scanning for entry signals...');
|
|
|
|
// Check if position was opened while we were scanning
|
|
const positionData = await this.checkPositions();
|
|
|
|
if (positionData.hasPosition) {
|
|
console.log('📈 NEW POSITION DETECTED: Switching to position monitoring...');
|
|
|
|
// Clear opportunity scanning
|
|
if (this.intervalId) {
|
|
clearInterval(this.intervalId);
|
|
this.intervalId = null;
|
|
}
|
|
|
|
// Start position monitoring
|
|
await this.startPositionMonitoring(positionData);
|
|
return;
|
|
}
|
|
|
|
// Run normal analysis for entry opportunities
|
|
console.log('🔄 Running entry analysis...');
|
|
// TODO: Add your normal analysis logic here
|
|
|
|
this.stats.totalCycles++;
|
|
this.stats.lastActivity = new Date().toISOString();
|
|
|
|
} catch (error) {
|
|
console.error('❌ Opportunity check error:', error);
|
|
this.stats.consecutiveErrors++;
|
|
}
|
|
}
|
|
|
|
async runEmergencyAnalysis(positionData) {
|
|
console.log('🚨 EMERGENCY ANALYSIS: Price approaching stop loss!');
|
|
console.log(`📊 Position: ${positionData.position.side} ${positionData.position.size} at $${positionData.position.entryPrice}`);
|
|
console.log(`💰 Current PnL: $${positionData.position.unrealizedPnl}`);
|
|
console.log(`🎯 Distance to SL: ${positionData.stopLossProximity.distancePercent}%`);
|
|
|
|
try {
|
|
// Run AI analysis to determine market reversal potential
|
|
const analysis = await this.runAIAnalysisForDCA(positionData);
|
|
|
|
if (analysis && analysis.recommendation === 'DCA_DOUBLE_DOWN') {
|
|
console.log('🎯 AI DECISION: Market showing reversal signs - DOUBLING DOWN');
|
|
console.log(`📈 Confidence: ${analysis.confidence}%`);
|
|
console.log(`💰 DCA Amount: ${analysis.dcaAmount} SOL`);
|
|
console.log(`🎯 New Average: $${analysis.newAveragePrice}`);
|
|
|
|
// Execute DCA trade
|
|
await this.executeDCATradeAction(analysis, positionData);
|
|
|
|
} else if (analysis && analysis.recommendation === 'CLOSE_EARLY') {
|
|
console.log('🛑 AI DECISION: No reversal signs - CLOSING EARLY to minimize loss');
|
|
console.log(`📉 Confidence: ${analysis.confidence}%`);
|
|
|
|
// Execute early close
|
|
await this.executeEarlyCloseAction(positionData);
|
|
|
|
} else {
|
|
console.log('⏸️ AI DECISION: HOLD position - unclear signals');
|
|
console.log('⏰ Will re-analyze in 30 seconds');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Emergency analysis failed:', error);
|
|
console.log('⏰ Next check in 30 seconds due to high risk');
|
|
}
|
|
}
|
|
|
|
async runAIAnalysisForDCA(positionData) {
|
|
console.log('🧠 Running AI DCA Analysis...');
|
|
|
|
try {
|
|
// Get fresh market analysis
|
|
const response = await fetch('http://localhost:3000/api/analysis-optimized', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
symbol: positionData.position.symbol.replace('-PERP', 'USD'),
|
|
timeframes: ['5', '15', '1h'],
|
|
layouts: ['ai'],
|
|
analyze: true,
|
|
dcaMode: true, // Special DCA analysis mode
|
|
currentPosition: positionData.position
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Analysis failed: ${response.status}`);
|
|
}
|
|
|
|
const analysisData = await response.json();
|
|
const analysis = analysisData.analysis;
|
|
|
|
if (!analysis) {
|
|
console.log('❌ No analysis returned');
|
|
return null;
|
|
}
|
|
|
|
// Determine DCA strategy based on AI analysis
|
|
const dcaDecision = this.evaluateDCAOpportunity(analysis, positionData);
|
|
|
|
return dcaDecision;
|
|
|
|
} catch (error) {
|
|
console.error('❌ AI DCA analysis failed:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
evaluateDCAOpportunity(analysis, positionData) {
|
|
const currentPrice = positionData.position.currentPrice;
|
|
const entryPrice = positionData.position.entryPrice;
|
|
const unrealizedPnl = positionData.position.unrealizedPnl;
|
|
const confidence = analysis.confidence || 0;
|
|
|
|
// Calculate price movement from entry
|
|
const priceMovement = ((currentPrice - entryPrice) / entryPrice) * 100;
|
|
const isLongPosition = positionData.position.side === 'long';
|
|
|
|
console.log(`📊 DCA Evaluation:`);
|
|
console.log(` Price Movement: ${priceMovement.toFixed(2)}%`);
|
|
console.log(` AI Confidence: ${confidence}%`);
|
|
console.log(` Analysis: ${analysis.recommendation}`);
|
|
|
|
// DCA Logic for LONG positions
|
|
if (isLongPosition) {
|
|
// Price has dropped significantly (good DCA opportunity)
|
|
const hasDropped = priceMovement < -2; // 2%+ drop
|
|
const aiSaysBuy = analysis.recommendation?.toLowerCase().includes('buy');
|
|
const highConfidence = confidence > 75;
|
|
const oversoldSignals = analysis.technicalIndicators?.rsi < 35; // Oversold
|
|
|
|
if (hasDropped && aiSaysBuy && highConfidence) {
|
|
const dcaAmount = this.calculateDCAAmount(positionData);
|
|
const newAveragePrice = this.calculateNewAveragePrice(
|
|
positionData.position.size,
|
|
entryPrice,
|
|
dcaAmount,
|
|
currentPrice
|
|
);
|
|
|
|
return {
|
|
recommendation: 'DCA_DOUBLE_DOWN',
|
|
confidence: confidence,
|
|
dcaAmount: dcaAmount,
|
|
newAveragePrice: newAveragePrice,
|
|
reasoning: `AI detects reversal: ${analysis.reasoning || 'Strong buy signal'}`
|
|
};
|
|
}
|
|
}
|
|
|
|
// DCA Logic for SHORT positions
|
|
if (!isLongPosition) {
|
|
// Price has risen significantly (good DCA opportunity for shorts)
|
|
const hasRisen = priceMovement > 2; // 2%+ rise
|
|
const aiSaysSell = analysis.recommendation?.toLowerCase().includes('sell');
|
|
const highConfidence = confidence > 75;
|
|
const overboughtSignals = analysis.technicalIndicators?.rsi > 65; // Overbought
|
|
|
|
if (hasRisen && aiSaysSell && highConfidence) {
|
|
const dcaAmount = this.calculateDCAAmount(positionData);
|
|
const newAveragePrice = this.calculateNewAveragePrice(
|
|
positionData.position.size,
|
|
entryPrice,
|
|
dcaAmount,
|
|
currentPrice
|
|
);
|
|
|
|
return {
|
|
recommendation: 'DCA_DOUBLE_DOWN',
|
|
confidence: confidence,
|
|
dcaAmount: dcaAmount,
|
|
newAveragePrice: newAveragePrice,
|
|
reasoning: `AI detects short opportunity: ${analysis.reasoning || 'Strong sell signal'}`
|
|
};
|
|
}
|
|
}
|
|
|
|
// If no DCA opportunity but low confidence, suggest early close
|
|
if (confidence < 40 && Math.abs(unrealizedPnl) > 20) {
|
|
return {
|
|
recommendation: 'CLOSE_EARLY',
|
|
confidence: 100 - confidence, // Inverse confidence for closing
|
|
reasoning: 'Low AI confidence + significant loss suggests early exit'
|
|
};
|
|
}
|
|
|
|
return {
|
|
recommendation: 'HOLD',
|
|
confidence: confidence,
|
|
reasoning: 'No clear DCA or exit signals'
|
|
};
|
|
}
|
|
|
|
calculateDCAAmount(positionData) {
|
|
// Calculate safe DCA amount (max 50% of current position)
|
|
const currentSize = positionData.position.size;
|
|
const maxDCASize = currentSize * 0.5; // Max 50% of current position
|
|
|
|
// Could also factor in available balance, but for now use conservative 50%
|
|
return Math.min(maxDCASize, 2.0); // Cap at 2 SOL for safety
|
|
}
|
|
|
|
calculateNewAveragePrice(currentSize, currentEntryPrice, dcaAmount, dcaPrice) {
|
|
const totalValue = (currentSize * currentEntryPrice) + (dcaAmount * dcaPrice);
|
|
const totalSize = currentSize + dcaAmount;
|
|
return totalValue / totalSize;
|
|
}
|
|
|
|
async executeDCATradeAction(analysis, positionData) {
|
|
console.log('🎯 EXECUTING DCA TRADE...');
|
|
|
|
try {
|
|
const tradeParams = {
|
|
symbol: positionData.position.symbol,
|
|
side: positionData.position.side === 'long' ? 'BUY' : 'SELL',
|
|
amount: analysis.dcaAmount,
|
|
orderType: 'MARKET',
|
|
leverage: 10, // Use moderate leverage for DCA
|
|
stopLoss: 1.0,
|
|
takeProfit: 3.0,
|
|
isExecution: true,
|
|
dcaMode: true,
|
|
analysis: analysis
|
|
};
|
|
|
|
const response = await fetch('http://localhost:3000/api/trading/execute', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(tradeParams)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
console.log('✅ DCA TRADE EXECUTED SUCCESSFULLY!');
|
|
console.log(`💰 Added ${analysis.dcaAmount} SOL to position`);
|
|
console.log(`📊 New Average Price: $${analysis.newAveragePrice.toFixed(4)}`);
|
|
} else {
|
|
console.error('❌ DCA trade failed:', result.error);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error executing DCA trade:', error);
|
|
}
|
|
}
|
|
|
|
async executeEarlyCloseAction(positionData) {
|
|
console.log('🛑 EXECUTING EARLY CLOSE...');
|
|
|
|
try {
|
|
const closeParams = {
|
|
symbol: positionData.position.symbol,
|
|
side: positionData.position.side === 'long' ? 'SELL' : 'BUY',
|
|
amount: positionData.position.size,
|
|
orderType: 'MARKET',
|
|
isExecution: true,
|
|
earlyClose: true,
|
|
reason: 'Emergency early close due to low AI confidence'
|
|
};
|
|
|
|
const response = await fetch('http://localhost:3000/api/trading/execute', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(closeParams)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
console.log('✅ POSITION CLOSED EARLY');
|
|
console.log(`💰 Final P&L: ${positionData.position.unrealizedPnl >= 0 ? '+' : ''}$${positionData.position.unrealizedPnl.toFixed(2)}`);
|
|
} else {
|
|
console.error('❌ Early close failed:', result.error);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error executing early close:', error);
|
|
}
|
|
}
|
|
|
|
async stop() {
|
|
try {
|
|
console.log('🛑 STOPPING POSITION-AWARE AUTOMATION...');
|
|
this.isRunning = false;
|
|
this.stats.status = 'Stopped';
|
|
this.stats.positionMonitoringActive = false;
|
|
console.log('✅ AUTOMATION STATUS: isRunning =', this.isRunning);
|
|
|
|
if (this.intervalId) {
|
|
clearInterval(this.intervalId);
|
|
this.intervalId = null;
|
|
console.log('⏹️ Opportunity scanning stopped');
|
|
}
|
|
|
|
if (this.positionCheckInterval) {
|
|
clearInterval(this.positionCheckInterval);
|
|
this.positionCheckInterval = null;
|
|
console.log('⏹️ Position monitoring stopped');
|
|
}
|
|
|
|
return { success: true, message: 'Position-aware automation stopped successfully' };
|
|
} catch (error) {
|
|
console.error('Failed to stop position-aware automation:', error);
|
|
return { success: false, message: 'Failed to stop automation: ' + error.message };
|
|
}
|
|
}
|
|
|
|
getStatus() {
|
|
const baseStatus = {
|
|
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: 'POSITION_AWARE',
|
|
...this.stats
|
|
};
|
|
|
|
// Add descriptive status based on current mode
|
|
if (this.isRunning) {
|
|
if (this.stats.positionMonitoringActive) {
|
|
baseStatus.detailedStatus = 'Monitoring active position for stop loss proximity';
|
|
baseStatus.nextAction = 'Position risk assessment and monitoring';
|
|
} else {
|
|
baseStatus.detailedStatus = 'Scanning for trading opportunities';
|
|
baseStatus.nextAction = 'Entry signal analysis';
|
|
}
|
|
} else {
|
|
baseStatus.detailedStatus = 'Stopped - No monitoring active';
|
|
baseStatus.nextAction = 'Start automation to begin monitoring';
|
|
}
|
|
|
|
return baseStatus;
|
|
}
|
|
}
|
|
|
|
// Export singleton instance
|
|
const positionAwareAutomation = new PositionAwareAutomation();
|
|
export { positionAwareAutomation };
|