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/package.json +1 -1
- package/src/lib/data.js +245 -471
- package/src/lib/m/s1-models.js +173 -0
- package/src/lib/m/s1.js +354 -735
- package/src/lib/api.js +0 -198
- package/src/lib/api2.js +0 -353
- package/src/lib/core.js +0 -539
- package/src/lib/core2.js +0 -341
- package/src/lib/data2.js +0 -492
- package/src/lib/decoder.js +0 -599
- package/src/lib/m/s2.js +0 -34
- package/src/lib/n/r1.js +0 -454
- package/src/lib/n/r2.js +0 -514
- package/src/lib/n/r3.js +0 -631
- package/src/lib/n/r4.js +0 -401
- package/src/lib/n/r5.js +0 -335
- package/src/lib/n/r6.js +0 -425
- package/src/lib/n/r7.js +0 -530
- package/src/lib/o/l1.js +0 -44
- package/src/lib/o/l2.js +0 -427
- package/src/lib/python-bridge.js +0 -206
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 };
|