hedgequantx 1.1.1 → 1.2.32

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/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "1.1.1",
3
+ "version": "1.2.32",
4
4
  "description": "Prop Futures Algo Trading CLI - Connect to Topstep, Alpha Futures, and other prop firms",
5
- "main": "src/index.js",
5
+ "main": "src/app.js",
6
6
  "bin": {
7
7
  "hedgequantx": "./bin/cli.js",
8
8
  "hqx": "./bin/cli.js"
@@ -45,7 +45,7 @@
45
45
  "chalk": "^4.1.2",
46
46
  "commander": "^11.1.0",
47
47
  "figlet": "^1.7.0",
48
- "inquirer": "^8.2.6",
48
+ "inquirer": "^7.3.3",
49
49
  "ora": "^5.4.1",
50
50
  "ws": "^8.18.3"
51
51
  }
package/src/app.js ADDED
@@ -0,0 +1,550 @@
1
+ /**
2
+ * @fileoverview Main application router
3
+ * @module app
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const inquirer = require('inquirer');
8
+ const ora = require('ora');
9
+ const figlet = require('figlet');
10
+ const { execSync } = require('child_process');
11
+ const path = require('path');
12
+
13
+ const { ProjectXService, connections } = require('./services');
14
+ const { PROPFIRM_CHOICES, getPropFirmsByPlatform } = require('./config');
15
+ const { getDevice, getSeparator, printLogo, getLogoWidth, drawBoxHeader, drawBoxFooter, centerText, createBoxMenu } = require('./ui');
16
+ const { validateUsername, validatePassword, maskSensitive } = require('./security');
17
+
18
+ // Pages
19
+ const { showStats } = require('./pages/stats');
20
+ const { showAccounts } = require('./pages/accounts');
21
+ const { algoTradingMenu } = require('./pages/algo');
22
+
23
+ // Current service reference
24
+ let currentService = null;
25
+
26
+ /**
27
+ * Displays the application banner with stats if connected
28
+ */
29
+ const banner = async () => {
30
+ console.clear();
31
+ const boxWidth = getLogoWidth();
32
+ const innerWidth = boxWidth - 2;
33
+ const version = require('../package.json').version;
34
+
35
+ // Get stats if connected (only active accounts: status === 0)
36
+ let statsInfo = null;
37
+ if (connections.count() > 0) {
38
+ try {
39
+ const allAccounts = await connections.getAllAccounts();
40
+ const activeAccounts = allAccounts.filter(acc => acc.status === 0);
41
+ let totalBalance = 0;
42
+ let totalStartingBalance = 0;
43
+ let totalPnl = 0;
44
+
45
+ activeAccounts.forEach(account => {
46
+ totalBalance += account.balance || 0;
47
+ totalStartingBalance += account.startingBalance || 0;
48
+ totalPnl += account.profitAndLoss || 0;
49
+ });
50
+
51
+ const pnl = totalPnl !== 0 ? totalPnl : (totalBalance - totalStartingBalance);
52
+ const pnlPercent = totalStartingBalance > 0 ? ((pnl / totalStartingBalance) * 100).toFixed(1) : '0.0';
53
+
54
+ statsInfo = {
55
+ connections: connections.count(),
56
+ accounts: activeAccounts.length,
57
+ balance: totalBalance,
58
+ pnl: pnl,
59
+ pnlPercent: pnlPercent
60
+ };
61
+ } catch (e) {
62
+ // Ignore errors
63
+ }
64
+ }
65
+
66
+ // Draw logo HQX
67
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
68
+
69
+ const logo = [
70
+ '██╗ ██╗ ██████╗ ',
71
+ '██║ ██║██╔═══██╗',
72
+ '███████║██║ ██║',
73
+ '██╔══██║██║▄▄ ██║',
74
+ '██║ ██║╚██████╔╝',
75
+ '╚═╝ ╚═╝ ╚══▀▀═╝ '
76
+ ];
77
+ const logoX = [
78
+ '██╗ ██╗',
79
+ '╚██╗██╔╝',
80
+ ' ╚███╔╝ ',
81
+ ' ██╔██╗ ',
82
+ '██╔╝ ██╗',
83
+ '╚═╝ ╚═╝'
84
+ ];
85
+
86
+ logo.forEach((line, i) => {
87
+ const mainPart = chalk.cyan(line);
88
+ const xPart = chalk.yellow(logoX[i]);
89
+ const fullLine = mainPart + xPart;
90
+ const totalLen = line.length + logoX[i].length;
91
+ const padding = innerWidth - totalLen;
92
+ const leftPad = Math.floor(padding / 2);
93
+ const rightPad = padding - leftPad;
94
+ console.log(chalk.cyan('║') + ' '.repeat(leftPad) + fullLine + ' '.repeat(rightPad) + chalk.cyan('║'));
95
+ });
96
+
97
+ // Tagline
98
+ console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
99
+ console.log(chalk.cyan('║') + chalk.white(centerText(`Prop Futures Algo Trading v${version}`, innerWidth)) + chalk.cyan('║'));
100
+
101
+ // Stats bar if connected
102
+ if (statsInfo) {
103
+ console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
104
+
105
+ const pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
106
+ const pnlSign = statsInfo.pnl >= 0 ? '+' : '';
107
+
108
+ const connStr = `Connections: ${statsInfo.connections}`;
109
+ const accStr = `Accounts: ${statsInfo.accounts}`;
110
+ const balVal = `$${statsInfo.balance.toLocaleString()}`;
111
+ const pnlVal = `$${statsInfo.pnl.toLocaleString()} (${pnlSign}${statsInfo.pnlPercent}%)`;
112
+
113
+ const statsLen = connStr.length + 4 + accStr.length + 4 + 8 + balVal.length + 4 + 5 + pnlVal.length;
114
+ const statsLeftPad = Math.floor((innerWidth - statsLen) / 2);
115
+ const statsRightPad = innerWidth - statsLen - statsLeftPad;
116
+
117
+ console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
118
+ chalk.white(connStr) + ' ' +
119
+ chalk.white(accStr) + ' ' +
120
+ chalk.white('Balance: ') + chalk.green(balVal) + ' ' +
121
+ chalk.white('P&L: ') + pnlColor(pnlVal) + ' '.repeat(statsRightPad) + chalk.cyan('║')
122
+ );
123
+ }
124
+
125
+ console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
126
+ console.log();
127
+ };
128
+
129
+ /**
130
+ * Login prompt with validation
131
+ * @param {string} propfirmName - PropFirm display name
132
+ * @returns {Promise<{username: string, password: string}>}
133
+ */
134
+ const loginPrompt = async (propfirmName) => {
135
+ const device = getDevice();
136
+ console.log();
137
+ console.log(chalk.cyan(`Connecting to ${propfirmName}...`));
138
+ console.log();
139
+
140
+ const credentials = await inquirer.prompt([
141
+ {
142
+ type: 'input',
143
+ name: 'username',
144
+ message: chalk.white.bold('Username:'),
145
+ validate: (input) => {
146
+ try {
147
+ validateUsername(input);
148
+ return true;
149
+ } catch (e) {
150
+ return e.message;
151
+ }
152
+ }
153
+ },
154
+ {
155
+ type: 'password',
156
+ name: 'password',
157
+ message: chalk.white.bold('Password:'),
158
+ mask: '*',
159
+ validate: (input) => {
160
+ try {
161
+ validatePassword(input);
162
+ return true;
163
+ } catch (e) {
164
+ return e.message;
165
+ }
166
+ }
167
+ }
168
+ ]);
169
+
170
+ return credentials;
171
+ };
172
+
173
+ /**
174
+ * ProjectX platform connection menu
175
+ */
176
+ const projectXMenu = async () => {
177
+ const propfirms = getPropFirmsByPlatform('ProjectX');
178
+ const boxWidth = getLogoWidth();
179
+ const innerWidth = boxWidth - 2;
180
+ const numCols = 3;
181
+ const colWidth = Math.floor(innerWidth / numCols);
182
+
183
+ // Build numbered list
184
+ const numbered = propfirms.map((pf, i) => ({
185
+ num: i + 1,
186
+ key: pf.key,
187
+ name: pf.displayName
188
+ }));
189
+
190
+ // PropFirm selection box
191
+ console.log();
192
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
193
+ console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
194
+ console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
195
+
196
+ // Display in 3 columns
197
+ const rows = Math.ceil(numbered.length / numCols);
198
+ for (let row = 0; row < rows; row++) {
199
+ let line = '';
200
+ for (let col = 0; col < numCols; col++) {
201
+ const idx = row + col * rows;
202
+ if (idx < numbered.length) {
203
+ const item = numbered[idx];
204
+ const text = `[${item.num}] ${item.name}`;
205
+ const coloredText = chalk.cyan(`[${item.num}]`) + ' ' + chalk.white(item.name);
206
+ const textLen = text.length;
207
+ const padding = colWidth - textLen - 2;
208
+ line += ' ' + coloredText + ' '.repeat(Math.max(0, padding));
209
+ } else {
210
+ line += ' '.repeat(colWidth);
211
+ }
212
+ }
213
+ // Adjust for exact width
214
+ const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
215
+ const adjust = innerWidth - lineLen;
216
+ console.log(chalk.cyan('║') + line + ' '.repeat(Math.max(0, adjust)) + chalk.cyan('║'));
217
+ }
218
+
219
+ console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
220
+ const backText = ' ' + chalk.red('[X] Back');
221
+ const backLen = '[X] Back'.length + 2;
222
+ console.log(chalk.cyan('║') + backText + ' '.repeat(innerWidth - backLen) + chalk.cyan('║'));
223
+ console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
224
+ console.log();
225
+
226
+ const validInputs = numbered.map(n => n.num.toString());
227
+ validInputs.push('x', 'X');
228
+
229
+ const { action } = await inquirer.prompt([
230
+ {
231
+ type: 'input',
232
+ name: 'action',
233
+ message: chalk.cyan(`Enter choice (1-${numbered.length}/X):`),
234
+ validate: (input) => {
235
+ if (validInputs.includes(input)) return true;
236
+ return `Please enter 1-${numbered.length} or X`;
237
+ }
238
+ }
239
+ ]);
240
+
241
+ if (action.toLowerCase() === 'x') return null;
242
+
243
+ const selectedIdx = parseInt(action) - 1;
244
+ const selectedPropfirm = numbered[selectedIdx];
245
+
246
+ const credentials = await loginPrompt(selectedPropfirm.name);
247
+ const spinner = ora('Authenticating...').start();
248
+
249
+ try {
250
+ const service = new ProjectXService(selectedPropfirm.key);
251
+ const result = await service.login(credentials.username, credentials.password);
252
+
253
+ if (result.success) {
254
+ await service.getUser();
255
+ connections.add('projectx', service, service.propfirm.name);
256
+ currentService = service;
257
+ spinner.succeed(`Connected to ${service.propfirm.name}`);
258
+ return service;
259
+ } else {
260
+ spinner.fail(result.error || 'Authentication failed');
261
+ return null;
262
+ }
263
+ } catch (error) {
264
+ spinner.fail(error.message);
265
+ return null;
266
+ }
267
+ };
268
+
269
+ /**
270
+ * Main connection menu
271
+ */
272
+ const mainMenu = async () => {
273
+ const boxWidth = getLogoWidth();
274
+ const innerWidth = boxWidth - 2;
275
+ const col1Width = Math.floor(innerWidth / 2);
276
+ const col2Width = innerWidth - col1Width;
277
+
278
+ // Connection menu box
279
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
280
+ console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PLATFORM', innerWidth)) + chalk.cyan('║'));
281
+ console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
282
+
283
+ // Menu row helper (2 columns)
284
+ const menuRow = (left, right) => {
285
+ const leftText = ' ' + left;
286
+ const rightText = right ? ' ' + right : '';
287
+ const leftLen = leftText.replace(/\x1b\[[0-9;]*m/g, '').length;
288
+ const rightLen = rightText.replace(/\x1b\[[0-9;]*m/g, '').length;
289
+ const leftPad = col1Width - leftLen;
290
+ const rightPad = col2Width - rightLen;
291
+ console.log(chalk.cyan('║') + leftText + ' '.repeat(Math.max(0, leftPad)) + rightText + ' '.repeat(Math.max(0, rightPad)) + chalk.cyan('║'));
292
+ };
293
+
294
+ menuRow(chalk.cyan('[1] ProjectX'), chalk.gray('[2] Rithmic (Coming Soon)'));
295
+ menuRow(chalk.gray('[3] Tradovate (Coming Soon)'), chalk.red('[X] Exit'));
296
+
297
+ console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
298
+ console.log();
299
+
300
+ const { action } = await inquirer.prompt([
301
+ {
302
+ type: 'input',
303
+ name: 'action',
304
+ message: chalk.cyan('Enter choice (1/X):'),
305
+ validate: (input) => {
306
+ const valid = ['1', 'x', 'X'];
307
+ if (valid.includes(input)) return true;
308
+ return 'Please enter 1 or X';
309
+ }
310
+ }
311
+ ]);
312
+
313
+ // Map input to action
314
+ const actionMap = {
315
+ '1': 'projectx',
316
+ 'x': 'exit',
317
+ 'X': 'exit'
318
+ };
319
+
320
+ return actionMap[action] || 'exit';
321
+ };
322
+
323
+ /**
324
+ * Dashboard menu after login
325
+ * @param {Object} service - Connected service
326
+ */
327
+ const dashboardMenu = async (service) => {
328
+ const user = service.user;
329
+ const boxWidth = getLogoWidth();
330
+ const innerWidth = boxWidth - 2;
331
+
332
+ // Dashboard box header
333
+ console.log();
334
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
335
+ console.log(chalk.cyan('║') + chalk.white.bold(centerText('DASHBOARD', innerWidth)) + chalk.cyan('║'));
336
+ console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
337
+
338
+ // Connection info
339
+ const connInfo = chalk.green('Connected to ' + service.propfirm.name);
340
+ const connLen = ('Connected to ' + service.propfirm.name).length;
341
+ console.log(chalk.cyan('║') + ' ' + connInfo + ' '.repeat(innerWidth - connLen - 2) + chalk.cyan('║'));
342
+
343
+ if (user) {
344
+ const userInfo = 'Welcome, ' + user.userName.toUpperCase() + '!';
345
+ console.log(chalk.cyan('║') + ' ' + chalk.white(userInfo) + ' '.repeat(innerWidth - userInfo.length - 2) + chalk.cyan('║'));
346
+ }
347
+
348
+ console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
349
+
350
+ // Menu options in 2 columns
351
+ const col1Width = Math.floor(innerWidth / 2);
352
+ const col2Width = innerWidth - col1Width;
353
+
354
+ const menuRow = (left, right) => {
355
+ const leftText = ' ' + left;
356
+ const rightText = right ? ' ' + right : '';
357
+ const leftPad = col1Width - leftText.replace(/\x1b\[[0-9;]*m/g, '').length;
358
+ const rightPad = col2Width - rightText.replace(/\x1b\[[0-9;]*m/g, '').length;
359
+ console.log(chalk.cyan('║') + leftText + ' '.repeat(Math.max(0, leftPad)) + rightText + ' '.repeat(Math.max(0, rightPad)) + chalk.cyan('║'));
360
+ };
361
+
362
+ menuRow(chalk.cyan('[1] View Accounts'), chalk.cyan('[2] View Stats'));
363
+ menuRow(chalk.cyan('[+] Add Prop-Account'), chalk.cyan('[A] Algo-Trading'));
364
+ menuRow(chalk.yellow('[U] Update HQX'), chalk.red('[X] Disconnect'));
365
+
366
+ console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
367
+ console.log();
368
+
369
+ const { action } = await inquirer.prompt([
370
+ {
371
+ type: 'input',
372
+ name: 'action',
373
+ message: chalk.cyan('Enter choice (1/2/+/A/U/X):'),
374
+ validate: (input) => {
375
+ const valid = ['1', '2', '+', 'a', 'A', 'u', 'U', 'x', 'X'];
376
+ if (valid.includes(input)) return true;
377
+ return 'Please enter a valid option';
378
+ }
379
+ }
380
+ ]);
381
+
382
+ // Map input to action
383
+ const actionMap = {
384
+ '1': 'accounts',
385
+ '2': 'stats',
386
+ '+': 'add_prop_account',
387
+ 'a': 'algotrading',
388
+ 'A': 'algotrading',
389
+ 'u': 'update',
390
+ 'U': 'update',
391
+ 'x': 'disconnect',
392
+ 'X': 'disconnect'
393
+ };
394
+
395
+ return actionMap[action] || 'accounts';
396
+ };
397
+
398
+ /**
399
+ * Handles the update process with auto-restart
400
+ */
401
+ const handleUpdate = async () => {
402
+ const { spawn } = require('child_process');
403
+ const pkg = require('../package.json');
404
+ const currentVersion = pkg.version;
405
+ const spinner = ora('Checking for updates...').start();
406
+
407
+ try {
408
+ const cliPath = path.resolve(__dirname, '..');
409
+
410
+ // Get current commit
411
+ const beforeCommit = execSync('git rev-parse --short HEAD', { cwd: cliPath, stdio: 'pipe' }).toString().trim();
412
+
413
+ // Fetch to check for updates
414
+ execSync('git fetch origin main', { cwd: cliPath, stdio: 'pipe' });
415
+
416
+ // Check if behind
417
+ const behindCount = execSync('git rev-list HEAD..origin/main --count', { cwd: cliPath, stdio: 'pipe' }).toString().trim();
418
+
419
+ if (parseInt(behindCount) === 0) {
420
+ spinner.succeed('Already up to date!');
421
+ console.log(chalk.cyan(` Version: v${currentVersion}`));
422
+ console.log(chalk.gray(` Commit: ${beforeCommit}`));
423
+ return;
424
+ }
425
+
426
+ // Stash local changes
427
+ spinner.text = 'Stashing local changes...';
428
+ try {
429
+ execSync('git stash --include-untracked', { cwd: cliPath, stdio: 'pipe' });
430
+ } catch (e) {
431
+ // If stash fails, reset
432
+ execSync('git checkout -- .', { cwd: cliPath, stdio: 'pipe' });
433
+ }
434
+
435
+ // Pull latest
436
+ spinner.text = 'Downloading updates...';
437
+ execSync('git pull origin main', { cwd: cliPath, stdio: 'pipe' });
438
+ const afterCommit = execSync('git rev-parse --short HEAD', { cwd: cliPath, stdio: 'pipe' }).toString().trim();
439
+
440
+ // Install dependencies
441
+ spinner.text = 'Installing dependencies...';
442
+ try {
443
+ execSync('npm install --silent', { cwd: cliPath, stdio: 'pipe' });
444
+ } catch (e) { /* ignore */ }
445
+
446
+ // Get new version
447
+ delete require.cache[require.resolve('../package.json')];
448
+ const newPkg = require('../package.json');
449
+ const newVersion = newPkg.version;
450
+
451
+ spinner.succeed('CLI updated!');
452
+ console.log();
453
+ console.log(chalk.green(` Version: v${currentVersion} -> v${newVersion}`));
454
+ console.log(chalk.gray(` Commits: ${beforeCommit} -> ${afterCommit} (${behindCount} new)`));
455
+ console.log();
456
+ console.log(chalk.cyan(' Restarting...'));
457
+ console.log();
458
+
459
+ // Restart CLI
460
+ const child = spawn(process.argv[0], [path.join(cliPath, 'bin', 'cli.js')], {
461
+ cwd: cliPath,
462
+ stdio: 'inherit',
463
+ shell: true
464
+ });
465
+
466
+ child.on('exit', (code) => {
467
+ process.exit(code);
468
+ });
469
+
470
+ // Stop current process loop
471
+ return 'restart';
472
+
473
+ } catch (error) {
474
+ spinner.fail('Update failed: ' + error.message);
475
+ }
476
+ };
477
+
478
+ /**
479
+ * Main application loop
480
+ */
481
+ const run = async () => {
482
+ await banner();
483
+
484
+ // Try to restore session
485
+ const spinner = ora('Restoring session...').start();
486
+ const restored = await connections.restoreFromStorage();
487
+
488
+ if (restored) {
489
+ spinner.succeed('Session restored');
490
+ currentService = connections.getAll()[0].service;
491
+ } else {
492
+ spinner.info('No active session');
493
+ }
494
+
495
+ // Main loop
496
+ while (true) {
497
+ // Refresh banner with stats
498
+ await banner();
499
+
500
+ if (!connections.isConnected()) {
501
+ const choice = await mainMenu();
502
+
503
+ if (choice === 'exit') {
504
+ console.log(chalk.gray('Goodbye!'));
505
+ process.exit(0);
506
+ }
507
+
508
+ if (choice === 'projectx') {
509
+ const service = await projectXMenu();
510
+ if (service) currentService = service;
511
+ }
512
+ } else {
513
+ const action = await dashboardMenu(currentService);
514
+
515
+ switch (action) {
516
+ case 'accounts':
517
+ await showAccounts(currentService);
518
+ break;
519
+
520
+ case 'stats':
521
+ await showStats(currentService);
522
+ break;
523
+ case 'add_prop_account':
524
+ const newService = await projectXMenu();
525
+ if (newService) {
526
+ currentService = newService;
527
+ }
528
+ break;
529
+ case 'algotrading':
530
+ await algoTradingMenu(currentService);
531
+ break;
532
+ case 'update':
533
+ const updateResult = await handleUpdate();
534
+ if (updateResult === 'restart') return; // Stop loop, new process spawned
535
+ break;
536
+ case 'disconnect':
537
+ const connCount = connections.count();
538
+ connections.disconnectAll();
539
+ currentService = null;
540
+ console.log(chalk.yellow(`Disconnected ${connCount} connection${connCount > 1 ? 's' : ''}`));
541
+ break;
542
+ case 'exit':
543
+ console.log(chalk.gray('Goodbye!'));
544
+ process.exit(0);
545
+ }
546
+ }
547
+ }
548
+ };
549
+
550
+ module.exports = { run, banner, loginPrompt, mainMenu, dashboardMenu };
@@ -1,8 +1,16 @@
1
1
  /**
2
- * Configuration Exports
2
+ * @fileoverview Configuration module exports
3
+ * @module config
3
4
  */
4
5
 
5
- const { PROPFIRMS, PROPFIRM_CHOICES } = require('./propfirms');
6
+ const {
7
+ PROPFIRMS,
8
+ PROPFIRM_CHOICES,
9
+ getPropFirm,
10
+ getPropFirmById,
11
+ getPropFirmsByPlatform
12
+ } = require('./propfirms');
13
+
6
14
  const {
7
15
  ACCOUNT_STATUS,
8
16
  ACCOUNT_TYPE,
@@ -13,8 +21,14 @@ const {
13
21
  } = require('./constants');
14
22
 
15
23
  module.exports = {
24
+ // PropFirms
16
25
  PROPFIRMS,
17
26
  PROPFIRM_CHOICES,
27
+ getPropFirm,
28
+ getPropFirmById,
29
+ getPropFirmsByPlatform,
30
+
31
+ // Constants
18
32
  ACCOUNT_STATUS,
19
33
  ACCOUNT_TYPE,
20
34
  ORDER_STATUS,