- 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
234 lines
7.3 KiB
JavaScript
234 lines
7.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Host-based 24/7 Trading Automation
|
|
* Runs on the host system, not inside the container
|
|
*/
|
|
|
|
const https = require('https');
|
|
const http = require('http');
|
|
|
|
class HostAutomation {
|
|
constructor() {
|
|
this.config = {
|
|
symbol: 'SOLUSD',
|
|
timeframe: '60',
|
|
intervalMinutes: 60,
|
|
autoExecuteThreshold: 60,
|
|
apiHost: 'localhost:9001',
|
|
requestTimeout: 30000
|
|
};
|
|
|
|
this.stats = {
|
|
startTime: new Date(),
|
|
totalCycles: 0,
|
|
totalTrades: 0,
|
|
successfulCycles: 0,
|
|
failedCycles: 0,
|
|
lastSignal: null,
|
|
lastTrade: null
|
|
};
|
|
}
|
|
|
|
async log(message) {
|
|
const timestamp = new Date().toISOString();
|
|
console.log(`[${timestamp}] ${message}`);
|
|
}
|
|
|
|
async makeRequest(url, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const isHttps = url.startsWith('https');
|
|
const client = isHttps ? https : http;
|
|
|
|
const requestOptions = {
|
|
timeout: this.config.requestTimeout,
|
|
...options
|
|
};
|
|
|
|
const req = client.request(url, requestOptions, (res) => {
|
|
let data = '';
|
|
res.on('data', chunk => data += chunk);
|
|
res.on('end', () => {
|
|
try {
|
|
const parsed = JSON.parse(data);
|
|
resolve(parsed);
|
|
} catch (e) {
|
|
resolve({ success: false, error: 'Invalid JSON response' });
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (error) => {
|
|
this.log(`❌ Request error: ${error.message}`);
|
|
resolve(null);
|
|
});
|
|
|
|
req.on('timeout', () => {
|
|
this.log(`⏱️ Request timeout: ${url}`);
|
|
req.destroy();
|
|
resolve(null);
|
|
});
|
|
|
|
if (options.body) {
|
|
req.write(options.body);
|
|
}
|
|
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
async makePostRequest(url, data) {
|
|
return new Promise((resolve, reject) => {
|
|
const postData = JSON.stringify(data);
|
|
|
|
const options = {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Content-Length': Buffer.byteLength(postData)
|
|
},
|
|
timeout: this.config.requestTimeout
|
|
};
|
|
|
|
const req = http.request(url, options, (res) => {
|
|
let responseData = '';
|
|
res.on('data', chunk => responseData += chunk);
|
|
res.on('end', () => {
|
|
try {
|
|
const parsed = JSON.parse(responseData);
|
|
resolve(parsed);
|
|
} catch (e) {
|
|
resolve({ success: false, error: 'Invalid JSON response' });
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (error) => {
|
|
this.log(`❌ POST request error: ${error.message}`);
|
|
resolve(null);
|
|
});
|
|
|
|
req.on('timeout', () => {
|
|
this.log(`⏱️ POST request timeout: ${url}`);
|
|
req.destroy();
|
|
resolve(null);
|
|
});
|
|
|
|
req.write(postData);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
async runAnalysisCycle() {
|
|
this.stats.totalCycles++;
|
|
await this.log(`🔄 Analysis cycle #${this.stats.totalCycles}`);
|
|
|
|
try {
|
|
await this.log(`📡 Fetching analysis from API...`);
|
|
const data = await this.makeRequest(`http://${this.config.apiHost}/api/ai-analysis/latest?symbol=${this.config.symbol}&timeframe=${this.config.timeframe}`);
|
|
|
|
if (data && data.success && data.data && data.data.analysis) {
|
|
const analysis = data.data.analysis;
|
|
this.stats.lastSignal = {
|
|
time: new Date(),
|
|
recommendation: analysis.recommendation,
|
|
confidence: analysis.confidence
|
|
};
|
|
|
|
await this.log(`📊 Signal: ${analysis.recommendation} (${analysis.confidence}% confidence)`);
|
|
|
|
if (analysis.confidence >= this.config.autoExecuteThreshold &&
|
|
(analysis.recommendation === 'BUY' || analysis.recommendation === 'SELL')) {
|
|
|
|
await this.log(`🚀 Executing ${analysis.recommendation} trade with ${analysis.confidence}% confidence`);
|
|
|
|
const trade = await this.executeTrade(analysis);
|
|
if (trade && trade.success) {
|
|
this.stats.totalTrades++;
|
|
this.stats.lastTrade = {
|
|
time: new Date(),
|
|
type: analysis.recommendation,
|
|
confidence: analysis.confidence,
|
|
price: trade.trade?.entry || analysis.entry?.price || 'unknown'
|
|
};
|
|
await this.log(`✅ Trade executed: ${analysis.recommendation} at ${trade.trade?.entry || analysis.entry?.price}`);
|
|
} else {
|
|
await this.log(`❌ Trade execution failed`);
|
|
}
|
|
} else {
|
|
await this.log(`⏸️ Confidence ${analysis.confidence}% below threshold ${this.config.autoExecuteThreshold}% - holding`);
|
|
}
|
|
|
|
this.stats.successfulCycles++;
|
|
} else {
|
|
await this.log(`❌ No valid analysis data received`);
|
|
this.stats.failedCycles++;
|
|
}
|
|
} catch (error) {
|
|
await this.log(`❌ Cycle error: ${error.message}`);
|
|
this.stats.failedCycles++;
|
|
}
|
|
|
|
await this.printStats();
|
|
|
|
const nextCycle = new Date(Date.now() + this.config.intervalMinutes * 60 * 1000);
|
|
await this.log(`⏰ Next cycle: ${nextCycle.toLocaleTimeString()}`);
|
|
|
|
setTimeout(() => this.runAnalysisCycle(), this.config.intervalMinutes * 60 * 1000);
|
|
}
|
|
|
|
async executeTrade(analysis) {
|
|
try {
|
|
await this.log(`💱 Preparing trade data...`);
|
|
const tradeData = {
|
|
symbol: this.config.symbol,
|
|
side: analysis.recommendation,
|
|
amount: 100,
|
|
entry: analysis.entry?.price || 150,
|
|
confidence: analysis.confidence,
|
|
reasoning: analysis.reasoning || 'Automated trade',
|
|
source: 'host_automation_24x7'
|
|
};
|
|
|
|
await this.log(`📤 Sending trade request...`);
|
|
const result = await this.makePostRequest(`http://${this.config.apiHost}/api/safe-paper-trading/create-trade`, tradeData);
|
|
|
|
return result;
|
|
} catch (error) {
|
|
await this.log(`❌ Trade execution error: ${error.message}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async printStats() {
|
|
const uptime = Math.floor((Date.now() - this.stats.startTime) / 1000 / 60);
|
|
const successRate = this.stats.totalCycles > 0 ? Math.round((this.stats.successfulCycles / this.stats.totalCycles) * 100) : 0;
|
|
|
|
await this.log(`📈 Stats: ${this.stats.totalCycles} cycles (${successRate}% success), ${this.stats.totalTrades} trades, ${uptime}m uptime`);
|
|
|
|
if (this.stats.lastSignal) {
|
|
const signalAge = Math.floor((Date.now() - this.stats.lastSignal.time) / 1000 / 60);
|
|
await this.log(`🎯 Last signal: ${this.stats.lastSignal.recommendation} (${this.stats.lastSignal.confidence}%) ${signalAge}m ago`);
|
|
}
|
|
|
|
if (this.stats.lastTrade) {
|
|
const tradeAge = Math.floor((Date.now() - this.stats.lastTrade.time) / 1000 / 60);
|
|
await this.log(`💰 Last trade: ${this.stats.lastTrade.type} at ${this.stats.lastTrade.price} ${tradeAge}m ago`);
|
|
}
|
|
}
|
|
|
|
async start() {
|
|
await this.log('🚀 Host-based 24/7 Trading Automation Started');
|
|
await this.log(`📊 Config: ${this.config.symbol} every ${this.config.intervalMinutes}m, threshold: ${this.config.autoExecuteThreshold}%`);
|
|
await this.log(`🌐 API Host: ${this.config.apiHost}`);
|
|
|
|
await this.runAnalysisCycle();
|
|
}
|
|
}
|
|
|
|
const automation = new HostAutomation();
|
|
automation.start().catch(error => {
|
|
console.error('💥 Automation failed to start:', error);
|
|
process.exit(1);
|
|
});
|