hedgequantx 2.9.48 → 2.9.49
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/app.js +1 -1
- package/src/config/propfirms.js +1 -8
- package/src/lib/data.js +175 -256
- package/src/pages/algo/algo-executor.js +10 -8
- package/src/pages/algo/copy-executor.js +9 -7
- package/src/pages/algo/copy-trading.js +1 -1
- package/src/pages/algo/custom-strategy.js +1 -1
- package/src/pages/algo/one-account.js +1 -1
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -77,7 +77,7 @@ const refreshStats = async () => {
|
|
|
77
77
|
|
|
78
78
|
try {
|
|
79
79
|
const allAccounts = await connections.getAllAccounts();
|
|
80
|
-
// Filter active accounts: status ===
|
|
80
|
+
// Filter active accounts: status === 'active' (Rithmic) OR status === 0 OR no status
|
|
81
81
|
const activeAccounts = allAccounts.filter(acc =>
|
|
82
82
|
acc.status === 0 || acc.status === 'active' || acc.status === undefined || acc.status === null
|
|
83
83
|
);
|
package/src/config/propfirms.js
CHANGED
|
@@ -15,14 +15,7 @@ const PROPFIRMS = {
|
|
|
15
15
|
rithmicSystem: 'Apex',
|
|
16
16
|
wsEndpoint: 'wss://ritpa11120.11.rithmic.com:443',
|
|
17
17
|
},
|
|
18
|
-
|
|
19
|
-
id: 'topsteptrader',
|
|
20
|
-
name: 'TopstepTrader',
|
|
21
|
-
displayName: 'TopstepTrader',
|
|
22
|
-
platform: 'Rithmic',
|
|
23
|
-
rithmicSystem: 'TopstepTrader',
|
|
24
|
-
wsEndpoint: 'wss://ritpa11120.11.rithmic.com:443'
|
|
25
|
-
},
|
|
18
|
+
|
|
26
19
|
mes_capital: {
|
|
27
20
|
id: 'mes-capital',
|
|
28
21
|
name: 'MES Capital',
|
package/src/lib/data.js
CHANGED
|
@@ -1,328 +1,247 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* =============================================================================
|
|
3
|
-
* MARKET DATA FEED -
|
|
3
|
+
* MARKET DATA FEED - Rithmic TICKER_PLANT Real-Time Data
|
|
4
4
|
* =============================================================================
|
|
5
|
-
* Connects to
|
|
5
|
+
* Connects to Rithmic TICKER_PLANT for real-time market data
|
|
6
6
|
*
|
|
7
7
|
* Events emitted:
|
|
8
8
|
* - tick: Quote/trade updates (price, bid, ask, volume)
|
|
9
|
-
* - quote: Quote updates only
|
|
10
|
-
* - trade: Trade executions only
|
|
11
|
-
* - depth: DOM/Level 2 updates
|
|
12
9
|
* - connected: Connection established
|
|
13
10
|
* - disconnected: Connection lost
|
|
14
11
|
* - error: Connection error
|
|
15
|
-
*
|
|
16
|
-
* SOURCE: Based on ProjectX Gateway RTC API
|
|
17
12
|
*/
|
|
18
13
|
|
|
19
14
|
'use strict';
|
|
20
15
|
|
|
21
16
|
const EventEmitter = require('events');
|
|
22
|
-
const {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// RTC URL pattern: api.xxx.com -> rtc.xxx.com
|
|
26
|
-
const PROPFIRMS = {
|
|
27
|
-
topstep: { gatewayApi: 'api.topstepx.com' },
|
|
28
|
-
alpha_futures: { gatewayApi: 'api.alphafutures.projectx.com' },
|
|
29
|
-
tickticktrader: { gatewayApi: 'api.tickticktrader.projectx.com' },
|
|
30
|
-
bulenox: { gatewayApi: 'api.bulenox.projectx.com' },
|
|
31
|
-
tradeday: { gatewayApi: 'api.tradeday.projectx.com' },
|
|
32
|
-
blusky: { gatewayApi: 'api.blusky.projectx.com' },
|
|
33
|
-
goat_futures: { gatewayApi: 'api.goatfutures.projectx.com' },
|
|
34
|
-
futures_desk: { gatewayApi: 'api.thefuturesdesk.projectx.com' },
|
|
35
|
-
daytraders: { gatewayApi: 'api.daytraders.projectx.com' },
|
|
36
|
-
e8_futures: { gatewayApi: 'api.e8futures.projectx.com' },
|
|
37
|
-
blue_guardian: { gatewayApi: 'api.blueguardianfutures.projectx.com' },
|
|
38
|
-
futures_elite: { gatewayApi: 'api.futureselite.projectx.com' },
|
|
39
|
-
fxify: { gatewayApi: 'api.fxify.projectx.com' },
|
|
40
|
-
hola_prime: { gatewayApi: 'api.holaprime.projectx.com' },
|
|
41
|
-
top_one_futures: { gatewayApi: 'api.toponefutures.projectx.com' },
|
|
42
|
-
funding_futures: { gatewayApi: 'api.fundingfutures.projectx.com' },
|
|
43
|
-
tx3_funding: { gatewayApi: 'api.tx3funding.projectx.com' },
|
|
44
|
-
lucid_trading: { gatewayApi: 'api.lucidtrading.projectx.com' },
|
|
45
|
-
tradeify: { gatewayApi: 'api.tradeify.projectx.com' }
|
|
46
|
-
};
|
|
17
|
+
const { RithmicConnection } = require('../services/rithmic/connection');
|
|
18
|
+
const { proto } = require('../services/rithmic/protobuf');
|
|
19
|
+
const { REQ, RES, STREAM, RITHMIC_ENDPOINTS, RITHMIC_SYSTEMS } = require('../services/rithmic/constants');
|
|
47
20
|
|
|
48
21
|
// =============================================================================
|
|
49
|
-
// MARKET DATA FEED CLASS
|
|
22
|
+
// MARKET DATA FEED CLASS (Rithmic TICKER_PLANT)
|
|
50
23
|
// =============================================================================
|
|
51
24
|
|
|
52
25
|
class MarketDataFeed extends EventEmitter {
|
|
53
26
|
constructor(options = {}) {
|
|
54
27
|
super();
|
|
55
28
|
|
|
56
|
-
this.propfirmKey = (options.propfirm || 'topstep').toLowerCase().replace(/\s+/g, '_');
|
|
57
29
|
this.connection = null;
|
|
58
30
|
this.connected = false;
|
|
59
31
|
this.subscriptions = new Set();
|
|
60
|
-
this.
|
|
61
|
-
this.
|
|
32
|
+
this.credentials = null;
|
|
33
|
+
this.config = null;
|
|
62
34
|
}
|
|
63
35
|
|
|
64
36
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
37
|
+
* Connect to Rithmic TICKER_PLANT
|
|
38
|
+
* @param {Object} rithmicCredentials - Credentials from RithmicService.getRithmicCredentials()
|
|
67
39
|
*/
|
|
68
|
-
|
|
69
|
-
const propfirm = PROPFIRMS[propfirmKey] || PROPFIRMS.topstep;
|
|
70
|
-
|
|
71
|
-
if (propfirm.gatewayApi) {
|
|
72
|
-
// Convert api.xxx.com to rtc.xxx.com
|
|
73
|
-
const rtcHost = propfirm.gatewayApi.replace(/^api\./, 'rtc.');
|
|
74
|
-
return `https://${rtcHost}/hubs/market`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Fallback for topstep
|
|
78
|
-
return 'https://rtc.topstepx.com/hubs/market';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Connect to market data hub
|
|
83
|
-
*/
|
|
84
|
-
async connect(token, propfirmKey, contractId = null) {
|
|
40
|
+
async connect(rithmicCredentials) {
|
|
85
41
|
if (this.connected) return;
|
|
86
42
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const hubUrl = this._getMarketHubUrl(this.propfirmKey);
|
|
90
|
-
|
|
91
|
-
// CRITICAL: Token must be in URL query string (same as HQX-TG and Python SDK)
|
|
92
|
-
const urlWithToken = `${hubUrl}?access_token=${encodeURIComponent(this.token)}`;
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
// CRITICAL: skipNegotiation=true + WebSockets transport is REQUIRED for ProjectX/TopstepX
|
|
96
|
-
// This is how the Python SDK and HQX-TG connect successfully
|
|
97
|
-
this.connection = new HubConnectionBuilder()
|
|
98
|
-
.withUrl(urlWithToken, {
|
|
99
|
-
skipNegotiation: true,
|
|
100
|
-
transport: HttpTransportType.WebSockets
|
|
101
|
-
})
|
|
102
|
-
.withAutomaticReconnect({
|
|
103
|
-
nextRetryDelayInMilliseconds: (ctx) => {
|
|
104
|
-
if (ctx.previousRetryCount >= this.maxReconnectAttempts) return null;
|
|
105
|
-
return Math.min(1000 * Math.pow(2, ctx.previousRetryCount), 30000);
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
.configureLogging(LogLevel.Warning)
|
|
109
|
-
.build();
|
|
110
|
-
|
|
111
|
-
// Set server timeout and keepalive
|
|
112
|
-
this.connection.serverTimeoutInMilliseconds = 60000; // 60s
|
|
113
|
-
this.connection.keepAliveIntervalInMilliseconds = 15000; // 15s
|
|
114
|
-
|
|
115
|
-
this._setupEventHandlers();
|
|
116
|
-
await this.connection.start();
|
|
117
|
-
|
|
118
|
-
this.connected = true;
|
|
119
|
-
this.reconnectAttempts = 0;
|
|
120
|
-
this.emit('connected');
|
|
121
|
-
|
|
122
|
-
if (contractId) {
|
|
123
|
-
await this.subscribe(null, contractId);
|
|
124
|
-
}
|
|
125
|
-
} catch (error) {
|
|
126
|
-
this.connected = false;
|
|
127
|
-
this.emit('error', error);
|
|
128
|
-
throw error;
|
|
43
|
+
if (!rithmicCredentials || !rithmicCredentials.userId || !rithmicCredentials.password) {
|
|
44
|
+
throw new Error('Rithmic credentials required (userId, password, systemName, gateway)');
|
|
129
45
|
}
|
|
130
|
-
}
|
|
131
46
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
*/
|
|
135
|
-
_setupEventHandlers() {
|
|
136
|
-
if (!this.connection) return;
|
|
137
|
-
|
|
138
|
-
// Quote updates - GatewayQuote receives (contractId, data)
|
|
139
|
-
this.connection.on('GatewayQuote', (contractId, data) => {
|
|
140
|
-
// Handle both (contractId, data) and single object formats
|
|
141
|
-
const quote = data || contractId;
|
|
142
|
-
const cid = data ? contractId : (quote.symbol || quote.symbolId);
|
|
143
|
-
|
|
144
|
-
const tick = {
|
|
145
|
-
type: 'quote',
|
|
146
|
-
contractId: cid,
|
|
147
|
-
symbol: quote.symbolName || quote.symbol || cid,
|
|
148
|
-
price: quote.lastPrice,
|
|
149
|
-
bid: quote.bestBid,
|
|
150
|
-
ask: quote.bestAsk,
|
|
151
|
-
change: quote.change,
|
|
152
|
-
changePercent: quote.changePercent,
|
|
153
|
-
open: quote.open,
|
|
154
|
-
high: quote.high,
|
|
155
|
-
low: quote.low,
|
|
156
|
-
volume: quote.volume,
|
|
157
|
-
timestamp: quote.timestamp ? new Date(quote.timestamp).getTime() : Date.now()
|
|
158
|
-
};
|
|
159
|
-
this.emit('tick', tick);
|
|
160
|
-
this.emit('quote', tick);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// Trade executions - GatewayTrade receives (contractId, data[])
|
|
164
|
-
this.connection.on('GatewayTrade', (contractId, trades) => {
|
|
165
|
-
// Handle both formats
|
|
166
|
-
const tradeList = Array.isArray(trades) ? trades : (Array.isArray(contractId) ? contractId : [trades || contractId]);
|
|
167
|
-
const cid = typeof contractId === 'string' ? contractId : null;
|
|
168
|
-
|
|
169
|
-
for (const trade of tradeList) {
|
|
170
|
-
if (!trade) continue;
|
|
171
|
-
const tick = {
|
|
172
|
-
type: 'trade',
|
|
173
|
-
contractId: cid || trade.symbolId,
|
|
174
|
-
price: trade.price,
|
|
175
|
-
volume: trade.volume,
|
|
176
|
-
side: trade.type === 0 ? 'buy' : 'sell',
|
|
177
|
-
lastTradeSide: trade.type === 0 ? 'buy' : 'sell',
|
|
178
|
-
timestamp: trade.timestamp ? new Date(trade.timestamp).getTime() : Date.now()
|
|
179
|
-
};
|
|
180
|
-
this.emit('tick', tick);
|
|
181
|
-
this.emit('trade', tick);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
47
|
+
this.credentials = rithmicCredentials;
|
|
48
|
+
this.connection = new RithmicConnection();
|
|
184
49
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
this.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (error) {
|
|
225
|
-
errMsg = error.message || error.toString();
|
|
226
|
-
if (error.stack) errMsg += ' | ' + error.stack.split('\n')[0];
|
|
227
|
-
}
|
|
228
|
-
this.emit('disconnected', new Error(errMsg));
|
|
229
|
-
});
|
|
50
|
+
this.config = {
|
|
51
|
+
uri: rithmicCredentials.gateway || RITHMIC_ENDPOINTS.CHICAGO,
|
|
52
|
+
systemName: rithmicCredentials.systemName || RITHMIC_SYSTEMS.PAPER,
|
|
53
|
+
userId: rithmicCredentials.userId,
|
|
54
|
+
password: rithmicCredentials.password,
|
|
55
|
+
appName: 'HQX-CLI',
|
|
56
|
+
appVersion: '2.0.0',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await this.connection.connect(this.config);
|
|
61
|
+
|
|
62
|
+
// Setup message handler for market data
|
|
63
|
+
this.connection.on('message', (msg) => this._handleMessage(msg));
|
|
64
|
+
|
|
65
|
+
// Login to TICKER_PLANT
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const timeout = setTimeout(() => {
|
|
68
|
+
reject(new Error('TICKER_PLANT login timeout'));
|
|
69
|
+
}, 15000);
|
|
70
|
+
|
|
71
|
+
this.connection.once('loggedIn', () => {
|
|
72
|
+
clearTimeout(timeout);
|
|
73
|
+
this.connected = true;
|
|
74
|
+
this.emit('connected');
|
|
75
|
+
resolve(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.connection.once('loginFailed', (data) => {
|
|
79
|
+
clearTimeout(timeout);
|
|
80
|
+
reject(new Error(data.message || 'TICKER_PLANT login failed'));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.connection.login('TICKER_PLANT');
|
|
84
|
+
});
|
|
85
|
+
} catch (e) {
|
|
86
|
+
this.emit('error', e);
|
|
87
|
+
throw e;
|
|
88
|
+
}
|
|
230
89
|
}
|
|
231
90
|
|
|
232
91
|
/**
|
|
233
|
-
* Subscribe to
|
|
92
|
+
* Subscribe to market data for a symbol
|
|
93
|
+
* @param {string} symbol - Symbol name (e.g., 'ESH6')
|
|
94
|
+
* @param {string} exchange - Exchange (default: 'CME')
|
|
234
95
|
*/
|
|
235
|
-
async subscribe(symbol,
|
|
236
|
-
if (!this.
|
|
237
|
-
throw new Error('Not connected');
|
|
96
|
+
async subscribe(symbol, exchange = 'CME') {
|
|
97
|
+
if (!this.connected) {
|
|
98
|
+
throw new Error('Not connected to TICKER_PLANT');
|
|
238
99
|
}
|
|
239
100
|
|
|
240
|
-
const
|
|
241
|
-
|
|
101
|
+
const key = `${symbol}:${exchange}`;
|
|
102
|
+
if (this.subscriptions.has(key)) return;
|
|
103
|
+
|
|
242
104
|
try {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
105
|
+
// Subscribe to LAST_TRADE and BBO (Best Bid/Offer)
|
|
106
|
+
// UpdateBits: 1 = LAST_TRADE, 2 = BBO, 3 = BOTH
|
|
107
|
+
this.connection.send('RequestMarketDataUpdate', {
|
|
108
|
+
templateId: REQ.MARKET_DATA,
|
|
109
|
+
symbol: symbol,
|
|
110
|
+
exchange: exchange,
|
|
111
|
+
request: 1, // SUBSCRIBE
|
|
112
|
+
updateBits: 3, // LAST_TRADE + BBO
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
this.subscriptions.add(key);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
this.emit('error', e);
|
|
118
|
+
throw e;
|
|
251
119
|
}
|
|
252
120
|
}
|
|
253
121
|
|
|
254
122
|
/**
|
|
255
|
-
* Unsubscribe from
|
|
123
|
+
* Unsubscribe from market data for a symbol
|
|
256
124
|
*/
|
|
257
|
-
async unsubscribe(
|
|
258
|
-
if (!this.
|
|
125
|
+
async unsubscribe(symbol, exchange = 'CME') {
|
|
126
|
+
if (!this.connected) return;
|
|
127
|
+
|
|
128
|
+
const key = `${symbol}:${exchange}`;
|
|
129
|
+
if (!this.subscriptions.has(key)) return;
|
|
259
130
|
|
|
260
131
|
try {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
132
|
+
this.connection.send('RequestMarketDataUpdate', {
|
|
133
|
+
templateId: REQ.MARKET_DATA,
|
|
134
|
+
symbol: symbol,
|
|
135
|
+
exchange: exchange,
|
|
136
|
+
request: 2, // UNSUBSCRIBE
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
this.subscriptions.delete(key);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
// Ignore unsubscribe errors
|
|
268
142
|
}
|
|
269
143
|
}
|
|
270
144
|
|
|
271
145
|
/**
|
|
272
|
-
*
|
|
146
|
+
* Disconnect from TICKER_PLANT
|
|
273
147
|
*/
|
|
274
|
-
async
|
|
275
|
-
|
|
148
|
+
async disconnect() {
|
|
149
|
+
// Unsubscribe from all
|
|
150
|
+
for (const key of this.subscriptions) {
|
|
151
|
+
const [symbol, exchange] = key.split(':');
|
|
152
|
+
await this.unsubscribe(symbol, exchange);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (this.connection) {
|
|
276
156
|
try {
|
|
277
|
-
await this.connection.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
} catch (error) {
|
|
281
|
-
// Continue
|
|
157
|
+
await this.connection.disconnect();
|
|
158
|
+
} catch (e) {
|
|
159
|
+
// Ignore
|
|
282
160
|
}
|
|
283
161
|
}
|
|
162
|
+
|
|
163
|
+
this.connection = null;
|
|
164
|
+
this.connected = false;
|
|
165
|
+
this.subscriptions.clear();
|
|
166
|
+
this.emit('disconnected');
|
|
284
167
|
}
|
|
285
168
|
|
|
286
169
|
/**
|
|
287
|
-
*
|
|
170
|
+
* Handle incoming messages from TICKER_PLANT
|
|
288
171
|
*/
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
this.connection = null;
|
|
309
|
-
this.connected = false;
|
|
310
|
-
this.subscriptions.clear();
|
|
172
|
+
_handleMessage(msg) {
|
|
173
|
+
const { templateId, data } = msg;
|
|
174
|
+
|
|
175
|
+
switch (templateId) {
|
|
176
|
+
case RES.MARKET_DATA:
|
|
177
|
+
// Subscription confirmed
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
case STREAM.LAST_TRADE:
|
|
181
|
+
this._handleLastTrade(data);
|
|
182
|
+
break;
|
|
183
|
+
|
|
184
|
+
case STREAM.BBO:
|
|
185
|
+
this._handleBBO(data);
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
default:
|
|
189
|
+
// Ignore other messages
|
|
190
|
+
break;
|
|
311
191
|
}
|
|
312
192
|
}
|
|
313
193
|
|
|
314
194
|
/**
|
|
315
|
-
*
|
|
195
|
+
* Handle LastTrade message
|
|
316
196
|
*/
|
|
317
|
-
|
|
318
|
-
|
|
197
|
+
_handleLastTrade(data) {
|
|
198
|
+
try {
|
|
199
|
+
const trade = proto.decode('LastTrade', data);
|
|
200
|
+
|
|
201
|
+
const tick = {
|
|
202
|
+
type: 'trade',
|
|
203
|
+
symbol: trade.symbol,
|
|
204
|
+
exchange: trade.exchange,
|
|
205
|
+
price: trade.tradePrice,
|
|
206
|
+
size: trade.tradeSize,
|
|
207
|
+
volume: trade.volume,
|
|
208
|
+
side: trade.aggressor === 1 ? 'buy' : trade.aggressor === 2 ? 'sell' : 'unknown',
|
|
209
|
+
timestamp: Date.now(),
|
|
210
|
+
ssboe: trade.ssboe,
|
|
211
|
+
usecs: trade.usecs,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
this.emit('tick', tick);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
// Ignore decode errors
|
|
217
|
+
}
|
|
319
218
|
}
|
|
320
219
|
|
|
321
220
|
/**
|
|
322
|
-
*
|
|
221
|
+
* Handle BestBidOffer message
|
|
323
222
|
*/
|
|
324
|
-
|
|
325
|
-
|
|
223
|
+
_handleBBO(data) {
|
|
224
|
+
try {
|
|
225
|
+
const bbo = proto.decode('BestBidOffer', data);
|
|
226
|
+
|
|
227
|
+
const tick = {
|
|
228
|
+
type: 'quote',
|
|
229
|
+
symbol: bbo.symbol,
|
|
230
|
+
exchange: bbo.exchange,
|
|
231
|
+
bid: bbo.bidPrice,
|
|
232
|
+
bidSize: bbo.bidSize,
|
|
233
|
+
ask: bbo.askPrice,
|
|
234
|
+
askSize: bbo.askSize,
|
|
235
|
+
price: bbo.bidPrice && bbo.askPrice ? (bbo.bidPrice + bbo.askPrice) / 2 : null,
|
|
236
|
+
timestamp: Date.now(),
|
|
237
|
+
ssboe: bbo.ssboe,
|
|
238
|
+
usecs: bbo.usecs,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
this.emit('tick', tick);
|
|
242
|
+
} catch (e) {
|
|
243
|
+
// Ignore decode errors
|
|
244
|
+
}
|
|
326
245
|
}
|
|
327
246
|
}
|
|
328
247
|
|
|
@@ -13,7 +13,7 @@ const { SupervisionEngine } = require('../../services/ai-supervision');
|
|
|
13
13
|
/**
|
|
14
14
|
* Execute algo strategy with market data
|
|
15
15
|
* @param {Object} params - Execution parameters
|
|
16
|
-
* @param {Object} params.service -
|
|
16
|
+
* @param {Object} params.service - Rithmic trading service
|
|
17
17
|
* @param {Object} params.account - Account object
|
|
18
18
|
* @param {Object} params.contract - Contract object
|
|
19
19
|
* @param {Object} params.config - Algo config (contracts, target, risk, showName)
|
|
@@ -77,8 +77,8 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
77
77
|
const strategy = new StrategyClass({ tickSize });
|
|
78
78
|
strategy.initialize(contractId, tickSize);
|
|
79
79
|
|
|
80
|
-
// Initialize Market Data Feed
|
|
81
|
-
const marketFeed = new MarketDataFeed(
|
|
80
|
+
// Initialize Market Data Feed (Rithmic TICKER_PLANT)
|
|
81
|
+
const marketFeed = new MarketDataFeed();
|
|
82
82
|
|
|
83
83
|
// Log startup
|
|
84
84
|
ui.addLog('info', `Strategy: ${strategyName}${supervisionEnabled ? ' + AI' : ''}`);
|
|
@@ -207,12 +207,14 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
207
207
|
marketFeed.on('error', (err) => ui.addLog('error', `Market: ${err.message}`));
|
|
208
208
|
marketFeed.on('disconnected', () => { stats.connected = false; ui.addLog('error', 'Market disconnected'); });
|
|
209
209
|
|
|
210
|
-
// Connect to market data
|
|
210
|
+
// Connect to market data (Rithmic TICKER_PLANT)
|
|
211
211
|
try {
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
const rithmicCredentials = service.getRithmicCredentials?.();
|
|
213
|
+
if (!rithmicCredentials) {
|
|
214
|
+
throw new Error('Rithmic credentials not available');
|
|
215
|
+
}
|
|
216
|
+
await marketFeed.connect(rithmicCredentials);
|
|
217
|
+
await marketFeed.subscribe(symbolName, contract.exchange || 'CME');
|
|
216
218
|
} catch (e) {
|
|
217
219
|
ui.addLog('error', `Failed to connect: ${e.message}`);
|
|
218
220
|
}
|
|
@@ -77,8 +77,8 @@ const launchCopyTrading = async (config) => {
|
|
|
77
77
|
const strategy = new StrategyClass({ tickSize });
|
|
78
78
|
strategy.initialize(contractId, tickSize);
|
|
79
79
|
|
|
80
|
-
// Initialize Market Data Feed
|
|
81
|
-
const marketFeed = new MarketDataFeed(
|
|
80
|
+
// Initialize Market Data Feed (Rithmic TICKER_PLANT)
|
|
81
|
+
const marketFeed = new MarketDataFeed();
|
|
82
82
|
|
|
83
83
|
// Log startup
|
|
84
84
|
ui.addLog('info', `Lead: ${leadName} | Followers: ${followers.length}`);
|
|
@@ -186,12 +186,14 @@ const launchCopyTrading = async (config) => {
|
|
|
186
186
|
marketFeed.on('error', (err) => ui.addLog('error', `Market: ${err.message}`));
|
|
187
187
|
marketFeed.on('disconnected', () => { stats.connected = false; ui.addLog('error', 'Disconnected'); });
|
|
188
188
|
|
|
189
|
-
// Connect to market data
|
|
189
|
+
// Connect to market data (Rithmic TICKER_PLANT)
|
|
190
190
|
try {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
const rithmicCredentials = leadService.getRithmicCredentials?.();
|
|
192
|
+
if (!rithmicCredentials) {
|
|
193
|
+
throw new Error('Rithmic credentials not available');
|
|
194
|
+
}
|
|
195
|
+
await marketFeed.connect(rithmicCredentials);
|
|
196
|
+
await marketFeed.subscribe(symbolName, contract.exchange || 'CME');
|
|
195
197
|
} catch (e) {
|
|
196
198
|
ui.addLog('error', `Connect failed: ${e.message}`);
|
|
197
199
|
}
|
|
@@ -40,7 +40,7 @@ const copyTradingMenu = async () => {
|
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// Filter active accounts: status ===
|
|
43
|
+
// Filter active accounts: status === 'active' (Rithmic) OR status === 0 OR no status
|
|
44
44
|
const activeAccounts = allAccounts.filter(acc =>
|
|
45
45
|
acc.status === 0 || acc.status === 'active' || acc.status === undefined || acc.status === null
|
|
46
46
|
);
|
|
@@ -46,7 +46,7 @@ const customStrategyMenu = async (service) => {
|
|
|
46
46
|
|
|
47
47
|
if (!allAccounts?.length) { spinner.fail('No accounts found'); await prompts.waitForEnter(); return; }
|
|
48
48
|
|
|
49
|
-
// Filter active accounts: status ===
|
|
49
|
+
// Filter active accounts: status === 'active' (Rithmic) OR status === 0 OR no status
|
|
50
50
|
const activeAccounts = allAccounts.filter(acc =>
|
|
51
51
|
acc.status === 0 || acc.status === 'active' || acc.status === undefined || acc.status === null
|
|
52
52
|
);
|
|
@@ -41,7 +41,7 @@ const oneAccountMenu = async (service) => {
|
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// Filter active accounts: status ===
|
|
44
|
+
// Filter active accounts: status === 'active' (Rithmic) OR status === 0 OR no status
|
|
45
45
|
const activeAccounts = allAccounts.filter(acc =>
|
|
46
46
|
acc.status === 0 || acc.status === 'active' || acc.status === undefined || acc.status === null
|
|
47
47
|
);
|