hedgequantx 2.9.127 → 2.9.128

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/lib/data.js +37 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.127",
3
+ "version": "2.9.128",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/lib/data.js CHANGED
@@ -28,9 +28,11 @@ class MarketDataFeed extends EventEmitter {
28
28
 
29
29
  this.connection = null;
30
30
  this.connected = false;
31
+ this.connecting = false; // Prevent multiple simultaneous connects
31
32
  this.subscriptions = new Set();
32
33
  this.credentials = null;
33
34
  this.config = null;
35
+ this._reconnectTimer = null;
34
36
  }
35
37
 
36
38
  /**
@@ -39,9 +41,18 @@ class MarketDataFeed extends EventEmitter {
39
41
  * @param {number} retries - Number of connection retries (default: 2)
40
42
  */
41
43
  async connect(rithmicCredentials, retries = 2) {
42
- if (this.connected) return;
44
+ // Prevent multiple simultaneous connection attempts
45
+ if (this.connected || this.connecting) return;
46
+ this.connecting = true;
47
+
48
+ // Clear any pending reconnect
49
+ if (this._reconnectTimer) {
50
+ clearTimeout(this._reconnectTimer);
51
+ this._reconnectTimer = null;
52
+ }
43
53
 
44
54
  if (!rithmicCredentials || !rithmicCredentials.userId || !rithmicCredentials.password) {
55
+ this.connecting = false;
45
56
  throw new Error('Rithmic credentials required (userId, password, systemName, gateway)');
46
57
  }
47
58
 
@@ -67,32 +78,42 @@ class MarketDataFeed extends EventEmitter {
67
78
  await new Promise(r => setTimeout(r, 2000)); // Wait 2s before retry
68
79
  }
69
80
 
81
+ // Clean up previous connection if any
82
+ if (this.connection) {
83
+ this.connection.removeAllListeners();
84
+ try { await this.connection.disconnect(); } catch (_) {}
85
+ }
86
+
70
87
  this.connection = new RithmicConnection();
71
88
 
72
- // Connection timeout handler
89
+ // Connection error handler
73
90
  this.connection.on('error', (err) => {
74
- this.emit('debug', `TICKER_PLANT connection error: ${err.message}`);
91
+ this.emit('debug', `TICKER_PLANT error: ${err.message}`);
75
92
  });
76
93
 
77
94
  await this.connection.connect(this.config);
78
- this.emit('debug', `TICKER_PLANT WebSocket connected to ${this.config.uri}`);
79
95
 
80
96
  // Setup message handler for market data
81
97
  this.connection.on('message', (msg) => this._handleMessage(msg));
82
98
 
83
- // Handle disconnection for auto-reconnect
84
- this.connection.on('disconnected', ({ code, reason }) => {
99
+ // Handle disconnection for auto-reconnect (only once per connection)
100
+ this.connection.once('disconnected', ({ code, reason }) => {
85
101
  this.connected = false;
102
+ this.connecting = false;
103
+ this.emit('debug', `TICKER_PLANT closed: code=${code} reason=${reason || 'none'}`);
86
104
  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);
105
+ // Auto-reconnect if not manual close and not already reconnecting
106
+ // Skip reconnect if code 1000 (normal) or 1008 (policy violation - likely duplicate login)
107
+ if (code !== 1000 && code !== 1008 && this.credentials && !this._reconnectTimer) {
108
+ this._reconnectTimer = setTimeout(() => {
109
+ this._reconnectTimer = null;
110
+ this.connect(this.credentials, 1).catch(() => {});
111
+ }, 5000); // Wait 5s to avoid rapid reconnect loops
91
112
  }
92
113
  });
93
114
 
94
115
  // Login to TICKER_PLANT
95
- return await new Promise((resolve, reject) => {
116
+ const result = await new Promise((resolve, reject) => {
96
117
  const timeout = setTimeout(() => {
97
118
  reject(new Error('TICKER_PLANT login timeout'));
98
119
  }, 15000);
@@ -100,6 +121,7 @@ class MarketDataFeed extends EventEmitter {
100
121
  this.connection.once('loggedIn', () => {
101
122
  clearTimeout(timeout);
102
123
  this.connected = true;
124
+ this.connecting = false;
103
125
  this.emit('connected');
104
126
  resolve(true);
105
127
  });
@@ -111,10 +133,13 @@ class MarketDataFeed extends EventEmitter {
111
133
 
112
134
  this.connection.login('TICKER_PLANT');
113
135
  });
136
+
137
+ return result;
114
138
  } catch (e) {
115
139
  lastError = e;
116
140
  this.emit('debug', `TICKER_PLANT attempt ${attempt + 1} failed: ${e.message}`);
117
141
  if (this.connection) {
142
+ this.connection.removeAllListeners();
118
143
  try { await this.connection.disconnect(); } catch (_) {}
119
144
  this.connection = null;
120
145
  }
@@ -122,6 +147,7 @@ class MarketDataFeed extends EventEmitter {
122
147
  }
123
148
 
124
149
  // All retries failed
150
+ this.connecting = false;
125
151
  this.emit('error', lastError);
126
152
  throw lastError;
127
153
  }