hedgequantx 2.6.163 → 2.7.1
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/README.md +15 -88
- package/bin/cli.js +0 -11
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +1 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +8 -5
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +116 -99
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +63 -120
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -111
- package/src/services/rithmic/protobuf.js +384 -138
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/direct-providers.js +0 -323
- package/src/services/ai/providers/index.js +0 -62
- package/src/services/ai/providers/other-providers.js +0 -104
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-exit-logic.js +0 -174
- package/src/services/position-manager.js +0 -438
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data-decoders.js +0 -229
- package/src/services/rithmic/market-data.js +0 -272
- package/src/services/rithmic/orders-fast.js +0 -246
- package/src/services/rithmic/proto-decoders.js +0 -403
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-signal-calc.js +0 -147
- package/src/services/strategy/hft-tick.js +0 -407
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -392
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/orders.js +0 -145
- package/src/services/tradovate/websocket.js +0 -97
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Position Manager for Fast Scalping
|
|
3
|
-
* @module services/position-manager
|
|
4
|
-
*
|
|
5
|
-
* Manages position lifecycle with:
|
|
6
|
-
* - 10 second minimum hold (prop firm rule - NON-NEGOTIABLE)
|
|
7
|
-
* - Intelligent exit based on momentum (OFI 50%, Kalman 25%, Z-Score 25%)
|
|
8
|
-
* - Trailing stop after profit threshold
|
|
9
|
-
* - 60 second failsafe exit (NON-NEGOTIABLE)
|
|
10
|
-
* - VPIN protection filter
|
|
11
|
-
*
|
|
12
|
-
* Data source: Rithmic ORDER_PLANT (fills), PNL_PLANT (positions), TICKER_PLANT (prices)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const EventEmitter = require('events');
|
|
16
|
-
const { FAST_SCALPING } = require('../config/settings');
|
|
17
|
-
const { logger } = require('../utils/logger');
|
|
18
|
-
const { MOMENTUM, WEIGHTS } = require('./position-constants');
|
|
19
|
-
const { checkExitConditions, checkBreakevenActivation, calculatePnlTicks } = require('./position-exit-logic');
|
|
20
|
-
|
|
21
|
-
const log = logger.scope('PositionMgr');
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Position Manager Service
|
|
25
|
-
* Handles position lifecycle for fast scalping strategy
|
|
26
|
-
*/
|
|
27
|
-
class PositionManager extends EventEmitter {
|
|
28
|
-
constructor(rithmicService, strategy = null) {
|
|
29
|
-
super();
|
|
30
|
-
this.rithmic = rithmicService;
|
|
31
|
-
this.strategy = strategy;
|
|
32
|
-
|
|
33
|
-
this.positions = new Map();
|
|
34
|
-
this.latestPrices = new Map();
|
|
35
|
-
this.contractInfo = new Map();
|
|
36
|
-
|
|
37
|
-
this._monitorInterval = null;
|
|
38
|
-
this._isRunning = false;
|
|
39
|
-
|
|
40
|
-
this._onOrderFilled = this._onOrderFilled.bind(this);
|
|
41
|
-
this._onPriceUpdate = this._onPriceUpdate.bind(this);
|
|
42
|
-
this._onPositionUpdate = this._onPositionUpdate.bind(this);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
setStrategy(strategy) {
|
|
46
|
-
this.strategy = strategy;
|
|
47
|
-
log.debug('Strategy reference set');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
setContractInfo(symbol, info) {
|
|
51
|
-
this.contractInfo.set(symbol, {
|
|
52
|
-
tickSize: info.tickSize,
|
|
53
|
-
tickValue: info.tickValue,
|
|
54
|
-
contractId: info.contractId,
|
|
55
|
-
});
|
|
56
|
-
log.debug('Contract info set', { symbol, tickSize: info.tickSize, tickValue: info.tickValue });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
start() {
|
|
60
|
-
if (this._isRunning) return;
|
|
61
|
-
|
|
62
|
-
log.info('Starting position manager', {
|
|
63
|
-
minHoldMs: FAST_SCALPING.MIN_HOLD_MS,
|
|
64
|
-
maxHoldMs: FAST_SCALPING.MAX_HOLD_MS,
|
|
65
|
-
targetTicks: FAST_SCALPING.TARGET_TICKS,
|
|
66
|
-
stopTicks: FAST_SCALPING.STOP_TICKS,
|
|
67
|
-
momentumWeights: WEIGHTS,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
this.rithmic.on('orderFilled', this._onOrderFilled);
|
|
71
|
-
this.rithmic.on('priceUpdate', this._onPriceUpdate);
|
|
72
|
-
this.rithmic.on('positionUpdate', this._onPositionUpdate);
|
|
73
|
-
|
|
74
|
-
this._monitorInterval = setInterval(() => {
|
|
75
|
-
this._monitorPositions();
|
|
76
|
-
}, FAST_SCALPING.MONITOR_INTERVAL_MS);
|
|
77
|
-
|
|
78
|
-
this._isRunning = true;
|
|
79
|
-
log.debug('Position manager started');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
stop() {
|
|
83
|
-
if (!this._isRunning) return;
|
|
84
|
-
|
|
85
|
-
log.info('Stopping position manager');
|
|
86
|
-
|
|
87
|
-
this.rithmic.off('orderFilled', this._onOrderFilled);
|
|
88
|
-
this.rithmic.off('priceUpdate', this._onPriceUpdate);
|
|
89
|
-
this.rithmic.off('positionUpdate', this._onPositionUpdate);
|
|
90
|
-
|
|
91
|
-
if (this._monitorInterval) {
|
|
92
|
-
clearInterval(this._monitorInterval);
|
|
93
|
-
this._monitorInterval = null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
this._isRunning = false;
|
|
97
|
-
log.debug('Position manager stopped');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
registerEntry(entryResult, orderData, contractInfo = null) {
|
|
101
|
-
if (!entryResult.success) {
|
|
102
|
-
log.warn('Cannot register failed entry', { error: entryResult.error });
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const { orderTag, entryTime, latencyMs } = entryResult;
|
|
107
|
-
|
|
108
|
-
const info = contractInfo || this.contractInfo.get(orderData.symbol) || {
|
|
109
|
-
tickSize: null,
|
|
110
|
-
tickValue: null,
|
|
111
|
-
contractId: orderData.contractId || orderData.symbol,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const position = {
|
|
115
|
-
orderTag,
|
|
116
|
-
accountId: orderData.accountId,
|
|
117
|
-
symbol: orderData.symbol,
|
|
118
|
-
exchange: orderData.exchange || 'CME',
|
|
119
|
-
side: orderData.side,
|
|
120
|
-
size: orderData.size,
|
|
121
|
-
entryPrice: null,
|
|
122
|
-
entryTime,
|
|
123
|
-
fillTime: null,
|
|
124
|
-
highWaterMark: null,
|
|
125
|
-
lowWaterMark: null,
|
|
126
|
-
status: 'pending',
|
|
127
|
-
holdComplete: false,
|
|
128
|
-
breakevenActive: false,
|
|
129
|
-
breakevenPrice: null,
|
|
130
|
-
exitReason: null,
|
|
131
|
-
latencyMs,
|
|
132
|
-
tickSize: info.tickSize,
|
|
133
|
-
tickValue: info.tickValue,
|
|
134
|
-
contractId: info.contractId,
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
this.positions.set(orderTag, position);
|
|
138
|
-
|
|
139
|
-
log.debug('Registered entry', {
|
|
140
|
-
orderTag,
|
|
141
|
-
symbol: orderData.symbol,
|
|
142
|
-
side: orderData.side === 0 ? 'LONG' : 'SHORT',
|
|
143
|
-
size: orderData.size,
|
|
144
|
-
latencyMs,
|
|
145
|
-
tickSize: info.tickSize,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
return orderTag;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
_onOrderFilled(fillInfo) {
|
|
152
|
-
const { orderTag, avgFillPrice, symbol } = fillInfo;
|
|
153
|
-
|
|
154
|
-
if (!orderTag) return;
|
|
155
|
-
|
|
156
|
-
const position = this.positions.get(orderTag);
|
|
157
|
-
if (!position) {
|
|
158
|
-
// Check if this is an exit fill
|
|
159
|
-
for (const [tag, pos] of this.positions) {
|
|
160
|
-
if (pos.status === 'exiting' && pos.symbol === symbol) {
|
|
161
|
-
this._handleExitFill(tag, pos, avgFillPrice);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
log.debug('Fill for untracked order', { orderTag });
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (position.status === 'pending') {
|
|
170
|
-
this._handleEntryFill(orderTag, position, avgFillPrice);
|
|
171
|
-
} else if (position.status === 'exiting') {
|
|
172
|
-
this._handleExitFill(orderTag, position, avgFillPrice);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
_handleEntryFill(orderTag, position, avgFillPrice) {
|
|
177
|
-
position.entryPrice = avgFillPrice;
|
|
178
|
-
position.fillTime = Date.now();
|
|
179
|
-
position.highWaterMark = avgFillPrice;
|
|
180
|
-
position.lowWaterMark = avgFillPrice;
|
|
181
|
-
position.status = 'holding';
|
|
182
|
-
|
|
183
|
-
const fillLatency = position.fillTime - position.entryTime;
|
|
184
|
-
|
|
185
|
-
log.info('ENTRY FILLED', {
|
|
186
|
-
orderTag,
|
|
187
|
-
symbol: position.symbol,
|
|
188
|
-
side: position.side === 0 ? 'LONG' : 'SHORT',
|
|
189
|
-
size: position.size,
|
|
190
|
-
price: avgFillPrice,
|
|
191
|
-
entryLatencyMs: position.latencyMs,
|
|
192
|
-
fillLatencyMs: fillLatency,
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
this.emit('entryFilled', { orderTag, position, fillLatencyMs: fillLatency });
|
|
196
|
-
|
|
197
|
-
setTimeout(() => this._onHoldComplete(orderTag), FAST_SCALPING.MIN_HOLD_MS);
|
|
198
|
-
setTimeout(() => this._failsafeExit(orderTag), FAST_SCALPING.MAX_HOLD_MS);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
_handleExitFill(orderTag, position, avgFillPrice) {
|
|
202
|
-
position.status = 'closed';
|
|
203
|
-
const holdDuration = Date.now() - position.fillTime;
|
|
204
|
-
const pnlTicks = this._calculatePnlTicks(position, avgFillPrice);
|
|
205
|
-
|
|
206
|
-
log.info('EXIT FILLED', {
|
|
207
|
-
orderTag,
|
|
208
|
-
symbol: position.symbol,
|
|
209
|
-
exitPrice: avgFillPrice,
|
|
210
|
-
entryPrice: position.entryPrice,
|
|
211
|
-
pnlTicks,
|
|
212
|
-
holdDurationMs: holdDuration,
|
|
213
|
-
reason: position.exitReason,
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
this.emit('exitFilled', {
|
|
217
|
-
orderTag,
|
|
218
|
-
position,
|
|
219
|
-
exitPrice: avgFillPrice,
|
|
220
|
-
pnlTicks,
|
|
221
|
-
holdDurationMs: holdDuration,
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
this.positions.delete(orderTag);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
_failsafeExit(orderTag) {
|
|
228
|
-
const position = this.positions.get(orderTag);
|
|
229
|
-
if (!position) return;
|
|
230
|
-
|
|
231
|
-
if (position.status === 'holding' || position.status === 'active') {
|
|
232
|
-
const currentPrice = this.latestPrices.get(position.symbol);
|
|
233
|
-
const pnlTicks = currentPrice ? this._calculatePnlTicks(position, currentPrice) : 0;
|
|
234
|
-
|
|
235
|
-
log.warn('FAILSAFE EXIT - 60s max hold exceeded', { orderTag, symbol: position.symbol, pnlTicks });
|
|
236
|
-
|
|
237
|
-
this._executeExit(orderTag, {
|
|
238
|
-
type: 'failsafe',
|
|
239
|
-
reason: '60s max hold exceeded (NON-NEGOTIABLE)',
|
|
240
|
-
pnlTicks,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
_onPriceUpdate(priceData) {
|
|
246
|
-
const { symbol, price } = priceData;
|
|
247
|
-
|
|
248
|
-
this.latestPrices.set(symbol, price);
|
|
249
|
-
|
|
250
|
-
for (const [, position] of this.positions) {
|
|
251
|
-
if (position.symbol === symbol && (position.status === 'holding' || position.status === 'active') && position.entryPrice) {
|
|
252
|
-
if (position.side === 0) {
|
|
253
|
-
position.highWaterMark = Math.max(position.highWaterMark, price);
|
|
254
|
-
} else {
|
|
255
|
-
position.lowWaterMark = Math.min(position.lowWaterMark, price);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
_onPositionUpdate(posData) {
|
|
262
|
-
log.debug('Position update from PNL_PLANT', { symbol: posData?.symbol, qty: posData?.quantity });
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
_onHoldComplete(orderTag) {
|
|
266
|
-
const position = this.positions.get(orderTag);
|
|
267
|
-
if (!position) return;
|
|
268
|
-
|
|
269
|
-
if (position.status === 'holding') {
|
|
270
|
-
position.holdComplete = true;
|
|
271
|
-
position.status = 'active';
|
|
272
|
-
|
|
273
|
-
log.info('Hold complete - now monitoring for exit', {
|
|
274
|
-
orderTag,
|
|
275
|
-
symbol: position.symbol,
|
|
276
|
-
entryPrice: position.entryPrice,
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
this.emit('holdComplete', { orderTag, position });
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
_monitorPositions() {
|
|
284
|
-
const now = Date.now();
|
|
285
|
-
|
|
286
|
-
for (const [orderTag, position] of this.positions) {
|
|
287
|
-
if (position.status !== 'active') continue;
|
|
288
|
-
|
|
289
|
-
const currentPrice = this.latestPrices.get(position.symbol);
|
|
290
|
-
if (!currentPrice) continue;
|
|
291
|
-
|
|
292
|
-
const holdDuration = now - position.fillTime;
|
|
293
|
-
const pnlTicks = this._calculatePnlTicks(position, currentPrice);
|
|
294
|
-
|
|
295
|
-
// Check for breakeven activation
|
|
296
|
-
const beActivation = checkBreakevenActivation(position, pnlTicks, (p) => this._getTickSize(p));
|
|
297
|
-
if (beActivation) {
|
|
298
|
-
position.breakevenPrice = beActivation.breakevenPrice;
|
|
299
|
-
position.breakevenActive = true;
|
|
300
|
-
|
|
301
|
-
log.info('BREAKEVEN ACTIVATED', {
|
|
302
|
-
symbol: position.symbol,
|
|
303
|
-
entryPrice: position.entryPrice,
|
|
304
|
-
bePrice: position.breakevenPrice,
|
|
305
|
-
pnlTicks,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
this.emit('breakevenActivated', {
|
|
309
|
-
orderTag: position.orderTag,
|
|
310
|
-
position,
|
|
311
|
-
breakevenPrice: position.breakevenPrice,
|
|
312
|
-
pnlTicks,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Check exit conditions
|
|
317
|
-
const exitReason = checkExitConditions({
|
|
318
|
-
position,
|
|
319
|
-
currentPrice,
|
|
320
|
-
pnlTicks,
|
|
321
|
-
holdDuration,
|
|
322
|
-
strategy: this.strategy,
|
|
323
|
-
getTickSize: (p) => this._getTickSize(p),
|
|
324
|
-
latestPrices: this.latestPrices,
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
if (exitReason) {
|
|
328
|
-
this._executeExit(orderTag, exitReason);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
_executeExit(orderTag, exitReason) {
|
|
334
|
-
const position = this.positions.get(orderTag);
|
|
335
|
-
if (!position || position.status === 'exiting' || position.status === 'closed') return;
|
|
336
|
-
|
|
337
|
-
position.status = 'exiting';
|
|
338
|
-
position.exitReason = exitReason;
|
|
339
|
-
|
|
340
|
-
log.info('Executing EXIT', {
|
|
341
|
-
orderTag,
|
|
342
|
-
symbol: position.symbol,
|
|
343
|
-
reason: exitReason.reason,
|
|
344
|
-
pnlTicks: exitReason.pnlTicks,
|
|
345
|
-
momentum: exitReason.momentum,
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
const exitSide = position.side === 0 ? 1 : 0;
|
|
349
|
-
|
|
350
|
-
const exitResult = this.rithmic.fastExit({
|
|
351
|
-
accountId: position.accountId,
|
|
352
|
-
symbol: position.symbol,
|
|
353
|
-
exchange: position.exchange,
|
|
354
|
-
size: position.size,
|
|
355
|
-
side: exitSide,
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
if (exitResult.success) {
|
|
359
|
-
log.debug('Exit order fired', {
|
|
360
|
-
orderTag: position.orderTag,
|
|
361
|
-
exitOrderTag: exitResult.orderTag,
|
|
362
|
-
latencyMs: exitResult.latencyMs,
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
this.emit('exitOrderFired', {
|
|
366
|
-
orderTag,
|
|
367
|
-
exitOrderTag: exitResult.orderTag,
|
|
368
|
-
exitReason,
|
|
369
|
-
latencyMs: exitResult.latencyMs,
|
|
370
|
-
});
|
|
371
|
-
} else {
|
|
372
|
-
log.error('Exit order FAILED', { orderTag, error: exitResult.error });
|
|
373
|
-
position.status = 'active';
|
|
374
|
-
position.exitReason = null;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
_getTickSize(position) {
|
|
379
|
-
if (position.tickSize !== null && position.tickSize !== undefined) {
|
|
380
|
-
return position.tickSize;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const info = this.contractInfo.get(position.symbol);
|
|
384
|
-
if (info && info.tickSize) {
|
|
385
|
-
return info.tickSize;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
log.warn('No tick size available for symbol', { symbol: position.symbol });
|
|
389
|
-
return null;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
_getTickValue(position) {
|
|
393
|
-
if (position.tickValue !== null && position.tickValue !== undefined) {
|
|
394
|
-
return position.tickValue;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const info = this.contractInfo.get(position.symbol);
|
|
398
|
-
if (info && info.tickValue) {
|
|
399
|
-
return info.tickValue;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
log.warn('No tick value available for symbol', { symbol: position.symbol });
|
|
403
|
-
return null;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
_calculatePnlTicks(position, currentPrice) {
|
|
407
|
-
return calculatePnlTicks(position, currentPrice, (p) => this._getTickSize(p));
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
getActivePositions() {
|
|
411
|
-
return Array.from(this.positions.values()).filter(
|
|
412
|
-
p => p.status === 'holding' || p.status === 'active'
|
|
413
|
-
);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
getPosition(orderTag) {
|
|
417
|
-
return this.positions.get(orderTag) || null;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
canEnter(symbol) {
|
|
421
|
-
for (const position of this.positions.values()) {
|
|
422
|
-
if (position.symbol === symbol && position.status !== 'closed') {
|
|
423
|
-
return false;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
return true;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
getMomentumThresholds() {
|
|
430
|
-
return { ...MOMENTUM };
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
getMomentumWeights() {
|
|
434
|
-
return { ...WEIGHTS };
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
module.exports = { PositionManager };
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Position Momentum Calculator
|
|
3
|
-
* Calculates momentum using strategy's math models (OFI, Kalman, Z-Score)
|
|
4
|
-
*
|
|
5
|
-
* Data sources:
|
|
6
|
-
* - OFI: Strategy's computeOrderFlowImbalance()
|
|
7
|
-
* - Kalman: Strategy's kalmanStates
|
|
8
|
-
* - Z-Score: Strategy's computeZScore()
|
|
9
|
-
* - VPIN: Strategy's computeVPIN()
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const { logger } = require('../utils/logger');
|
|
13
|
-
const { WEIGHTS } = require('./position-constants');
|
|
14
|
-
|
|
15
|
-
const log = logger.scope('PositionMomentum');
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get OFI (Order Flow Imbalance) from strategy
|
|
19
|
-
* @param {Object} strategy - Strategy instance
|
|
20
|
-
* @param {string} contractId - Contract identifier
|
|
21
|
-
* @returns {number|null} OFI value [-1, 1] or null if unavailable
|
|
22
|
-
*/
|
|
23
|
-
const getOFI = (strategy, contractId) => {
|
|
24
|
-
if (!strategy) return null;
|
|
25
|
-
|
|
26
|
-
// Try strategy's computeOrderFlowImbalance (direct calculation from bars)
|
|
27
|
-
if (typeof strategy.computeOrderFlowImbalance === 'function') {
|
|
28
|
-
const bars = strategy.getBarHistory?.(contractId);
|
|
29
|
-
if (bars && bars.length >= 20) {
|
|
30
|
-
try {
|
|
31
|
-
return strategy.computeOrderFlowImbalance(bars);
|
|
32
|
-
} catch (error) {
|
|
33
|
-
log.debug('OFI calculation failed', { error: error.message });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Try getModelValues (pre-calculated values)
|
|
39
|
-
const modelValues = strategy.getModelValues?.(contractId);
|
|
40
|
-
if (modelValues && modelValues.rawOfi !== undefined) {
|
|
41
|
-
return modelValues.rawOfi;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return null;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get Kalman velocity from strategy's Kalman filter
|
|
49
|
-
* @param {Object} strategy - Strategy instance
|
|
50
|
-
* @param {string} contractId - Contract identifier
|
|
51
|
-
* @param {number} currentPrice - Latest price
|
|
52
|
-
* @returns {number|null} Velocity value or null if unavailable
|
|
53
|
-
*/
|
|
54
|
-
const getKalmanVelocity = (strategy, contractId, currentPrice) => {
|
|
55
|
-
if (!strategy) return null;
|
|
56
|
-
|
|
57
|
-
// Try to access kalmanStates from strategy
|
|
58
|
-
if (strategy.kalmanStates) {
|
|
59
|
-
const state = strategy.kalmanStates.get(contractId);
|
|
60
|
-
if (state && typeof state.estimate === 'number') {
|
|
61
|
-
if (currentPrice !== undefined && currentPrice !== null) {
|
|
62
|
-
return currentPrice - state.estimate;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return null;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Get Z-Score from strategy
|
|
72
|
-
* @param {Object} strategy - Strategy instance
|
|
73
|
-
* @param {string} contractId - Contract identifier
|
|
74
|
-
* @returns {number|null} Z-Score value or null if unavailable
|
|
75
|
-
*/
|
|
76
|
-
const getZScore = (strategy, contractId) => {
|
|
77
|
-
if (!strategy) return null;
|
|
78
|
-
|
|
79
|
-
// Try strategy's computeZScore (direct calculation from price buffer)
|
|
80
|
-
if (typeof strategy.computeZScore === 'function') {
|
|
81
|
-
const prices = strategy.priceBuffer?.get(contractId);
|
|
82
|
-
if (prices && prices.length >= 50) {
|
|
83
|
-
try {
|
|
84
|
-
return strategy.computeZScore(prices);
|
|
85
|
-
} catch (error) {
|
|
86
|
-
log.debug('Z-Score calculation failed', { error: error.message });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return null;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Get VPIN from strategy
|
|
96
|
-
* @param {Object} strategy - Strategy instance
|
|
97
|
-
* @param {string} contractId - Contract identifier
|
|
98
|
-
* @returns {number|null} VPIN value [0, 1] or null if unavailable
|
|
99
|
-
*/
|
|
100
|
-
const getVPIN = (strategy, contractId) => {
|
|
101
|
-
if (!strategy) return null;
|
|
102
|
-
|
|
103
|
-
// Try strategy's computeVPIN (direct calculation from volume buffer)
|
|
104
|
-
if (typeof strategy.computeVPIN === 'function') {
|
|
105
|
-
const volumes = strategy.volumeBuffer?.get(contractId);
|
|
106
|
-
if (volumes && volumes.length >= 50) {
|
|
107
|
-
try {
|
|
108
|
-
return strategy.computeVPIN(volumes);
|
|
109
|
-
} catch (error) {
|
|
110
|
-
log.debug('VPIN calculation failed', { error: error.message });
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Try getModelValues (pre-calculated, stored as 1 - vpin for scoring)
|
|
116
|
-
const modelValues = strategy.getModelValues?.(contractId);
|
|
117
|
-
if (modelValues && typeof modelValues.vpin === 'number') {
|
|
118
|
-
return 1 - modelValues.vpin;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return null;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Calculate momentum score using strategy's existing math models
|
|
126
|
-
* Weighted: OFI (50%) + Kalman Velocity (25%) + Z-Score (25%)
|
|
127
|
-
*
|
|
128
|
-
* @param {Object} params - Parameters
|
|
129
|
-
* @param {Object} params.strategy - Strategy instance
|
|
130
|
-
* @param {string} params.contractId - Contract identifier
|
|
131
|
-
* @param {number} params.side - Position side (0=Long, 1=Short)
|
|
132
|
-
* @param {number} params.currentPrice - Latest price
|
|
133
|
-
* @param {number|null} params.tickSize - Tick size from API
|
|
134
|
-
* @returns {number|null} Momentum score [-1 to 1], positive = favorable, null if insufficient data
|
|
135
|
-
*/
|
|
136
|
-
const calculateMomentum = ({ strategy, contractId, side, currentPrice, tickSize }) => {
|
|
137
|
-
if (!strategy) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Get individual model values (all from API/strategy, not invented)
|
|
142
|
-
const ofi = getOFI(strategy, contractId);
|
|
143
|
-
const velocity = getKalmanVelocity(strategy, contractId, currentPrice);
|
|
144
|
-
const zscore = getZScore(strategy, contractId);
|
|
145
|
-
|
|
146
|
-
// Count how many models have data
|
|
147
|
-
let availableModels = 0;
|
|
148
|
-
let totalWeight = 0;
|
|
149
|
-
let weightedSum = 0;
|
|
150
|
-
|
|
151
|
-
// 1. OFI (50%) - Order Flow Imbalance
|
|
152
|
-
if (ofi !== null) {
|
|
153
|
-
// For long: positive OFI = favorable, For short: negative OFI = favorable
|
|
154
|
-
const favorableOfi = side === 0 ? ofi : -ofi;
|
|
155
|
-
const ofiScore = Math.min(1, Math.max(-1, favorableOfi));
|
|
156
|
-
weightedSum += ofiScore * WEIGHTS.OFI;
|
|
157
|
-
totalWeight += WEIGHTS.OFI;
|
|
158
|
-
availableModels++;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// 2. Kalman Velocity (25%)
|
|
162
|
-
if (velocity !== null && tickSize !== null) {
|
|
163
|
-
// Normalize velocity: favorable direction = positive
|
|
164
|
-
const favorableVelocity = side === 0 ? velocity : -velocity;
|
|
165
|
-
// Normalize to [-1, 1]: 4 ticks of velocity = 1.0 score
|
|
166
|
-
const normalizedVelocity = favorableVelocity / (tickSize * 4);
|
|
167
|
-
const velocityScore = Math.min(1, Math.max(-1, normalizedVelocity));
|
|
168
|
-
weightedSum += velocityScore * WEIGHTS.KALMAN;
|
|
169
|
-
totalWeight += WEIGHTS.KALMAN;
|
|
170
|
-
availableModels++;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// 3. Z-Score (25%) - Progression toward mean
|
|
174
|
-
if (zscore !== null) {
|
|
175
|
-
let zscoreScore;
|
|
176
|
-
if (side === 0) {
|
|
177
|
-
// Long: entered when Z < -threshold, improving = Z moving toward 0
|
|
178
|
-
zscoreScore = zscore > -0.5 ? 0.5 : -0.5;
|
|
179
|
-
} else {
|
|
180
|
-
// Short: entered when Z > threshold, improving = Z moving toward 0
|
|
181
|
-
zscoreScore = zscore < 0.5 ? 0.5 : -0.5;
|
|
182
|
-
}
|
|
183
|
-
weightedSum += zscoreScore * WEIGHTS.ZSCORE;
|
|
184
|
-
totalWeight += WEIGHTS.ZSCORE;
|
|
185
|
-
availableModels++;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Need at least 1 model with data to calculate momentum
|
|
189
|
-
if (availableModels === 0 || totalWeight === 0) {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Normalize by actual total weight (in case some models unavailable)
|
|
194
|
-
const momentum = weightedSum / totalWeight;
|
|
195
|
-
|
|
196
|
-
// Clamp to [-1, 1]
|
|
197
|
-
return Math.min(1, Math.max(-1, momentum));
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
module.exports = {
|
|
201
|
-
getOFI,
|
|
202
|
-
getKalmanVelocity,
|
|
203
|
-
getZScore,
|
|
204
|
-
getVPIN,
|
|
205
|
-
calculateMomentum,
|
|
206
|
-
};
|