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.
- package/package.json +1 -1
- package/src/lib/data.js +37 -11
package/package.json
CHANGED
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
|
-
|
|
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
|
|
89
|
+
// Connection error handler
|
|
73
90
|
this.connection.on('error', (err) => {
|
|
74
|
-
this.emit('debug', `TICKER_PLANT
|
|
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.
|
|
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
|
|
89
|
-
|
|
90
|
-
setTimeout(() =>
|
|
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
|
-
|
|
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
|
}
|