hedgequantx 2.7.15 → 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.
package/src/lib/core2.js DELETED
@@ -1,341 +0,0 @@
1
- /**
2
- * Copy Trading Engine - Lightweight
3
- * Trades on Lead account, mirrors to Follower
4
- */
5
-
6
- const EventEmitter = require('events');
7
- const { A1 } = require('./api');
8
- const { D1 } = require('./data');
9
- const { M2 } = require('./m/s2');
10
-
11
- // Core module
12
- const {
13
- RithmicAdapter: N1,
14
- isRithmicPropFirm: isN,
15
- getPropFirmConfig: getPC,
16
- buildRithmicSymbol: buildS,
17
- getCurrentFrontMonth: getFM
18
- } = require('./n/r1');
19
-
20
- class C2 extends EventEmitter {
21
- constructor(sessionId, config, callbacks) {
22
- super();
23
- this.sessionId = sessionId;
24
- this.config = config;
25
- this.callbacks = callbacks;
26
- this.running = false;
27
-
28
- // APIs
29
- this.leadApi = null;
30
- this.followerApi = null;
31
- this.marketData = null;
32
- this.strategy = null;
33
-
34
- // Rithmic adapters (new unified module)
35
- this.leadN1 = null;
36
- this.followerN1 = null;
37
-
38
- // State
39
- this.position = null;
40
- this.lastPrice = 0;
41
- this.stats = { trades: 0, wins: 0, losses: 0, pnl: 0 };
42
-
43
- // P&L from API - NO local calculations
44
- this.apiPnL = { lead: 0, follower: 0, lastClosedPnL: 0 };
45
- }
46
-
47
- async start() {
48
- this.running = true;
49
- this._log('info', '=== COPY TRADING ENGINE ===');
50
-
51
- // Init Lead API
52
- if (isN(this.config.leadPropfirm)) {
53
- const creds = this.config.leadRithmicCredentials;
54
- const propfirmConfig = getPC(this.config.leadPropfirm);
55
-
56
- this.leadN1 = new N1({ debug: false, usePool: true });
57
- await this.leadN1.connect({
58
- userId: creds.userId,
59
- password: creds.password,
60
- propfirm: this.config.leadPropfirm
61
- }, { marketData: true, trading: true, pnl: true });
62
-
63
- // Listen for P&L updates from API
64
- this.leadN1.on('accountPnL', (p) => {
65
- this.apiPnL.lead = p.closedPnL || p.totalPnL || 0;
66
- });
67
-
68
- this.leadApi = this._createRithmicTradingWrapper(this.leadN1, this.config.leadSymbol);
69
- this._log('info', `Lead: Rithmic connected (${propfirmConfig?.displayName || this.config.leadPropfirm})`);
70
- } else {
71
- this.leadApi = new A1(this.config.leadToken, this.config.leadPropfirm);
72
- this._log('info', 'Lead: ProjectX ready');
73
- }
74
-
75
- // Init Follower API
76
- if (isN(this.config.followerPropfirm)) {
77
- const creds = this.config.followerRithmicCredentials;
78
- const propfirmConfig = getPC(this.config.followerPropfirm);
79
-
80
- this.followerN1 = new N1({ debug: false, usePool: true });
81
- await this.followerN1.connect({
82
- userId: creds.userId,
83
- password: creds.password,
84
- propfirm: this.config.followerPropfirm
85
- }, { marketData: false, trading: true, pnl: false });
86
-
87
- this.followerApi = this._createRithmicTradingWrapper(this.followerN1, this.config.followerSymbol || this.config.leadSymbol);
88
- this._log('info', `Follower: Rithmic connected (${propfirmConfig?.displayName || this.config.followerPropfirm})`);
89
- } else {
90
- this.followerApi = new A1(this.config.followerToken, this.config.followerPropfirm);
91
- this._log('info', 'Follower: ProjectX ready');
92
- }
93
-
94
- // Init market data from Lead
95
- await this._initMarketData();
96
-
97
- // Init strategy
98
- this.strategy = new M2({
99
- dailyTarget: this.config.dailyTarget,
100
- maxRisk: this.config.maxRisk,
101
- contracts: this.config.leadContracts || 1
102
- });
103
-
104
- this.strategy.on('signal', (s) => this._onSignal(s));
105
- this.strategy.on('log', (l) => this._log(l.type, l.message));
106
-
107
- this._log('info', 'Monitoring market...');
108
- }
109
-
110
- async _initMarketData() {
111
- if (isN(this.config.leadPropfirm)) {
112
- // Use the lead Rithmic adapter for market data (already connected with marketData: true)
113
- const sym = this._rithmicSymbol(this.config.leadSymbol);
114
- await this.leadN1.subscribe(sym, 'CME');
115
- this._log('info', `Subscribed: ${sym}`);
116
-
117
- // Create a market data wrapper for event forwarding
118
- const EventEmitter = require('events');
119
- this.marketData = new EventEmitter();
120
-
121
- this.leadN1.on('tick', (d) => {
122
- this.marketData.emit('tick', d);
123
- });
124
- this.leadN1.on('trade', (d) => {
125
- this.marketData.emit('trade', d);
126
- });
127
- this.leadN1.on('quote', (d) => {
128
- this.marketData.emit('tick', {
129
- price: d.price || (d.bid + d.ask) / 2,
130
- bid: d.bid,
131
- ask: d.ask,
132
- spread: d.spread,
133
- timestamp: d.timestamp
134
- });
135
- });
136
-
137
- this.marketData.disconnect = async () => {
138
- // Disconnection handled by adapter cleanup
139
- };
140
- } else {
141
- this.marketData = new D1({
142
- symbol: this.config.leadSymbol,
143
- contractId: this.config.leadContractId
144
- });
145
- await this.marketData.connect(
146
- this.config.leadToken,
147
- this.config.leadPropfirm,
148
- this.config.leadContractId
149
- );
150
- this._log('info', `Subscribed: ${this.config.leadSymbol}`);
151
- }
152
-
153
- this.marketData.on('tick', (d) => {
154
- this.lastPrice = d.price;
155
- if (this.strategy) this.strategy.onTick(d);
156
- });
157
-
158
- this.marketData.on('trade', (d) => {
159
- if (this.strategy) this.strategy.onTrade(d);
160
- });
161
- }
162
-
163
- async _onSignal(signal) {
164
- if (!this.running) return;
165
-
166
- const { side, action, reason } = signal;
167
-
168
- if (action === 'open') {
169
- this._log('signal', `${side.toUpperCase()} @ ${this.lastPrice.toFixed(2)}`);
170
- await this._openBoth(side);
171
- } else if (action === 'close') {
172
- this._log('signal', `CLOSE - ${reason}`);
173
- await this._closeBoth(reason);
174
- }
175
- }
176
-
177
- async _openBoth(side) {
178
- const lQty = this.config.leadContracts || 1;
179
- const fQty = this.config.followerContracts || lQty;
180
-
181
- // Open Lead
182
- const leadRes = await this._order(this.leadApi, this.config.leadAccountId, this.config.leadContractId, side, lQty);
183
- if (!leadRes.success) {
184
- this._log('error', `Lead order failed: ${leadRes.error}`);
185
- return;
186
- }
187
- this._log('trade', `Lead: ${side.toUpperCase()} ${lQty}`);
188
-
189
- // Copy to Follower
190
- const followerRes = await this._order(this.followerApi, this.config.followerAccountId, this.config.followerContractId, side, fQty);
191
- if (followerRes.success) {
192
- this._log('trade', `Follower: ${side.toUpperCase()} ${fQty}`);
193
- this.callbacks.onCopy?.({ side, quantity: fQty });
194
- } else {
195
- this._log('error', `Follower failed - closing Lead`);
196
- await this._close(this.leadApi, this.config.leadAccountId, this.config.leadContractId);
197
- return;
198
- }
199
-
200
- this.position = { side, leadQty: lQty, followerQty: fQty, entry: this.lastPrice };
201
- this.stats.trades++;
202
- }
203
-
204
- async _closeBoth(reason) {
205
- if (!this.position) return;
206
-
207
- // Close both in parallel
208
- await Promise.all([
209
- this._close(this.leadApi, this.config.leadAccountId, this.config.leadContractId),
210
- this._close(this.followerApi, this.config.followerAccountId, this.config.followerContractId)
211
- ]);
212
-
213
- // P&L comes from API - NO local calculation
214
- // Use delta from last closed P&L
215
- const currentClosedPnL = this.apiPnL.lead;
216
- const pnl = currentClosedPnL - this.apiPnL.lastClosedPnL;
217
- this.apiPnL.lastClosedPnL = currentClosedPnL;
218
- this.stats.pnl = currentClosedPnL; // Total P&L from API
219
- pnl >= 0 ? this.stats.wins++ : this.stats.losses++;
220
-
221
- this._log(pnl >= 0 ? 'trade' : 'loss', `Closed: ${pnl >= 0 ? '+' : ''}$${pnl.toFixed(2)}`);
222
-
223
- this.callbacks.onTrade?.({ pnl, reason });
224
- this.position = null;
225
-
226
- // Check targets
227
- if (this.stats.pnl >= this.config.dailyTarget) {
228
- this._log('success', `TARGET! +$${this.stats.pnl.toFixed(2)}`);
229
- await this.stop('target');
230
- } else if (this.stats.pnl <= -this.config.maxRisk) {
231
- this._log('error', `MAX RISK! -$${Math.abs(this.stats.pnl).toFixed(2)}`);
232
- await this.stop('risk');
233
- }
234
- }
235
-
236
- async _order(api, accountId, contractId, side, qty) {
237
- try {
238
- return await api.placeMarketOrder(accountId, contractId, side, qty, this.lastPrice);
239
- } catch (e) {
240
- return { success: false, error: e.message };
241
- }
242
- }
243
-
244
- async _close(api, accountId, contractId) {
245
- try {
246
- if (api.closePosition) {
247
- return await api.closePosition(accountId, contractId);
248
- }
249
- // Rithmic: place opposite order
250
- const closeSide = this.position.side === 'long' ? 'sell' : 'buy';
251
- const qty = api === this.leadApi ? this.position.leadQty : this.position.followerQty;
252
- return await api.placeMarketOrder(accountId, contractId, closeSide, qty);
253
- } catch (e) {
254
- return { success: false, error: e.message };
255
- }
256
- }
257
-
258
- // NO LOCAL P&L CALCULATION
259
- // P&L comes from API: PNL_PLANT (Rithmic) or /api/Position (ProjectX)
260
-
261
- _rithmicSymbol(sym) {
262
- if (/^[A-Z]{2,4}[FGHJKMNQUVXZ]\d{1,2}$/.test(sym)) return sym;
263
- const bases = ['MNQ', 'NQ', 'MES', 'ES', 'MYM', 'YM'];
264
- let base = 'MNQ';
265
- for (const b of bases) {
266
- if (sym.toUpperCase().includes(b)) { base = b; break; }
267
- }
268
- // Use new module to get front month - returns full symbol directly
269
- return getFM(base);
270
- }
271
-
272
- /**
273
- * Create a trading API wrapper for Rithmic adapter
274
- */
275
- _createRithmicTradingWrapper(adapter, symbolInput) {
276
- const symbol = this._rithmicSymbol(symbolInput);
277
-
278
- return {
279
- async placeMarketOrder(accountId, contractId, side, quantity) {
280
- try {
281
- const result = await adapter.placeMarketOrder(accountId, symbol, side, quantity, 'CME');
282
- return { success: true, orderId: result.orderId };
283
- } catch (error) {
284
- return { success: false, error: error.message };
285
- }
286
- },
287
-
288
- async closePosition(accountId, contractId) {
289
- try {
290
- await adapter.closePosition(accountId, symbol, 'CME');
291
- return { success: true };
292
- } catch (error) {
293
- return { success: false, error: error.message };
294
- }
295
- },
296
-
297
- disconnect() {
298
- // Handled by adapter cleanup in stop()
299
- }
300
- };
301
- }
302
-
303
- async stop(reason = 'manual') {
304
- if (!this.running) return;
305
- this.running = false;
306
-
307
- this._log('info', `Stopping: ${reason}`);
308
-
309
- // Close positions
310
- if (this.position) {
311
- await this._closeBoth(reason);
312
- }
313
-
314
- // Cleanup market data
315
- if (this.marketData?.disconnect) await this.marketData.disconnect();
316
-
317
- // Cleanup Rithmic adapters
318
- if (this.leadN1) {
319
- await this.leadN1.disconnect();
320
- this.leadN1 = null;
321
- }
322
- if (this.followerN1) {
323
- await this.followerN1.disconnect();
324
- this.followerN1 = null;
325
- }
326
-
327
- // Cleanup ProjectX APIs
328
- if (this.leadApi?.disconnect && !this.leadN1) this.leadApi.disconnect();
329
- if (this.followerApi?.disconnect && !this.followerN1) this.followerApi.disconnect();
330
-
331
- this._log('info', `Final P&L: $${this.stats.pnl.toFixed(2)}`);
332
- this.emit('stopped', { reason, stats: this.stats });
333
- }
334
-
335
- _log(type, message) {
336
- console.log(`[COPY] [${type.toUpperCase()}] ${message}`);
337
- this.callbacks.onLog?.({ type, message });
338
- }
339
- }
340
-
341
- module.exports = { C2 };