feat: implement 24/7 server-side automation with AI learning integration
Core Features: - True 24/7 automation runs without browser dependency - Server-side process in Docker container - Auto-executes paper trades with ≥60% confidence - Integrates with existing AI learning system - Safe paper trading mode only (zero real money risk) - working-24x7.js: Main automation process (currently running) - check-automation.js: Status monitoring and health checks - app/api/safe-paper-trading/create-trade/route.js: Paper trade API - app/api/automation-24x7/route.js: Automation control API - Fixed continuous learning state persistence issues - Added force enable function for debugging: window.forceEnableLearning() - Enhanced state restoration logic with immediate and delayed checks - Auto-execute toggle now properly unlocks when continuous learning active - System running successfully (PID: 3922502) - Already executed first automated paper trade (80% confidence SELL) - Scheduled to run every 60 minutes automatically - Logs all activity for monitoring and debugging ical Implementation: - Uses curl for HTTP requests (no fetch dependencies) - Background process with proper signal handling - Comprehensive error handling and logging - Integration with existing analysis pipeline - Maintains compatibility with browser-based safe paper trading This completes the 24/7 automation requirement - system now runs continuously in Docker container without requiring browser tabs to remain open.
This commit is contained in:
271
automation-daemon.js
Normal file
271
automation-daemon.js
Normal file
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Simple 24/7 Background Automation Daemon
|
||||
* Runs as a background process in Docker container
|
||||
*/
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
class BackgroundAutomation {
|
||||
constructor() {
|
||||
this.logFile = '/tmp/automation-24x7.log';
|
||||
this.pidFile = '/tmp/automation-24x7.pid';
|
||||
this.config = {
|
||||
symbol: 'SOLUSD',
|
||||
timeframe: '60',
|
||||
intervalMinutes: 60,
|
||||
autoExecuteThreshold: 60
|
||||
};
|
||||
}
|
||||
|
||||
async log(message) {
|
||||
const timestamp = new Date().toISOString();
|
||||
const logEntry = `[${timestamp}] ${message}\n`;
|
||||
console.log(logEntry.trim());
|
||||
|
||||
try {
|
||||
await fs.appendFile(this.logFile, logEntry);
|
||||
} catch (error) {
|
||||
// Ignore file errors
|
||||
}
|
||||
}
|
||||
|
||||
async isRunning() {
|
||||
try {
|
||||
const pidData = await fs.readFile(this.pidFile, 'utf8');
|
||||
const pid = parseInt(pidData.trim());
|
||||
|
||||
// Check if process exists
|
||||
try {
|
||||
process.kill(pid, 0); // Signal 0 checks if process exists
|
||||
return { running: true, pid };
|
||||
} catch (error) {
|
||||
// Process doesn't exist
|
||||
await fs.unlink(this.pidFile).catch(() => {});
|
||||
return { running: false, pid: null };
|
||||
}
|
||||
} catch (error) {
|
||||
return { running: false, pid: null };
|
||||
}
|
||||
}
|
||||
|
||||
async start() {
|
||||
const status = await this.isRunning();
|
||||
if (status.running) {
|
||||
await this.log(`⚠️ Automation already running (PID: ${status.pid})`);
|
||||
return { success: false, message: 'Already running', pid: status.pid };
|
||||
}
|
||||
|
||||
await this.log('🚀 Starting 24/7 Background Automation...');
|
||||
|
||||
// Start the main automation loop
|
||||
this.startAutomationLoop();
|
||||
|
||||
await this.log(`✅ Automation started (PID: ${process.pid})`);
|
||||
return { success: true, message: 'Started', pid: process.pid };
|
||||
}
|
||||
|
||||
async startAutomationLoop() {
|
||||
// Save PID
|
||||
await fs.writeFile(this.pidFile, process.pid.toString());
|
||||
|
||||
// Handle cleanup on exit
|
||||
process.on('SIGTERM', () => this.cleanup());
|
||||
process.on('SIGINT', () => this.cleanup());
|
||||
process.on('exit', () => this.cleanup());
|
||||
|
||||
let cycleCount = 0;
|
||||
|
||||
// Main automation loop
|
||||
const runCycle = async () => {
|
||||
try {
|
||||
cycleCount++;
|
||||
await this.log(`🔄 Starting analysis cycle #${cycleCount}`);
|
||||
|
||||
// Get analysis
|
||||
const analysis = await this.getAnalysis();
|
||||
if (analysis && analysis.confidence >= this.config.autoExecuteThreshold) {
|
||||
await this.log(`🎯 Executing trade: ${analysis.recommendation} (${analysis.confidence}% confidence)`);
|
||||
await this.createPaperTrade(analysis);
|
||||
} else if (analysis) {
|
||||
await this.log(`⏸️ No trade: ${analysis.recommendation} (${analysis.confidence}% confidence < ${this.config.autoExecuteThreshold}% threshold)`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
await this.log(`❌ Cycle error: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Run first cycle immediately
|
||||
await runCycle();
|
||||
|
||||
// Schedule recurring cycles
|
||||
setInterval(runCycle, this.config.intervalMinutes * 60 * 1000);
|
||||
|
||||
await this.log(`⏰ Scheduled to run every ${this.config.intervalMinutes} minutes`);
|
||||
}
|
||||
|
||||
async getAnalysis() {
|
||||
try {
|
||||
// Use curl to avoid fetch dependencies
|
||||
const { exec } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
const { stdout } = await execAsync(`curl -s "http://localhost:9001/api/ai-analysis/latest?symbol=${this.config.symbol}&timeframe=${this.config.timeframe}"`);
|
||||
|
||||
const data = JSON.parse(stdout);
|
||||
|
||||
if (data.success && data.data && data.data.analysis) {
|
||||
return data.data.analysis;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
await this.log(`❌ Analysis error: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async createPaperTrade(analysis) {
|
||||
try {
|
||||
// Use curl to avoid fetch dependencies
|
||||
const { exec } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
const tradeData = {
|
||||
symbol: this.config.symbol,
|
||||
side: analysis.recommendation,
|
||||
amount: 100,
|
||||
entry: analysis.entry,
|
||||
stopLoss: analysis.stopLoss,
|
||||
takeProfit: analysis.takeProfit,
|
||||
confidence: analysis.confidence,
|
||||
reasoning: analysis.reasoning,
|
||||
source: '24x7_daemon'
|
||||
};
|
||||
|
||||
const curlCommand = `curl -s -X POST http://localhost:9001/api/safe-paper-trading/create-trade \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '${JSON.stringify(tradeData)}'`;
|
||||
|
||||
const { stdout } = await execAsync(curlCommand);
|
||||
const result = JSON.parse(stdout);
|
||||
|
||||
if (result.success) {
|
||||
await this.log(`✅ Paper trade created: ${result.trade.id}`);
|
||||
} else {
|
||||
await this.log(`❌ Trade creation failed: ${result.message}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
await this.log(`❌ Trade creation error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
const status = await this.isRunning();
|
||||
if (!status.running) {
|
||||
await this.log('⚠️ Automation not running');
|
||||
return { success: false, message: 'Not running' };
|
||||
}
|
||||
|
||||
try {
|
||||
process.kill(status.pid, 'SIGTERM');
|
||||
await fs.unlink(this.pidFile).catch(() => {});
|
||||
await this.log(`🛑 Automation stopped (PID: ${status.pid})`);
|
||||
return { success: true, message: 'Stopped', pid: status.pid };
|
||||
} catch (error) {
|
||||
await this.log(`❌ Stop error: ${error.message}`);
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
try {
|
||||
await fs.unlink(this.pidFile).catch(() => {});
|
||||
await this.log('🧹 Cleanup completed');
|
||||
} catch (error) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
async getStatus() {
|
||||
const status = await this.isRunning();
|
||||
|
||||
let logs = '';
|
||||
try {
|
||||
const logData = await fs.readFile(this.logFile, 'utf8');
|
||||
logs = logData.split('\n').slice(-10).join('\n'); // Last 10 lines
|
||||
} catch (error) {
|
||||
logs = 'No logs available';
|
||||
}
|
||||
|
||||
return {
|
||||
running: status.running,
|
||||
pid: status.pid,
|
||||
config: this.config,
|
||||
recentLogs: logs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Create instance
|
||||
const daemon = new BackgroundAutomation();
|
||||
|
||||
// CLI interface
|
||||
if (require.main === module) {
|
||||
const command = process.argv[2];
|
||||
|
||||
switch (command) {
|
||||
case 'start':
|
||||
daemon.start().then(result => {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
if (result.success) {
|
||||
// Keep process alive
|
||||
process.stdin.resume();
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
daemon.stop().then(result => {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
process.exit(0);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
daemon.getStatus().then(status => {
|
||||
console.log(JSON.stringify(status, null, 2));
|
||||
process.exit(0);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`
|
||||
🤖 24/7 Background Automation Daemon
|
||||
|
||||
Usage:
|
||||
node automation-daemon.js start # Start in background
|
||||
node automation-daemon.js stop # Stop daemon
|
||||
node automation-daemon.js status # Check status
|
||||
|
||||
Features:
|
||||
✅ True background process
|
||||
✅ Survives browser close
|
||||
✅ Auto-executes trades ≥60% confidence
|
||||
✅ Logs all activity
|
||||
✅ Safe paper trading only
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
// Export for API use
|
||||
module.exports = { daemon };
|
||||
Reference in New Issue
Block a user