hedgequantx 2.6.161 → 2.6.163

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/package.json +1 -1
  2. package/src/menus/ai-agent-connect.js +181 -0
  3. package/src/menus/ai-agent-models.js +219 -0
  4. package/src/menus/ai-agent-oauth.js +292 -0
  5. package/src/menus/ai-agent-ui.js +141 -0
  6. package/src/menus/ai-agent.js +88 -1489
  7. package/src/pages/algo/copy-engine.js +449 -0
  8. package/src/pages/algo/copy-trading.js +11 -543
  9. package/src/pages/algo/smart-logs-data.js +218 -0
  10. package/src/pages/algo/smart-logs.js +9 -214
  11. package/src/pages/algo/ui-constants.js +144 -0
  12. package/src/pages/algo/ui-summary.js +184 -0
  13. package/src/pages/algo/ui.js +42 -526
  14. package/src/pages/stats-calculations.js +191 -0
  15. package/src/pages/stats-ui.js +381 -0
  16. package/src/pages/stats.js +14 -507
  17. package/src/services/ai/client-analysis.js +194 -0
  18. package/src/services/ai/client-models.js +333 -0
  19. package/src/services/ai/client.js +6 -489
  20. package/src/services/ai/index.js +2 -257
  21. package/src/services/ai/providers/direct-providers.js +323 -0
  22. package/src/services/ai/providers/index.js +8 -472
  23. package/src/services/ai/providers/other-providers.js +104 -0
  24. package/src/services/ai/proxy-install.js +249 -0
  25. package/src/services/ai/proxy-manager.js +29 -411
  26. package/src/services/ai/proxy-remote.js +161 -0
  27. package/src/services/ai/supervisor-optimize.js +215 -0
  28. package/src/services/ai/supervisor-sync.js +178 -0
  29. package/src/services/ai/supervisor.js +50 -515
  30. package/src/services/ai/validation.js +250 -0
  31. package/src/services/hqx-server-events.js +110 -0
  32. package/src/services/hqx-server-handlers.js +217 -0
  33. package/src/services/hqx-server-latency.js +136 -0
  34. package/src/services/hqx-server.js +51 -403
  35. package/src/services/position-constants.js +28 -0
  36. package/src/services/position-exit-logic.js +174 -0
  37. package/src/services/position-manager.js +90 -629
  38. package/src/services/position-momentum.js +206 -0
  39. package/src/services/projectx/accounts.js +142 -0
  40. package/src/services/projectx/index.js +40 -289
  41. package/src/services/projectx/trading.js +180 -0
  42. package/src/services/rithmic/contracts.js +218 -0
  43. package/src/services/rithmic/handlers.js +2 -208
  44. package/src/services/rithmic/index.js +28 -712
  45. package/src/services/rithmic/latency-tracker.js +182 -0
  46. package/src/services/rithmic/market-data-decoders.js +229 -0
  47. package/src/services/rithmic/market-data.js +1 -278
  48. package/src/services/rithmic/orders-fast.js +246 -0
  49. package/src/services/rithmic/orders.js +1 -251
  50. package/src/services/rithmic/proto-decoders.js +403 -0
  51. package/src/services/rithmic/protobuf.js +7 -443
  52. package/src/services/rithmic/specs.js +146 -0
  53. package/src/services/rithmic/trade-history.js +254 -0
  54. package/src/services/strategy/hft-signal-calc.js +147 -0
  55. package/src/services/strategy/hft-tick.js +33 -133
  56. package/src/services/tradovate/index.js +6 -119
  57. package/src/services/tradovate/orders.js +145 -0
