Files
openclaw-backups/skills/openclaw-self-healing/scripts/tqqq-finnhub-monitor.js

169 lines
4.3 KiB
JavaScript

#!/usr/bin/env node
/**
* TQQQ Finnhub 24/7 모니터링 시스템
*
* Finnhub WebSocket으로 실시간 모니터링
* Stop-Loss: $47.00 (환경변수로 변경 가능)
*/
const WebSocket = require('ws');
const { exec } = require('child_process');
// 환경변수
const FINNHUB_API_KEY = process.env.FINNHUB_API_KEY || '';
const STOP_LOSS_PRICE = parseFloat(process.env.TQQQ_STOP_LOSS || '47.00');
const TICKER = 'TQQQ';
const DISCORD_CHANNEL = '1469190686145384513'; // #jarvis-market
// 상태
let lastPrice = null;
let alertSent = false;
let consecutiveBreaches = 0;
const BREACH_THRESHOLD = 3;
let lastAlertTime = 0;
const ALERT_COOLDOWN = 300000; // 5분
/**
* Stop-Loss 체크
*/
function checkStopLoss(price) {
const now = Date.now();
lastPrice = price;
if (price < STOP_LOSS_PRICE) {
consecutiveBreaches++;
console.log(`⚠️ Price: $${price.toFixed(2)} | Stop-Loss: $${STOP_LOSS_PRICE} | Breaches: ${consecutiveBreaches}/${BREACH_THRESHOLD}`);
if (consecutiveBreaches >= BREACH_THRESHOLD && !alertSent && (now - lastAlertTime) > ALERT_COOLDOWN) {
sendDiscordAlert(price);
alertSent = true;
lastAlertTime = now;
}
} else {
if (consecutiveBreaches > 0) {
console.log(`✅ Price recovered: $${price.toFixed(2)}`);
consecutiveBreaches = 0;
alertSent = false;
}
}
}
/**
* Discord 알림
*/
function sendDiscordAlert(price) {
const kstTime = new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' });
const krwPrice = Math.round(price * 1465.09); // USD to KRW
const message = `🚨 **TQQQ Stop-Loss 트리거**
**현재가:** $${price.toFixed(2)} (₩${krwPrice.toLocaleString('ko-KR')})
**손절선:** $${STOP_LOSS_PRICE.toFixed(2)}
**시각:** ${kstTime}
⚠️ 즉시 확인 필요!`;
// Use message tool via Node.js (direct API call)
const https = require('https');
const payload = JSON.stringify({
action: 'send',
channel: 'discord',
target: `channel:${DISCORD_CHANNEL}`,
message: message
});
const options = {
hostname: 'localhost',
port: 18789,
path: '/rpc',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload)
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
if (res.statusCode === 200) {
console.log('✅ Discord 알림 전송 완료');
} else {
console.error(`❌ Discord 알림 실패: ${res.statusCode} ${data}`);
}
});
});
req.on('error', (error) => {
console.error(`❌ Discord 알림 실패: ${error.message}`);
});
req.write(JSON.stringify({
jsonrpc: '2.0',
id: Date.now(),
method: 'tools/call',
params: {
name: 'message',
arguments: {
action: 'send',
channel: 'discord',
target: `channel:${DISCORD_CHANNEL}`,
message: message
}
}
}));
req.end();
}
/**
* Finnhub WebSocket
*/
function startWebSocket() {
console.log('🔌 Finnhub WebSocket 연결 시작...');
const ws = new WebSocket(`wss://ws.finnhub.io?token=${FINNHUB_API_KEY}`);
ws.on('open', () => {
console.log('✅ Finnhub WebSocket 연결됨');
ws.send(JSON.stringify({ type: 'subscribe', symbol: TICKER }));
console.log(`📡 ${TICKER} 구독 시작`);
console.log(`🛑 Stop-Loss: $${STOP_LOSS_PRICE.toFixed(2)}`);
});
ws.on('message', (data) => {
const message = JSON.parse(data);
if (message.type === 'trade' && message.data && message.data.length > 0) {
message.data.forEach(trade => {
checkStopLoss(trade.p);
});
}
});
ws.on('error', (error) => {
console.error(`❌ WebSocket 에러: ${error.message}`);
});
ws.on('close', () => {
console.log('🔌 WebSocket 연결 종료');
console.log('🔄 5초 후 재연결...');
setTimeout(startWebSocket, 5000);
});
return ws;
}
// 시작
console.log('🚀 TQQQ Finnhub 24/7 모니터링');
console.log(`📊 Ticker: ${TICKER}`);
console.log(`🛑 Stop-Loss: $${STOP_LOSS_PRICE.toFixed(2)}`);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
if (!FINNHUB_API_KEY) {
console.error('❌ FINNHUB_API_KEY 환경변수 필요');
process.exit(1);
}
startWebSocket();