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
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rithmic Orders Module
|
|
3
|
+
* Order placement, cancellation, and history
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { REQ } = require('./constants');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Place order via ORDER_PLANT
|
|
10
|
+
* @param {RithmicService} service - The Rithmic service instance
|
|
11
|
+
* @param {Object} orderData - Order parameters
|
|
12
|
+
*/
|
|
13
|
+
const placeOrder = async (service, orderData) => {
|
|
14
|
+
if (!service.orderConn || !service.loginInfo) {
|
|
15
|
+
return { success: false, error: 'Not connected' };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
service.orderConn.send('RequestNewOrder', {
|
|
20
|
+
templateId: REQ.NEW_ORDER,
|
|
21
|
+
userMsg: ['HQX'],
|
|
22
|
+
fcmId: service.loginInfo.fcmId,
|
|
23
|
+
ibId: service.loginInfo.ibId,
|
|
24
|
+
accountId: orderData.accountId,
|
|
25
|
+
symbol: orderData.symbol,
|
|
26
|
+
exchange: orderData.exchange || 'CME',
|
|
27
|
+
quantity: orderData.size,
|
|
28
|
+
transactionType: orderData.side === 0 ? 1 : 2, // 1=Buy, 2=Sell
|
|
29
|
+
duration: 1, // DAY
|
|
30
|
+
orderType: orderData.type === 2 ? 1 : 2, // 1=Market, 2=Limit
|
|
31
|
+
price: orderData.price || 0,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return { success: true, message: 'Order submitted' };
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return { success: false, error: error.message };
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Cancel order
|
|
42
|
+
* @param {RithmicService} service - The Rithmic service instance
|
|
43
|
+
* @param {string} orderId - Order ID to cancel
|
|
44
|
+
*/
|
|
45
|
+
const cancelOrder = async (service, orderId) => {
|
|
46
|
+
if (!service.orderConn || !service.loginInfo) {
|
|
47
|
+
return { success: false, error: 'Not connected' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
service.orderConn.send('RequestCancelOrder', {
|
|
52
|
+
templateId: REQ.CANCEL_ORDER,
|
|
53
|
+
userMsg: ['HQX'],
|
|
54
|
+
fcmId: service.loginInfo.fcmId,
|
|
55
|
+
ibId: service.loginInfo.ibId,
|
|
56
|
+
orderId: orderId,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return { success: true };
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return { success: false, error: error.message };
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get active orders
|
|
67
|
+
* @param {RithmicService} service - The Rithmic service instance
|
|
68
|
+
*/
|
|
69
|
+
const getOrders = async (service) => {
|
|
70
|
+
if (!service.orderConn || !service.loginInfo) {
|
|
71
|
+
return { success: true, orders: [] };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const orders = [];
|
|
76
|
+
const timeout = setTimeout(() => {
|
|
77
|
+
resolve({ success: true, orders });
|
|
78
|
+
}, 3000);
|
|
79
|
+
|
|
80
|
+
const orderHandler = (notification) => {
|
|
81
|
+
if (notification.orderId) {
|
|
82
|
+
orders.push({
|
|
83
|
+
orderId: notification.orderId,
|
|
84
|
+
symbol: notification.symbol,
|
|
85
|
+
exchange: notification.exchange,
|
|
86
|
+
side: notification.transactionType === 1 ? 'BUY' : 'SELL',
|
|
87
|
+
quantity: notification.quantity,
|
|
88
|
+
filledQuantity: notification.filledQuantity || 0,
|
|
89
|
+
price: notification.price,
|
|
90
|
+
orderType: notification.orderType,
|
|
91
|
+
status: notification.status,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
service.once('ordersReceived', () => {
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
service.removeListener('orderNotification', orderHandler);
|
|
99
|
+
resolve({ success: true, orders });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
service.on('orderNotification', orderHandler);
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
for (const acc of service.accounts) {
|
|
106
|
+
service.orderConn.send('RequestShowOrders', {
|
|
107
|
+
templateId: REQ.SHOW_ORDERS,
|
|
108
|
+
userMsg: ['HQX'],
|
|
109
|
+
fcmId: acc.fcmId || service.loginInfo.fcmId,
|
|
110
|
+
ibId: acc.ibId || service.loginInfo.ibId,
|
|
111
|
+
accountId: acc.accountId,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
clearTimeout(timeout);
|
|
116
|
+
resolve({ success: false, error: e.message, orders: [] });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get order history
|
|
123
|
+
* @param {RithmicService} service - The Rithmic service instance
|
|
124
|
+
* @param {string} date - Date in YYYYMMDD format
|
|
125
|
+
*/
|
|
126
|
+
const getOrderHistory = async (service, date) => {
|
|
127
|
+
if (!service.orderConn || !service.loginInfo) {
|
|
128
|
+
return { success: true, orders: [] };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const dateStr = date || new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
132
|
+
|
|
133
|
+
return new Promise((resolve) => {
|
|
134
|
+
const orders = [];
|
|
135
|
+
const timeout = setTimeout(() => {
|
|
136
|
+
resolve({ success: true, orders });
|
|
137
|
+
}, 3000);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
for (const acc of service.accounts) {
|
|
141
|
+
service.orderConn.send('RequestShowOrderHistorySummary', {
|
|
142
|
+
templateId: REQ.SHOW_ORDER_HISTORY,
|
|
143
|
+
userMsg: ['HQX'],
|
|
144
|
+
fcmId: acc.fcmId || service.loginInfo.fcmId,
|
|
145
|
+
ibId: acc.ibId || service.loginInfo.ibId,
|
|
146
|
+
accountId: acc.accountId,
|
|
147
|
+
date: dateStr,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setTimeout(() => {
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
resolve({ success: true, orders });
|
|
154
|
+
}, 2000);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
clearTimeout(timeout);
|
|
157
|
+
resolve({ success: false, error: e.message, orders: [] });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Close position (market order to flatten)
|
|
164
|
+
* @param {RithmicService} service - The Rithmic service instance
|
|
165
|
+
* @param {string} accountId - Account ID
|
|
166
|
+
* @param {string} symbol - Symbol to close
|
|
167
|
+
*/
|
|
168
|
+
const closePosition = async (service, accountId, symbol) => {
|
|
169
|
+
const positions = Array.from(service.positions.values());
|
|
170
|
+
const position = positions.find(p => p.accountId === accountId && p.symbol === symbol);
|
|
171
|
+
|
|
172
|
+
if (!position) {
|
|
173
|
+
return { success: false, error: 'Position not found' };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return placeOrder(service, {
|
|
177
|
+
accountId,
|
|
178
|
+
symbol,
|
|
179
|
+
exchange: position.exchange,
|
|
180
|
+
size: Math.abs(position.quantity),
|
|
181
|
+
side: position.quantity > 0 ? 1 : 0, // Sell if long, Buy if short
|
|
182
|
+
type: 2, // Market
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
placeOrder,
|
|
188
|
+
cancelOrder,
|
|
189
|
+
getOrders,
|
|
190
|
+
getOrderHistory,
|
|
191
|
+
closePosition
|
|
192
|
+
};
|
package/src/ui/index.js
CHANGED
|
@@ -24,6 +24,26 @@ const {
|
|
|
24
24
|
} = require('./table');
|
|
25
25
|
const { createBoxMenu } = require('./menu');
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Ensure stdin is ready for inquirer prompts
|
|
29
|
+
* This fixes input leaking to bash after session restore or algo trading
|
|
30
|
+
*/
|
|
31
|
+
const prepareStdin = () => {
|
|
32
|
+
try {
|
|
33
|
+
// Remove any raw mode that might be left from previous operations
|
|
34
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
35
|
+
process.stdin.setRawMode(false);
|
|
36
|
+
}
|
|
37
|
+
// Remove any lingering keypress listeners
|
|
38
|
+
process.stdin.removeAllListeners('keypress');
|
|
39
|
+
process.stdin.removeAllListeners('data');
|
|
40
|
+
// Pause stdin so inquirer can take control
|
|
41
|
+
process.stdin.pause();
|
|
42
|
+
} catch (e) {
|
|
43
|
+
// Ignore errors
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
27
47
|
module.exports = {
|
|
28
48
|
// Device
|
|
29
49
|
detectDevice,
|
|
@@ -47,5 +67,7 @@ module.exports = {
|
|
|
47
67
|
draw2ColSeparator,
|
|
48
68
|
fmtRow,
|
|
49
69
|
// Menu
|
|
50
|
-
createBoxMenu
|
|
70
|
+
createBoxMenu,
|
|
71
|
+
// Stdin
|
|
72
|
+
prepareStdin
|
|
51
73
|
};
|