diamond-in commited on
Commit
f450b99
·
verified ·
1 Parent(s): 3c4d9eb

Update js/socket.js

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