@@ -0,0 +1,250 @@
1
+ /**
2
+ * AI Provider Validation Functions
3
+ * @module services/ai/validation
4
+ *
5
+ * API key and endpoint validation for all AI providers.
6
+ */
7
+
8
+ const { getProvider } = require('./providers');
9
+
10
+ /**
11
+ * Validate API key with provider
12
+ */
13
+ const validateConnection = async (providerId, optionId, credentials) => {
14
+ const provider = getProvider(providerId);
15
+ if (!provider) return { valid: false, error: 'Invalid provider' };
16
+
17
+ try {
18
+ switch (providerId) {
19
+ case 'anthropic':
20
+ return await validateAnthropic(credentials);
21
+ case 'openai':
22
+ return await validateOpenAI(credentials);
23
+ case 'gemini':
24
+ return await validateGemini(credentials);
25
+ case 'deepseek':
26
+ return await validateDeepSeek(credentials);
27
+ case 'groq':
28
+ return await validateGroq(credentials);
29
+ case 'ollama':
30
+ return await validateOllama(credentials);
31
+ case 'lmstudio':
32
+ return await validateLMStudio(credentials);
33
+ case 'custom':
34
+ return await validateCustom(credentials);
35
+ case 'openrouter':
36
+ return await validateOpenRouter(credentials);
37
+ case 'xai':
38
+ case 'mistral':
39
+ case 'perplexity':
40
+ case 'together':
41
+ case 'qwen':
42
+ case 'moonshot':
43
+ case 'yi':
44
+ case 'zhipu':
45
+ case 'baichuan':
46
+ return await validateOpenAICompatible(provider, credentials);
47
+ default:
48
+ return { valid: false, error: 'Unknown provider' };
49
+ }
50
+ } catch (error) {
51
+ return { valid: false, error: error.message };
52
+ }
53
+ };
54
+
55
+ /** Validate Anthropic API key */
56
+ const validateAnthropic = async (credentials) => {
57
+ try {
58
+ const token = credentials.apiKey || credentials.sessionKey || credentials.accessToken;
59
+ if (!token) return { valid: false, error: 'No API key provided' };
60
+
61
+ const response = await fetch('https://api.anthropic.com/v1/models', {
62
+ method: 'GET',
63
+ headers: {
64
+ 'x-api-key': token,
65
+ 'anthropic-version': '2023-06-01'
66
+ }
67
+ });
68
+
69
+ if (response.ok) {
70
+ const data = await response.json();
71
+ if (data.data && Array.isArray(data.data) && data.data.length > 0) {
72
+ return { valid: true, tokenType: 'api_key' };
73
+ }
74
+ return { valid: false, error: 'API returned no models' };
75
+ }
76
+
77
+ const error = await response.json();
78
+ return { valid: false, error: error.error?.message || 'Invalid API key' };
79
+ } catch (e) {
80
+ return { valid: false, error: e.message };
81
+ }
82
+ };
83
+
84
+ /** Validate OpenAI API key */
85
+ const validateOpenAI = async (credentials) => {
86
+ try {
87
+ const response = await fetch('https://api.openai.com/v1/models', {
88
+ headers: {
89
+ 'Authorization': `Bearer ${credentials.apiKey || credentials.accessToken}`
90
+ }
91
+ });
92
+
93
+ if (response.ok) return { valid: true };
94
+ return { valid: false, error: 'Invalid API key' };
95
+ } catch (e) {
96
+ return { valid: false, error: e.message };
97
+ }
98
+ };
99
+
100
+ /** Validate Gemini API key */
101
+ const validateGemini = async (credentials) => {
102
+ try {
103
+ const response = await fetch(
104
+ `https://generativelanguage.googleapis.com/v1/models?key=${credentials.apiKey}`
105
+ );
106
+
107
+ if (response.ok) return { valid: true };
108
+ return { valid: false, error: 'Invalid API key' };
109
+ } catch (e) {
110
+ return { valid: false, error: e.message };
111
+ }
112
+ };
113
+
114
+ /** Validate DeepSeek API key */
115
+ const validateDeepSeek = async (credentials) => {
116
+ try {
117
+ const response = await fetch('https://api.deepseek.com/v1/models', {
118
+ headers: { 'Authorization': `Bearer ${credentials.apiKey}` }
119
+ });
120
+
121
+ if (response.ok) return { valid: true };
122
+ return { valid: false, error: 'Invalid API key' };
123
+ } catch (e) {
124
+ return { valid: false, error: e.message };
125
+ }
126
+ };
127
+
128
+ /** Validate Groq API key */
129
+ const validateGroq = async (credentials) => {
130
+ try {
131
+ const response = await fetch('https://api.groq.com/openai/v1/models', {
132
+ headers: { 'Authorization': `Bearer ${credentials.apiKey}` }
133
+ });
134
+
135
+ if (response.ok) return { valid: true };
136
+ return { valid: false, error: 'Invalid API key' };
137
+ } catch (e) {
138
+ return { valid: false, error: e.message };
139
+ }
140
+ };
141
+
142
+ /** Validate Ollama local instance */
143
+ const validateOllama = async (credentials) => {
144
+ try {
145
+ const endpoint = credentials.endpoint || 'http://localhost:11434';
146
+ const response = await fetch(`${endpoint}/api/tags`);
147
+
148
+ if (response.ok) {
149
+ const data = await response.json();
150
+ return { valid: true, models: data.models?.map(m => m.name) || [] };
151
+ }
152
+
153
+ return { valid: false, error: 'Cannot connect to Ollama' };
154
+ } catch (e) {
155
+ return { valid: false, error: 'Ollama not running. Start with: ollama serve' };
156
+ }
157
+ };
158
+
159
+ /** Validate LM Studio local instance */
160
+ const validateLMStudio = async (credentials) => {
161
+ try {
162
+ const endpoint = credentials.endpoint || 'http://localhost:1234/v1';
163
+ const response = await fetch(`${endpoint}/models`);
164
+
165
+ if (response.ok) {
166
+ const data = await response.json();
167
+ return { valid: true, models: data.data?.map(m => m.id) || [] };
168
+ }
169
+
170
+ return { valid: false, error: 'Cannot connect to LM Studio' };
171
+ } catch (e) {
172
+ return { valid: false, error: 'LM Studio not running. Start local server first.' };
173
+ }
174
+ };
175
+
176
+ /** Validate custom endpoint */
177
+ const validateCustom = async (credentials) => {
178
+ try {
179
+ const response = await fetch(`${credentials.endpoint}/models`, {
180
+ headers: credentials.apiKey ? { 'Authorization': `Bearer ${credentials.apiKey}` } : {}
181
+ });
182
+
183
+ if (response.ok) return { valid: true };
184
+ return { valid: false, error: 'Cannot connect to endpoint' };
185
+ } catch (e) {
186
+ return { valid: false, error: e.message };
187
+ }
188
+ };
189
+
190
+ /** Validate OpenRouter API key */
191
+ const validateOpenRouter = async (credentials) => {
192
+ try {
193
+ const response = await fetch('https://openrouter.ai/api/v1/models', {
194
+ headers: { 'Authorization': `Bearer ${credentials.apiKey}` }
195
+ });
196
+
197
+ if (response.ok) return { valid: true };
198
+ return { valid: false, error: 'Invalid API key' };
199
+ } catch (e) {
200
+ return { valid: false, error: e.message };
201
+ }
202
+ };
203
+
204
+ /** Validate OpenAI-compatible providers */
205
+ const validateOpenAICompatible = async (provider, credentials) => {
206
+ try {
207
+ const endpoint = provider.endpoint;
208
+ const response = await fetch(`${endpoint}/models`, {
209
+ headers: {
210
+ 'Authorization': `Bearer ${credentials.apiKey}`,
211
+ 'Content-Type': 'application/json'
212
+ }
213
+ });
214
+
215
+ if (response.ok) return { valid: true };
216
+
217
+ // Some providers don't have /models endpoint, try a simple chat
218
+ const chatResponse = await fetch(`${endpoint}/chat/completions`, {
219
+ method: 'POST',
220
+ headers: {
221
+ 'Authorization': `Bearer ${credentials.apiKey}`,
222
+ 'Content-Type': 'application/json'
223
+ },
224
+ body: JSON.stringify({
225
+ model: provider.defaultModel,
226
+ messages: [{ role: 'user', content: 'hi' }],
227
+ max_tokens: 5
228
+ })
229
+ });
230
+
231
+ if (chatResponse.ok) return { valid: true };
232
+ return { valid: false, error: 'Invalid API key or endpoint' };
233
+ } catch (e) {
234
+ return { valid: false, error: e.message };
235
+ }
236
+ };
237
+
238
+ module.exports = {
239
+ validateConnection,
240
+ validateAnthropic,
241
+ validateOpenAI,
242
+ validateGemini,
243
+ validateDeepSeek,
244
+ validateGroq,
245
+ validateOllama,
246
+ validateLMStudio,
247
+ validateCustom,
248
+ validateOpenRouter,
249
+ validateOpenAICompatible,
250
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @fileoverview HQX Server Event Management
3
+ * @module services/hqx-server-events
4
+ *
5
+ * Lightweight event emitter for HQX WebSocket service.
6
+ * Optimized for performance with inlined loops.
7
+ */
8
+
9
+ /**
10
+ * Event emitter mixin for HQXServerService
11
+ * These functions should be called with .call(this) to bind to service instance
12
+ */
13
+
14
+ /**
15
+ * Register event listener
16
+ * @param {Map} listeners - Listeners map
17
+ * @param {string} event - Event name
18
+ * @param {Function} callback - Callback function
19
+ */
20
+ function on(listeners, event, callback) {
21
+ if (!listeners.has(event)) {
22
+ listeners.set(event, []);
23
+ }
24
+ listeners.get(event).push(callback);
25
+ }
26
+
27
+ /**
28
+ * Remove event listener
29
+ * @param {Map} listeners - Listeners map
30
+ * @param {string} event - Event name
31
+ * @param {Function} callback - Callback function
32
+ */
33
+ function off(listeners, event, callback) {
34
+ const callbacks = listeners.get(event);
35
+ if (callbacks) {
36
+ const index = callbacks.indexOf(callback);
37
+ if (index > -1) callbacks.splice(index, 1);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Emit event (inlined for speed)
43
+ * @param {Map} listeners - Listeners map
44
+ * @param {string} event - Event name
45
+ * @param {*} data - Event data
46
+ */
47
+ function emit(listeners, event, data) {
48
+ const callbacks = listeners.get(event);
49
+ if (!callbacks) return;
50
+
51
+ for (let i = 0; i < callbacks.length; i++) {
52
+ try {
53
+ callbacks[i](data);
54
+ } catch {
55
+ // Don't let callback errors break the loop
56
+ }
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Clear all listeners
62
+ * @param {Map} listeners - Listeners map
63
+ */
64
+ function clearAll(listeners) {
65
+ listeners.clear();
66
+ }
67
+
68
+ /**
69
+ * Remove all listeners for a specific event
70
+ * @param {Map} listeners - Listeners map
71
+ * @param {string} event - Event name
72
+ */
73
+ function removeAllListeners(listeners, event) {
74
+ if (event) {
75
+ listeners.delete(event);
76
+ } else {
77
+ listeners.clear();
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Get listener count for an event
83
+ * @param {Map} listeners - Listeners map
84
+ * @param {string} event - Event name
85
+ * @returns {number}
86
+ */
87
+ function listenerCount(listeners, event) {
88
+ const callbacks = listeners.get(event);
89
+ return callbacks ? callbacks.length : 0;
90
+ }
91
+
92
+ /**
93
+ * Check if event has listeners
94
+ * @param {Map} listeners - Listeners map
95
+ * @param {string} event - Event name
96
+ * @returns {boolean}
97
+ */
98
+ function hasListeners(listeners, event) {
99
+ return listenerCount(listeners, event) > 0;
100
+ }
101
+
102
+ module.exports = {
103
+ on,
104
+ off,
105
+ emit,
106
+ clearAll,
107
+ removeAllListeners,
108
+ listenerCount,
109
+ hasListeners,
110
+ };
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @fileoverview HQX Server Message Handlers
3
+ * @module services/hqx-server-handlers
4
+ *
5
+ * Message parsing and handling for HQX WebSocket.
6
+ * Optimized for ultra-low latency trading signals.
7
+ */
8
+
9
+ const { logger } = require('../utils/logger');
10
+ const log = logger.scope('HQX');
11
+
12
+ /** Message types as bytes for faster switching */
13
+ const MSG_TYPE = {
14
+ // Outgoing
15
+ PING: 0x01,
16
+ START_ALGO: 0x10,
17
+ STOP_ALGO: 0x11,
18
+ START_COPY: 0x12,
19
+ ORDER: 0x20,
20
+
21
+ // Incoming
22
+ PONG: 0x81,
23
+ SIGNAL: 0x90,
24
+ TRADE: 0x91,
25
+ FILL: 0x92,
26
+ LOG: 0xA0,
27
+ STATS: 0xA1,
28
+ ERROR: 0xFF,
29
+ };
30
+
31
+ /**
32
+ * Fast JSON parse with type hint
33
+ * @param {string|Buffer} data
34
+ * @returns {Object}
35
+ */
36
+ const fastParse = (data) => {
37
+ const str = typeof data === 'string' ? data : data.toString('utf8');
38
+ return JSON.parse(str);
39
+ };
40
+
41
+ /**
42
+ * Handle binary trading signal (zero-copy)
43
+ * @param {Buffer} data
44
+ * @param {Function} emit - Emit function
45
+ */
46
+ function handleBinarySignal(data, emit) {
47
+ // Binary format: [type:1][timestamp:8][side:1][price:8][qty:4]
48
+ if (data.length >= 22) {
49
+ const signal = {
50
+ timestamp: data.readBigInt64LE(1),
51
+ side: data.readUInt8(9),
52
+ price: data.readDoubleLE(10),
53
+ quantity: data.readUInt32LE(18),
54
+ };
55
+ emit('signal', signal);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Handle pong with latency calculation
61
+ * @param {Buffer} data
62
+ * @param {bigint} receiveTime
63
+ * @param {number} lastPingTime
64
+ * @param {Function} updateLatency
65
+ */
66
+ function handlePong(data, receiveTime, lastPingTime, updateLatency) {
67
+ if (lastPingTime > 0) {
68
+ // Use high-resolution timer
69
+ const latency = Number(receiveTime - lastPingTime) / 1e6; // ns to ms
70
+ updateLatency(latency);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Handle JSON message
76
+ * @param {Object} message
77
+ * @param {bigint} receiveTime
78
+ * @param {Function} emit
79
+ * @param {Function} updateLatency
80
+ */
81
+ function handleJsonMessage(message, receiveTime, emit, updateLatency) {
82
+ // Calculate latency from server timestamp
83
+ if (message.timestamp) {
84
+ const latency = Date.now() - message.timestamp;
85
+ if (latency >= 0 && latency < 5000) {
86
+ updateLatency(latency);
87
+ }
88
+ }
89
+
90
+ // Fast dispatch
91
+ switch (message.type) {
92
+ case 'signal':
93
+ emit('signal', message.data);
94
+ break;
95
+ case 'trade':
96
+ emit('trade', message.data);
97
+ break;
98
+ case 'fill':
99
+ emit('fill', message.data);
100
+ break;
101
+ case 'log':
102
+ emit('log', message.data);
103
+ break;
104
+ case 'stats':
105
+ emit('stats', message.data);
106
+ break;
107
+ case 'error':
108
+ emit('error', message.data);
109
+ break;
110
+ case 'pong':
111
+ // Already handled in binary path
112
+ break;
113
+ default:
114
+ emit('message', message);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Process incoming message
120
+ * @param {Object} ctx - Service context
121
+ * @param {Buffer|string} data - Raw message data
122
+ * @param {Function} emit - Emit function
123
+ * @param {Function} updateLatency - Latency update function
124
+ */
125
+ function processMessage(ctx, data, emit, updateLatency) {
126
+ const receiveTime = process.hrtime.bigint();
127
+ ctx.messagesReceived++;
128
+ ctx.bytesReceived += data.length;
129
+
130
+ try {
131
+ // Try binary format first (faster)
132
+ if (Buffer.isBuffer(data) && data.length > 0) {
133
+ const msgType = data.readUInt8(0);
134
+
135
+ // Fast path for pong
136
+ if (msgType === MSG_TYPE.PONG) {
137
+ handlePong(data, receiveTime, ctx.lastPingTime, updateLatency);
138
+ return;
139
+ }
140
+
141
+ // Binary signal (fastest path)
142
+ if (msgType === MSG_TYPE.SIGNAL) {
143
+ handleBinarySignal(data, emit);
144
+ return;
145
+ }
146
+ }
147
+
148
+ // JSON fallback
149
+ const message = fastParse(data);
150
+ handleJsonMessage(message, receiveTime, emit, updateLatency);
151
+
152
+ } catch (err) {
153
+ log.warn('Message parse error', { error: err.message });
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Build algo config payload
159
+ * @param {Object} config
160
+ * @returns {Object}
161
+ */
162
+ function buildAlgoPayload(config) {
163
+ return {
164
+ accountId: config.accountId,
165
+ contractId: config.contractId,
166
+ symbol: config.symbol,
167
+ contracts: config.contracts,
168
+ dailyTarget: config.dailyTarget,
169
+ maxRisk: config.maxRisk,
170
+ propfirm: config.propfirm,
171
+ propfirmToken: config.propfirmToken,
172
+ rithmicCredentials: config.rithmicCredentials || null,
173
+ copyTrading: config.copyTrading || false,
174
+ followerSymbol: config.followerSymbol,
175
+ followerContracts: config.followerContracts,
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Build copy trading config payload
181
+ * @param {Object} config
182
+ * @returns {Object}
183
+ */
184
+ function buildCopyTradingPayload(config) {
185
+ return {
186
+ // Lead
187
+ leadAccountId: config.leadAccountId,
188
+ leadContractId: config.leadContractId,
189
+ leadSymbol: config.leadSymbol,
190
+ leadContracts: config.leadContracts,
191
+ leadPropfirm: config.leadPropfirm,
192
+ leadToken: config.leadToken,
193
+ leadRithmicCredentials: config.leadRithmicCredentials,
194
+ // Follower
195
+ followerAccountId: config.followerAccountId,
196
+ followerContractId: config.followerContractId,
197
+ followerSymbol: config.followerSymbol,
198
+ followerContracts: config.followerContracts,
199
+ followerPropfirm: config.followerPropfirm,
200
+ followerToken: config.followerToken,
201
+ followerRithmicCredentials: config.followerRithmicCredentials,
202
+ // Targets
203
+ dailyTarget: config.dailyTarget,
204
+ maxRisk: config.maxRisk,
205
+ };
206
+ }
207
+
208
+ module.exports = {
209
+ MSG_TYPE,
210
+ fastParse,
211
+ handleBinarySignal,
212
+ handlePong,
213
+ handleJsonMessage,
214
+ processMessage,
215
+ buildAlgoPayload,
216
+ buildCopyTradingPayload,
217
+ };
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @fileoverview HQX Server Latency Management
3
+ * @module services/hqx-server-latency
4
+ *
5
+ * Handles latency tracking, adaptive heartbeat, and connection statistics.
6
+ */
7
+
8
+ /**
9
+ * Latency manager mixin for HQXServerService
10
+ * Call with .call(this) to bind to service instance
11
+ */
12
+
13
+ /**
14
+ * Update latency statistics
15
+ * @param {number} latency - Latency in ms
16
+ */
17
+ function updateLatency(latency) {
18
+ this.latency = latency;
19
+ this.minLatency = Math.min(this.minLatency, latency);
20
+ this.maxLatency = Math.max(this.maxLatency, latency);
21
+
22
+ // Rolling average (last 100 samples)
23
+ this.latencySamples.push(latency);
24
+ if (this.latencySamples.length > 100) {
25
+ this.latencySamples.shift();
26
+ }
27
+ this.avgLatency = this.latencySamples.reduce((a, b) => a + b, 0) / this.latencySamples.length;
28
+
29
+ // Adapt heartbeat based on latency
30
+ adaptHeartbeat.call(this);
31
+
32
+ this._emit('latency', {
33
+ current: latency,
34
+ min: this.minLatency,
35
+ max: this.maxLatency,
36
+ avg: this.avgLatency
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Adapt heartbeat interval based on connection quality
42
+ */
43
+ function adaptHeartbeat() {
44
+ // Good connection: slower heartbeat (less overhead)
45
+ // Poor connection: faster heartbeat (detect issues quickly)
46
+ if (this.avgLatency < 10) {
47
+ this.adaptiveHeartbeat = 2000; // <10ms: 2s heartbeat
48
+ } else if (this.avgLatency < 50) {
49
+ this.adaptiveHeartbeat = 1000; // <50ms: 1s heartbeat
50
+ } else if (this.avgLatency < 100) {
51
+ this.adaptiveHeartbeat = 500; // <100ms: 500ms heartbeat
52
+ } else {
53
+ this.adaptiveHeartbeat = 250; // High latency: 250ms heartbeat
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Start adaptive heartbeat
59
+ * @param {Function} sendPing - Function to send ping
60
+ */
61
+ function startHeartbeat(sendPing) {
62
+ stopHeartbeat.call(this);
63
+
64
+ const heartbeat = () => {
65
+ if (this.connected) {
66
+ sendPing();
67
+
68
+ // Schedule next with adaptive interval
69
+ this.pingInterval = setTimeout(heartbeat, this.adaptiveHeartbeat);
70
+ }
71
+ };
72
+
73
+ // First ping immediately
74
+ sendPing();
75
+ this.pingInterval = setTimeout(heartbeat, this.adaptiveHeartbeat);
76
+ }
77
+
78
+ /**
79
+ * Stop heartbeat
80
+ */
81
+ function stopHeartbeat() {
82
+ if (this.pingInterval) {
83
+ clearTimeout(this.pingInterval);
84
+ this.pingInterval = null;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get latency statistics
90
+ * @returns {Object}
91
+ */
92
+ function getLatencyStats() {
93
+ return {
94
+ current: this.latency,
95
+ min: this.minLatency === Infinity ? 0 : this.minLatency,
96
+ max: this.maxLatency,
97
+ avg: this.avgLatency,
98
+ samples: this.latencySamples.length,
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Get connection statistics
104
+ * @returns {Object}
105
+ */
106
+ function getConnectionStats() {
107
+ return {
108
+ connected: this.connected,
109
+ messagesSent: this.messagesSent,
110
+ messagesReceived: this.messagesReceived,
111
+ bytesReceived: this.bytesReceived,
112
+ heartbeatInterval: this.adaptiveHeartbeat,
113
+ latency: getLatencyStats.call(this),
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Reset latency stats
119
+ */
120
+ function resetLatencyStats() {
121
+ this.latencySamples = [];
122
+ this.minLatency = Infinity;
123
+ this.maxLatency = 0;
124
+ this.avgLatency = 0;
125
+ this.latency = 0;
126
+ }
127
+
128
+ module.exports = {
129
+ updateLatency,
130
+ adaptHeartbeat,
131
+ startHeartbeat,
132
+ stopHeartbeat,
133
+ getLatencyStats,
134
+ getConnectionStats,
135
+ resetLatencyStats,
136
+ };