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

121 lines
3.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Finnhub 실시간 TQQQ/QQQ 모니터
* Usage: FINNHUB_API_KEY=your_key node realtime-tqqq-monitor.js
*/
const WebSocket = require('ws');
const FINNHUB_API_KEY = process.env.FINNHUB_API_KEY || 'd62ho41r01qlugeq3ge0d62ho41r01qlugeq3geg';
if (!FINNHUB_API_KEY) {
console.error('❌ Error: FINNHUB_API_KEY가 설정되지 않았습니다.');
process.exit(1);
}
const socket = new WebSocket(`wss://ws.finnhub.io?token=${FINNHUB_API_KEY}`);
const prices = {
TQQQ: null,
QQQ: null,
};
const STOP_LOSS_PERCENT = -10; // -10% 손절선
let initialPortfolio = 9000; // $9,000
socket.on('open', () => {
console.log('✅ Finnhub WebSocket 연결 성공!');
console.log('📊 실시간 모니터링 시작...\n');
// TQQQ, QQQ 구독
socket.send(JSON.stringify({ type: 'subscribe', symbol: 'TQQQ' }));
socket.send(JSON.stringify({ type: 'subscribe', symbol: 'QQQ' }));
});
socket.on('message', (data) => {
try {
const message = JSON.parse(data);
if (message.type === 'trade' && message.data) {
message.data.forEach(trade => {
const symbol = trade.s;
const price = trade.p;
const volume = trade.v;
const timestamp = new Date(trade.t).toLocaleTimeString('ko-KR');
// 가격 업데이트
if (symbol === 'TQQQ' || symbol === 'QQQ') {
const prevPrice = prices[symbol];
prices[symbol] = price;
const change = prevPrice ? ((price - prevPrice) / prevPrice * 100).toFixed(2) : '0.00';
const changeIcon = parseFloat(change) >= 0 ? '📈' : '📉';
console.log(`${changeIcon} ${symbol}: $${price.toFixed(2)} | 변동: ${change}% | 거래량: ${volume.toLocaleString()} | ${timestamp}`);
// 손절선 체크 (TQQQ만)
if (symbol === 'TQQQ') {
checkStopLoss(price);
checkTargets(price);
}
}
});
}
} catch (error) {
console.error('❌ Error parsing message:', error.message);
}
});
socket.on('error', (error) => {
console.error('❌ WebSocket Error:', error.message);
});
socket.on('close', () => {
console.log('🔌 WebSocket 연결 종료');
});
// 손절선 체크
function checkStopLoss(currentPrice) {
// 예시: $46 평단가 기준
const avgPrice = 46;
const stopLossPrice = avgPrice * (1 + STOP_LOSS_PERCENT / 100);
const currentLoss = ((currentPrice - avgPrice) / avgPrice * 100).toFixed(2);
if (currentPrice <= stopLossPrice) {
console.log('\n🚨🚨🚨 손절선 터짐! 🚨🚨🚨');
console.log(`현재가: $${currentPrice.toFixed(2)}`);
console.log(`손절선: $${stopLossPrice.toFixed(2)}`);
console.log(`손실: ${currentLoss}%`);
console.log('⚠️ 즉시 전량 매도 필요!\n');
} else if (currentPrice <= stopLossPrice * 1.02) {
console.log(`\n⚠️ 경고: 손절선 2% 근접 ($${currentPrice.toFixed(2)} vs $${stopLossPrice.toFixed(2)})\n`);
}
}
// 목표가 체크
function checkTargets(currentPrice) {
const avgPrice = 46;
const targets = [
{ price: 50, name: '1차 목표', action: '30% 매도' },
{ price: 54, name: '2차 목표 (복구)', action: '50% 매도' },
{ price: 58, name: '3차 목표', action: '전량 매도' },
];
targets.forEach(target => {
if (currentPrice >= target.price && currentPrice <= target.price * 1.01) {
const profit = ((currentPrice - avgPrice) / avgPrice * 100).toFixed(2);
console.log(`\n🎯 ${target.name} 도달!`);
console.log(`현재가: $${currentPrice.toFixed(2)} | 수익: +${profit}%`);
console.log(`액션: ${target.action}\n`);
}
});
}
// Ctrl+C로 종료 시
process.on('SIGINT', () => {
console.log('\n👋 모니터링 종료...');
socket.close();
process.exit(0);
});