hedgequantx 2.7.14 → 2.7.16

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.
@@ -222,10 +222,19 @@ const getTradingAccounts = async (service) => {
222
222
  debug(`Account ${acc.accountId} rmsInfo:`, JSON.stringify(rmsInfo));
223
223
 
224
224
  // REAL DATA FROM RITHMIC ONLY - NO DEFAULTS
225
- const accountBalance = pnlData.accountBalance ? parseFloat(pnlData.accountBalance) : null;
226
- const openPnL = pnlData.openPositionPnl ? parseFloat(pnlData.openPositionPnl) : null;
227
- const closedPnL = pnlData.closedPositionPnl ? parseFloat(pnlData.closedPositionPnl) : null;
228
- const dayPnL = pnlData.dayPnl ? parseFloat(pnlData.dayPnl) : null;
225
+ // Use !== undefined to handle 0 values correctly
226
+ const accountBalance = pnlData.accountBalance !== undefined ? parseFloat(pnlData.accountBalance) : null;
227
+ const openPnL = pnlData.openPositionPnl !== undefined ? parseFloat(pnlData.openPositionPnl) : null;
228
+ const closedPnL = pnlData.closedPositionPnl !== undefined ? parseFloat(pnlData.closedPositionPnl) : null;
229
+ const dayPnL = pnlData.dayPnl !== undefined ? parseFloat(pnlData.dayPnl) : null;
230
+
231
+ // Calculate P&L: prefer dayPnl, fallback to open+closed
232
+ let profitAndLoss = null;
233
+ if (dayPnL !== null) {
234
+ profitAndLoss = dayPnL;
235
+ } else if (openPnL !== null || closedPnL !== null) {
236
+ profitAndLoss = (openPnL || 0) + (closedPnL || 0);
237
+ }
229
238
 
230
239
  return {
231
240
  accountId: hashAccountId(acc.accountId),
@@ -233,11 +242,17 @@ const getTradingAccounts = async (service) => {
233
242
  accountName: acc.accountId, // Never expose real name - only account ID
234
243
  name: acc.accountId, // Never expose real name - only account ID
235
244
  balance: accountBalance,
236
- profitAndLoss: dayPnL !== null ? dayPnL : (openPnL !== null || closedPnL !== null ? (openPnL || 0) + (closedPnL || 0) : null),
245
+ profitAndLoss: profitAndLoss,
237
246
  openPnL: openPnL,
238
247
  todayPnL: closedPnL,
239
- status: rmsInfo.status || null, // Real status from API
240
- algorithm: rmsInfo.algorithm || null, // Trading algorithm/type
248
+ // Status/Type: Rithmic API doesn't provide user-friendly values
249
+ // "admin only" and "Max Loss" are RMS internal values, not account status
250
+ // Set to null to show "--" in UI (per RULES.md - no fake data)
251
+ status: null,
252
+ type: null,
253
+ // Keep RMS data for reference
254
+ rmsStatus: rmsInfo.status || null,
255
+ rmsAlgorithm: rmsInfo.algorithm || null,
241
256
  lossLimit: rmsInfo.lossLimit || null,
242
257
  minAccountBalance: rmsInfo.minAccountBalance || null,
243
258
  buyLimit: rmsInfo.buyLimit || null,
package/src/lib/api.js DELETED
@@ -1,198 +0,0 @@
1
- /**
2
- * Trading API - REAL ORDER EXECUTION
3
- * Connects to ProjectX/TopstepX API for real trades
4
- */
5
-
6
- const https = require('https');
7
-
8
- const PROPFIRM_APIS = {
9
- topstep: 'api.topstepx.com',
10
- alpha_futures: 'api.alphafutures.projectx.com',
11
- tickticktrader: 'api.tickticktrader.projectx.com',
12
- bulenox: 'api.bulenox.projectx.com',
13
- tradeday: 'api.tradeday.projectx.com'
14
- };
15
-
16
- class TradingAPI {
17
- constructor(token, propfirm = 'topstep') {
18
- this.token = token;
19
- this.apiHost = PROPFIRM_APIS[propfirm] || PROPFIRM_APIS.topstep;
20
- }
21
-
22
- async _request(path, method = 'POST', data = null) {
23
- return new Promise((resolve, reject) => {
24
- const options = {
25
- hostname: this.apiHost,
26
- port: 443,
27
- path: path,
28
- method: method,
29
- headers: {
30
- 'Content-Type': 'application/json',
31
- 'Accept': 'application/json',
32
- 'Authorization': `Bearer ${this.token}`
33
- },
34
- timeout: 10000
35
- };
36
-
37
- const req = https.request(options, (res) => {
38
- let body = '';
39
- res.on('data', chunk => body += chunk);
40
- res.on('end', () => {
41
- try {
42
- resolve({ status: res.statusCode, data: JSON.parse(body) });
43
- } catch (e) {
44
- resolve({ status: res.statusCode, data: body });
45
- }
46
- });
47
- });
48
-
49
- req.on('error', reject);
50
- req.on('timeout', () => {
51
- req.destroy();
52
- reject(new Error('Request timeout'));
53
- });
54
-
55
- if (data) req.write(JSON.stringify(data));
56
- req.end();
57
- });
58
- }
59
-
60
- /**
61
- * Place a market order - REAL EXECUTION
62
- * ProjectX API order types:
63
- * 1 = Limit (requires limitPrice)
64
- * 2 = Market (no price required)
65
- * 3 = Stop Market (requires stopPrice)
66
- * 4 = Stop Limit (requires both)
67
- */
68
- async placeMarketOrder(accountId, contractId, side, quantity, currentPrice = 0) {
69
- // Type 2 = Market order - tested and confirmed working
70
- const orderData = {
71
- accountId: parseInt(accountId),
72
- contractId: contractId,
73
- type: 2, // Market order
74
- side: side === 'buy' ? 0 : 1, // 0=Buy, 1=Sell
75
- size: quantity
76
- };
77
-
78
- console.log(`[TRADING] MARKET ${side.toUpperCase()} ${quantity}x ${contractId}`);
79
-
80
- const response = await this._request('/api/Order/place', 'POST', orderData);
81
-
82
- if (response.status === 200 && response.data.success) {
83
- console.log(`[TRADING] FILLED - OrderId: ${response.data.orderId}`);
84
- return { success: true, order: response.data };
85
- } else {
86
- console.log(`[TRADING] REJECTED: ${response.data.errorMessage || 'Unknown error'}`);
87
- return { success: false, error: response.data.errorMessage || 'Order failed' };
88
- }
89
- }
90
-
91
- /**
92
- * Place a limit order - REAL EXECUTION
93
- */
94
- async placeLimitOrder(accountId, contractId, side, quantity, price) {
95
- const orderData = {
96
- accountId: parseInt(accountId),
97
- contractId: contractId,
98
- type: 0, // Limit order
99
- side: side === 'buy' ? 0 : 1,
100
- size: quantity,
101
- limitPrice: price
102
- };
103
-
104
- console.log(`[TRADING] Placing LIMIT order: ${side.toUpperCase()} ${quantity}x ${contractId} @ ${price}`);
105
-
106
- const response = await this._request('/api/Order/place', 'POST', orderData);
107
-
108
- if (response.status === 200 && response.data.success) {
109
- console.log(`[TRADING] Limit order placed: ${JSON.stringify(response.data)}`);
110
- return { success: true, order: response.data };
111
- } else {
112
- return { success: false, error: response.data.errorMessage || 'Order failed' };
113
- }
114
- }
115
-
116
- /**
117
- * Place a stop order - REAL EXECUTION
118
- * ProjectX API: type 2 = Stop Market
119
- */
120
- async placeStopOrder(accountId, contractId, side, quantity, stopPrice) {
121
- const orderData = {
122
- accountId: parseInt(accountId),
123
- contractId: contractId,
124
- type: 2, // Stop Market order
125
- side: side === 'buy' ? 0 : 1,
126
- size: quantity,
127
- stopPrice: stopPrice
128
- };
129
-
130
- console.log(`[TRADING] Placing STOP order: ${side.toUpperCase()} ${quantity}x ${contractId} @ stop ${stopPrice}`);
131
-
132
- const response = await this._request('/api/Order/place', 'POST', orderData);
133
-
134
- console.log(`[TRADING] Stop response: ${JSON.stringify(response.data)}`);
135
-
136
- if (response.status === 200 && response.data.success) {
137
- console.log(`[TRADING] Stop order placed - OrderId: ${response.data.orderId}`);
138
- return { success: true, order: response.data };
139
- } else {
140
- console.log(`[TRADING] Stop order REJECTED: ${response.data.errorMessage || 'Unknown error'}`);
141
- return { success: false, error: response.data.errorMessage || 'Order failed' };
142
- }
143
- }
144
-
145
- /**
146
- * Cancel an order
147
- */
148
- async cancelOrder(orderId) {
149
- const response = await this._request('/api/Order/cancel', 'POST', { orderId: parseInt(orderId) });
150
- return { success: response.status === 200 && response.data.success };
151
- }
152
-
153
- /**
154
- * Close a position - REAL EXECUTION
155
- */
156
- async closePosition(accountId, contractId) {
157
- console.log(`[TRADING] Closing position: ${contractId}`);
158
-
159
- const response = await this._request('/api/Position/closeContract', 'POST', {
160
- accountId: parseInt(accountId),
161
- contractId: contractId
162
- });
163
-
164
- if (response.status === 200 && response.data.success) {
165
- console.log(`[TRADING] Position CLOSED`);
166
- return { success: true };
167
- } else {
168
- console.log(`[TRADING] Close FAILED: ${JSON.stringify(response.data)}`);
169
- return { success: false, error: response.data.errorMessage || 'Close failed' };
170
- }
171
- }
172
-
173
- /**
174
- * Get open positions
175
- */
176
- async getPositions(accountId) {
177
- const response = await this._request('/api/Position/searchOpen', 'POST', { accountId: parseInt(accountId) });
178
-
179
- if (response.status === 200) {
180
- return { success: true, positions: response.data.positions || response.data || [] };
181
- }
182
- return { success: false, positions: [] };
183
- }
184
-
185
- /**
186
- * Get open orders
187
- */
188
- async getOrders(accountId) {
189
- const response = await this._request('/api/Order/searchOpen', 'POST', { accountId: parseInt(accountId) });
190
-
191
- if (response.status === 200) {
192
- return { success: true, orders: response.data.orders || response.data || [] };
193
- }
194
- return { success: false, orders: [] };
195
- }
196
- }
197
-
198
- module.exports = { TradingAPI };
package/src/lib/api2.js DELETED
@@ -1,353 +0,0 @@
1
- /**
2
- * Rithmic Trading API - REAL ORDER EXECUTION
3
- * Connects to Rithmic via WebSocket/Protobuf for order execution
4
- */
5
-
6
- const WebSocket = require('ws');
7
- const protobuf = require('protobufjs');
8
- const path = require('path');
9
-
10
- const RITHMIC_GATEWAYS = {
11
- CHICAGO: 'wss://rprotocol.rithmic.com:443',
12
- EUROPE: 'wss://rprotocol-ie.rithmic.com:443',
13
- TEST: 'wss://rituz00100.rithmic.com:443'
14
- };
15
-
16
- const PROTO_PATH = path.join(__dirname, '../../protos/rithmic');
17
-
18
- class RithmicTradingAPI {
19
- constructor(config) {
20
- this.config = config;
21
- this.ws = null;
22
- this.isConnected = false;
23
- this.protoRoot = null;
24
- this.pendingOrders = new Map();
25
- this.orderIdCounter = 1;
26
-
27
- // Proto message types
28
- this.RequestLogin = null;
29
- this.ResponseLogin = null;
30
- this.RequestNewOrder = null;
31
- this.ResponseNewOrder = null;
32
- this.ExchangeOrderNotification = null;
33
- }
34
-
35
- /**
36
- * Load protobuf definitions
37
- */
38
- async _loadProtos() {
39
- if (this.protoRoot) return;
40
-
41
- try {
42
- this.protoRoot = await protobuf.load([
43
- path.join(PROTO_PATH, 'request_login.proto'),
44
- path.join(PROTO_PATH, 'response_login.proto'),
45
- path.join(PROTO_PATH, 'request_new_order.proto'),
46
- path.join(PROTO_PATH, 'response_new_order.proto'),
47
- path.join(PROTO_PATH, 'exchange_order_notification.proto'),
48
- path.join(PROTO_PATH, 'request_cancel_all_orders.proto'),
49
- path.join(PROTO_PATH, 'request_heartbeat.proto'),
50
- path.join(PROTO_PATH, 'response_heartbeat.proto')
51
- ]);
52
-
53
- this.RequestLogin = this.protoRoot.lookupType('rti.RequestLogin');
54
- this.ResponseLogin = this.protoRoot.lookupType('rti.ResponseLogin');
55
- this.RequestNewOrder = this.protoRoot.lookupType('rti.RequestNewOrder');
56
- this.ResponseNewOrder = this.protoRoot.lookupType('rti.ResponseNewOrder');
57
- this.ExchangeOrderNotification = this.protoRoot.lookupType('rti.ExchangeOrderNotification');
58
- this.RequestCancelAllOrders = this.protoRoot.lookupType('rti.RequestCancelAllOrders');
59
- this.RequestHeartbeat = this.protoRoot.lookupType('rti.RequestHeartbeat');
60
-
61
- console.log('[RITHMIC-TRADE] Protos loaded');
62
- } catch (error) {
63
- console.error('[RITHMIC-TRADE] Failed to load protos:', error.message);
64
- throw error;
65
- }
66
- }
67
-
68
- /**
69
- * Connect to Rithmic ORDER_PLANT for trading
70
- */
71
- async connect() {
72
- if (this.isConnected) return true;
73
-
74
- await this._loadProtos();
75
-
76
- const { userId, password, systemName, gateway } = this.config;
77
- const gatewayUrl = gateway || RITHMIC_GATEWAYS.CHICAGO;
78
-
79
- console.log(`[RITHMIC-TRADE] Connecting to ${gatewayUrl} for order execution`);
80
-
81
- return new Promise((resolve, reject) => {
82
- this.ws = new WebSocket(gatewayUrl, { rejectUnauthorized: false });
83
-
84
- const timeout = setTimeout(() => {
85
- this.ws.close();
86
- reject(new Error('Connection timeout'));
87
- }, 15000);
88
-
89
- this.ws.on('open', async () => {
90
- console.log('[RITHMIC-TRADE] WebSocket connected, sending login...');
91
-
92
- const loginRequest = this.RequestLogin.create({
93
- templateId: 10,
94
- templateVersion: '3.9',
95
- user: userId,
96
- password: password,
97
- appName: 'App',
98
- appVersion: '1.0.0',
99
- systemName: systemName,
100
- infraType: 2 // ORDER_PLANT for trading
101
- });
102
-
103
- const buffer = this.RequestLogin.encode(loginRequest).finish();
104
- this.ws.send(buffer);
105
- });
106
-
107
- this.ws.on('message', (data) => {
108
- this._handleMessage(data, (success, error) => {
109
- if (success !== undefined) {
110
- clearTimeout(timeout);
111
- if (success) {
112
- this.isConnected = true;
113
- this._startHeartbeat();
114
- resolve(true);
115
- } else {
116
- reject(new Error(error || 'Login failed'));
117
- }
118
- }
119
- });
120
- });
121
-
122
- this.ws.on('error', (error) => {
123
- console.error('[RITHMIC-TRADE] Error:', error.message);
124
- clearTimeout(timeout);
125
- reject(error);
126
- });
127
-
128
- this.ws.on('close', () => {
129
- this.isConnected = false;
130
- this._stopHeartbeat();
131
- });
132
- });
133
- }
134
-
135
- /**
136
- * Handle incoming message
137
- */
138
- _handleMessage(data, loginCallback) {
139
- try {
140
- const buffer = new Uint8Array(data);
141
-
142
- // Try ResponseLogin
143
- try {
144
- const response = this.ResponseLogin.decode(buffer);
145
- if (response.templateId === 11) {
146
- const rpCode = Array.isArray(response.rpCode) ? response.rpCode[0] : response.rpCode;
147
- if (rpCode === '0') {
148
- console.log('[RITHMIC-TRADE] Login successful');
149
- loginCallback(true);
150
- } else {
151
- console.log(`[RITHMIC-TRADE] Login failed: ${response.textMsg || rpCode}`);
152
- loginCallback(false, response.textMsg);
153
- }
154
- return;
155
- }
156
- } catch (e) {}
157
-
158
- // Try ResponseNewOrder
159
- try {
160
- const response = this.ResponseNewOrder.decode(buffer);
161
- if (response.templateId === 313) {
162
- this._handleOrderResponse(response);
163
- return;
164
- }
165
- } catch (e) {}
166
-
167
- // Try ExchangeOrderNotification (fill, reject, etc.)
168
- try {
169
- const notification = this.ExchangeOrderNotification.decode(buffer);
170
- if (notification.templateId === 351) {
171
- this._handleOrderNotification(notification);
172
- return;
173
- }
174
- } catch (e) {}
175
-
176
- } catch (error) {
177
- console.error('[RITHMIC-TRADE] Message decode error:', error.message);
178
- }
179
- }
180
-
181
- /**
182
- * Handle order response
183
- */
184
- _handleOrderResponse(response) {
185
- const basketId = response.basketId;
186
- const pending = this.pendingOrders.get(basketId);
187
-
188
- if (pending) {
189
- const rpCode = Array.isArray(response.rpCode) ? response.rpCode[0] : response.rpCode;
190
- if (rpCode === '0') {
191
- console.log(`[RITHMIC-TRADE] Order acknowledged: ${basketId}`);
192
- pending.resolve({ success: true, basketId });
193
- } else {
194
- console.log(`[RITHMIC-TRADE] Order rejected: ${response.textMsg || rpCode}`);
195
- pending.reject(new Error(response.textMsg || 'Order rejected'));
196
- }
197
- this.pendingOrders.delete(basketId);
198
- }
199
- }
200
-
201
- /**
202
- * Handle order notification (fill, cancel, etc.)
203
- */
204
- _handleOrderNotification(notification) {
205
- const status = notification.notifyType;
206
- const symbol = notification.symbol;
207
- const fillPrice = notification.avgFillPrice;
208
- const filledQty = notification.filledQty;
209
-
210
- console.log(`[RITHMIC-TRADE] Order notification: ${symbol} - Status: ${status}, Filled: ${filledQty} @ ${fillPrice}`);
211
- }
212
-
213
- /**
214
- * Place a market order - REAL EXECUTION
215
- */
216
- async placeMarketOrder(accountId, contractId, side, quantity) {
217
- if (!this.isConnected) {
218
- await this.connect();
219
- }
220
-
221
- const basketId = `X_${Date.now()}_${this.orderIdCounter++}`;
222
-
223
- // Extract symbol from contractId (e.g., "MNQH5" or just use as-is)
224
- const symbol = this._extractSymbol(contractId);
225
-
226
- const orderRequest = this.RequestNewOrder.create({
227
- templateId: 312,
228
- userMsg: [basketId],
229
- symbol: symbol,
230
- exchange: 'CME',
231
- quantity: quantity,
232
- price: 0,
233
- triggerPrice: 0,
234
- transactionType: side === 'buy' ? 1 : 2, // 1=Buy, 2=Sell
235
- duration: 1, // DAY
236
- priceType: 1, // Market
237
- tradeRoute: 'DEFAULT',
238
- accountId: accountId.toString(),
239
- basketId: basketId
240
- });
241
-
242
- console.log(`[RITHMIC-TRADE] MARKET ${side.toUpperCase()} ${quantity}x ${symbol}`);
243
-
244
- return new Promise((resolve, reject) => {
245
- this.pendingOrders.set(basketId, { resolve, reject });
246
-
247
- const buffer = this.RequestNewOrder.encode(orderRequest).finish();
248
- this.ws.send(buffer);
249
-
250
- // Timeout after 10 seconds
251
- setTimeout(() => {
252
- if (this.pendingOrders.has(basketId)) {
253
- this.pendingOrders.delete(basketId);
254
- reject(new Error('Order timeout'));
255
- }
256
- }, 10000);
257
- });
258
- }
259
-
260
- /**
261
- * Close position - REAL EXECUTION
262
- */
263
- async closePosition(accountId, contractId) {
264
- // For Rithmic, closing a position means placing an opposite order
265
- // We need to get the current position first, but for simplicity,
266
- // we'll use cancel all and let the caller handle the close order
267
- console.log(`[RITHMIC-TRADE] Close position requested: ${contractId}`);
268
-
269
- // Cancel all pending orders first
270
- try {
271
- await this.cancelAllOrders(accountId);
272
- } catch (e) {
273
- console.log('[RITHMIC-TRADE] Cancel all orders failed:', e.message);
274
- }
275
-
276
- return { success: true };
277
- }
278
-
279
- /**
280
- * Cancel all orders
281
- */
282
- async cancelAllOrders(accountId) {
283
- if (!this.isConnected) return { success: false };
284
-
285
- const request = this.RequestCancelAllOrders.create({
286
- templateId: 316,
287
- accountId: accountId.toString()
288
- });
289
-
290
- const buffer = this.RequestCancelAllOrders.encode(request).finish();
291
- this.ws.send(buffer);
292
-
293
- console.log('[RITHMIC-TRADE] Cancel all orders sent');
294
- return { success: true };
295
- }
296
-
297
- /**
298
- * Extract symbol from various formats
299
- */
300
- _extractSymbol(contractId) {
301
- if (!contractId) return 'MNQH5';
302
-
303
- // If already looks like Rithmic symbol, return as-is
304
- if (/^[A-Z]{2,4}[FGHJKMNQUVXZ]\d{1,2}$/.test(contractId)) {
305
- return contractId;
306
- }
307
-
308
- // Try to extract from longer format
309
- const match = contractId.match(/([A-Z]{2,4})([FGHJKMNQUVXZ])(\d{1,2})/);
310
- if (match) {
311
- return `${match[1]}${match[2]}${match[3]}`;
312
- }
313
-
314
- return contractId;
315
- }
316
-
317
- /**
318
- * Start heartbeat
319
- */
320
- _startHeartbeat() {
321
- this.heartbeatInterval = setInterval(() => {
322
- if (this.isConnected && this.ws) {
323
- const heartbeat = this.RequestHeartbeat.create({ templateId: 18 });
324
- const buffer = this.RequestHeartbeat.encode(heartbeat).finish();
325
- this.ws.send(buffer);
326
- }
327
- }, 30000);
328
- }
329
-
330
- /**
331
- * Stop heartbeat
332
- */
333
- _stopHeartbeat() {
334
- if (this.heartbeatInterval) {
335
- clearInterval(this.heartbeatInterval);
336
- this.heartbeatInterval = null;
337
- }
338
- }
339
-
340
- /**
341
- * Disconnect
342
- */
343
- disconnect() {
344
- this._stopHeartbeat();
345
- if (this.ws) {
346
- this.ws.close();
347
- this.ws = null;
348
- }
349
- this.isConnected = false;
350
- }
351
- }
352
-
353
- module.exports = { RithmicTradingAPI, RITHMIC_GATEWAYS };