hedgequantx 1.2.147 → 1.3.1
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 +115 -71
- package/package.json +1 -1
- package/src/app.js +1 -22
- package/src/menus/connect.js +2 -1
- package/src/menus/dashboard.js +135 -31
- package/src/services/index.js +1 -1
- package/src/services/projectx/index.js +345 -0
- package/src/services/projectx/market.js +145 -0
- package/src/services/projectx/stats.js +110 -0
- package/src/services/rithmic/accounts.js +183 -0
- package/src/services/rithmic/handlers.js +191 -0
- package/src/services/rithmic/index.js +69 -673
- package/src/services/rithmic/orders.js +192 -0
- package/src/ui/index.js +23 -1
- package/src/services/projectx.js +0 -771
|
@@ -5,49 +5,50 @@
|
|
|
5
5
|
|
|
6
6
|
const EventEmitter = require('events');
|
|
7
7
|
const { RithmicConnection } = require('./connection');
|
|
8
|
-
const {
|
|
9
|
-
const {
|
|
8
|
+
const { RITHMIC_ENDPOINTS, RITHMIC_SYSTEMS } = require('./constants');
|
|
9
|
+
const { createOrderHandler, createPnLHandler } = require('./handlers');
|
|
10
|
+
const { fetchAccounts, getTradingAccounts, requestPnLSnapshot, subscribePnLUpdates, getPositions, hashAccountId } = require('./accounts');
|
|
11
|
+
const { placeOrder, cancelOrder, getOrders, getOrderHistory, closePosition } = require('./orders');
|
|
12
|
+
|
|
13
|
+
// PropFirm configurations
|
|
14
|
+
const PROPFIRM_CONFIGS = {
|
|
15
|
+
'apex': { name: 'Apex Trader Funding', systemName: 'Apex', defaultBalance: 300000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
16
|
+
'apex_rithmic': { name: 'Apex Trader Funding', systemName: 'Apex', defaultBalance: 300000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
17
|
+
'topstep_r': { name: 'Topstep (Rithmic)', systemName: RITHMIC_SYSTEMS.TOPSTEP, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
18
|
+
'bulenox_r': { name: 'Bulenox (Rithmic)', systemName: RITHMIC_SYSTEMS.BULENOX, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
19
|
+
'earn2trade': { name: 'Earn2Trade', systemName: RITHMIC_SYSTEMS.EARN_2_TRADE, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
20
|
+
'mescapital': { name: 'MES Capital', systemName: RITHMIC_SYSTEMS.MES_CAPITAL, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
21
|
+
'tradefundrr': { name: 'TradeFundrr', systemName: RITHMIC_SYSTEMS.TRADEFUNDRR, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
22
|
+
'thetradingpit': { name: 'The Trading Pit', systemName: RITHMIC_SYSTEMS.THE_TRADING_PIT, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
23
|
+
'fundedfutures': { name: 'Funded Futures Network', systemName: RITHMIC_SYSTEMS.FUNDED_FUTURES_NETWORK, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
24
|
+
'propshop': { name: 'PropShop Trader', systemName: RITHMIC_SYSTEMS.PROPSHOP_TRADER, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
25
|
+
'4proptrader': { name: '4PropTrader', systemName: RITHMIC_SYSTEMS.FOUR_PROP_TRADER, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
26
|
+
'daytraders': { name: 'DayTraders.com', systemName: RITHMIC_SYSTEMS.DAY_TRADERS, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
27
|
+
'10xfutures': { name: '10X Futures', systemName: RITHMIC_SYSTEMS.TEN_X_FUTURES, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
28
|
+
'lucidtrading': { name: 'Lucid Trading', systemName: RITHMIC_SYSTEMS.LUCID_TRADING, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
29
|
+
'thrivetrading': { name: 'Thrive Trading', systemName: RITHMIC_SYSTEMS.THRIVE_TRADING, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
30
|
+
'legendstrading': { name: 'Legends Trading', systemName: RITHMIC_SYSTEMS.LEGENDS_TRADING, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
31
|
+
};
|
|
10
32
|
|
|
11
33
|
class RithmicService extends EventEmitter {
|
|
12
34
|
constructor(propfirmKey) {
|
|
13
35
|
super();
|
|
14
36
|
this.propfirmKey = propfirmKey;
|
|
15
|
-
this.propfirm =
|
|
37
|
+
this.propfirm = PROPFIRM_CONFIGS[propfirmKey] || {
|
|
38
|
+
name: propfirmKey,
|
|
39
|
+
systemName: 'Rithmic Paper Trading',
|
|
40
|
+
defaultBalance: 150000,
|
|
41
|
+
gateway: RITHMIC_ENDPOINTS.PAPER
|
|
42
|
+
};
|
|
16
43
|
this.orderConn = null;
|
|
17
44
|
this.pnlConn = null;
|
|
18
45
|
this.loginInfo = null;
|
|
19
46
|
this.accounts = [];
|
|
20
|
-
this.accountPnL = new Map();
|
|
21
|
-
this.positions = new Map();
|
|
22
|
-
this.orders = [];
|
|
47
|
+
this.accountPnL = new Map();
|
|
48
|
+
this.positions = new Map();
|
|
49
|
+
this.orders = [];
|
|
23
50
|
this.user = null;
|
|
24
|
-
this.credentials = null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Get PropFirm configuration
|
|
29
|
-
* Note: Apex and other prop firms use the Chicago gateway (rprotocol.rithmic.com), NOT Paper Trading endpoint
|
|
30
|
-
*/
|
|
31
|
-
getPropFirmConfig(key) {
|
|
32
|
-
const propfirms = {
|
|
33
|
-
'apex': { name: 'Apex Trader Funding', systemName: 'Apex', defaultBalance: 300000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
34
|
-
'apex_rithmic': { name: 'Apex Trader Funding', systemName: 'Apex', defaultBalance: 300000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
35
|
-
'topstep_r': { name: 'Topstep (Rithmic)', systemName: RITHMIC_SYSTEMS.TOPSTEP, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
36
|
-
'bulenox_r': { name: 'Bulenox (Rithmic)', systemName: RITHMIC_SYSTEMS.BULENOX, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
37
|
-
'earn2trade': { name: 'Earn2Trade', systemName: RITHMIC_SYSTEMS.EARN_2_TRADE, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
38
|
-
'mescapital': { name: 'MES Capital', systemName: RITHMIC_SYSTEMS.MES_CAPITAL, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
39
|
-
'tradefundrr': { name: 'TradeFundrr', systemName: RITHMIC_SYSTEMS.TRADEFUNDRR, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
40
|
-
'thetradingpit': { name: 'The Trading Pit', systemName: RITHMIC_SYSTEMS.THE_TRADING_PIT, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
41
|
-
'fundedfutures': { name: 'Funded Futures Network', systemName: RITHMIC_SYSTEMS.FUNDED_FUTURES_NETWORK, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
42
|
-
'propshop': { name: 'PropShop Trader', systemName: RITHMIC_SYSTEMS.PROPSHOP_TRADER, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
43
|
-
'4proptrader': { name: '4PropTrader', systemName: RITHMIC_SYSTEMS.FOUR_PROP_TRADER, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
44
|
-
'daytraders': { name: 'DayTraders.com', systemName: RITHMIC_SYSTEMS.DAY_TRADERS, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
45
|
-
'10xfutures': { name: '10X Futures', systemName: RITHMIC_SYSTEMS.TEN_X_FUTURES, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
46
|
-
'lucidtrading': { name: 'Lucid Trading', systemName: RITHMIC_SYSTEMS.LUCID_TRADING, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
47
|
-
'thrivetrading': { name: 'Thrive Trading', systemName: RITHMIC_SYSTEMS.THRIVE_TRADING, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
48
|
-
'legendstrading': { name: 'Legends Trading', systemName: RITHMIC_SYSTEMS.LEGENDS_TRADING, defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
49
|
-
};
|
|
50
|
-
return propfirms[key] || { name: key, systemName: 'Rithmic Paper Trading', defaultBalance: 150000, gateway: RITHMIC_ENDPOINTS.PAPER };
|
|
51
|
+
this.credentials = null;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
/**
|
|
@@ -55,10 +56,7 @@ class RithmicService extends EventEmitter {
|
|
|
55
56
|
*/
|
|
56
57
|
async login(username, password) {
|
|
57
58
|
try {
|
|
58
|
-
// Connect to ORDER_PLANT
|
|
59
59
|
this.orderConn = new RithmicConnection();
|
|
60
|
-
|
|
61
|
-
// Use propfirm-specific gateway (Chicago for Apex and most prop firms)
|
|
62
60
|
const gateway = this.propfirm.gateway || RITHMIC_ENDPOINTS.CHICAGO;
|
|
63
61
|
|
|
64
62
|
const config = {
|
|
@@ -71,12 +69,9 @@ class RithmicService extends EventEmitter {
|
|
|
71
69
|
};
|
|
72
70
|
|
|
73
71
|
await this.orderConn.connect(config);
|
|
72
|
+
this.orderConn.on('message', createOrderHandler(this));
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
this.orderConn.on('message', (msg) => this.handleOrderMessage(msg));
|
|
77
|
-
|
|
78
|
-
// Login
|
|
79
|
-
return new Promise((resolve, reject) => {
|
|
74
|
+
return new Promise((resolve) => {
|
|
80
75
|
const timeout = setTimeout(() => {
|
|
81
76
|
resolve({ success: false, error: 'Login timeout - server did not respond' });
|
|
82
77
|
}, 30000);
|
|
@@ -86,14 +81,8 @@ class RithmicService extends EventEmitter {
|
|
|
86
81
|
this.loginInfo = data;
|
|
87
82
|
this.user = { userName: username, fcmId: data.fcmId, ibId: data.ibId };
|
|
88
83
|
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
await this.fetchAccounts();
|
|
92
|
-
} catch (e) {
|
|
93
|
-
// Accounts fetch failed, ignore
|
|
94
|
-
}
|
|
84
|
+
try { await fetchAccounts(this); } catch (e) {}
|
|
95
85
|
|
|
96
|
-
// Create default account if none found
|
|
97
86
|
if (this.accounts.length === 0) {
|
|
98
87
|
this.accounts = [{
|
|
99
88
|
accountId: username,
|
|
@@ -103,10 +92,8 @@ class RithmicService extends EventEmitter {
|
|
|
103
92
|
}];
|
|
104
93
|
}
|
|
105
94
|
|
|
106
|
-
// Store credentials for PNL connection
|
|
107
95
|
this.credentials = { username, password };
|
|
108
96
|
|
|
109
|
-
// Format accounts for response
|
|
110
97
|
const formattedAccounts = this.accounts.map(acc => ({
|
|
111
98
|
accountId: acc.accountId,
|
|
112
99
|
accountName: acc.accountName || acc.accountId,
|
|
@@ -116,11 +103,7 @@ class RithmicService extends EventEmitter {
|
|
|
116
103
|
status: 0
|
|
117
104
|
}));
|
|
118
105
|
|
|
119
|
-
resolve({
|
|
120
|
-
success: true,
|
|
121
|
-
user: this.user,
|
|
122
|
-
accounts: formattedAccounts
|
|
123
|
-
});
|
|
106
|
+
resolve({ success: true, user: this.user, accounts: formattedAccounts });
|
|
124
107
|
});
|
|
125
108
|
|
|
126
109
|
this.orderConn.once('loginFailed', (data) => {
|
|
@@ -142,8 +125,6 @@ class RithmicService extends EventEmitter {
|
|
|
142
125
|
async connectPnL(username, password) {
|
|
143
126
|
try {
|
|
144
127
|
this.pnlConn = new RithmicConnection();
|
|
145
|
-
|
|
146
|
-
// Use propfirm-specific gateway (Chicago for Apex and most prop firms)
|
|
147
128
|
const gateway = this.propfirm.gateway || RITHMIC_ENDPOINTS.CHICAGO;
|
|
148
129
|
|
|
149
130
|
const config = {
|
|
@@ -156,9 +137,9 @@ class RithmicService extends EventEmitter {
|
|
|
156
137
|
};
|
|
157
138
|
|
|
158
139
|
await this.pnlConn.connect(config);
|
|
159
|
-
this.pnlConn.on('message', (
|
|
140
|
+
this.pnlConn.on('message', createPnLHandler(this));
|
|
160
141
|
|
|
161
|
-
return new Promise((resolve
|
|
142
|
+
return new Promise((resolve) => {
|
|
162
143
|
const timeout = setTimeout(() => resolve(false), 10000);
|
|
163
144
|
|
|
164
145
|
this.pnlConn.once('loggedIn', () => {
|
|
@@ -178,463 +159,29 @@ class RithmicService extends EventEmitter {
|
|
|
178
159
|
}
|
|
179
160
|
}
|
|
180
161
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
async
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// Quick timeout - don't wait too long for accounts
|
|
191
|
-
return new Promise((resolve) => {
|
|
192
|
-
const accounts = [];
|
|
193
|
-
|
|
194
|
-
const timeout = setTimeout(() => {
|
|
195
|
-
this.accounts = accounts;
|
|
196
|
-
resolve(accounts);
|
|
197
|
-
}, 2000); // 2 seconds max
|
|
198
|
-
|
|
199
|
-
this.once('accountReceived', (account) => {
|
|
200
|
-
accounts.push(account);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
this.once('accountListComplete', () => {
|
|
204
|
-
clearTimeout(timeout);
|
|
205
|
-
this.accounts = accounts;
|
|
206
|
-
resolve(accounts);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// Request account list
|
|
210
|
-
try {
|
|
211
|
-
this.orderConn.send('RequestAccountList', {
|
|
212
|
-
templateId: REQ.ACCOUNT_LIST,
|
|
213
|
-
userMsg: ['HQX'],
|
|
214
|
-
fcmId: this.loginInfo.fcmId,
|
|
215
|
-
ibId: this.loginInfo.ibId,
|
|
216
|
-
});
|
|
217
|
-
} catch (e) {
|
|
218
|
-
clearTimeout(timeout);
|
|
219
|
-
resolve([]);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Get trading accounts (formatted like ProjectX)
|
|
226
|
-
*/
|
|
227
|
-
async getTradingAccounts() {
|
|
228
|
-
// Only try to fetch if we don't have accounts yet
|
|
229
|
-
if (this.accounts.length === 0 && this.orderConn && this.loginInfo) {
|
|
230
|
-
try {
|
|
231
|
-
await this.fetchAccounts();
|
|
232
|
-
} catch (e) {
|
|
233
|
-
// Ignore fetch errors
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
let tradingAccounts = this.accounts.map((acc, index) => {
|
|
238
|
-
const pnl = this.accountPnL.get(acc.accountId) || {};
|
|
239
|
-
const balance = parseFloat(pnl.accountBalance || pnl.marginBalance || pnl.cashOnHand || 0) || this.propfirm.defaultBalance;
|
|
240
|
-
const startingBalance = this.propfirm.defaultBalance;
|
|
241
|
-
const profitAndLoss = balance - startingBalance;
|
|
242
|
-
|
|
243
|
-
return {
|
|
244
|
-
accountId: this.hashAccountId(acc.accountId),
|
|
245
|
-
rithmicAccountId: acc.accountId,
|
|
246
|
-
accountName: acc.accountName || acc.accountId,
|
|
247
|
-
name: acc.accountName || acc.accountId,
|
|
248
|
-
balance: balance,
|
|
249
|
-
startingBalance: startingBalance,
|
|
250
|
-
profitAndLoss: profitAndLoss,
|
|
251
|
-
status: 0, // Active
|
|
252
|
-
platform: 'Rithmic',
|
|
253
|
-
propfirm: this.propfirm.name,
|
|
254
|
-
};
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// If no accounts but user is logged in, create a default account from login info
|
|
258
|
-
if (tradingAccounts.length === 0 && this.user) {
|
|
259
|
-
const userName = this.user.userName || 'Unknown';
|
|
260
|
-
tradingAccounts = [{
|
|
261
|
-
accountId: this.hashAccountId(userName),
|
|
262
|
-
rithmicAccountId: userName,
|
|
263
|
-
accountName: userName,
|
|
264
|
-
name: userName,
|
|
265
|
-
balance: this.propfirm.defaultBalance,
|
|
266
|
-
startingBalance: this.propfirm.defaultBalance,
|
|
267
|
-
profitAndLoss: 0,
|
|
268
|
-
status: 0, // Active
|
|
269
|
-
platform: 'Rithmic',
|
|
270
|
-
propfirm: this.propfirm.name,
|
|
271
|
-
}];
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return { success: true, accounts: tradingAccounts };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Request PnL snapshot for accounts
|
|
279
|
-
*/
|
|
280
|
-
async requestPnLSnapshot() {
|
|
281
|
-
if (!this.pnlConn || !this.loginInfo) return;
|
|
282
|
-
|
|
283
|
-
for (const acc of this.accounts) {
|
|
284
|
-
this.pnlConn.send('RequestPnLPositionSnapshot', {
|
|
285
|
-
templateId: REQ.PNL_POSITION_SNAPSHOT,
|
|
286
|
-
userMsg: ['HQX'],
|
|
287
|
-
fcmId: acc.fcmId || this.loginInfo.fcmId,
|
|
288
|
-
ibId: acc.ibId || this.loginInfo.ibId,
|
|
289
|
-
accountId: acc.accountId,
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Wait for responses
|
|
294
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Subscribe to PnL updates
|
|
299
|
-
*/
|
|
300
|
-
subscribePnLUpdates() {
|
|
301
|
-
if (!this.pnlConn || !this.loginInfo) return;
|
|
302
|
-
|
|
303
|
-
for (const acc of this.accounts) {
|
|
304
|
-
this.pnlConn.send('RequestPnLPositionUpdates', {
|
|
305
|
-
templateId: REQ.PNL_POSITION_UPDATES,
|
|
306
|
-
userMsg: ['HQX'],
|
|
307
|
-
request: 1, // Subscribe
|
|
308
|
-
fcmId: acc.fcmId || this.loginInfo.fcmId,
|
|
309
|
-
ibId: acc.ibId || this.loginInfo.ibId,
|
|
310
|
-
accountId: acc.accountId,
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Handle ORDER_PLANT messages
|
|
317
|
-
*/
|
|
318
|
-
handleOrderMessage(msg) {
|
|
319
|
-
const { templateId, data } = msg;
|
|
320
|
-
|
|
321
|
-
switch (templateId) {
|
|
322
|
-
case RES.LOGIN_INFO:
|
|
323
|
-
this.onLoginInfo(data);
|
|
324
|
-
break;
|
|
325
|
-
case RES.ACCOUNT_LIST:
|
|
326
|
-
this.onAccountList(data);
|
|
327
|
-
break;
|
|
328
|
-
case RES.TRADE_ROUTES:
|
|
329
|
-
this.onTradeRoutes(data);
|
|
330
|
-
break;
|
|
331
|
-
case RES.SHOW_ORDERS:
|
|
332
|
-
this.onShowOrdersResponse(data);
|
|
333
|
-
break;
|
|
334
|
-
case STREAM.EXCHANGE_NOTIFICATION:
|
|
335
|
-
this.onExchangeNotification(data);
|
|
336
|
-
break;
|
|
337
|
-
case STREAM.ORDER_NOTIFICATION:
|
|
338
|
-
this.onOrderNotification(data);
|
|
339
|
-
break;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
onShowOrdersResponse(data) {
|
|
344
|
-
try {
|
|
345
|
-
const res = proto.decode('ResponseShowOrders', data);
|
|
346
|
-
if (res.rpCode?.[0] === '0') {
|
|
347
|
-
// End of orders list
|
|
348
|
-
this.emit('ordersReceived');
|
|
349
|
-
}
|
|
350
|
-
} catch (e) {
|
|
351
|
-
// Ignore
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Handle PNL_PLANT messages
|
|
357
|
-
*/
|
|
358
|
-
handlePnLMessage(msg) {
|
|
359
|
-
const { templateId, data } = msg;
|
|
360
|
-
|
|
361
|
-
switch (templateId) {
|
|
362
|
-
case RES.PNL_POSITION_SNAPSHOT:
|
|
363
|
-
case RES.PNL_POSITION_UPDATES:
|
|
364
|
-
// OK response
|
|
365
|
-
break;
|
|
366
|
-
case STREAM.ACCOUNT_PNL_UPDATE:
|
|
367
|
-
this.onAccountPnLUpdate(data);
|
|
368
|
-
break;
|
|
369
|
-
case STREAM.INSTRUMENT_PNL_UPDATE:
|
|
370
|
-
this.onInstrumentPnLUpdate(data);
|
|
371
|
-
break;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
onLoginInfo(data) {
|
|
376
|
-
try {
|
|
377
|
-
const res = proto.decode('ResponseLoginInfo', data);
|
|
378
|
-
this.emit('loginInfoReceived', {
|
|
379
|
-
fcmId: res.fcmId,
|
|
380
|
-
ibId: res.ibId,
|
|
381
|
-
firstName: res.firstName,
|
|
382
|
-
lastName: res.lastName,
|
|
383
|
-
userType: res.userType,
|
|
384
|
-
});
|
|
385
|
-
} catch (e) {
|
|
386
|
-
// Ignore
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
onAccountList(data) {
|
|
391
|
-
try {
|
|
392
|
-
const res = proto.decode('ResponseAccountList', data);
|
|
393
|
-
|
|
394
|
-
if (res.rpCode?.[0] === '0') {
|
|
395
|
-
// End of list
|
|
396
|
-
this.emit('accountListComplete');
|
|
397
|
-
} else if (res.accountId) {
|
|
398
|
-
const account = {
|
|
399
|
-
fcmId: res.fcmId,
|
|
400
|
-
ibId: res.ibId,
|
|
401
|
-
accountId: res.accountId,
|
|
402
|
-
accountName: res.accountName,
|
|
403
|
-
accountCurrency: res.accountCurrency,
|
|
404
|
-
};
|
|
405
|
-
this.accounts.push(account);
|
|
406
|
-
this.emit('accountReceived', account);
|
|
407
|
-
}
|
|
408
|
-
} catch (e) {
|
|
409
|
-
// Ignore
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
onTradeRoutes(data) {
|
|
414
|
-
try {
|
|
415
|
-
const res = proto.decode('ResponseTradeRoutes', data);
|
|
416
|
-
this.emit('tradeRoutes', res);
|
|
417
|
-
} catch (e) {
|
|
418
|
-
// Ignore
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
onAccountPnLUpdate(data) {
|
|
423
|
-
try {
|
|
424
|
-
const pnl = decodeAccountPnL(data);
|
|
425
|
-
if (pnl.accountId) {
|
|
426
|
-
this.accountPnL.set(pnl.accountId, {
|
|
427
|
-
accountBalance: parseFloat(pnl.accountBalance || 0),
|
|
428
|
-
cashOnHand: parseFloat(pnl.cashOnHand || 0),
|
|
429
|
-
marginBalance: parseFloat(pnl.marginBalance || 0),
|
|
430
|
-
openPositionPnl: parseFloat(pnl.openPositionPnl || 0),
|
|
431
|
-
closedPositionPnl: parseFloat(pnl.closedPositionPnl || 0),
|
|
432
|
-
dayPnl: parseFloat(pnl.dayPnl || 0),
|
|
433
|
-
});
|
|
434
|
-
this.emit('pnlUpdate', pnl);
|
|
435
|
-
}
|
|
436
|
-
} catch (e) {
|
|
437
|
-
// Ignore
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
onInstrumentPnLUpdate(data) {
|
|
442
|
-
// Handle instrument-level PnL - this contains position data
|
|
443
|
-
try {
|
|
444
|
-
const pos = decodeInstrumentPnL(data);
|
|
445
|
-
if (pos.symbol && pos.accountId) {
|
|
446
|
-
const key = `${pos.accountId}:${pos.symbol}:${pos.exchange}`;
|
|
447
|
-
// Net quantity can come from netQuantity field or calculated from buy/sell
|
|
448
|
-
const netQty = pos.netQuantity || pos.openPositionQuantity || ((pos.buyQty || 0) - (pos.sellQty || 0));
|
|
449
|
-
|
|
450
|
-
if (netQty !== 0) {
|
|
451
|
-
// We have an open position
|
|
452
|
-
this.positions.set(key, {
|
|
453
|
-
accountId: pos.accountId,
|
|
454
|
-
symbol: pos.symbol,
|
|
455
|
-
exchange: pos.exchange || 'CME',
|
|
456
|
-
quantity: netQty,
|
|
457
|
-
averagePrice: pos.avgOpenFillPrice || 0,
|
|
458
|
-
openPnl: parseFloat(pos.openPositionPnl || pos.dayOpenPnl || 0),
|
|
459
|
-
closedPnl: parseFloat(pos.closedPositionPnl || pos.dayClosedPnl || 0),
|
|
460
|
-
dayPnl: parseFloat(pos.dayPnl || 0),
|
|
461
|
-
isSnapshot: pos.isSnapshot || false,
|
|
462
|
-
});
|
|
463
|
-
} else {
|
|
464
|
-
// Position closed
|
|
465
|
-
this.positions.delete(key);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
this.emit('positionUpdate', this.positions.get(key));
|
|
469
|
-
}
|
|
470
|
-
} catch (e) {
|
|
471
|
-
// Ignore decode errors
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
onExchangeNotification(data) {
|
|
476
|
-
this.emit('exchangeNotification', data);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
onOrderNotification(data) {
|
|
480
|
-
this.emit('orderNotification', data);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Hash account ID to numeric (for compatibility)
|
|
485
|
-
*/
|
|
486
|
-
hashAccountId(str) {
|
|
487
|
-
let hash = 0;
|
|
488
|
-
for (let i = 0; i < str.length; i++) {
|
|
489
|
-
const char = str.charCodeAt(i);
|
|
490
|
-
hash = (hash << 5) - hash + char;
|
|
491
|
-
hash = hash & hash;
|
|
492
|
-
}
|
|
493
|
-
return Math.abs(hash);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* Get user info
|
|
498
|
-
*/
|
|
499
|
-
async getUser() {
|
|
500
|
-
return this.user;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Get positions via PNL_PLANT
|
|
505
|
-
* Positions are streamed via InstrumentPnLPositionUpdate (template 450)
|
|
506
|
-
*/
|
|
507
|
-
async getPositions() {
|
|
508
|
-
// If PNL connection not established, try to connect
|
|
509
|
-
if (!this.pnlConn && this.credentials) {
|
|
510
|
-
await this.connectPnL(this.credentials.username, this.credentials.password);
|
|
511
|
-
// Request snapshot to populate positions
|
|
512
|
-
await this.requestPnLSnapshot();
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// Return cached positions
|
|
516
|
-
const positions = Array.from(this.positions.values()).map(pos => ({
|
|
517
|
-
symbol: pos.symbol,
|
|
518
|
-
exchange: pos.exchange,
|
|
519
|
-
quantity: pos.quantity,
|
|
520
|
-
averagePrice: pos.averagePrice,
|
|
521
|
-
unrealizedPnl: pos.openPnl,
|
|
522
|
-
realizedPnl: pos.closedPnl,
|
|
523
|
-
side: pos.quantity > 0 ? 'LONG' : 'SHORT',
|
|
524
|
-
}));
|
|
525
|
-
|
|
526
|
-
return { success: true, positions };
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* Get orders via ORDER_PLANT
|
|
531
|
-
* Uses RequestShowOrders (template 320) -> ResponseShowOrders (template 321)
|
|
532
|
-
*/
|
|
533
|
-
async getOrders() {
|
|
534
|
-
if (!this.orderConn || !this.loginInfo) {
|
|
535
|
-
return { success: true, orders: [] };
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
return new Promise((resolve) => {
|
|
539
|
-
const orders = [];
|
|
540
|
-
const timeout = setTimeout(() => {
|
|
541
|
-
resolve({ success: true, orders });
|
|
542
|
-
}, 3000);
|
|
162
|
+
// Delegate to modules
|
|
163
|
+
async getTradingAccounts() { return getTradingAccounts(this); }
|
|
164
|
+
async getPositions() { return getPositions(this); }
|
|
165
|
+
async getOrders() { return getOrders(this); }
|
|
166
|
+
async getOrderHistory(date) { return getOrderHistory(this, date); }
|
|
167
|
+
async placeOrder(orderData) { return placeOrder(this, orderData); }
|
|
168
|
+
async cancelOrder(orderId) { return cancelOrder(this, orderId); }
|
|
169
|
+
async closePosition(accountId, symbol) { return closePosition(this, accountId, symbol); }
|
|
543
170
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
quantity: notification.quantity,
|
|
554
|
-
filledQuantity: notification.filledQuantity || 0,
|
|
555
|
-
price: notification.price,
|
|
556
|
-
orderType: notification.orderType,
|
|
557
|
-
status: notification.status,
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
this.once('ordersReceived', () => {
|
|
563
|
-
clearTimeout(timeout);
|
|
564
|
-
this.removeListener('orderNotification', orderHandler);
|
|
565
|
-
resolve({ success: true, orders });
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
this.on('orderNotification', orderHandler);
|
|
569
|
-
|
|
570
|
-
// Send request
|
|
571
|
-
try {
|
|
572
|
-
for (const acc of this.accounts) {
|
|
573
|
-
this.orderConn.send('RequestShowOrders', {
|
|
574
|
-
templateId: REQ.SHOW_ORDERS,
|
|
575
|
-
userMsg: ['HQX'],
|
|
576
|
-
fcmId: acc.fcmId || this.loginInfo.fcmId,
|
|
577
|
-
ibId: acc.ibId || this.loginInfo.ibId,
|
|
578
|
-
accountId: acc.accountId,
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
} catch (e) {
|
|
582
|
-
clearTimeout(timeout);
|
|
583
|
-
resolve({ success: false, error: e.message, orders: [] });
|
|
584
|
-
}
|
|
585
|
-
});
|
|
171
|
+
// Stubs for API compatibility
|
|
172
|
+
async getUser() { return this.user; }
|
|
173
|
+
async getLifetimeStats() { return { success: true, stats: null }; }
|
|
174
|
+
async getDailyStats() { return { success: true, stats: [] }; }
|
|
175
|
+
async getTradeHistory() { return { success: true, trades: [] }; }
|
|
176
|
+
|
|
177
|
+
async getMarketStatus() {
|
|
178
|
+
const status = this.checkMarketHours();
|
|
179
|
+
return { success: true, isOpen: status.isOpen, message: status.message };
|
|
586
180
|
}
|
|
587
181
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
*/
|
|
591
|
-
async getLifetimeStats(accountId) {
|
|
592
|
-
return { success: true, stats: null };
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* Get daily stats (stub for Rithmic - not available via API)
|
|
597
|
-
*/
|
|
598
|
-
async getDailyStats(accountId) {
|
|
599
|
-
return { success: true, stats: [] };
|
|
600
|
-
}
|
|
182
|
+
getToken() { return this.loginInfo ? 'connected' : null; }
|
|
183
|
+
getPropfirm() { return this.propfirmKey || 'apex'; }
|
|
601
184
|
|
|
602
|
-
/**
|
|
603
|
-
* Get trade history (stub for Rithmic)
|
|
604
|
-
*/
|
|
605
|
-
async getTradeHistory(accountId, days = 30) {
|
|
606
|
-
return { success: true, trades: [] };
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* Get market status
|
|
611
|
-
*/
|
|
612
|
-
async getMarketStatus(accountId) {
|
|
613
|
-
const marketHours = this.checkMarketHours();
|
|
614
|
-
return {
|
|
615
|
-
success: true,
|
|
616
|
-
isOpen: marketHours.isOpen,
|
|
617
|
-
message: marketHours.message,
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* Get token (stub - Rithmic uses WebSocket, not tokens)
|
|
623
|
-
*/
|
|
624
|
-
getToken() {
|
|
625
|
-
return this.loginInfo ? 'connected' : null;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/**
|
|
629
|
-
* Get propfirm name
|
|
630
|
-
*/
|
|
631
|
-
getPropfirm() {
|
|
632
|
-
return this.propfirmKey || 'apex';
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* Get Rithmic credentials for HQX Server
|
|
637
|
-
*/
|
|
638
185
|
getRithmicCredentials() {
|
|
639
186
|
if (!this.credentials) return null;
|
|
640
187
|
return {
|
|
@@ -645,11 +192,7 @@ class RithmicService extends EventEmitter {
|
|
|
645
192
|
};
|
|
646
193
|
}
|
|
647
194
|
|
|
648
|
-
/**
|
|
649
|
-
* Search contracts (stub - would need TICKER_PLANT)
|
|
650
|
-
*/
|
|
651
195
|
async searchContracts(searchText) {
|
|
652
|
-
// Common futures contracts
|
|
653
196
|
const contracts = [
|
|
654
197
|
{ symbol: 'ESH5', name: 'E-mini S&P 500 Mar 2025', exchange: 'CME' },
|
|
655
198
|
{ symbol: 'NQH5', name: 'E-mini NASDAQ-100 Mar 2025', exchange: 'CME' },
|
|
@@ -662,173 +205,26 @@ class RithmicService extends EventEmitter {
|
|
|
662
205
|
return contracts.filter(c => c.symbol.includes(search) || c.name.toUpperCase().includes(search));
|
|
663
206
|
}
|
|
664
207
|
|
|
665
|
-
/**
|
|
666
|
-
* Place order via ORDER_PLANT
|
|
667
|
-
*/
|
|
668
|
-
async placeOrder(orderData) {
|
|
669
|
-
if (!this.orderConn || !this.loginInfo) {
|
|
670
|
-
return { success: false, error: 'Not connected' };
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
try {
|
|
674
|
-
this.orderConn.send('RequestNewOrder', {
|
|
675
|
-
templateId: REQ.NEW_ORDER,
|
|
676
|
-
userMsg: ['HQX'],
|
|
677
|
-
fcmId: this.loginInfo.fcmId,
|
|
678
|
-
ibId: this.loginInfo.ibId,
|
|
679
|
-
accountId: orderData.accountId,
|
|
680
|
-
symbol: orderData.symbol,
|
|
681
|
-
exchange: orderData.exchange || 'CME',
|
|
682
|
-
quantity: orderData.size,
|
|
683
|
-
transactionType: orderData.side === 0 ? 1 : 2, // 1=Buy, 2=Sell
|
|
684
|
-
duration: 1, // DAY
|
|
685
|
-
orderType: orderData.type === 2 ? 1 : 2, // 1=Market, 2=Limit
|
|
686
|
-
price: orderData.price || 0,
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
return { success: true, message: 'Order submitted' };
|
|
690
|
-
} catch (error) {
|
|
691
|
-
return { success: false, error: error.message };
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* Cancel order
|
|
697
|
-
*/
|
|
698
|
-
async cancelOrder(orderId) {
|
|
699
|
-
if (!this.orderConn || !this.loginInfo) {
|
|
700
|
-
return { success: false, error: 'Not connected' };
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
try {
|
|
704
|
-
this.orderConn.send('RequestCancelOrder', {
|
|
705
|
-
templateId: REQ.CANCEL_ORDER,
|
|
706
|
-
userMsg: ['HQX'],
|
|
707
|
-
fcmId: this.loginInfo.fcmId,
|
|
708
|
-
ibId: this.loginInfo.ibId,
|
|
709
|
-
orderId: orderId,
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
return { success: true };
|
|
713
|
-
} catch (error) {
|
|
714
|
-
return { success: false, error: error.message };
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
/**
|
|
719
|
-
* Close position (market order to flatten)
|
|
720
|
-
*/
|
|
721
|
-
async closePosition(accountId, symbol) {
|
|
722
|
-
// Get current position
|
|
723
|
-
const positions = Array.from(this.positions.values());
|
|
724
|
-
const position = positions.find(p => p.accountId === accountId && p.symbol === symbol);
|
|
725
|
-
|
|
726
|
-
if (!position) {
|
|
727
|
-
return { success: false, error: 'Position not found' };
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
// Place opposite order
|
|
731
|
-
return this.placeOrder({
|
|
732
|
-
accountId,
|
|
733
|
-
symbol,
|
|
734
|
-
exchange: position.exchange,
|
|
735
|
-
size: Math.abs(position.quantity),
|
|
736
|
-
side: position.quantity > 0 ? 1 : 0, // Sell if long, Buy if short
|
|
737
|
-
type: 2, // Market
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* Get order history
|
|
743
|
-
* Uses RequestShowOrderHistorySummary (template 324)
|
|
744
|
-
*/
|
|
745
|
-
async getOrderHistory(date) {
|
|
746
|
-
if (!this.orderConn || !this.loginInfo) {
|
|
747
|
-
return { success: true, orders: [] };
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// Default to today
|
|
751
|
-
const dateStr = date || new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
752
|
-
|
|
753
|
-
return new Promise((resolve) => {
|
|
754
|
-
const orders = [];
|
|
755
|
-
const timeout = setTimeout(() => {
|
|
756
|
-
resolve({ success: true, orders });
|
|
757
|
-
}, 3000);
|
|
758
|
-
|
|
759
|
-
try {
|
|
760
|
-
for (const acc of this.accounts) {
|
|
761
|
-
this.orderConn.send('RequestShowOrderHistorySummary', {
|
|
762
|
-
templateId: REQ.SHOW_ORDER_HISTORY,
|
|
763
|
-
userMsg: ['HQX'],
|
|
764
|
-
fcmId: acc.fcmId || this.loginInfo.fcmId,
|
|
765
|
-
ibId: acc.ibId || this.loginInfo.ibId,
|
|
766
|
-
accountId: acc.accountId,
|
|
767
|
-
date: dateStr,
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Wait for response
|
|
772
|
-
setTimeout(() => {
|
|
773
|
-
clearTimeout(timeout);
|
|
774
|
-
resolve({ success: true, orders });
|
|
775
|
-
}, 2000);
|
|
776
|
-
} catch (e) {
|
|
777
|
-
clearTimeout(timeout);
|
|
778
|
-
resolve({ success: false, error: e.message, orders: [] });
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
/**
|
|
784
|
-
* Check market hours (same as ProjectX)
|
|
785
|
-
*/
|
|
786
208
|
checkMarketHours() {
|
|
787
209
|
const now = new Date();
|
|
788
210
|
const utcDay = now.getUTCDay();
|
|
789
211
|
const utcHour = now.getUTCHours();
|
|
790
|
-
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
const ctOffset =
|
|
212
|
+
|
|
213
|
+
const isDST = now.getTimezoneOffset() < Math.max(
|
|
214
|
+
new Date(now.getFullYear(), 0, 1).getTimezoneOffset(),
|
|
215
|
+
new Date(now.getFullYear(), 6, 1).getTimezoneOffset()
|
|
216
|
+
);
|
|
217
|
+
const ctOffset = isDST ? 5 : 6;
|
|
796
218
|
const ctHour = (utcHour - ctOffset + 24) % 24;
|
|
797
219
|
const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
|
|
798
220
|
|
|
799
|
-
|
|
800
|
-
if (ctDay ===
|
|
801
|
-
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// Sunday before 5PM CT
|
|
805
|
-
if (ctDay === 0 && ctHour < 17) {
|
|
806
|
-
return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Friday after 4PM CT
|
|
810
|
-
if (ctDay === 5 && ctHour >= 16) {
|
|
811
|
-
return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// Daily maintenance 4PM-5PM CT (Mon-Thu)
|
|
815
|
-
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) {
|
|
816
|
-
return { isOpen: false, message: 'Daily maintenance (4:00-5:00 PM CT)' };
|
|
817
|
-
}
|
|
818
|
-
|
|
221
|
+
if (ctDay === 6) return { isOpen: false, message: 'Market closed (Saturday)' };
|
|
222
|
+
if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
|
|
223
|
+
if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
|
|
224
|
+
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: 'Daily maintenance (4:00-5:00 PM CT)' };
|
|
819
225
|
return { isOpen: true, message: 'Market is open' };
|
|
820
226
|
}
|
|
821
227
|
|
|
822
|
-
isDST(date) {
|
|
823
|
-
const jan = new Date(date.getFullYear(), 0, 1);
|
|
824
|
-
const jul = new Date(date.getFullYear(), 6, 1);
|
|
825
|
-
const stdOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
|
|
826
|
-
return date.getTimezoneOffset() < stdOffset;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
* Disconnect all connections
|
|
831
|
-
*/
|
|
832
228
|
async disconnect() {
|
|
833
229
|
if (this.orderConn) {
|
|
834
230
|
await this.orderConn.disconnect();
|