hedgequantx 2.6.162 → 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 +235 -0
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +60 -291
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -361
- package/src/services/rithmic/protobuf.js +5 -195
- 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/index.js +0 -526
- 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-manager.js +0 -528
- 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.js +0 -549
- 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-tick.js +0 -507
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -505
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/websocket.js +0 -97
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview ProjectX API Service
|
|
3
|
-
* @module services/projectx
|
|
4
|
-
*
|
|
5
|
-
* STRICT RULE: Display ONLY values returned by API. No estimation, no simulation.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { request } = require('../../utils/http');
|
|
9
|
-
const { PROPFIRMS } = require('../../config');
|
|
10
|
-
const { TIMEOUTS, DEBUG } = require('../../config/settings');
|
|
11
|
-
const { getLimiter } = require('../../security/rateLimit');
|
|
12
|
-
const {
|
|
13
|
-
validateUsername,
|
|
14
|
-
validatePassword,
|
|
15
|
-
validateApiKey,
|
|
16
|
-
validateAccountId,
|
|
17
|
-
sanitizeString,
|
|
18
|
-
maskSensitive,
|
|
19
|
-
} = require('../../security');
|
|
20
|
-
const { getMarketHolidays, checkHoliday, checkMarketHours } = require('./market');
|
|
21
|
-
const { calculateLifetimeStats, calculateDailyPnL, formatTrades } = require('./stats');
|
|
22
|
-
const { logger } = require('../../utils/logger');
|
|
23
|
-
const trading = require('./trading');
|
|
24
|
-
const accounts = require('./accounts');
|
|
25
|
-
|
|
26
|
-
const log = logger.scope('ProjectX');
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* ProjectX API Service for prop firm connections
|
|
30
|
-
*/
|
|
31
|
-
class ProjectXService {
|
|
32
|
-
/**
|
|
33
|
-
* @param {string} [propfirmKey='topstep'] - PropFirm identifier
|
|
34
|
-
*/
|
|
35
|
-
constructor(propfirmKey = 'topstep') {
|
|
36
|
-
this.propfirm = PROPFIRMS[propfirmKey] || PROPFIRMS.topstep;
|
|
37
|
-
this.propfirmKey = propfirmKey;
|
|
38
|
-
this.token = null;
|
|
39
|
-
this.user = null;
|
|
40
|
-
this._limiters = {
|
|
41
|
-
api: getLimiter('api'),
|
|
42
|
-
login: getLimiter('login'),
|
|
43
|
-
orders: getLimiter('orders'),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ==================== GETTERS ====================
|
|
48
|
-
|
|
49
|
-
getToken() { return this.token; }
|
|
50
|
-
getPropfirm() { return this.propfirmKey; }
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Store credentials for token refresh
|
|
54
|
-
* @param {string} userName - Username
|
|
55
|
-
* @param {string} password - Password
|
|
56
|
-
*/
|
|
57
|
-
storeCredentials(userName, password) {
|
|
58
|
-
this._credentials = { userName, password };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get a fresh token by re-authenticating
|
|
63
|
-
* Required for WebSocket connections as TopStep invalidates old sessions
|
|
64
|
-
* @returns {Promise<string|null>} Fresh token or null if failed
|
|
65
|
-
*/
|
|
66
|
-
async getFreshToken() {
|
|
67
|
-
if (this._credentials) {
|
|
68
|
-
const result = await this.login(this._credentials.userName, this._credentials.password);
|
|
69
|
-
if (result.success) {
|
|
70
|
-
return this.token;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return this.token; // Fallback to existing token
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ==================== HTTP ====================
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Make an API request with rate limiting
|
|
80
|
-
* @private
|
|
81
|
-
*/
|
|
82
|
-
async _request(host, path, method = 'GET', data = null, limiterType = 'api') {
|
|
83
|
-
const limiter = this._limiters[limiterType] || this._limiters.api;
|
|
84
|
-
return limiter.execute(() => this._doRequest(host, path, method, data));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Execute the actual HTTP request
|
|
89
|
-
* @private
|
|
90
|
-
*/
|
|
91
|
-
async _doRequest(host, path, method, data) {
|
|
92
|
-
const url = `https://${host}${path}`;
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
const response = await request(url, {
|
|
96
|
-
method,
|
|
97
|
-
body: data,
|
|
98
|
-
token: this.token,
|
|
99
|
-
timeout: TIMEOUTS.API_REQUEST,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
return response;
|
|
103
|
-
} catch (err) {
|
|
104
|
-
log.error('Request failed', { path, error: err.message });
|
|
105
|
-
throw err;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ==================== AUTH ====================
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Login with username and password
|
|
113
|
-
* @param {string} userName - Username
|
|
114
|
-
* @param {string} password - Password
|
|
115
|
-
* @returns {Promise<{success: boolean, token?: string, error?: string}>}
|
|
116
|
-
*/
|
|
117
|
-
async login(userName, password) {
|
|
118
|
-
try {
|
|
119
|
-
validateUsername(userName);
|
|
120
|
-
validatePassword(password, { requireUppercase: false, requireNumber: false });
|
|
121
|
-
|
|
122
|
-
const response = await this._request(
|
|
123
|
-
this.propfirm.userApi, '/Login', 'POST',
|
|
124
|
-
{ userName: sanitizeString(userName), password },
|
|
125
|
-
'login'
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
if (response.statusCode === 200 && response.data.token) {
|
|
129
|
-
this.token = response.data.token;
|
|
130
|
-
this.storeCredentials(userName, password); // Store for token refresh
|
|
131
|
-
log.info('Login successful', { user: sanitizeString(userName) });
|
|
132
|
-
return { success: true, token: maskSensitive(this.token) };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const error = response.data.errorMessage || 'Invalid credentials';
|
|
136
|
-
log.warn('Login failed', { error });
|
|
137
|
-
return { success: false, error };
|
|
138
|
-
} catch (err) {
|
|
139
|
-
log.error('Login error', { error: err.message });
|
|
140
|
-
return { success: false, error: err.message };
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Login with API key
|
|
146
|
-
* @param {string} userName - Username
|
|
147
|
-
* @param {string} apiKey - API key
|
|
148
|
-
* @returns {Promise<{success: boolean, token?: string, error?: string}>}
|
|
149
|
-
*/
|
|
150
|
-
async loginWithApiKey(userName, apiKey) {
|
|
151
|
-
try {
|
|
152
|
-
validateUsername(userName);
|
|
153
|
-
validateApiKey(apiKey);
|
|
154
|
-
|
|
155
|
-
const response = await this._request(
|
|
156
|
-
this.propfirm.userApi, '/Login/key', 'POST',
|
|
157
|
-
{ userName: sanitizeString(userName), apiKey },
|
|
158
|
-
'login'
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
if (response.statusCode === 200 && response.data.token) {
|
|
162
|
-
this.token = response.data.token;
|
|
163
|
-
log.info('API key login successful');
|
|
164
|
-
return { success: true, token: maskSensitive(this.token) };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return { success: false, error: response.data.errorMessage || 'Invalid API key' };
|
|
168
|
-
} catch (err) {
|
|
169
|
-
log.error('API key login error', { error: err.message });
|
|
170
|
-
return { success: false, error: err.message };
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Logout and clear token
|
|
176
|
-
*/
|
|
177
|
-
logout() {
|
|
178
|
-
this.token = null;
|
|
179
|
-
this.user = null;
|
|
180
|
-
log.debug('Logged out');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ==================== USER ====================
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Get user information
|
|
187
|
-
* @returns {Promise<{success: boolean, user?: Object, error?: string}>}
|
|
188
|
-
*/
|
|
189
|
-
async getUser() {
|
|
190
|
-
try {
|
|
191
|
-
const response = await this._request(this.propfirm.userApi, '/User', 'GET');
|
|
192
|
-
|
|
193
|
-
if (response.statusCode === 200) {
|
|
194
|
-
this.user = response.data;
|
|
195
|
-
return { success: true, user: response.data };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return { success: false, error: 'Failed to get user info' };
|
|
199
|
-
} catch (err) {
|
|
200
|
-
return { success: false, error: err.message };
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// ==================== ACCOUNTS ====================
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Get trading accounts with REAL P&L from API
|
|
208
|
-
* @returns {Promise<{success: boolean, accounts: Array, error?: string}>}
|
|
209
|
-
*/
|
|
210
|
-
async getTradingAccounts() {
|
|
211
|
-
return accounts.getTradingAccounts(
|
|
212
|
-
(host, path, method, data) => this._request(host, path, method, data),
|
|
213
|
-
this.propfirm
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// ==================== TRADING ====================
|
|
218
|
-
|
|
219
|
-
/** Get open positions */
|
|
220
|
-
async getPositions(accountId) {
|
|
221
|
-
return trading.getPositions(
|
|
222
|
-
(host, path, method, data) => this._request(host, path, method, data),
|
|
223
|
-
this.propfirm.gatewayApi, accountId
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** Get open orders */
|
|
228
|
-
async getOrders(accountId) {
|
|
229
|
-
return trading.getOrders(
|
|
230
|
-
(host, path, method, data) => this._request(host, path, method, data),
|
|
231
|
-
this.propfirm.gatewayApi, accountId
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/** Get trades for today */
|
|
236
|
-
async getTrades(accountId) {
|
|
237
|
-
return trading.getTrades(
|
|
238
|
-
(host, path, method, data) => this._request(host, path, method, data),
|
|
239
|
-
this.propfirm.gatewayApi, accountId
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** Place an order */
|
|
244
|
-
async placeOrder(orderData) {
|
|
245
|
-
return trading.placeOrder(
|
|
246
|
-
(host, path, method, data, limiter) => this._request(host, path, method, data, limiter),
|
|
247
|
-
this.propfirm.gatewayApi, orderData
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** Cancel an order */
|
|
252
|
-
async cancelOrder(orderId) {
|
|
253
|
-
return trading.cancelOrder(
|
|
254
|
-
(host, path, method, data, limiter) => this._request(host, path, method, data, limiter),
|
|
255
|
-
this.propfirm.gatewayApi, orderId
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/** Cancel all open orders */
|
|
260
|
-
async cancelAllOrders(accountId) {
|
|
261
|
-
return trading.cancelAllOrders(
|
|
262
|
-
(host, path, method, data, limiter) => this._request(host, path, method, data, limiter),
|
|
263
|
-
this.propfirm.gatewayApi, accountId
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** Close a position */
|
|
268
|
-
async closePosition(accountId, contractId) {
|
|
269
|
-
return trading.closePosition(
|
|
270
|
-
(host, path, method, data, limiter) => this._request(host, path, method, data, limiter),
|
|
271
|
-
this.propfirm.gatewayApi, accountId, contractId
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// ==================== TRADES & STATS ====================
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Get trade history
|
|
279
|
-
* @param {number|string} accountId - Account ID
|
|
280
|
-
* @param {number} [days=30] - Days of history
|
|
281
|
-
* @returns {Promise<{success: boolean, trades: Array, error?: string}>}
|
|
282
|
-
*/
|
|
283
|
-
async getTradeHistory(accountId, days = 30) {
|
|
284
|
-
try {
|
|
285
|
-
const id = validateAccountId(accountId);
|
|
286
|
-
const endDate = new Date();
|
|
287
|
-
const startDate = new Date();
|
|
288
|
-
startDate.setDate(startDate.getDate() - days);
|
|
289
|
-
|
|
290
|
-
const response = await this._request(
|
|
291
|
-
this.propfirm.gatewayApi,
|
|
292
|
-
'/api/Trade/search',
|
|
293
|
-
'POST',
|
|
294
|
-
{
|
|
295
|
-
accountId: id,
|
|
296
|
-
startTimestamp: startDate.toISOString(),
|
|
297
|
-
endTimestamp: endDate.toISOString(),
|
|
298
|
-
}
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
if (response.statusCode === 200 && response.data) {
|
|
302
|
-
const trades = Array.isArray(response.data) ? response.data : (response.data.trades || []);
|
|
303
|
-
return { success: true, trades: formatTrades(trades) };
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return { success: true, trades: [] };
|
|
307
|
-
} catch (err) {
|
|
308
|
-
return { success: true, trades: [], error: err.message };
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Get daily statistics
|
|
314
|
-
* @param {number|string} accountId - Account ID
|
|
315
|
-
* @returns {Promise<{success: boolean, stats: Array, error?: string}>}
|
|
316
|
-
*/
|
|
317
|
-
async getDailyStats(accountId) {
|
|
318
|
-
try {
|
|
319
|
-
const id = validateAccountId(accountId);
|
|
320
|
-
const now = new Date();
|
|
321
|
-
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
322
|
-
|
|
323
|
-
const response = await this._request(
|
|
324
|
-
this.propfirm.gatewayApi,
|
|
325
|
-
'/api/Trade/search',
|
|
326
|
-
'POST',
|
|
327
|
-
{
|
|
328
|
-
accountId: id,
|
|
329
|
-
startTimestamp: startOfMonth.toISOString(),
|
|
330
|
-
endTimestamp: now.toISOString(),
|
|
331
|
-
}
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
if (response.statusCode === 200 && response.data) {
|
|
335
|
-
const trades = Array.isArray(response.data) ? response.data : (response.data.trades || []);
|
|
336
|
-
return { success: true, stats: calculateDailyPnL(trades) };
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return { success: false, stats: [] };
|
|
340
|
-
} catch (err) {
|
|
341
|
-
return { success: false, stats: [], error: err.message };
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Get lifetime statistics
|
|
347
|
-
* @param {number|string} accountId - Account ID
|
|
348
|
-
* @returns {Promise<{success: boolean, stats: Object|null, error?: string}>}
|
|
349
|
-
*/
|
|
350
|
-
async getLifetimeStats(accountId) {
|
|
351
|
-
try {
|
|
352
|
-
const tradesResult = await this.getTradeHistory(accountId, 90);
|
|
353
|
-
|
|
354
|
-
if (!tradesResult.success || !tradesResult.trades.length) {
|
|
355
|
-
return { success: true, stats: null };
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return { success: true, stats: calculateLifetimeStats(tradesResult.trades) };
|
|
359
|
-
} catch (err) {
|
|
360
|
-
return { success: false, stats: null, error: err.message };
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// ==================== CONTRACTS ====================
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Get available contracts from Gateway API
|
|
368
|
-
* Returns RAW data from API - NO static mapping, NO mock data
|
|
369
|
-
*
|
|
370
|
-
* @returns {Promise<{success: boolean, contracts: Array, error?: string}>}
|
|
371
|
-
*/
|
|
372
|
-
async getContracts() {
|
|
373
|
-
try {
|
|
374
|
-
const response = await this._request(
|
|
375
|
-
this.propfirm.gatewayApi,
|
|
376
|
-
'/api/Contract/available',
|
|
377
|
-
'POST',
|
|
378
|
-
{ live: false }
|
|
379
|
-
);
|
|
380
|
-
|
|
381
|
-
if (response.statusCode === 200) {
|
|
382
|
-
const rawContracts = response.data.contracts || response.data || [];
|
|
383
|
-
|
|
384
|
-
// Return active contracts with RAW API data only
|
|
385
|
-
const contracts = rawContracts
|
|
386
|
-
.filter(c => c.activeContract === true)
|
|
387
|
-
.sort((a, b) => {
|
|
388
|
-
const grpA = a.contractGroup || '';
|
|
389
|
-
const grpB = b.contractGroup || '';
|
|
390
|
-
if (grpA !== grpB) return grpA.localeCompare(grpB);
|
|
391
|
-
return (a.name || '').localeCompare(b.name || '');
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
return { success: true, contracts };
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return { success: false, contracts: [], error: 'Failed to fetch contracts' };
|
|
398
|
-
} catch (err) {
|
|
399
|
-
return { success: false, contracts: [], error: err.message };
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Search contracts
|
|
405
|
-
* @param {string} searchText - Search text
|
|
406
|
-
* @returns {Promise<{success: boolean, contracts: Array, error?: string}>}
|
|
407
|
-
*/
|
|
408
|
-
async searchContracts(searchText) {
|
|
409
|
-
try {
|
|
410
|
-
const response = await this._request(
|
|
411
|
-
this.propfirm.gatewayApi,
|
|
412
|
-
'/api/Contract/search',
|
|
413
|
-
'POST',
|
|
414
|
-
{ searchText: sanitizeString(searchText), live: false }
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
if (response.statusCode === 200) {
|
|
418
|
-
return { success: true, contracts: response.data.contracts || response.data || [] };
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return { success: false, contracts: [] };
|
|
422
|
-
} catch (err) {
|
|
423
|
-
return { success: false, contracts: [], error: err.message };
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// ==================== MARKET STATUS ====================
|
|
428
|
-
|
|
429
|
-
getMarketHolidays() { return getMarketHolidays(); }
|
|
430
|
-
checkHoliday() { return checkHoliday(); }
|
|
431
|
-
checkMarketHours() { return checkMarketHours(); }
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Get market status
|
|
435
|
-
* @returns {Promise<{success: boolean, isOpen: boolean, message: string}>}
|
|
436
|
-
*/
|
|
437
|
-
async getMarketStatus() {
|
|
438
|
-
const hours = checkMarketHours();
|
|
439
|
-
return { success: true, isOpen: hours.isOpen, message: hours.message };
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
module.exports = { ProjectXService };
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ProjectX Market Hours & Holidays
|
|
3
|
-
* CME Futures trading hours and US market holidays
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get nth weekday of a month
|
|
8
|
-
*/
|
|
9
|
-
const getNthWeekday = (year, month, weekday, n) => {
|
|
10
|
-
let count = 0;
|
|
11
|
-
for (let day = 1; day <= 31; day++) {
|
|
12
|
-
const d = new Date(year, month, day);
|
|
13
|
-
if (d.getMonth() !== month) break;
|
|
14
|
-
if (d.getDay() === weekday) {
|
|
15
|
-
count++;
|
|
16
|
-
if (count === n) return d.toISOString().split('T')[0];
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Get last weekday of a month
|
|
24
|
-
*/
|
|
25
|
-
const getLastWeekday = (year, month, weekday) => {
|
|
26
|
-
const lastDay = new Date(year, month + 1, 0);
|
|
27
|
-
for (let day = lastDay.getDate(); day >= 1; day--) {
|
|
28
|
-
const d = new Date(year, month, day);
|
|
29
|
-
if (d.getDay() === weekday) return d.toISOString().split('T')[0];
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Get Good Friday (Friday before Easter)
|
|
36
|
-
*/
|
|
37
|
-
const getGoodFriday = (year) => {
|
|
38
|
-
const a = year % 19;
|
|
39
|
-
const b = Math.floor(year / 100);
|
|
40
|
-
const c = year % 100;
|
|
41
|
-
const d = Math.floor(b / 4);
|
|
42
|
-
const e = b % 4;
|
|
43
|
-
const f = Math.floor((b + 8) / 25);
|
|
44
|
-
const g = Math.floor((b - f + 1) / 3);
|
|
45
|
-
const h = (19 * a + b - d - g + 15) % 30;
|
|
46
|
-
const i = Math.floor(c / 4);
|
|
47
|
-
const k = c % 4;
|
|
48
|
-
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
|
49
|
-
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
|
50
|
-
const month = Math.floor((h + l - 7 * m + 114) / 31) - 1;
|
|
51
|
-
const day = ((h + l - 7 * m + 114) % 31) + 1;
|
|
52
|
-
|
|
53
|
-
const easter = new Date(year, month, day);
|
|
54
|
-
const goodFriday = new Date(easter);
|
|
55
|
-
goodFriday.setDate(easter.getDate() - 2);
|
|
56
|
-
return goodFriday.toISOString().split('T')[0];
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get day after a date
|
|
61
|
-
*/
|
|
62
|
-
const getDayAfter = (dateStr) => {
|
|
63
|
-
const d = new Date(dateStr);
|
|
64
|
-
d.setDate(d.getDate() + 1);
|
|
65
|
-
return d.toISOString().split('T')[0];
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Get US market holidays for the current year
|
|
70
|
-
*/
|
|
71
|
-
const getMarketHolidays = () => {
|
|
72
|
-
const year = new Date().getFullYear();
|
|
73
|
-
|
|
74
|
-
return [
|
|
75
|
-
{ date: `${year}-01-01`, name: "New Year's Day", earlyClose: false },
|
|
76
|
-
{ date: getNthWeekday(year, 0, 1, 3), name: 'MLK Day', earlyClose: false },
|
|
77
|
-
{ date: getNthWeekday(year, 1, 1, 3), name: "Presidents' Day", earlyClose: false },
|
|
78
|
-
{ date: getGoodFriday(year), name: 'Good Friday', earlyClose: false },
|
|
79
|
-
{ date: getLastWeekday(year, 4, 1), name: 'Memorial Day', earlyClose: false },
|
|
80
|
-
{ date: `${year}-06-19`, name: 'Juneteenth', earlyClose: false },
|
|
81
|
-
{ date: `${year}-07-04`, name: 'Independence Day', earlyClose: false },
|
|
82
|
-
{ date: `${year}-07-03`, name: 'Independence Day Eve', earlyClose: true },
|
|
83
|
-
{ date: getNthWeekday(year, 8, 1, 1), name: 'Labor Day', earlyClose: false },
|
|
84
|
-
{ date: getNthWeekday(year, 10, 4, 4), name: 'Thanksgiving', earlyClose: false },
|
|
85
|
-
{ date: getDayAfter(getNthWeekday(year, 10, 4, 4)), name: 'Black Friday', earlyClose: true },
|
|
86
|
-
{ date: `${year}-12-25`, name: 'Christmas Day', earlyClose: false },
|
|
87
|
-
{ date: `${year}-12-24`, name: 'Christmas Eve', earlyClose: true },
|
|
88
|
-
{ date: `${year}-12-31`, name: "New Year's Eve", earlyClose: true },
|
|
89
|
-
];
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Check if today is a market holiday
|
|
94
|
-
*/
|
|
95
|
-
const checkHoliday = () => {
|
|
96
|
-
const today = new Date().toISOString().split('T')[0];
|
|
97
|
-
const holidays = getMarketHolidays();
|
|
98
|
-
const holiday = holidays.find(h => h.date === today);
|
|
99
|
-
|
|
100
|
-
if (holiday) {
|
|
101
|
-
return { isHoliday: !holiday.earlyClose, holiday };
|
|
102
|
-
}
|
|
103
|
-
return { isHoliday: false };
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Get current trading session based on ET time
|
|
108
|
-
* Sessions (in ET):
|
|
109
|
-
* - ASIA: 6:00 PM - 2:00 AM (18:00-02:00)
|
|
110
|
-
* - LONDON: 2:00 AM - 8:00 AM (02:00-08:00)
|
|
111
|
-
* - AMERICAN: 8:00 AM - 5:00 PM (08:00-17:00)
|
|
112
|
-
* - MAINTENANCE: 5:00 PM - 6:00 PM (17:00-18:00)
|
|
113
|
-
*/
|
|
114
|
-
const getTradingSession = () => {
|
|
115
|
-
const now = new Date();
|
|
116
|
-
// Convert to ET (UTC-5 or UTC-4 for DST)
|
|
117
|
-
const etOptions = { timeZone: 'America/New_York', hour: 'numeric', hour12: false };
|
|
118
|
-
const etHour = parseInt(new Intl.DateTimeFormat('en-US', etOptions).format(now));
|
|
119
|
-
|
|
120
|
-
// Determine session based on ET hour
|
|
121
|
-
if (etHour >= 18 || etHour < 2) {
|
|
122
|
-
return 'ASIA';
|
|
123
|
-
} else if (etHour >= 2 && etHour < 8) {
|
|
124
|
-
return 'LONDON';
|
|
125
|
-
} else if (etHour >= 8 && etHour < 17) {
|
|
126
|
-
return 'AMERICAN';
|
|
127
|
-
} else {
|
|
128
|
-
return 'MAINTENANCE';
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Check if futures market is open based on CME hours and holidays
|
|
134
|
-
*/
|
|
135
|
-
const checkMarketHours = () => {
|
|
136
|
-
const now = new Date();
|
|
137
|
-
const utcDay = now.getUTCDay();
|
|
138
|
-
const utcHour = now.getUTCHours();
|
|
139
|
-
|
|
140
|
-
const holidayCheck = checkHoliday();
|
|
141
|
-
if (holidayCheck.isHoliday) {
|
|
142
|
-
return { isOpen: false, message: `Market closed - ${holidayCheck.holiday.name}`, session: null };
|
|
143
|
-
}
|
|
144
|
-
if (holidayCheck.holiday && holidayCheck.holiday.earlyClose && utcHour >= 18) {
|
|
145
|
-
return { isOpen: false, message: `Market closed early - ${holidayCheck.holiday.name}`, session: null };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (utcDay === 6) {
|
|
149
|
-
return { isOpen: false, message: 'Market closed - Weekend (Saturday)', session: null };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (utcDay === 0 && utcHour < 23) {
|
|
153
|
-
return { isOpen: false, message: 'Market closed - Opens Sunday 6:00 PM ET', session: null };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (utcDay === 5 && utcHour >= 22) {
|
|
157
|
-
return { isOpen: false, message: 'Market closed - Weekend', session: null };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (utcHour === 22 && utcDay !== 5) {
|
|
161
|
-
return { isOpen: false, message: 'Market closed - Daily maintenance (5:00-6:00 PM ET)', session: 'MAINTENANCE' };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const session = getTradingSession();
|
|
165
|
-
return { isOpen: true, message: 'Market is open', session };
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
module.exports = {
|
|
169
|
-
getMarketHolidays,
|
|
170
|
-
checkHoliday,
|
|
171
|
-
checkMarketHours
|
|
172
|
-
};
|