Spaces:
Running
Running
File size: 5,433 Bytes
0f0f4df f450b99 bd8e21f 0f0f4df f450b99 0f0f4df f450b99 0f0f4df f450b99 065d3b0 0f0f4df 065d3b0 f450b99 0f0f4df f450b99 065d3b0 0f0f4df 065d3b0 0f0f4df 065d3b0 f450b99 065d3b0 f450b99 065d3b0 f450b99 065d3b0 f450b99 065d3b0 f450b99 065d3b0 f450b99 065d3b0 f450b99 065d3b0 f450b99 065d3b0 f450b99 0f0f4df |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
import { state } from './state.js';
import { getSeries, generateSmartMarkers } from './chart.js';
import { updatePrice, renderDom, spawnBubble, setStatus, updateDelta } from './ui.js';
// Main Socket Controller
export function startSocket() {
if (state.isReplay) return;
// Close existing connection if switching pairs
stopSocket();
const symbol = state.symbol.toUpperCase();
const exchange = state.exchange;
setStatus(`CONNECTING ${exchange}...`, 'warn');
if (exchange === 'BINANCE') {
startBinanceStream(symbol);
} else if (exchange === 'COINBASE') {
startCoinbaseStream(symbol);
}
}
export function stopSocket() {
if (state.socket) {
state.socket.close();
state.socket = null;
}
}
/* ==============================================
1. BINANCE LOGIC (USDT Pairs)
============================================== */
function startBinanceStream(symbol) {
// Normalize pair (e.g., BTC -> BTCUSDT)
const pair = `${symbol}USDT`.toLowerCase();
const url = `wss://stream.binance.com:9443/ws/${pair}@kline_1m/${pair}@aggTrade/${pair}@depth10`;
state.socket = new WebSocket(url);
state.socket.onopen = () => {
setStatus('● BINANCE LIVE', 'live');
};
state.socket.onmessage = (e) => {
if (state.isReplay) return;
const data = JSON.parse(e.data);
const type = data.e;
// --- A. KLINE (CANDLES + CHART MARKERS) ---
if (type === 'kline') {
handleBinanceKline(data.k);
}
// --- B. TRADES (SIDEBAR BUBBLES & FOOTPRINT) ---
if (type === 'aggTrade') {
handleBinanceTrade(data);
}
// --- C. DEPTH (ORDER BOOK) ---
if (data.lastUpdateId) {
renderDom(data.bids, data.asks);
}
};
state.socket.onclose = () => setStatus('DISCONNECTED', 'warn');
}
function handleBinanceKline(k) {
const c = {
time: k.t / 1000,
open: parseFloat(k.o),
high: parseFloat(k.h),
low: parseFloat(k.l),
close: parseFloat(k.c)
};
// 1. Update the Chart Series
getSeries().update(c);
updatePrice(c.close);
// 2. Sync Global State (Vital for markers)
// We replace the last candle in history if timestamps match, or push if new.
const lastIdx = state.candles.length - 1;
if (lastIdx >= 0 && state.candles[lastIdx].time === c.time) {
state.candles[lastIdx] = c;
} else {
state.candles.push(c);
}
// 3. REAL-TIME MARKER LOGIC (The new Feature)
// "x": true means this candle just closed. That's the perfect time to check if it was a whale candle.
if (k.x) {
// Recalculate markers based on updated history
generateSmartMarkers(state.candles);
}
}
function handleBinanceTrade(data) {
const qty = parseFloat(data.q);
const isMaker = data.m; // Binance: maker=true implies SELL pressure
// Sidebar Bubbles
if (qty > 0.02) spawnBubble(qty, isMaker);
// Delta / Footprint strip
updateDelta(qty, isMaker);
}
/* ==============================================
2. COINBASE LOGIC (USD Pairs)
============================================== */
function startCoinbaseStream(symbol) {
const pair = `${symbol}-USD`;
state.socket = new WebSocket('wss://ws-feed.exchange.coinbase.com');
state.socket.onopen = () => {
setStatus('● COINBASE LIVE', 'live');
const msg = {
type: "subscribe",
product_ids: [pair],
channels: ["level2", "matches", "ticker"]
};
state.socket.send(JSON.stringify(msg));
};
state.socket.onmessage = (e) => {
if (state.isReplay) return;
const data = JSON.parse(e.data);
// Price & Synthetic Candle
if (data.type === 'ticker') {
handleCoinbaseTicker(data);
}
// Trade Bubbles
if (data.type === 'match') {
handleCoinbaseTrade(data);
}
// DOM (Ignoring detailed L2 updates for this simple prototype)
};
}
let cbLastCandleTime = 0;
function handleCoinbaseTicker(data) {
const price = parseFloat(data.price);
const now = Math.floor(Date.now() / 1000);
const time = now - (now % 60); // Snap to minute start
updatePrice(price);
// Synthesize Candle logic
const lastIdx = state.candles.length - 1;
if (state.candles.length > 0 && state.candles[lastIdx].time === time) {
// Update Existing Current Minute
const c = state.candles[lastIdx];
c.close = price;
c.high = Math.max(c.high, price);
c.low = Math.min(c.low, price);
getSeries().update(c);
} else {
// Create New Minute Candle
const c = { time: time, open: price, high: price, low: price, close: price };
state.candles.push(c);
getSeries().update(c);
// Previous candle definitively "closed" because time shifted. Update markers now.
if (cbLastCandleTime !== 0 && cbLastCandleTime !== time) {
generateSmartMarkers(state.candles);
}
cbLastCandleTime = time;
}
}
function handleCoinbaseTrade(data) {
const qty = parseFloat(data.size);
const isSell = data.side === 'sell'; // Coinbase is straightforward
if (qty > 0.01) spawnBubble(qty, isSell);
updateDelta(qty, isSell);
} |