@@ -504,6 +506,8 @@ function initChart() {
}).observe(container);
loadKlines();
startKlineWs();
+ initWhaleCanvas();
+ startAggTradeWs();
}
async function loadKlines() {
@@ -610,6 +614,89 @@ function drawSupportResistance(candles) {
});
}
+// ── Whale trades (aggTrade canvas overlay) ────────
+const WHALE_THRESHOLD = 10000; // USDT
+const WHALE_MAX_AGE = 120; // saniye, ekranda kalma süresi
+const WHALE_MAX_R = 28; // maks çember yarıçapı (px)
+const WHALE_MIN_R = 6; // min çember yarıçapı (px)
+const WHALE_SCALE_AT = 500000; // bu hacimde max boyuta ulaşır
+
+let whaleCanvas = null;
+let whaleCtx = null;
+let aggTradeWs = null;
+let whaleTrades = []; // { price, qty, isBuy, usdtVal, ts }
+let whaleRafId = null;
+
+function startAggTradeWs() {
+ if (aggTradeWs) aggTradeWs.close();
+ const sym = bot.symbol.toLowerCase();
+ aggTradeWs = new WebSocket(`wss://stream.binance.com:9443/ws/${sym}@aggTrade`);
+ aggTradeWs.onmessage = e => {
+ const t = JSON.parse(e.data);
+ const price = parseFloat(t.p);
+ const qty = parseFloat(t.q);
+ const usdtVal = price * qty;
+ if (usdtVal < WHALE_THRESHOLD) return;
+ whaleTrades.push({ price, qty, isBuy: !t.m, usdtVal, ts: Date.now() / 1000 });
+ if (whaleTrades.length > 500) whaleTrades.shift();
+ };
+ aggTradeWs.onerror = () => {};
+ aggTradeWs.onclose = () => setTimeout(startAggTradeWs, 5000);
+}
+
+function initWhaleCanvas() {
+ whaleCanvas = document.getElementById('whale-canvas');
+ whaleCtx = whaleCanvas.getContext('2d');
+ const container = document.getElementById('chart-container');
+ function resize() {
+ whaleCanvas.width = container.clientWidth;
+ whaleCanvas.height = container.clientHeight;
+ }
+ resize();
+ new ResizeObserver(resize).observe(container);
+ renderWhaleTrades();
+}
+
+function renderWhaleTrades() {
+ whaleRafId = requestAnimationFrame(renderWhaleTrades);
+ if (!whaleCtx || !chart || !candleSeries) return;
+
+ const W = whaleCanvas.width;
+ const H = whaleCanvas.height;
+ whaleCtx.clearRect(0, 0, W, H);
+
+ const now = Date.now() / 1000;
+ // eski trade'leri temizle
+ whaleTrades = whaleTrades.filter(t => now - t.ts < WHALE_MAX_AGE);
+
+ for (const t of whaleTrades) {
+ const x = chart.timeScale().timeToCoordinate(Math.floor(t.ts));
+ const y = candleSeries.priceToCoordinate(t.price);
+ if (x === null || y === null) continue;
+
+ const age = now - t.ts;
+ const fade = Math.max(0, 1 - age / WHALE_MAX_AGE);
+ const ratio = Math.min(t.usdtVal / WHALE_SCALE_AT, 1);
+ const r = WHALE_MIN_R + (WHALE_MAX_R - WHALE_MIN_R) * ratio;
+ const alpha = fade * (t.isBuy ? 0.75 : 0.65);
+
+ const color = t.isBuy
+ ? `rgba(46,204,113,${alpha})`
+ : `rgba(231,76,60,${alpha})`;
+ const stroke = t.isBuy
+ ? `rgba(46,204,113,${Math.min(1, alpha * 1.4)})`
+ : `rgba(231,76,60,${Math.min(1, alpha * 1.4)})`;
+
+ whaleCtx.beginPath();
+ whaleCtx.arc(x, y, r, 0, Math.PI * 2);
+ whaleCtx.fillStyle = color;
+ whaleCtx.fill();
+ whaleCtx.strokeStyle = stroke;
+ whaleCtx.lineWidth = 1.5;
+ whaleCtx.stroke();
+ }
+}
+
let klineWs = null;
function startKlineWs() {
if (klineWs) klineWs.close();