/** * Dashboard 2 - Pro Trading Terminal */ class Dashboard2 { constructor() { this.symbol = 'BTCUSDT'; this.timeframe = '4h'; this.chart = null; this.candlestickSeries = null; this.data = []; this.indicators = { ema20: null, ema50: null, volume: null }; this.activeTool = 'crosshair'; this.isDrawing = false; this.drawingStart = null; this.drawings = []; } async init() { console.log('[Dashboard2] Initializing...'); this.initChart(); this.bindEvents(); this.initBattleAccordion(); await Promise.all([ this.loadMarketData(), this.loadFearGreed(), this.loadNews() ]); setTimeout(() => this.setupDrawing(), 500); setInterval(() => this.loadMarketData(true), 30000); setInterval(() => this.loadFearGreed(), 60000); this.showToast('Dashboard 2', 'Ready!', 'success'); } initChart() { const container = document.getElementById('tradingChart'); if (!container) return; this.chart = LightweightCharts.createChart(container, { layout: { background: { type: 'solid', color: '#ffffff' }, textColor: '#5a6b7c' }, grid: { vertLines: { color: 'rgba(0,180,180,0.04)' }, horzLines: { color: 'rgba(0,180,180,0.04)' } }, crosshair: { mode: LightweightCharts.CrosshairMode.Normal }, rightPriceScale: { borderColor: 'rgba(0,180,180,0.1)' }, timeScale: { borderColor: 'rgba(0,180,180,0.1)', timeVisible: true }, }); this.candlestickSeries = this.chart.addCandlestickSeries({ upColor: '#00c896', downColor: '#e91e8c', borderUpColor: '#00c896', borderDownColor: '#e91e8c', wickUpColor: '#00c896', wickDownColor: '#e91e8c', }); this.indicators.ema20 = this.chart.addLineSeries({ color: '#00d4d4', lineWidth: 2 }); this.indicators.ema50 = this.chart.addLineSeries({ color: '#0088cc', lineWidth: 2 }); this.indicators.volume = this.chart.addHistogramSeries({ priceFormat: { type: 'volume' }, priceScaleId: 'vol' }); this.chart.priceScale('vol').applyOptions({ scaleMargins: { top: 0.85, bottom: 0 } }); new ResizeObserver(e => { const { width, height } = e[0].contentRect; this.chart.applyOptions({ width, height }); }).observe(container); } bindEvents() { document.getElementById('symbolInput')?.addEventListener('change', e => { this.symbol = e.target.value.toUpperCase(); this.loadMarketData(); this.loadNews(); }); document.querySelectorAll('.tf-btn').forEach(btn => { btn.addEventListener('click', e => { document.querySelectorAll('.tf-btn').forEach(b => b.classList.remove('active')); e.target.classList.add('active'); this.timeframe = e.target.dataset.tf; this.loadMarketData(); }); }); document.querySelectorAll('.tool-btn').forEach(btn => { btn.addEventListener('click', () => this.selectTool(btn.dataset.tool)); }); } selectTool(tool) { if (tool === 'clear') { this.clearDrawings(); return; } this.activeTool = tool; this.isDrawing = false; document.querySelectorAll('.tool-btn').forEach(btn => { if (btn.dataset.tool !== 'clear') btn.classList.toggle('active', btn.dataset.tool === tool); }); } setupDrawing() { const container = document.getElementById('tradingChart'); if (!container || !this.chart) return; container.addEventListener('click', e => { if (this.activeTool === 'crosshair') return; const rect = container.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const time = this.chart.timeScale().coordinateToTime(x); const price = this.candlestickSeries.coordinateToPrice(y); if (!time || !price) return; if (this.activeTool === 'horizontal') { this.addHorizontalLine(price); return; } if (!this.isDrawing) { this.isDrawing = true; this.drawingStart = { time, price }; this.showToast('📍', 'Click end point', 'info'); } else { this.finishDrawing(time, price); } }); } addHorizontalLine(price) { const line = this.candlestickSeries.createPriceLine({ price, color: '#00d4d4', lineWidth: 2, axisLabelVisible: true }); this.drawings.push({ type: 'priceline', line }); this.showToast('✓', `Line at $${price.toFixed(0)}`, 'success'); } finishDrawing(endTime, endPrice) { if (!this.drawingStart) return; if (this.activeTool === 'trendline') { const line = this.chart.addLineSeries({ color: '#00d4d4', lineWidth: 2, lastValueVisible: false, priceLineVisible: false }); line.setData([ { time: this.drawingStart.time, value: this.drawingStart.price }, { time: endTime, value: endPrice } ]); this.drawings.push({ type: 'series', series: line }); } else if (this.activeTool === 'fib') { const diff = endPrice - this.drawingStart.price; [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1].forEach((lvl, i) => { const p = this.drawingStart.price + diff * lvl; const colors = ['#e91e8c', '#ff6b35', '#ffc107', '#00d4d4', '#00c896', '#0088cc', '#9c27b0']; const line = this.candlestickSeries.createPriceLine({ price: p, color: colors[i], lineWidth: 1, lineStyle: 2 }); this.drawings.push({ type: 'priceline', line }); }); } this.isDrawing = false; this.drawingStart = null; this.showToast('✓', `${this.activeTool} added`, 'success'); } clearDrawings() { this.drawings.forEach(d => { try { if (d.type === 'priceline') this.candlestickSeries.removePriceLine(d.line); else if (d.type === 'series') this.chart.removeSeries(d.series); } catch (e) {} }); this.drawings = []; this.showToast('✓', 'Cleared', 'info'); } async loadMarketData(silent = false) { if (!silent) document.getElementById('chartLoading')?.classList.remove('hidden'); try { const res = await fetch(`https://api.binance.com/api/v3/klines?symbol=${this.symbol}&interval=${this.timeframe}&limit=500`); const raw = await res.json(); this.data = raw.map(c => ({ time: Math.floor(c[0] / 1000), open: +c[1], high: +c[2], low: +c[3], close: +c[4], volume: +c[5] })); this.updateChart(); this.calcIndicators(); this.updatePrice(); this.updateLevels(); } catch (e) { console.error(e); } finally { document.getElementById('chartLoading')?.classList.add('hidden'); } } updateChart() { if (!this.candlestickSeries || !this.data.length) return; this.candlestickSeries.setData(this.data); this.indicators.volume?.setData(this.data.map(d => ({ time: d.time, value: d.volume, color: d.close > d.open ? 'rgba(0,200,150,0.4)' : 'rgba(233,30,140,0.4)' }))); this.chart.timeScale().fitContent(); } calcIndicators() { if (!this.data.length) return; const closes = this.data.map(d => d.close); const ema20 = this.ema(closes, 20); const ema50 = this.ema(closes, 50); this.indicators.ema20?.setData(ema20.map((v, i) => ({ time: this.data[i].time, value: v }))); this.indicators.ema50?.setData(ema50.map((v, i) => ({ time: this.data[i].time, value: v }))); const rsi = this.rsi(closes, 14); const macd = this.macd(closes); const latestRsi = rsi[rsi.length - 1]; const latestMacd = macd[macd.length - 1]; // === همزبان کردن همه کارتها === // RSI: > 50 = Bullish, < 50 = Bearish const rsiBullish = latestRsi > 50; // MACD: > 0 = Bullish, < 0 = Bearish const macdBullish = latestMacd > 0; // EMA: 20 > 50 = Bullish const emaBullish = ema20[ema20.length - 1] > ema50[ema50.length - 1]; // Price Action const pa = this.analyzePriceAction(); // === کارتها با زبان یکسان: Bullish / Bearish / Neutral === // RSI: > 55 = Bullish, < 45 = Bearish, else Neutral const rsiStatus = latestRsi > 55 ? 'bullish' : latestRsi < 45 ? 'bearish' : 'neutral'; const rsiStrong = latestRsi > 70 || latestRsi < 30; this.setVerdictWidget('rsi', rsiStatus, rsiStrong); // MACD const macdStatus = macdBullish ? 'bullish' : 'bearish'; this.setVerdictWidget('macd', macdStatus, false); // EMA Trend const emaStatus = emaBullish ? 'bullish' : 'bearish'; this.setVerdictWidget('trend', emaStatus, false); // Price Action - فقط از candle استفاده کن const isBullCandle = pa.candle.includes('Bull'); const isBearCandle = pa.candle.includes('Bear'); const paStatus = isBullCandle ? 'bullish' : isBearCandle ? 'bearish' : 'neutral'; const paStrong = pa.candle.includes('Strong'); this.setVerdictWidget('pa', paStatus, paStrong); // Update consensus this.updateConsensus([rsiStatus, macdStatus, emaStatus, paStatus]); // === پنلهای سمت راست === document.getElementById('panelRsi').textContent = latestRsi.toFixed(1); document.getElementById('panelRsi').className = 'metric-value ' + (rsiBullish ? 'bullish' : 'bearish'); document.getElementById('panelMacd').textContent = macdBullish ? 'Bullish' : 'Bearish'; document.getElementById('panelMacd').className = 'metric-value ' + (macdBullish ? 'bullish' : 'bearish'); document.getElementById('panelTrend').textContent = emaBullish ? 'Bullish' : 'Bearish'; document.getElementById('panelTrend').className = 'metric-value ' + (emaBullish ? 'bullish' : 'bearish'); const vol = this.data.slice(-24).reduce((s, d) => s + d.volume, 0); document.getElementById('panelVolume').textContent = (vol / 1e9).toFixed(2) + 'B'; // Price Action Panel document.getElementById('paPattern').textContent = pa.pattern; document.getElementById('paPattern').className = 'metric-value ' + (pa.bullish ? 'bullish' : 'bearish'); document.getElementById('paCandle').textContent = pa.candle; document.getElementById('paCandle').className = 'metric-value ' + (pa.candleBullish ? 'bullish' : 'bearish'); document.getElementById('paStructure').textContent = pa.structure; document.getElementById('paStructure').className = 'metric-value ' + (pa.structureBullish ? 'bullish' : 'bearish'); document.getElementById('paVerdict').textContent = pa.bullish ? 'Bullish' : 'Bearish'; document.getElementById('paVerdict').className = 'metric-value ' + (pa.bullish ? 'bullish' : 'bearish'); } setVerdictWidget(id, status, isStrong = false) { const verdictEl = document.getElementById(id + 'Verdict'); const labels = { bullish: 'Bullish', bearish: 'Bearish', neutral: 'Neutral' }; const icons = { bullish: '↑', bearish: '↓', neutral: '—' }; if (verdictEl) { // اگر پترن خالی است if (id === 'pa' && status === 'neutral') { verdictEl.textContent = '—'; verdictEl.className = 'widget-verdict neutral'; } else { verdictEl.textContent = `${icons[status]} ${labels[status]}`; // رنگ قویتر برای Strong const strongClass = isStrong ? '-strong' : ''; verdictEl.className = 'widget-verdict ' + status + strongClass; } } } updateConsensus(statuses) { const bullishCount = statuses.filter(s => s === 'bullish').length; const bearishCount = statuses.filter(s => s === 'bearish').length; // Update scores const bullScore = document.getElementById('bullScore'); const bearScore = document.getElementById('bearScore'); if (bullScore) bullScore.textContent = bullishCount; if (bearScore) bearScore.textContent = bearishCount; // Update power bars const bullPower = document.getElementById('bullPower'); const bearPower = document.getElementById('bearPower'); if (bullPower) bullPower.style.width = (bullishCount * 25) + '%'; if (bearPower) bearPower.style.width = (bearishCount * 25) + '%'; // Update label and push indicator const labelEl = document.getElementById('battleLabel'); const pushEl = document.getElementById('pushIndicator'); const bullFighter = document.getElementById('bullFighter'); const bearFighter = document.getElementById('bearFighter'); // Remove winner classes if (bullFighter) bullFighter.classList.remove('winner'); if (bearFighter) bearFighter.classList.remove('winner'); if (pushEl) pushEl.classList.remove('bull-winning', 'bear-winning'); if (bullishCount > bearishCount) { if (labelEl) { labelEl.textContent = 'Bulls Win!'; labelEl.className = 'battle-label bullish'; } if (pushEl) pushEl.classList.add('bull-winning'); if (bullFighter) bullFighter.classList.add('winner'); } else if (bearishCount > bullishCount) { if (labelEl) { labelEl.textContent = 'Bears Win!'; labelEl.className = 'battle-label bearish'; } if (pushEl) pushEl.classList.add('bear-winning'); if (bearFighter) bearFighter.classList.add('winner'); } else { if (labelEl) { labelEl.textContent = 'Draw'; labelEl.className = 'battle-label neutral'; } } this.updateSignalFromConsensus(bullishCount, bearishCount, 0); } initBattleAccordion() { const header = document.getElementById('battleHeader'); const panel = header?.closest('.battle-panel'); if (header && panel) { header.addEventListener('click', () => { panel.classList.toggle('open'); }); } } updateSignalFromConsensus(bullish, bearish, neutral) { let sig = 'HOLD', conf = 50; if (bullish === 4) { sig = 'STRONG BUY'; conf = 95; } else if (bullish === 3) { sig = 'BUY'; conf = 80; } else if (bearish === 4) { sig = 'STRONG SELL'; conf = 95; } else if (bearish === 3) { sig = 'SELL'; conf = 80; } else { sig = 'HOLD'; conf = 50; } const badge = document.getElementById('signalBadge'); if (badge) { badge.textContent = sig; badge.className = 'signal-badge ' + (sig.includes('BUY') ? 'buy' : sig.includes('SELL') ? 'sell' : 'hold'); } const confEl = document.getElementById('panelConfidence'); if (confEl) { confEl.textContent = conf + '%'; confEl.className = 'metric-value ' + (sig.includes('BUY') ? 'bullish' : sig.includes('SELL') ? 'bearish' : ''); } } analyzePriceAction() { if (this.data.length < 5) return { pattern: '--', candle: '--', structure: '--', bullish: true, candleBullish: true, structureBullish: true }; const recent = this.data.slice(-5); const last = recent[recent.length - 1]; const prev = recent[recent.length - 2]; // Candle Analysis const body = Math.abs(last.close - last.open); const upperWick = last.high - Math.max(last.open, last.close); const lowerWick = Math.min(last.open, last.close) - last.low; const candleBullish = last.close > last.open; let candle = 'Neutral'; if (body > (upperWick + lowerWick) * 2) { candle = candleBullish ? 'Strong Bull' : 'Strong Bear'; } else if (lowerWick > body * 2 && upperWick < body) { candle = 'Hammer'; } else if (upperWick > body * 2 && lowerWick < body) { candle = 'Shooting Star'; } else if (body < (last.high - last.low) * 0.1) { candle = 'Doji'; } else { candle = candleBullish ? 'Bullish' : 'Bearish'; } // Structure - Higher Highs/Lower Lows const highs = recent.map(d => d.high); const lows = recent.map(d => d.low); const hh = highs[4] > highs[3] && highs[3] > highs[2]; const ll = lows[4] < lows[3] && lows[3] < lows[2]; const hl = lows[4] > lows[3]; const lh = highs[4] < highs[3]; let structure = 'Consolidation'; let structureBullish = true; if (hh && hl) { structure = 'HH + HL'; structureBullish = true; } else if (ll && lh) { structure = 'LL + LH'; structureBullish = false; } else if (hh) { structure = 'Higher Highs'; structureBullish = true; } else if (ll) { structure = 'Lower Lows'; structureBullish = false; } // Pattern Detection let pattern = 'No Pattern'; let patternBullish = candleBullish; // Engulfing if (last.close > last.open && prev.close < prev.open && last.open < prev.close && last.close > prev.open) { pattern = 'Engulfing'; patternBullish = true; } else if (last.close < last.open && prev.close > prev.open && last.open > prev.close && last.close < prev.open) { pattern = 'Engulfing'; patternBullish = false; } // Morning/Evening Star const mid = recent[recent.length - 3]; if (mid && Math.abs(mid.close - mid.open) < (mid.high - mid.low) * 0.1) { if (recent[recent.length - 4].close < recent[recent.length - 4].open && candleBullish) { pattern = 'Morning Star'; patternBullish = true; } else if (recent[recent.length - 4].close > recent[recent.length - 4].open && !candleBullish) { pattern = 'Evening Star'; patternBullish = false; } } // Overall verdict const bullishScore = (candleBullish ? 1 : 0) + (structureBullish ? 1 : 0) + (patternBullish ? 1 : 0); const overallBullish = bullishScore >= 2; return { pattern: pattern, candle: candle, structure: structure, bullish: overallBullish, candleBullish: candleBullish, structureBullish: structureBullish }; } updatePrice() { if (!this.data.length) return; const l = this.data[this.data.length - 1]; const p = this.data[this.data.length - 2]; const chg = ((l.close - p.close) / p.close) * 100; document.getElementById('currentPrice').textContent = `$${l.close.toLocaleString('en-US', { minimumFractionDigits: 2 })}`; const chgEl = document.getElementById('priceChange'); chgEl.textContent = `${chg >= 0 ? '+' : ''}${chg.toFixed(2)}%`; chgEl.className = 'price-change ' + (chg >= 0 ? 'positive' : 'negative'); document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); document.getElementById('currentLevel').textContent = `$${l.close.toLocaleString('en-US', { maximumFractionDigits: 0 })}`; } updateLevels() { const recent = this.data.slice(-50); const high = Math.max(...recent.map(d => d.high)); const low = Math.min(...recent.map(d => d.low)); document.getElementById('resistance').textContent = `$${high.toLocaleString('en-US', { maximumFractionDigits: 0 })}`; document.getElementById('support').textContent = `$${low.toLocaleString('en-US', { maximumFractionDigits: 0 })}`; } async loadFearGreed() { try { const res = await fetch('https://api.alternative.me/fng/?limit=1'); const data = await res.json(); const val = +data.data[0].value; const lbl = data.data[0].value_classification; this.updateFG(val, lbl); } catch (e) { this.updateFG(23, 'Extreme Fear'); } } updateFG(val, lbl) { const scoreEl = document.getElementById('fgScore'); const lblEl = document.getElementById('fgLabel'); const indEl = document.getElementById('fgIndicator'); const cls = val <= 40 ? 'fear' : val >= 60 ? 'greed' : 'neutral'; if (scoreEl) { scoreEl.textContent = val; scoreEl.className = 'fg-score ' + cls; } if (lblEl) lblEl.textContent = lbl; if (indEl) indEl.style.left = val + '%'; } async loadNews() { const coin = this.symbol.replace('USDT', ''); const news = [ { title: `${coin} breaks key resistance, analysts bullish`, score: 78, src: 'CoinDesk', time: '1h' }, { title: `Institutional buying pressure on ${coin}`, score: 72, src: 'Bloomberg', time: '2h' }, { title: `${coin} network sees record transactions`, score: 65, src: 'Reuters', time: '3h' }, { title: `Major exchange lists new ${coin} pairs`, score: 58, src: 'CoinTelegraph', time: '4h' }, { title: `${coin} volatility rises amid uncertainty`, score: 42, src: 'Decrypt', time: '5h' }, ]; const feed = document.getElementById('newsFeed'); if (feed) { feed.innerHTML = news.map(n => { const cls = n.score >= 60 ? 'positive' : n.score <= 45 ? 'negative' : 'neutral'; const icon = this.getNewsIcon(cls); return `