hedgequantx 2.6.163 → 2.7.0
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.
- package/README.md +15 -88
- package/bin/cli.js +0 -11
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +1 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +6 -3
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +116 -99
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +63 -120
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -111
- package/src/services/rithmic/protobuf.js +384 -138
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/direct-providers.js +0 -323
- package/src/services/ai/providers/index.js +0 -62
- package/src/services/ai/providers/other-providers.js +0 -104
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-exit-logic.js +0 -174
- package/src/services/position-manager.js +0 -438
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data-decoders.js +0 -229
- package/src/services/rithmic/market-data.js +0 -272
- package/src/services/rithmic/orders-fast.js +0 -246
- package/src/services/rithmic/proto-decoders.js +0 -403
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-signal-calc.js +0 -147
- package/src/services/strategy/hft-tick.js +0 -407
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -392
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/orders.js +0 -145
- package/src/services/tradovate/websocket.js +0 -97
|
@@ -1,250 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,110 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,217 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,136 +0,0 @@
|
|
|
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
|
-
};
|