- Fixed field mapping between API and frontend (amount→positionSize, entry→entryPrice, createdAt→timestamp) - Updated API sync function to properly convert API trade format to frontend format - Resolved display issues: 'Invalid Date', missing entry price, missing trade size - Added trade monitoring system and automation improvements - Enhanced automation with simple-automation.js for reliable 24/7 operation - Working automation now detecting 85% confidence BUY signals and executing trades
390 lines
13 KiB
JavaScript
390 lines
13 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Enhanced 24/7 Automation with Global Market Sentiment
|
|
* Integrates Fear & Greed Index and macro indicators for better trading decisions
|
|
*/
|
|
|
|
// Import M2 Money Supply indicator
|
|
const M2MoneySupplyIndicator = require('./m2-money-supply-indicator');
|
|
|
|
class EnhancedGlobalAutomation {
|
|
constructor() {
|
|
this.config = {
|
|
symbol: 'SOLUSD',
|
|
timeframe: '60',
|
|
intervalMinutes: 60,
|
|
autoExecuteThreshold: 60,
|
|
// API endpoint for container environment
|
|
apiHost: '192.168.0.1:9001',
|
|
// Enhanced with sentiment-based adjustments
|
|
sentimentThresholds: {
|
|
extremeFear: 75, // Lower threshold during extreme fear (more aggressive)
|
|
fear: 65, // Moderate lowering during fear
|
|
neutral: 60, // Base threshold
|
|
greed: 70, // Higher threshold during greed (more selective)
|
|
extremeGreed: 80 // Much higher threshold during extreme greed
|
|
}
|
|
};
|
|
this.cycleCount = 0;
|
|
this.m2Indicator = new M2MoneySupplyIndicator();
|
|
|
|
this.marketSentiment = {
|
|
fearGreedIndex: null,
|
|
bitcoinDominance: null,
|
|
m2Analysis: null,
|
|
marketRegime: 'UNKNOWN',
|
|
riskLevel: 'MODERATE',
|
|
lastUpdate: null
|
|
};
|
|
|
|
this.stats = {
|
|
startTime: new Date(),
|
|
totalCycles: 0,
|
|
totalTrades: 0,
|
|
sentimentAdjustments: 0,
|
|
regimeChanges: 0
|
|
};
|
|
}
|
|
|
|
async log(message) {
|
|
const timestamp = new Date().toISOString();
|
|
const logEntry = `[${timestamp}] ${message}`;
|
|
console.log(logEntry);
|
|
}
|
|
|
|
async updateMarketSentiment() {
|
|
try {
|
|
await this.log('📊 Updating global market sentiment...');
|
|
|
|
// Try to get Fear & Greed Index
|
|
const fearGreed = await this.getFearGreedIndex();
|
|
|
|
// Get Bitcoin Dominance (mock for now - in production use real API)
|
|
const btcDominance = await this.getBitcoinDominance();
|
|
|
|
// Calculate overall market regime
|
|
const regime = this.calculateMarketRegime(fearGreed, btcDominance);
|
|
|
|
// Update sentiment state
|
|
this.marketSentiment = {
|
|
fearGreedIndex: fearGreed,
|
|
bitcoinDominance: btcDominance,
|
|
marketRegime: regime.regime,
|
|
riskLevel: regime.riskLevel,
|
|
lastUpdate: new Date(),
|
|
tradingImplication: regime.tradingImplication
|
|
};
|
|
|
|
await this.log(`📈 Market Regime: ${regime.regime} | Risk: ${regime.riskLevel} | F&G: ${fearGreed || 'N/A'}`);
|
|
|
|
return this.marketSentiment;
|
|
|
|
} catch (error) {
|
|
await this.log(`❌ Sentiment update error: ${error.message}`);
|
|
return this.marketSentiment;
|
|
}
|
|
}
|
|
|
|
async getFearGreedIndex() {
|
|
try {
|
|
// Try multiple sources for Fear & Greed data
|
|
const sources = [
|
|
'https://api.alternative.me/fng/',
|
|
// Could add backup sources here
|
|
];
|
|
|
|
for (const source of sources) {
|
|
try {
|
|
const { stdout } = await execAsync(`timeout 10 curl -s "${source}"`);
|
|
const data = JSON.parse(stdout);
|
|
|
|
if (data.data && data.data[0]) {
|
|
return {
|
|
value: parseInt(data.data[0].value),
|
|
classification: data.data[0].value_classification,
|
|
timestamp: data.data[0].timestamp
|
|
};
|
|
}
|
|
} catch (error) {
|
|
continue; // Try next source
|
|
}
|
|
}
|
|
|
|
// Fallback: estimate from recent price action
|
|
return await this.estimateFearGreedFromPriceAction();
|
|
|
|
} catch (error) {
|
|
await this.log(`⚠️ Fear & Greed unavailable, using price-based estimate`);
|
|
return await this.estimateFearGreedFromPriceAction();
|
|
}
|
|
}
|
|
|
|
async estimateFearGreedFromPriceAction() {
|
|
try {
|
|
// Get recent analysis to estimate sentiment
|
|
const response = await fetch(`http://${this.config.apiHost}/api/ai-analysis/latest?symbol=${this.config.symbol}&timeframe=${this.config.timeframe}`);
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.data && data.data.analysis) {
|
|
const analysis = data.data.analysis;
|
|
|
|
// Estimate Fear & Greed from technical indicators
|
|
let fearGreedEstimate = 50; // Neutral baseline
|
|
|
|
// Confidence level indicates sentiment strength
|
|
if (analysis.recommendation === 'BUY' && analysis.confidence > 70) {
|
|
fearGreedEstimate = 65; // Moderate greed
|
|
} else if (analysis.recommendation === 'SELL' && analysis.confidence > 70) {
|
|
fearGreedEstimate = 35; // Moderate fear
|
|
} else if (analysis.confidence < 50) {
|
|
fearGreedEstimate = 45; // Slight fear (uncertainty)
|
|
}
|
|
|
|
return {
|
|
value: fearGreedEstimate,
|
|
classification: this.classifyFearGreed(fearGreedEstimate),
|
|
timestamp: Date.now(),
|
|
source: 'price_action_estimate'
|
|
};
|
|
}
|
|
|
|
return { value: 50, classification: 'Neutral', timestamp: Date.now(), source: 'fallback' };
|
|
|
|
} catch (error) {
|
|
return { value: 50, classification: 'Neutral', timestamp: Date.now(), source: 'error_fallback' };
|
|
}
|
|
}
|
|
|
|
classifyFearGreed(value) {
|
|
if (value <= 25) return 'Extreme Fear';
|
|
if (value <= 45) return 'Fear';
|
|
if (value <= 55) return 'Neutral';
|
|
if (value <= 75) return 'Greed';
|
|
return 'Extreme Greed';
|
|
}
|
|
|
|
async getBitcoinDominance() {
|
|
// Mock implementation - in production, use real API like CoinGecko
|
|
// Estimate based on recent market conditions
|
|
return {
|
|
value: 52.5,
|
|
trend: 'stable',
|
|
interpretation: 'BTC holding ground vs altcoins'
|
|
};
|
|
}
|
|
|
|
calculateMarketRegime(fearGreed, btcDominance) {
|
|
let regime = 'NEUTRAL';
|
|
let riskLevel = 'MODERATE';
|
|
let tradingImplication = {};
|
|
|
|
if (fearGreed) {
|
|
const fgValue = fearGreed.value;
|
|
|
|
if (fgValue <= 25) {
|
|
regime = 'EXTREME_FEAR';
|
|
riskLevel = 'LOW'; // Low risk to buy during extreme fear
|
|
tradingImplication = {
|
|
action: 'AGGRESSIVE_BUY',
|
|
adjustedThreshold: this.config.sentimentThresholds.extremeFear,
|
|
reasoning: 'Extreme fear - contrarian opportunity'
|
|
};
|
|
} else if (fgValue <= 45) {
|
|
regime = 'FEAR';
|
|
riskLevel = 'LOW_MODERATE';
|
|
tradingImplication = {
|
|
action: 'CAUTIOUS_BUY',
|
|
adjustedThreshold: this.config.sentimentThresholds.fear,
|
|
reasoning: 'Fear present - good buying opportunity'
|
|
};
|
|
} else if (fgValue <= 55) {
|
|
regime = 'NEUTRAL';
|
|
riskLevel = 'MODERATE';
|
|
tradingImplication = {
|
|
action: 'NORMAL',
|
|
adjustedThreshold: this.config.sentimentThresholds.neutral,
|
|
reasoning: 'Balanced market - rely on technical analysis'
|
|
};
|
|
} else if (fgValue <= 75) {
|
|
regime = 'GREED';
|
|
riskLevel = 'MODERATE_HIGH';
|
|
tradingImplication = {
|
|
action: 'CAUTIOUS_SELL',
|
|
adjustedThreshold: this.config.sentimentThresholds.greed,
|
|
reasoning: 'Greed emerging - be more selective'
|
|
};
|
|
} else {
|
|
regime = 'EXTREME_GREED';
|
|
riskLevel = 'HIGH';
|
|
tradingImplication = {
|
|
action: 'DEFENSIVE',
|
|
adjustedThreshold: this.config.sentimentThresholds.extremeGreed,
|
|
reasoning: 'Extreme greed - potential market top'
|
|
};
|
|
}
|
|
}
|
|
|
|
return { regime, riskLevel, tradingImplication };
|
|
}
|
|
|
|
async start() {
|
|
await this.log('🚀 Enhanced 24/7 Automation with Global Sentiment Started');
|
|
await this.log(`📊 Base config: ${this.config.symbol} every ${this.config.intervalMinutes}m`);
|
|
|
|
// Update sentiment immediately
|
|
await this.updateMarketSentiment();
|
|
|
|
// Run first analysis cycle
|
|
await this.runEnhancedCycle();
|
|
|
|
// Schedule recurring cycles
|
|
setInterval(() => this.runEnhancedCycle().catch(console.error), this.config.intervalMinutes * 60 * 1000);
|
|
|
|
// Update sentiment every 30 minutes (more frequent than trading)
|
|
setInterval(() => this.updateMarketSentiment().catch(console.error), 30 * 60 * 1000);
|
|
|
|
await this.log(`⏰ Next cycle: ${new Date(Date.now() + this.config.intervalMinutes * 60 * 1000).toLocaleTimeString()}`);
|
|
}
|
|
|
|
async runEnhancedCycle() {
|
|
this.stats.totalCycles++;
|
|
await this.log(`🔄 Enhanced analysis cycle #${this.stats.totalCycles}`);
|
|
|
|
try {
|
|
// Update sentiment first
|
|
await this.updateMarketSentiment();
|
|
|
|
// Get current technical analysis
|
|
const response = await fetch(`http://${this.config.apiHost}/api/ai-analysis/latest?symbol=${this.config.symbol}&timeframe=${this.config.timeframe}`);
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.data && data.data.analysis) {
|
|
const analysis = data.data.analysis;
|
|
|
|
// Apply sentiment-based threshold adjustment
|
|
const adjustedThreshold = this.getAdjustedThreshold();
|
|
|
|
await this.log(`📊 Technical: ${analysis.recommendation} (${analysis.confidence}%)`);
|
|
await this.log(`🎯 Threshold: ${adjustedThreshold}% (adjusted for ${this.marketSentiment.marketRegime})`);
|
|
|
|
if (analysis.confidence >= adjustedThreshold) {
|
|
await this.log(`🎯 EXECUTING: ${analysis.confidence}% ≥ ${adjustedThreshold}% (${this.marketSentiment.tradingImplication?.reasoning})`);
|
|
await this.executeEnhancedTrade(analysis);
|
|
} else {
|
|
await this.log(`⏸️ NO TRADE: ${analysis.confidence}% < ${adjustedThreshold}% (sentiment-adjusted threshold)`);
|
|
|
|
// Log why sentiment affected the decision
|
|
if (adjustedThreshold !== this.config.autoExecuteThreshold) {
|
|
this.stats.sentimentAdjustments++;
|
|
await this.log(`📈 Sentiment Impact: ${this.marketSentiment.marketRegime} adjusted threshold by ${adjustedThreshold - this.config.autoExecuteThreshold}%`);
|
|
}
|
|
}
|
|
} else {
|
|
await this.log('❌ No technical analysis available');
|
|
}
|
|
|
|
} catch (error) {
|
|
await this.log(`❌ Enhanced cycle error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
getAdjustedThreshold() {
|
|
const baseThreshold = this.config.autoExecuteThreshold;
|
|
|
|
if (!this.marketSentiment.tradingImplication) {
|
|
return baseThreshold;
|
|
}
|
|
|
|
return this.marketSentiment.tradingImplication.adjustedThreshold || baseThreshold;
|
|
}
|
|
|
|
async executeEnhancedTrade(analysis) {
|
|
try {
|
|
// Enhance trade data with sentiment context
|
|
const tradeData = {
|
|
symbol: this.config.symbol,
|
|
side: analysis.recommendation,
|
|
amount: this.calculateSentimentAdjustedAmount(),
|
|
entry: analysis.entry,
|
|
stopLoss: analysis.stopLoss,
|
|
takeProfit: analysis.takeProfit,
|
|
confidence: analysis.confidence,
|
|
reasoning: analysis.reasoning,
|
|
source: 'enhanced_24x7_sentiment',
|
|
// Enhanced fields
|
|
marketRegime: this.marketSentiment.marketRegime,
|
|
fearGreedValue: this.marketSentiment.fearGreedIndex?.value,
|
|
riskLevel: this.marketSentiment.riskLevel,
|
|
sentimentAdjustment: this.marketSentiment.tradingImplication?.reasoning
|
|
};
|
|
|
|
// Create enhanced paper trade
|
|
const response = await fetch(`http://${this.config.apiHost}/api/safe-paper-trading/create-trade`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(tradeData)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
this.stats.totalTrades++;
|
|
await this.log(`✅ ENHANCED TRADE: ${result.trade.id} | Regime: ${this.marketSentiment.marketRegime}`);
|
|
} else {
|
|
await this.log(`❌ TRADE FAILED: ${result.message}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
await this.log(`❌ Enhanced trade execution error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
calculateSentimentAdjustedAmount() {
|
|
let baseAmount = 100;
|
|
|
|
// Adjust position size based on market sentiment
|
|
switch (this.marketSentiment.marketRegime) {
|
|
case 'EXTREME_FEAR':
|
|
return baseAmount * 1.5; // 50% larger positions during extreme fear
|
|
case 'FEAR':
|
|
return baseAmount * 1.2; // 20% larger during fear
|
|
case 'NEUTRAL':
|
|
return baseAmount; // Normal size
|
|
case 'GREED':
|
|
return baseAmount * 0.8; // 20% smaller during greed
|
|
case 'EXTREME_GREED':
|
|
return baseAmount * 0.5; // 50% smaller during extreme greed
|
|
default:
|
|
return baseAmount;
|
|
}
|
|
}
|
|
|
|
getStatus() {
|
|
const uptime = Math.floor((Date.now() - this.stats.startTime.getTime()) / 1000);
|
|
return {
|
|
isRunning: true,
|
|
config: this.config,
|
|
marketSentiment: this.marketSentiment,
|
|
stats: {
|
|
...this.stats,
|
|
uptime: `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m`,
|
|
sentimentAdjustmentRate: `${this.stats.sentimentAdjustments}/${this.stats.totalCycles}`,
|
|
nextCycle: new Date(Date.now() + (this.config.intervalMinutes * 60 * 1000))
|
|
},
|
|
enhancement: {
|
|
sentimentIntegration: 'ACTIVE',
|
|
fearGreedTracking: this.marketSentiment.fearGreedIndex ? 'ACTIVE' : 'ESTIMATED',
|
|
riskAdjustment: 'DYNAMIC',
|
|
positionSizing: 'SENTIMENT_BASED'
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// Create and start enhanced automation
|
|
const enhancedAutomation = new EnhancedGlobalAutomation();
|
|
enhancedAutomation.start();
|