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);
}