hedgequantx 2.9.125 → 2.9.127

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,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.125",
3
+ "version": "2.9.127",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/lib/data.js CHANGED
@@ -36,8 +36,9 @@ class MarketDataFeed extends EventEmitter {
36
36
  /**
37
37
  * Connect to Rithmic TICKER_PLANT
38
38
  * @param {Object} rithmicCredentials - Credentials from RithmicService.getRithmicCredentials()
39
+ * @param {number} retries - Number of connection retries (default: 2)
39
40
  */
40
- async connect(rithmicCredentials) {
41
+ async connect(rithmicCredentials, retries = 2) {
41
42
  if (this.connected) return;
42
43
 
43
44
  if (!rithmicCredentials || !rithmicCredentials.userId || !rithmicCredentials.password) {
@@ -45,7 +46,6 @@ class MarketDataFeed extends EventEmitter {
45
46
  }
46
47
 
47
48
  this.credentials = rithmicCredentials;
48
- this.connection = new RithmicConnection();
49
49
 
50
50
  this.config = {
51
51
  uri: rithmicCredentials.gateway || RITHMIC_ENDPOINTS.CHICAGO,
@@ -56,39 +56,74 @@ class MarketDataFeed extends EventEmitter {
56
56
  appVersion: '2.0.0',
57
57
  };
58
58
 
59
- try {
60
- // Ensure protobuf definitions are loaded
61
- await proto.load();
62
-
63
- await this.connection.connect(this.config);
59
+ // Ensure protobuf definitions are loaded
60
+ await proto.load();
64
61
 
65
- // Setup message handler for market data
66
- this.connection.on('message', (msg) => this._handleMessage(msg));
67
-
68
- // Login to TICKER_PLANT
69
- return new Promise((resolve, reject) => {
70
- const timeout = setTimeout(() => {
71
- reject(new Error('TICKER_PLANT login timeout'));
72
- }, 15000);
73
-
74
- this.connection.once('loggedIn', () => {
75
- clearTimeout(timeout);
76
- this.connected = true;
77
- this.emit('connected');
78
- resolve(true);
62
+ let lastError = null;
63
+ for (let attempt = 0; attempt <= retries; attempt++) {
64
+ try {
65
+ if (attempt > 0) {
66
+ this.emit('debug', `TICKER_PLANT retry ${attempt}/${retries}...`);
67
+ await new Promise(r => setTimeout(r, 2000)); // Wait 2s before retry
68
+ }
69
+
70
+ this.connection = new RithmicConnection();
71
+
72
+ // Connection timeout handler
73
+ this.connection.on('error', (err) => {
74
+ this.emit('debug', `TICKER_PLANT connection error: ${err.message}`);
79
75
  });
76
+
77
+ await this.connection.connect(this.config);
78
+ this.emit('debug', `TICKER_PLANT WebSocket connected to ${this.config.uri}`);
80
79
 
81
- this.connection.once('loginFailed', (data) => {
82
- clearTimeout(timeout);
83
- reject(new Error(data.message || 'TICKER_PLANT login failed'));
80
+ // Setup message handler for market data
81
+ this.connection.on('message', (msg) => this._handleMessage(msg));
82
+
83
+ // Handle disconnection for auto-reconnect
84
+ this.connection.on('disconnected', ({ code, reason }) => {
85
+ this.connected = false;
86
+ this.emit('disconnected', { code, reason });
87
+ // Auto-reconnect if not manual close
88
+ if (code !== 1000 && this.credentials) {
89
+ this.emit('debug', 'TICKER_PLANT disconnected, will reconnect in 3s...');
90
+ setTimeout(() => this.connect(this.credentials, 1), 3000);
91
+ }
84
92
  });
85
93
 
86
- this.connection.login('TICKER_PLANT');
87
- });
88
- } catch (e) {
89
- this.emit('error', e);
90
- throw e;
94
+ // Login to TICKER_PLANT
95
+ return await new Promise((resolve, reject) => {
96
+ const timeout = setTimeout(() => {
97
+ reject(new Error('TICKER_PLANT login timeout'));
98
+ }, 15000);
99
+
100
+ this.connection.once('loggedIn', () => {
101
+ clearTimeout(timeout);
102
+ this.connected = true;
103
+ this.emit('connected');
104
+ resolve(true);
105
+ });
106
+
107
+ this.connection.once('loginFailed', (data) => {
108
+ clearTimeout(timeout);
109
+ reject(new Error(data.message || 'TICKER_PLANT login failed'));
110
+ });
111
+
112
+ this.connection.login('TICKER_PLANT');
113
+ });
114
+ } catch (e) {
115
+ lastError = e;
116
+ this.emit('debug', `TICKER_PLANT attempt ${attempt + 1} failed: ${e.message}`);
117
+ if (this.connection) {
118
+ try { await this.connection.disconnect(); } catch (_) {}
119
+ this.connection = null;
120
+ }
121
+ }
91
122
  }
123
+
124
+ // All retries failed
125
+ this.emit('error', lastError);
126
+ throw lastError;
92
127
  }
93
128
 
94
129
  /**
@@ -172,11 +172,13 @@ class RithmicConnection extends EventEmitter {
172
172
 
173
173
  if (res.rpCode?.[0] === '0') {
174
174
  this.state = 'LOGGED_IN';
175
- this.startHeartbeat(res.heartbeatInterval || 60);
175
+ // Use heartbeat interval from server, minimum 10s, default 30s
176
+ const hbInterval = Math.max(10, res.heartbeatInterval || 30);
177
+ this.startHeartbeat(hbInterval);
176
178
  this.emit('loggedIn', {
177
179
  fcmId: res.fcmId,
178
180
  ibId: res.ibId,
179
- heartbeatInterval: res.heartbeatInterval,
181
+ heartbeatInterval: hbInterval,
180
182
  });
181
183
  } else {
182
184
  const errorCode = res.rpCode?.[0] || 'UNKNOWN';
@@ -202,13 +204,23 @@ class RithmicConnection extends EventEmitter {
202
204
 
203
205
  startHeartbeat(intervalSec) {
204
206
  this.stopHeartbeat();
207
+ // Send heartbeat at half the interval to be safe (minimum every 5 seconds)
208
+ const hbMs = Math.max(5000, Math.floor(intervalSec * 1000 / 2));
205
209
  this.heartbeatTimer = setInterval(() => {
206
210
  try {
207
- this.send('RequestHeartbeat', { templateId: REQ.HEARTBEAT });
211
+ if (this.ws?.readyState === WebSocket.OPEN) {
212
+ this.send('RequestHeartbeat', { templateId: REQ.HEARTBEAT });
213
+ }
208
214
  } catch (e) {
209
- // Ignore
215
+ // Ignore heartbeat errors
216
+ }
217
+ }, hbMs);
218
+ // Send first heartbeat immediately
219
+ try {
220
+ if (this.ws?.readyState === WebSocket.OPEN) {
221
+ this.send('RequestHeartbeat', { templateId: REQ.HEARTBEAT });
210
222
  }
211
- }, (intervalSec - 5) * 1000);
223
+ } catch (e) {}
212
224
  }
213
225
 
214
226
  stopHeartbeat() {