hedgequantx 1.8.48 → 2.3.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 +7 -6
- package/bin/cli.js +13 -7
- package/dist/algo/copy-engine.js +3 -0
- package/dist/algo/copy-engine.jsc +0 -0
- package/dist/algo/engine.js +3 -0
- package/dist/algo/engine.jsc +0 -0
- package/dist/algo/market-data-rithmic.js +3 -0
- package/dist/algo/market-data-rithmic.jsc +0 -0
- package/dist/algo/market-data.js +3 -0
- package/dist/algo/market-data.jsc +0 -0
- package/dist/algo/rithmic/connection.js +3 -0
- package/dist/algo/rithmic/connection.jsc +0 -0
- package/dist/algo/rithmic/constants.js +3 -0
- package/dist/algo/rithmic/constants.jsc +0 -0
- package/dist/algo/rithmic/index.js +3 -0
- package/dist/algo/rithmic/index.jsc +0 -0
- package/dist/algo/rithmic/market-data.js +3 -0
- package/dist/algo/rithmic/market-data.jsc +0 -0
- package/dist/algo/rithmic/pnl.js +3 -0
- package/dist/algo/rithmic/pnl.jsc +0 -0
- package/dist/algo/rithmic/pool.js +3 -0
- package/dist/algo/rithmic/pool.jsc +0 -0
- package/dist/algo/rithmic/trading.js +3 -0
- package/dist/algo/rithmic/trading.jsc +0 -0
- package/dist/algo/rithmic-decoder.js +3 -0
- package/dist/algo/rithmic-decoder.jsc +0 -0
- package/dist/algo/strategies/ultra-scalping-v2.js +3 -0
- package/dist/algo/strategies/ultra-scalping-v2.jsc +0 -0
- package/dist/algo/strategies/ultra-scalping.js +3 -0
- package/dist/algo/strategies/ultra-scalping.jsc +0 -0
- package/dist/algo/trading-api-rithmic.js +3 -0
- package/dist/algo/trading-api-rithmic.jsc +0 -0
- package/dist/algo/trading-api.js +3 -0
- package/dist/algo/trading-api.jsc +0 -0
- package/dist/algo/utils/smart-logger.js +3 -0
- package/dist/algo/utils/smart-logger.jsc +0 -0
- package/dist/algo/utils/smart-logs.js +3 -0
- package/dist/algo/utils/smart-logs.jsc +0 -0
- package/package.json +33 -10
- package/protos/rithmic/account_pnl_position_update.proto +59 -0
- package/protos/rithmic/base.proto +7 -0
- package/protos/rithmic/best_bid_offer.proto +39 -0
- package/protos/rithmic/exchange_order_notification.proto +140 -0
- package/protos/rithmic/instrument_pnl_position_update.proto +50 -0
- package/protos/rithmic/last_trade.proto +53 -0
- package/protos/rithmic/request_account_list.proto +20 -0
- package/protos/rithmic/request_cancel_all_orders.proto +15 -0
- package/protos/rithmic/request_front_month_contract.proto +10 -0
- package/protos/rithmic/request_heartbeat.proto +13 -0
- package/protos/rithmic/request_login.proto +28 -0
- package/protos/rithmic/request_login_info.proto +10 -0
- package/protos/rithmic/request_logout.proto +10 -0
- package/protos/rithmic/request_market_data_update.proto +42 -0
- package/protos/rithmic/request_new_order.proto +84 -0
- package/protos/rithmic/request_pnl_position_snapshot.proto +14 -0
- package/protos/rithmic/request_pnl_position_updates.proto +20 -0
- package/protos/rithmic/request_product_codes.proto +9 -0
- package/protos/rithmic/request_rithmic_system_info.proto +8 -0
- package/protos/rithmic/request_show_order_history.proto +16 -0
- package/protos/rithmic/request_show_order_history_dates.proto +10 -0
- package/protos/rithmic/request_show_order_history_summary.proto +14 -0
- package/protos/rithmic/request_show_orders.proto +14 -0
- package/protos/rithmic/request_subscribe_for_order_updates.proto +14 -0
- package/protos/rithmic/request_tick_bar_replay.proto +48 -0
- package/protos/rithmic/request_trade_routes.proto +11 -0
- package/protos/rithmic/response_account_list.proto +18 -0
- package/protos/rithmic/response_front_month_contract.proto +13 -0
- package/protos/rithmic/response_heartbeat.proto +14 -0
- package/protos/rithmic/response_login.proto +18 -0
- package/protos/rithmic/response_login_info.proto +24 -0
- package/protos/rithmic/response_logout.proto +11 -0
- package/protos/rithmic/response_market_data_update.proto +9 -0
- package/protos/rithmic/response_new_order.proto +18 -0
- package/protos/rithmic/response_pnl_position_snapshot.proto +11 -0
- package/protos/rithmic/response_pnl_position_updates.proto +11 -0
- package/protos/rithmic/response_product_codes.proto +12 -0
- package/protos/rithmic/response_rithmic_system_info.proto +12 -0
- package/protos/rithmic/response_show_order_history.proto +11 -0
- package/protos/rithmic/response_show_order_history_dates.proto +13 -0
- package/protos/rithmic/response_show_order_history_summary.proto +11 -0
- package/protos/rithmic/response_show_orders.proto +11 -0
- package/protos/rithmic/response_subscribe_for_order_updates.proto +11 -0
- package/protos/rithmic/response_tick_bar_replay.proto +40 -0
- package/protos/rithmic/response_trade_routes.proto +19 -0
- package/protos/rithmic/rithmic_order_notification.proto +124 -0
- package/src/app.js +136 -89
- package/src/config/index.js +27 -8
- package/src/config/settings.js +155 -0
- package/src/pages/accounts.js +2 -3
- package/src/pages/algo/copy-trading.js +293 -200
- package/src/pages/algo/one-account.js +1 -1
- package/src/security/encryption.js +81 -46
- package/src/security/index.js +12 -8
- package/src/security/rateLimit.js +68 -65
- package/src/security/validation.js +93 -79
- package/src/services/hqx-server.js +538 -206
- package/src/services/projectx/index.js +327 -204
- package/src/services/rithmic/index.js +288 -285
- package/src/services/session.js +184 -114
- package/src/services/tradovate/index.js +286 -297
- package/src/ui/index.js +53 -1
- package/src/utils/http.js +236 -0
- package/src/utils/index.js +11 -2
- package/src/utils/logger.js +64 -33
- package/src/utils/prompts.js +79 -71
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package rti;
|
|
4
|
+
|
|
5
|
+
message ResponseProductCodes {
|
|
6
|
+
int32 template_id = 154467; // Template ID = 112
|
|
7
|
+
repeated string rp_code = 132766; // Response code
|
|
8
|
+
string exchange = 110101; // Exchange
|
|
9
|
+
string product_code = 110102; // Product code (e.g., "MNQ", "ES")
|
|
10
|
+
string product_name = 110103; // Product name
|
|
11
|
+
string user_msg = 132760; // Echo of user message
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message ResponseRithmicSystemInfo
|
|
5
|
+
{
|
|
6
|
+
required int32 template_id = 154467;
|
|
7
|
+
repeated string user_msg = 132760;
|
|
8
|
+
repeated string rp_code = 132766;
|
|
9
|
+
|
|
10
|
+
repeated string system_name = 153628;
|
|
11
|
+
repeated bool has_aggregated_quotes = 153649;
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message ResponseShowOrderHistory
|
|
5
|
+
{
|
|
6
|
+
// PB_OFFSET = 100000, is the offset added for each MNM field id
|
|
7
|
+
|
|
8
|
+
required int32 template_id = 154467; // PB_OFFSET + MNM_TEMPLATE_ID
|
|
9
|
+
repeated string user_msg = 132760; // PB_OFFSET + MNM_USER_MSG
|
|
10
|
+
repeated string rp_code = 132766; // PB_OFFSET + MNM_RESPONSE_CODE
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message ResponseShowOrderHistoryDates
|
|
5
|
+
{
|
|
6
|
+
// CORRECT field IDs from rundef/async_rithmic (verified Dec 2025)
|
|
7
|
+
// PB_OFFSET = 100000
|
|
8
|
+
optional int32 template_id = 154467;
|
|
9
|
+
repeated string user_msg = 132760;
|
|
10
|
+
repeated string rq_handler_rp_code = 132764;
|
|
11
|
+
repeated string rp_code = 132766;
|
|
12
|
+
repeated string date = 110615; // Available dates (YYYYMMDD)
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message ResponseShowOrderHistorySummary
|
|
5
|
+
{
|
|
6
|
+
// Based on async_rithmic proto definitions
|
|
7
|
+
optional int32 template_id = 156580;
|
|
8
|
+
repeated string user_msg = 142744;
|
|
9
|
+
repeated string rp_code = 142750;
|
|
10
|
+
optional int32 fills_collected = 156569;
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message ResponseShowOrders
|
|
5
|
+
{
|
|
6
|
+
// PB_OFFSET = 100000, is the offset added for each MNM field id
|
|
7
|
+
|
|
8
|
+
required int32 template_id = 154467; // PB_OFFSET + MNM_TEMPLATE_ID
|
|
9
|
+
repeated string user_msg = 132760; // PB_OFFSET + MNM_USER_MSG
|
|
10
|
+
repeated string rp_code = 132766; // PB_OFFSET + MNM_RESPONSE_CODE
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message ResponseSubscribeForOrderUpdates
|
|
5
|
+
{
|
|
6
|
+
// PB_OFFSET = 100000, is the offset added for each MNM field id
|
|
7
|
+
|
|
8
|
+
required int32 template_id = 154467; // PB_OFFSET + MNM_TEMPLATE_ID
|
|
9
|
+
repeated string user_msg = 132760; // PB_OFFSET + MNM_USER_MSG
|
|
10
|
+
repeated string rp_code = 132766; // PB_OFFSET + MNM_RESPONSE_CODE
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message ResponseTickBarReplay
|
|
5
|
+
{
|
|
6
|
+
enum BarType {
|
|
7
|
+
TICK_BAR = 1;
|
|
8
|
+
RANGE_BAR = 2;
|
|
9
|
+
VOLUME_BAR = 3;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
enum BarSubType {
|
|
13
|
+
REGULAR = 1;
|
|
14
|
+
CUSTOM = 2;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
required int32 template_id = 154467;
|
|
18
|
+
optional string request_key = 132758;
|
|
19
|
+
repeated string user_msg = 132760;
|
|
20
|
+
repeated string rq_handler_rp_code = 132764;
|
|
21
|
+
repeated string rp_code = 132766;
|
|
22
|
+
|
|
23
|
+
optional string symbol = 110100;
|
|
24
|
+
optional string exchange = 110101;
|
|
25
|
+
|
|
26
|
+
optional BarType type = 119200;
|
|
27
|
+
optional BarSubType sub_type = 119208;
|
|
28
|
+
optional string type_specifier = 148162;
|
|
29
|
+
optional uint64 num_trades = 119204;
|
|
30
|
+
optional uint64 volume = 119205;
|
|
31
|
+
optional uint64 bid_volume = 119213;
|
|
32
|
+
optional uint64 ask_volume = 119214;
|
|
33
|
+
optional double open_price = 100019;
|
|
34
|
+
optional double close_price = 100021;
|
|
35
|
+
optional double high_price = 100012;
|
|
36
|
+
optional double low_price = 100013;
|
|
37
|
+
optional int32 custom_session_open_ssm = 119209;
|
|
38
|
+
repeated int32 data_bar_ssboe = 119202;
|
|
39
|
+
repeated int32 data_bar_usecs = 119203;
|
|
40
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message ResponseTradeRoutes
|
|
5
|
+
{
|
|
6
|
+
// PB_OFFSET = 100000, is the offset added for each MNM field id
|
|
7
|
+
|
|
8
|
+
required int32 template_id = 154467; // PB_OFFSET + MNM_TEMPLATE_ID
|
|
9
|
+
repeated string user_msg = 132760; // PB_OFFSET + MNM_USER_MSG
|
|
10
|
+
repeated string rq_handler_rp_code = 132764; // PB_OFFSET + MNM_REQUEST_HANDLER_RESPONSE_CODE
|
|
11
|
+
repeated string rp_code = 132766; // PB_OFFSET + MNM_RESPONSE_CODE
|
|
12
|
+
|
|
13
|
+
optional string fcm_id = 154013; // PB_OFFSET + MNM_FCM_ID
|
|
14
|
+
optional string ib_id = 154014; // PB_OFFSET + MNM_IB_ID
|
|
15
|
+
optional string exchange = 110101; // PB_OFFSET + MNM_EXCHANGE
|
|
16
|
+
optional string trade_route = 112016; // PB_OFFSET + MNM_TRADE_ROUTE
|
|
17
|
+
optional string status = 131407; // PB_OFFSET + MNM_SERVICE_STATE
|
|
18
|
+
optional bool is_default = 154689; // PB_OFFSET + MNM_DEFAULT_ROUTE
|
|
19
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message RithmicOrderNotification
|
|
5
|
+
{
|
|
6
|
+
enum NotifyType {
|
|
7
|
+
ORDER_RCVD_FROM_CLNT = 1;
|
|
8
|
+
MODIFY_RCVD_FROM_CLNT = 2;
|
|
9
|
+
CANCEL_RCVD_FROM_CLNT = 3;
|
|
10
|
+
OPEN_PENDING = 4;
|
|
11
|
+
MODIFY_PENDING = 5;
|
|
12
|
+
CANCEL_PENDING = 6;
|
|
13
|
+
ORDER_RCVD_BY_EXCH_GTWY = 7;
|
|
14
|
+
MODIFY_RCVD_BY_EXCH_GTWY = 8;
|
|
15
|
+
CANCEL_RCVD_BY_EXCH_GTWY = 9;
|
|
16
|
+
ORDER_SENT_TO_EXCH = 10;
|
|
17
|
+
MODIFY_SENT_TO_EXCH = 11;
|
|
18
|
+
CANCEL_SENT_TO_EXCH = 12;
|
|
19
|
+
OPEN = 13;
|
|
20
|
+
MODIFIED = 14;
|
|
21
|
+
COMPLETE = 15;
|
|
22
|
+
MODIFICATION_FAILED = 16;
|
|
23
|
+
CANCELLATION_FAILED = 17;
|
|
24
|
+
TRIGGER_PENDING = 18;
|
|
25
|
+
GENERIC = 19;
|
|
26
|
+
LINK_ORDERS_FAILED = 20;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
enum TransactionType {
|
|
30
|
+
BUY = 1;
|
|
31
|
+
SELL = 2;
|
|
32
|
+
SS = 3;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
enum Duration {
|
|
36
|
+
DAY = 1;
|
|
37
|
+
GTC = 2;
|
|
38
|
+
IOC = 3;
|
|
39
|
+
FOK = 4;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
enum PriceType {
|
|
43
|
+
LIMIT = 1;
|
|
44
|
+
MARKET = 2;
|
|
45
|
+
STOP_LIMIT = 3;
|
|
46
|
+
STOP_MARKET = 4;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
enum BracketType {
|
|
50
|
+
STOP_ONLY = 1;
|
|
51
|
+
TARGET_ONLY = 2;
|
|
52
|
+
TARGET_AND_STOP = 3;
|
|
53
|
+
STOP_ONLY_STATIC = 4;
|
|
54
|
+
TARGET_ONLY_STATIC = 5;
|
|
55
|
+
TARGET_AND_STOP_STATIC = 6;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
enum OrderPlacement {
|
|
59
|
+
MANUAL = 1;
|
|
60
|
+
AUTO = 2;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
required int32 template_id = 154467;
|
|
64
|
+
optional string user_tag = 154119;
|
|
65
|
+
|
|
66
|
+
optional NotifyType notify_type = 153625;
|
|
67
|
+
optional bool is_snapshot = 110121;
|
|
68
|
+
|
|
69
|
+
optional string status = 110303;
|
|
70
|
+
optional string basket_id = 110300;
|
|
71
|
+
optional string original_basket_id = 154497;
|
|
72
|
+
optional string linked_basket_ids = 110358;
|
|
73
|
+
|
|
74
|
+
optional string fcm_id = 154013;
|
|
75
|
+
optional string ib_id = 154014;
|
|
76
|
+
optional string user_id = 131003;
|
|
77
|
+
optional string account_id = 154008;
|
|
78
|
+
|
|
79
|
+
optional string symbol = 110100;
|
|
80
|
+
optional string exchange = 110101;
|
|
81
|
+
optional string trade_exchange = 112021;
|
|
82
|
+
optional string trade_route = 112016;
|
|
83
|
+
optional string exchange_order_id = 149238;
|
|
84
|
+
optional string instrument_type = 110116;
|
|
85
|
+
optional string completion_reason = 149273;
|
|
86
|
+
|
|
87
|
+
optional int32 quantity = 112004;
|
|
88
|
+
optional int32 quan_release_pending = 112027;
|
|
89
|
+
optional double price = 110306;
|
|
90
|
+
optional double trigger_price = 149247;
|
|
91
|
+
|
|
92
|
+
optional TransactionType transaction_type = 112003;
|
|
93
|
+
optional Duration duration = 112005;
|
|
94
|
+
optional PriceType price_type = 112008;
|
|
95
|
+
optional PriceType orig_price_type = 154770;
|
|
96
|
+
optional OrderPlacement manual_or_auto = 154710;
|
|
97
|
+
optional BracketType bracket_type = 157087;
|
|
98
|
+
|
|
99
|
+
optional double avg_fill_price = 110322;
|
|
100
|
+
|
|
101
|
+
optional int32 total_fill_size = 154111;
|
|
102
|
+
optional int32 total_unfilled_size = 154112;
|
|
103
|
+
|
|
104
|
+
optional string sequence_number = 112002;
|
|
105
|
+
optional string orig_sequence_number = 149263;
|
|
106
|
+
optional string cor_sequence_number = 149264;
|
|
107
|
+
|
|
108
|
+
optional string currency = 154382;
|
|
109
|
+
optional string country_code = 154172;
|
|
110
|
+
|
|
111
|
+
optional string text = 120008;
|
|
112
|
+
optional string report_text = 120028;
|
|
113
|
+
optional string remarks = 154806;
|
|
114
|
+
|
|
115
|
+
optional string window_name = 154629;
|
|
116
|
+
optional string originator_window_name = 154671;
|
|
117
|
+
|
|
118
|
+
optional int32 cancel_at_ssboe = 157085;
|
|
119
|
+
optional int32 cancel_at_usecs = 157086;
|
|
120
|
+
optional int32 cancel_after_secs = 154488;
|
|
121
|
+
|
|
122
|
+
optional int32 ssboe = 150100;
|
|
123
|
+
optional int32 usecs = 150101;
|
|
124
|
+
}
|
package/src/app.js
CHANGED
|
@@ -19,13 +19,22 @@ const { showAccounts } = require('./pages/accounts');
|
|
|
19
19
|
const { algoTradingMenu } = require('./pages/algo');
|
|
20
20
|
|
|
21
21
|
// Menus
|
|
22
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
projectXMenu,
|
|
24
|
+
rithmicMenu,
|
|
25
|
+
tradovateMenu,
|
|
26
|
+
addPropAccountMenu,
|
|
27
|
+
dashboardMenu,
|
|
28
|
+
handleUpdate,
|
|
29
|
+
} = require('./menus');
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
/** @type {Object|null} */
|
|
25
32
|
let currentService = null;
|
|
26
33
|
|
|
34
|
+
// ==================== TERMINAL ====================
|
|
35
|
+
|
|
27
36
|
/**
|
|
28
|
-
*
|
|
37
|
+
* Restore terminal state
|
|
29
38
|
*/
|
|
30
39
|
const restoreTerminal = () => {
|
|
31
40
|
try {
|
|
@@ -35,123 +44,153 @@ const restoreTerminal = () => {
|
|
|
35
44
|
process.stdin.setRawMode(false);
|
|
36
45
|
}
|
|
37
46
|
process.stdin.removeAllListeners('keypress');
|
|
38
|
-
} catch
|
|
47
|
+
} catch {
|
|
48
|
+
// Ignore terminal errors
|
|
49
|
+
}
|
|
39
50
|
};
|
|
40
51
|
|
|
52
|
+
// Signal handlers
|
|
41
53
|
process.on('exit', restoreTerminal);
|
|
42
54
|
process.on('SIGINT', () => { restoreTerminal(); process.exit(0); });
|
|
43
55
|
process.on('SIGTERM', () => { restoreTerminal(); process.exit(0); });
|
|
56
|
+
|
|
44
57
|
process.on('uncaughtException', (err) => {
|
|
45
58
|
restoreTerminal();
|
|
59
|
+
log.error('Uncaught Exception', { error: err.message });
|
|
46
60
|
console.error(chalk.red('Uncaught Exception:'), err.message);
|
|
47
61
|
process.exit(1);
|
|
48
62
|
});
|
|
63
|
+
|
|
49
64
|
process.on('unhandledRejection', (reason) => {
|
|
50
65
|
restoreTerminal();
|
|
66
|
+
log.error('Unhandled Rejection', { reason: String(reason) });
|
|
51
67
|
console.error(chalk.red('Unhandled Rejection:'), reason);
|
|
52
68
|
process.exit(1);
|
|
53
69
|
});
|
|
54
70
|
|
|
71
|
+
// ==================== STATS ====================
|
|
72
|
+
|
|
55
73
|
/**
|
|
56
|
-
* Refresh cached stats
|
|
74
|
+
* Refresh cached stats from all connections
|
|
57
75
|
*/
|
|
58
76
|
const refreshStats = async () => {
|
|
59
|
-
if (connections.count()
|
|
60
|
-
try {
|
|
61
|
-
const allAccounts = await connections.getAllAccounts();
|
|
62
|
-
const activeAccounts = allAccounts.filter(acc => acc.status === 0);
|
|
63
|
-
|
|
64
|
-
let totalBalance = null, totalPnl = null;
|
|
65
|
-
let hasBalanceData = false, hasPnlData = false;
|
|
66
|
-
|
|
67
|
-
activeAccounts.forEach(account => {
|
|
68
|
-
if (account.balance !== null && account.balance !== undefined) {
|
|
69
|
-
totalBalance = (totalBalance || 0) + account.balance;
|
|
70
|
-
hasBalanceData = true;
|
|
71
|
-
}
|
|
72
|
-
if (account.profitAndLoss !== null && account.profitAndLoss !== undefined) {
|
|
73
|
-
totalPnl = (totalPnl || 0) + account.profitAndLoss;
|
|
74
|
-
hasPnlData = true;
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
setCachedStats({
|
|
79
|
-
connections: connections.count(),
|
|
80
|
-
accounts: activeAccounts.length,
|
|
81
|
-
balance: hasBalanceData ? totalBalance : null,
|
|
82
|
-
pnl: hasPnlData ? totalPnl : null,
|
|
83
|
-
pnlPercent: null
|
|
84
|
-
});
|
|
85
|
-
} catch (e) {}
|
|
86
|
-
} else {
|
|
77
|
+
if (connections.count() === 0) {
|
|
87
78
|
clearCachedStats();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const allAccounts = await connections.getAllAccounts();
|
|
84
|
+
const activeAccounts = allAccounts.filter(acc => acc.status === 0);
|
|
85
|
+
|
|
86
|
+
let totalBalance = null;
|
|
87
|
+
let totalPnl = null;
|
|
88
|
+
let hasBalanceData = false;
|
|
89
|
+
let hasPnlData = false;
|
|
90
|
+
|
|
91
|
+
for (const account of activeAccounts) {
|
|
92
|
+
if (account.balance != null) {
|
|
93
|
+
totalBalance = (totalBalance || 0) + account.balance;
|
|
94
|
+
hasBalanceData = true;
|
|
95
|
+
}
|
|
96
|
+
if (account.profitAndLoss != null) {
|
|
97
|
+
totalPnl = (totalPnl || 0) + account.profitAndLoss;
|
|
98
|
+
hasPnlData = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setCachedStats({
|
|
103
|
+
connections: connections.count(),
|
|
104
|
+
accounts: activeAccounts.length,
|
|
105
|
+
balance: hasBalanceData ? totalBalance : null,
|
|
106
|
+
pnl: hasPnlData ? totalPnl : null,
|
|
107
|
+
pnlPercent: null,
|
|
108
|
+
});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
log.warn('Failed to refresh stats', { error: err.message });
|
|
88
111
|
}
|
|
89
112
|
};
|
|
90
113
|
|
|
114
|
+
// ==================== BANNER ====================
|
|
115
|
+
|
|
91
116
|
/**
|
|
92
|
-
* Display banner
|
|
117
|
+
* Display application banner
|
|
93
118
|
*/
|
|
94
119
|
const banner = async () => {
|
|
95
120
|
console.clear();
|
|
121
|
+
|
|
96
122
|
const termWidth = process.stdout.columns || 100;
|
|
97
123
|
const isMobile = termWidth < 60;
|
|
98
124
|
const boxWidth = isMobile ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
|
|
99
125
|
const innerWidth = boxWidth - 2;
|
|
100
126
|
const version = require('../package.json').version;
|
|
101
|
-
|
|
127
|
+
|
|
102
128
|
console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
|
|
129
|
+
|
|
130
|
+
// Logo
|
|
131
|
+
const logoLines = isMobile ? getMobileLogo() : getFullLogo();
|
|
103
132
|
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const padding = innerWidth - totalLen;
|
|
111
|
-
const leftPad = Math.floor(padding / 2);
|
|
112
|
-
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + fullLine + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
|
|
113
|
-
});
|
|
114
|
-
} else {
|
|
115
|
-
const logo = [
|
|
116
|
-
'██╗ ██╗███████╗██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗',
|
|
117
|
-
'██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝',
|
|
118
|
-
'███████║█████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ',
|
|
119
|
-
'██╔══██║██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ',
|
|
120
|
-
'██║ ██║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ',
|
|
121
|
-
'╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ '
|
|
122
|
-
];
|
|
123
|
-
const logoX = ['██╗ ██╗','╚██╗██╔╝',' ╚███╔╝ ',' ██╔██╗ ','██╔╝ ██╗','╚═╝ ╚═╝'];
|
|
124
|
-
logo.forEach((line, i) => {
|
|
125
|
-
const fullLine = chalk.cyan(line) + chalk.yellow(logoX[i]);
|
|
126
|
-
const totalLen = line.length + logoX[i].length;
|
|
127
|
-
const padding = innerWidth - totalLen;
|
|
128
|
-
const leftPad = Math.floor(padding / 2);
|
|
129
|
-
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + fullLine + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
|
|
130
|
-
});
|
|
133
|
+
for (const [hq, x] of logoLines) {
|
|
134
|
+
const fullLine = chalk.cyan(hq) + chalk.yellow(x);
|
|
135
|
+
const totalLen = hq.length + x.length;
|
|
136
|
+
const padding = innerWidth - totalLen;
|
|
137
|
+
const leftPad = Math.floor(padding / 2);
|
|
138
|
+
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + fullLine + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
|
|
131
139
|
}
|
|
132
|
-
|
|
140
|
+
|
|
133
141
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
142
|
+
|
|
134
143
|
const tagline = isMobile ? `HQX v${version}` : `Prop Futures Algo Trading v${version}`;
|
|
135
144
|
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
136
145
|
};
|
|
137
146
|
|
|
138
147
|
/**
|
|
139
|
-
*
|
|
148
|
+
* Get full logo lines
|
|
149
|
+
* @returns {Array<[string, string]>}
|
|
150
|
+
*/
|
|
151
|
+
const getFullLogo = () => [
|
|
152
|
+
['██╗ ██╗███████╗██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗', '██╗ ██╗'],
|
|
153
|
+
['██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝', '╚██╗██╔╝'],
|
|
154
|
+
['███████║█████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ', ' ╚███╔╝ '],
|
|
155
|
+
['██╔══██║██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ', ' ██╔██╗ '],
|
|
156
|
+
['██║ ██║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ', '██╔╝ ██╗'],
|
|
157
|
+
['╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ', '╚═╝ ╚═╝'],
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get mobile logo lines
|
|
162
|
+
* @returns {Array<[string, string]>}
|
|
163
|
+
*/
|
|
164
|
+
const getMobileLogo = () => [
|
|
165
|
+
['██╗ ██╗ ██████╗ ', '██╗ ██╗'],
|
|
166
|
+
['██║ ██║██╔═══██╗', '╚██╗██╔╝'],
|
|
167
|
+
['███████║██║ ██║', ' ╚███╔╝ '],
|
|
168
|
+
['██╔══██║██║▄▄ ██║', ' ██╔██╗ '],
|
|
169
|
+
['██║ ██║╚██████╔╝', '██╔╝ ██╗'],
|
|
170
|
+
['╚═╝ ╚═╝ ╚══▀▀═╝ ', '╚═╝ ╚═╝'],
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Display banner with closing border
|
|
140
175
|
*/
|
|
141
176
|
const bannerClosed = async () => {
|
|
142
177
|
await banner();
|
|
143
|
-
const
|
|
178
|
+
const termWidth = process.stdout.columns || 100;
|
|
179
|
+
const boxWidth = termWidth < 60 ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
|
|
144
180
|
console.log(chalk.cyan('╚' + '═'.repeat(boxWidth - 2) + '╝'));
|
|
145
181
|
};
|
|
146
182
|
|
|
183
|
+
// ==================== MENUS ====================
|
|
184
|
+
|
|
147
185
|
/**
|
|
148
|
-
* Main menu
|
|
186
|
+
* Main menu (platform selection)
|
|
187
|
+
* @returns {Promise<string>}
|
|
149
188
|
*/
|
|
150
189
|
const mainMenu = async () => {
|
|
151
190
|
const boxWidth = getLogoWidth();
|
|
152
191
|
const innerWidth = boxWidth - 2;
|
|
153
192
|
const col1Width = Math.floor(innerWidth / 2);
|
|
154
|
-
|
|
193
|
+
|
|
155
194
|
const menuRow = (left, right) => {
|
|
156
195
|
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
|
|
157
196
|
const rightPlain = right ? right.replace(/\x1b\[[0-9;]*m/g, '') : '';
|
|
@@ -159,29 +198,24 @@ const mainMenu = async () => {
|
|
|
159
198
|
const rightPadded = (right || '') + ' '.repeat(Math.max(0, innerWidth - col1Width - rightPlain.length));
|
|
160
199
|
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
161
200
|
};
|
|
162
|
-
|
|
163
|
-
// Continue from banner (use ╠ not ╔)
|
|
201
|
+
|
|
164
202
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
165
203
|
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PLATFORM', innerWidth)) + chalk.cyan('║'));
|
|
166
204
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
167
|
-
|
|
205
|
+
|
|
168
206
|
menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
|
|
169
207
|
menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] Exit'));
|
|
170
|
-
|
|
208
|
+
|
|
171
209
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
172
210
|
|
|
173
211
|
const input = await prompts.textInput(chalk.cyan('Select (1/2/3/X)'));
|
|
174
|
-
|
|
175
|
-
const actionMap = {
|
|
176
|
-
'1': 'projectx',
|
|
177
|
-
'2': 'rithmic',
|
|
178
|
-
'3': 'tradovate',
|
|
179
|
-
'x': 'exit'
|
|
180
|
-
};
|
|
181
212
|
|
|
182
|
-
|
|
213
|
+
const actions = { '1': 'projectx', '2': 'rithmic', '3': 'tradovate', 'x': 'exit' };
|
|
214
|
+
return actions[(input || '').toLowerCase()] || 'exit';
|
|
183
215
|
};
|
|
184
216
|
|
|
217
|
+
// ==================== MAIN LOOP ====================
|
|
218
|
+
|
|
185
219
|
/**
|
|
186
220
|
* Main application loop
|
|
187
221
|
*/
|
|
@@ -189,10 +223,11 @@ const run = async () => {
|
|
|
189
223
|
try {
|
|
190
224
|
log.info('Starting HQX CLI');
|
|
191
225
|
await bannerClosed();
|
|
192
|
-
|
|
226
|
+
|
|
227
|
+
// Restore session
|
|
193
228
|
const spinner = ora({ text: 'Restoring session...', color: 'yellow' }).start();
|
|
194
229
|
const restored = await connections.restoreFromStorage();
|
|
195
|
-
|
|
230
|
+
|
|
196
231
|
if (restored) {
|
|
197
232
|
spinner.succeed('Session restored');
|
|
198
233
|
currentService = connections.getAll()[0].service;
|
|
@@ -201,39 +236,45 @@ const run = async () => {
|
|
|
201
236
|
spinner.info('No active session');
|
|
202
237
|
}
|
|
203
238
|
|
|
239
|
+
// Main loop
|
|
204
240
|
while (true) {
|
|
205
241
|
try {
|
|
206
242
|
prepareStdin();
|
|
207
243
|
await banner();
|
|
208
|
-
|
|
244
|
+
|
|
209
245
|
if (!connections.isConnected()) {
|
|
210
246
|
const choice = await mainMenu();
|
|
211
|
-
|
|
247
|
+
|
|
212
248
|
if (choice === 'exit') {
|
|
213
249
|
console.log(chalk.gray('Goodbye!'));
|
|
214
250
|
process.exit(0);
|
|
215
251
|
}
|
|
216
|
-
|
|
252
|
+
|
|
217
253
|
let service = null;
|
|
218
254
|
if (choice === 'projectx') service = await projectXMenu();
|
|
219
255
|
else if (choice === 'rithmic') service = await rithmicMenu();
|
|
220
256
|
else if (choice === 'tradovate') service = await tradovateMenu();
|
|
221
|
-
|
|
257
|
+
|
|
222
258
|
if (service) {
|
|
223
259
|
currentService = service;
|
|
224
260
|
await refreshStats();
|
|
225
261
|
}
|
|
226
262
|
} else {
|
|
227
|
-
|
|
263
|
+
// Refresh stats before showing dashboard
|
|
264
|
+
await refreshStats();
|
|
228
265
|
|
|
266
|
+
const action = await dashboardMenu(currentService);
|
|
267
|
+
|
|
229
268
|
switch (action) {
|
|
230
269
|
case 'accounts':
|
|
231
270
|
await showAccounts(currentService);
|
|
232
271
|
break;
|
|
272
|
+
|
|
233
273
|
case 'stats':
|
|
234
274
|
await showStats(currentService);
|
|
235
275
|
break;
|
|
236
|
-
|
|
276
|
+
|
|
277
|
+
case 'add_prop_account': {
|
|
237
278
|
const platformChoice = await addPropAccountMenu();
|
|
238
279
|
let newService = null;
|
|
239
280
|
if (platformChoice === 'projectx') newService = await projectXMenu();
|
|
@@ -244,17 +285,21 @@ const run = async () => {
|
|
|
244
285
|
await refreshStats();
|
|
245
286
|
}
|
|
246
287
|
break;
|
|
288
|
+
}
|
|
289
|
+
|
|
247
290
|
case 'algotrading':
|
|
248
291
|
try {
|
|
249
292
|
await algoTradingMenu(currentService);
|
|
250
|
-
} catch (
|
|
251
|
-
console.log(chalk.red(` Algo error: ${
|
|
293
|
+
} catch (err) {
|
|
294
|
+
console.log(chalk.red(` Algo error: ${err.message}`));
|
|
252
295
|
prepareStdin();
|
|
253
296
|
}
|
|
254
297
|
break;
|
|
298
|
+
|
|
255
299
|
case 'update':
|
|
256
300
|
await handleUpdate();
|
|
257
301
|
break;
|
|
302
|
+
|
|
258
303
|
case 'disconnect':
|
|
259
304
|
connections.disconnectAll();
|
|
260
305
|
currentService = null;
|
|
@@ -264,10 +309,12 @@ const run = async () => {
|
|
|
264
309
|
}
|
|
265
310
|
}
|
|
266
311
|
} catch (loopError) {
|
|
312
|
+
log.error('Loop error', { error: loopError.message });
|
|
267
313
|
console.error(chalk.red('Error:'), loopError.message);
|
|
268
314
|
}
|
|
269
315
|
}
|
|
270
316
|
} catch (error) {
|
|
317
|
+
log.error('Fatal error', { error: error.message });
|
|
271
318
|
console.error(chalk.red('Fatal error:'), error.message);
|
|
272
319
|
process.exit(1);
|
|
273
320
|
}
|