hedgequantx 2.6.163 → 2.7.0
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 +6 -3
- 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,172 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ProjectX Market Hours & Holidays
|
|
3
|
-
* CME Futures trading hours and US market holidays
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get nth weekday of a month
|
|
8
|
-
*/
|
|
9
|
-
const getNthWeekday = (year, month, weekday, n) => {
|
|
10
|
-
let count = 0;
|
|
11
|
-
for (let day = 1; day <= 31; day++) {
|
|
12
|
-
const d = new Date(year, month, day);
|
|
13
|
-
if (d.getMonth() !== month) break;
|
|
14
|
-
if (d.getDay() === weekday) {
|
|
15
|
-
count++;
|
|
16
|
-
if (count === n) return d.toISOString().split('T')[0];
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Get last weekday of a month
|
|
24
|
-
*/
|
|
25
|
-
const getLastWeekday = (year, month, weekday) => {
|
|
26
|
-
const lastDay = new Date(year, month + 1, 0);
|
|
27
|
-
for (let day = lastDay.getDate(); day >= 1; day--) {
|
|
28
|
-
const d = new Date(year, month, day);
|
|
29
|
-
if (d.getDay() === weekday) return d.toISOString().split('T')[0];
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Get Good Friday (Friday before Easter)
|
|
36
|
-
*/
|
|
37
|
-
const getGoodFriday = (year) => {
|
|
38
|
-
const a = year % 19;
|
|
39
|
-
const b = Math.floor(year / 100);
|
|
40
|
-
const c = year % 100;
|
|
41
|
-
const d = Math.floor(b / 4);
|
|
42
|
-
const e = b % 4;
|
|
43
|
-
const f = Math.floor((b + 8) / 25);
|
|
44
|
-
const g = Math.floor((b - f + 1) / 3);
|
|
45
|
-
const h = (19 * a + b - d - g + 15) % 30;
|
|
46
|
-
const i = Math.floor(c / 4);
|
|
47
|
-
const k = c % 4;
|
|
48
|
-
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
|
49
|
-
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
|
50
|
-
const month = Math.floor((h + l - 7 * m + 114) / 31) - 1;
|
|
51
|
-
const day = ((h + l - 7 * m + 114) % 31) + 1;
|
|
52
|
-
|
|
53
|
-
const easter = new Date(year, month, day);
|
|
54
|
-
const goodFriday = new Date(easter);
|
|
55
|
-
goodFriday.setDate(easter.getDate() - 2);
|
|
56
|
-
return goodFriday.toISOString().split('T')[0];
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get day after a date
|
|
61
|
-
*/
|
|
62
|
-
const getDayAfter = (dateStr) => {
|
|
63
|
-
const d = new Date(dateStr);
|
|
64
|
-
d.setDate(d.getDate() + 1);
|
|
65
|
-
return d.toISOString().split('T')[0];
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Get US market holidays for the current year
|
|
70
|
-
*/
|
|
71
|
-
const getMarketHolidays = () => {
|
|
72
|
-
const year = new Date().getFullYear();
|
|
73
|
-
|
|
74
|
-
return [
|
|
75
|
-
{ date: `${year}-01-01`, name: "New Year's Day", earlyClose: false },
|
|
76
|
-
{ date: getNthWeekday(year, 0, 1, 3), name: 'MLK Day', earlyClose: false },
|
|
77
|
-
{ date: getNthWeekday(year, 1, 1, 3), name: "Presidents' Day", earlyClose: false },
|
|
78
|
-
{ date: getGoodFriday(year), name: 'Good Friday', earlyClose: false },
|
|
79
|
-
{ date: getLastWeekday(year, 4, 1), name: 'Memorial Day', earlyClose: false },
|
|
80
|
-
{ date: `${year}-06-19`, name: 'Juneteenth', earlyClose: false },
|
|
81
|
-
{ date: `${year}-07-04`, name: 'Independence Day', earlyClose: false },
|
|
82
|
-
{ date: `${year}-07-03`, name: 'Independence Day Eve', earlyClose: true },
|
|
83
|
-
{ date: getNthWeekday(year, 8, 1, 1), name: 'Labor Day', earlyClose: false },
|
|
84
|
-
{ date: getNthWeekday(year, 10, 4, 4), name: 'Thanksgiving', earlyClose: false },
|
|
85
|
-
{ date: getDayAfter(getNthWeekday(year, 10, 4, 4)), name: 'Black Friday', earlyClose: true },
|
|
86
|
-
{ date: `${year}-12-25`, name: 'Christmas Day', earlyClose: false },
|
|
87
|
-
{ date: `${year}-12-24`, name: 'Christmas Eve', earlyClose: true },
|
|
88
|
-
{ date: `${year}-12-31`, name: "New Year's Eve", earlyClose: true },
|
|
89
|
-
];
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Check if today is a market holiday
|
|
94
|
-
*/
|
|
95
|
-
const checkHoliday = () => {
|
|
96
|
-
const today = new Date().toISOString().split('T')[0];
|
|
97
|
-
const holidays = getMarketHolidays();
|
|
98
|
-
const holiday = holidays.find(h => h.date === today);
|
|
99
|
-
|
|
100
|
-
if (holiday) {
|
|
101
|
-
return { isHoliday: !holiday.earlyClose, holiday };
|
|
102
|
-
}
|
|
103
|
-
return { isHoliday: false };
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Get current trading session based on ET time
|
|
108
|
-
* Sessions (in ET):
|
|
109
|
-
* - ASIA: 6:00 PM - 2:00 AM (18:00-02:00)
|
|
110
|
-
* - LONDON: 2:00 AM - 8:00 AM (02:00-08:00)
|
|
111
|
-
* - AMERICAN: 8:00 AM - 5:00 PM (08:00-17:00)
|
|
112
|
-
* - MAINTENANCE: 5:00 PM - 6:00 PM (17:00-18:00)
|
|
113
|
-
*/
|
|
114
|
-
const getTradingSession = () => {
|
|
115
|
-
const now = new Date();
|
|
116
|
-
// Convert to ET (UTC-5 or UTC-4 for DST)
|
|
117
|
-
const etOptions = { timeZone: 'America/New_York', hour: 'numeric', hour12: false };
|
|
118
|
-
const etHour = parseInt(new Intl.DateTimeFormat('en-US', etOptions).format(now));
|
|
119
|
-
|
|
120
|
-
// Determine session based on ET hour
|
|
121
|
-
if (etHour >= 18 || etHour < 2) {
|
|
122
|
-
return 'ASIA';
|
|
123
|
-
} else if (etHour >= 2 && etHour < 8) {
|
|
124
|
-
return 'LONDON';
|
|
125
|
-
} else if (etHour >= 8 && etHour < 17) {
|
|
126
|
-
return 'AMERICAN';
|
|
127
|
-
} else {
|
|
128
|
-
return 'MAINTENANCE';
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Check if futures market is open based on CME hours and holidays
|
|
134
|
-
*/
|
|
135
|
-
const checkMarketHours = () => {
|
|
136
|
-
const now = new Date();
|
|
137
|
-
const utcDay = now.getUTCDay();
|
|
138
|
-
const utcHour = now.getUTCHours();
|
|
139
|
-
|
|
140
|
-
const holidayCheck = checkHoliday();
|
|
141
|
-
if (holidayCheck.isHoliday) {
|
|
142
|
-
return { isOpen: false, message: `Market closed - ${holidayCheck.holiday.name}`, session: null };
|
|
143
|
-
}
|
|
144
|
-
if (holidayCheck.holiday && holidayCheck.holiday.earlyClose && utcHour >= 18) {
|
|
145
|
-
return { isOpen: false, message: `Market closed early - ${holidayCheck.holiday.name}`, session: null };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (utcDay === 6) {
|
|
149
|
-
return { isOpen: false, message: 'Market closed - Weekend (Saturday)', session: null };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (utcDay === 0 && utcHour < 23) {
|
|
153
|
-
return { isOpen: false, message: 'Market closed - Opens Sunday 6:00 PM ET', session: null };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (utcDay === 5 && utcHour >= 22) {
|
|
157
|
-
return { isOpen: false, message: 'Market closed - Weekend', session: null };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (utcHour === 22 && utcDay !== 5) {
|
|
161
|
-
return { isOpen: false, message: 'Market closed - Daily maintenance (5:00-6:00 PM ET)', session: 'MAINTENANCE' };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const session = getTradingSession();
|
|
165
|
-
return { isOpen: true, message: 'Market is open', session };
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
module.exports = {
|
|
169
|
-
getMarketHolidays,
|
|
170
|
-
checkHoliday,
|
|
171
|
-
checkMarketHours
|
|
172
|
-
};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ProjectX Statistics Module
|
|
3
|
-
* Trade statistics calculations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Calculate lifetime statistics from trades
|
|
8
|
-
* @param {Array} trades - Array of trade objects
|
|
9
|
-
* @returns {Object} Calculated statistics
|
|
10
|
-
*/
|
|
11
|
-
const calculateLifetimeStats = (trades) => {
|
|
12
|
-
if (!trades || trades.length === 0) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const stats = {
|
|
17
|
-
totalTrades: trades.length,
|
|
18
|
-
winningTrades: 0,
|
|
19
|
-
losingTrades: 0,
|
|
20
|
-
totalWinAmount: 0,
|
|
21
|
-
totalLossAmount: 0,
|
|
22
|
-
bestTrade: 0,
|
|
23
|
-
worstTrade: 0,
|
|
24
|
-
totalVolume: 0,
|
|
25
|
-
maxConsecutiveWins: 0,
|
|
26
|
-
maxConsecutiveLosses: 0,
|
|
27
|
-
longTrades: 0,
|
|
28
|
-
shortTrades: 0
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
let consecutiveWins = 0;
|
|
32
|
-
let consecutiveLosses = 0;
|
|
33
|
-
|
|
34
|
-
trades.forEach(t => {
|
|
35
|
-
const pnl = t.profitAndLoss || t.pnl || 0;
|
|
36
|
-
const size = t.size || t.quantity || 1;
|
|
37
|
-
|
|
38
|
-
stats.totalVolume += Math.abs(size);
|
|
39
|
-
|
|
40
|
-
if (t.side === 0) stats.longTrades++;
|
|
41
|
-
else if (t.side === 1) stats.shortTrades++;
|
|
42
|
-
|
|
43
|
-
if (pnl > 0) {
|
|
44
|
-
stats.winningTrades++;
|
|
45
|
-
stats.totalWinAmount += pnl;
|
|
46
|
-
if (pnl > stats.bestTrade) stats.bestTrade = pnl;
|
|
47
|
-
consecutiveWins++;
|
|
48
|
-
consecutiveLosses = 0;
|
|
49
|
-
if (consecutiveWins > stats.maxConsecutiveWins) stats.maxConsecutiveWins = consecutiveWins;
|
|
50
|
-
} else if (pnl < 0) {
|
|
51
|
-
stats.losingTrades++;
|
|
52
|
-
stats.totalLossAmount += Math.abs(pnl);
|
|
53
|
-
if (pnl < stats.worstTrade) stats.worstTrade = pnl;
|
|
54
|
-
consecutiveLosses++;
|
|
55
|
-
consecutiveWins = 0;
|
|
56
|
-
if (consecutiveLosses > stats.maxConsecutiveLosses) stats.maxConsecutiveLosses = consecutiveLosses;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
stats.profitFactor = stats.totalLossAmount > 0 ? stats.totalWinAmount / stats.totalLossAmount : 0;
|
|
61
|
-
stats.avgWin = stats.winningTrades > 0 ? stats.totalWinAmount / stats.winningTrades : 0;
|
|
62
|
-
stats.avgLoss = stats.losingTrades > 0 ? stats.totalLossAmount / stats.losingTrades : 0;
|
|
63
|
-
|
|
64
|
-
return stats;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Calculate daily P&L from trades
|
|
69
|
-
* @param {Array} trades - Array of trade objects
|
|
70
|
-
* @returns {Array} Daily P&L array
|
|
71
|
-
*/
|
|
72
|
-
const calculateDailyPnL = (trades) => {
|
|
73
|
-
if (!trades || trades.length === 0) {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const dailyPnL = {};
|
|
78
|
-
|
|
79
|
-
trades.forEach(t => {
|
|
80
|
-
const ts = t.creationTimestamp || t.timestamp;
|
|
81
|
-
if (ts) {
|
|
82
|
-
const d = new Date(ts);
|
|
83
|
-
const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
84
|
-
dailyPnL[key] = (dailyPnL[key] || 0) + (t.profitAndLoss || t.pnl || 0);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
return Object.entries(dailyPnL).map(([date, pnl]) => ({ date, profitAndLoss: pnl }));
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Format trades for consistent output
|
|
93
|
-
* @param {Array} trades - Raw trades from API
|
|
94
|
-
* @returns {Array} Formatted trades
|
|
95
|
-
*/
|
|
96
|
-
const formatTrades = (trades) => {
|
|
97
|
-
if (!Array.isArray(trades)) return [];
|
|
98
|
-
|
|
99
|
-
return trades.map(t => ({
|
|
100
|
-
...t,
|
|
101
|
-
timestamp: t.creationTimestamp || t.timestamp,
|
|
102
|
-
pnl: t.profitAndLoss || t.pnl || 0
|
|
103
|
-
}));
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
module.exports = {
|
|
107
|
-
calculateLifetimeStats,
|
|
108
|
-
calculateDailyPnL,
|
|
109
|
-
formatTrades
|
|
110
|
-
};
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ProjectX Trading Operations
|
|
3
|
-
* @module services/projectx/trading
|
|
4
|
-
*
|
|
5
|
-
* Order management, position control, and trade history.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { validateAccountId } = require('../../security');
|
|
9
|
-
const { logger } = require('../../utils/logger');
|
|
10
|
-
|
|
11
|
-
const log = logger.scope('ProjectX');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Get open positions
|
|
15
|
-
* @param {Function} request - Request function
|
|
16
|
-
* @param {string} gatewayApi - Gateway API host
|
|
17
|
-
* @param {number|string} accountId - Account ID
|
|
18
|
-
*/
|
|
19
|
-
async function getPositions(request, gatewayApi, accountId) {
|
|
20
|
-
try {
|
|
21
|
-
const id = validateAccountId(accountId);
|
|
22
|
-
const response = await request(gatewayApi, '/api/Position/searchOpen', 'POST', { accountId: id });
|
|
23
|
-
|
|
24
|
-
if (response.statusCode === 200) {
|
|
25
|
-
const positions = response.data.positions || response.data || [];
|
|
26
|
-
return { success: true, positions: Array.isArray(positions) ? positions : [] };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return { success: true, positions: [] };
|
|
30
|
-
} catch (err) {
|
|
31
|
-
return { success: true, positions: [], error: err.message };
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Get open orders
|
|
37
|
-
* @param {Function} request - Request function
|
|
38
|
-
* @param {string} gatewayApi - Gateway API host
|
|
39
|
-
* @param {number|string} accountId - Account ID
|
|
40
|
-
*/
|
|
41
|
-
async function getOrders(request, gatewayApi, accountId) {
|
|
42
|
-
try {
|
|
43
|
-
const id = validateAccountId(accountId);
|
|
44
|
-
const response = await request(gatewayApi, '/api/Order/searchOpen', 'POST', { accountId: id });
|
|
45
|
-
|
|
46
|
-
if (response.statusCode === 200) {
|
|
47
|
-
const orders = response.data.orders || response.data || [];
|
|
48
|
-
return { success: true, orders: Array.isArray(orders) ? orders : [] };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return { success: true, orders: [] };
|
|
52
|
-
} catch (err) {
|
|
53
|
-
return { success: true, orders: [], error: err.message };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Get trades for today
|
|
59
|
-
* @param {Function} request - Request function
|
|
60
|
-
* @param {string} gatewayApi - Gateway API host
|
|
61
|
-
* @param {number|string} accountId - Account ID
|
|
62
|
-
*/
|
|
63
|
-
async function getTrades(request, gatewayApi, accountId) {
|
|
64
|
-
try {
|
|
65
|
-
const id = validateAccountId(accountId);
|
|
66
|
-
const now = new Date();
|
|
67
|
-
const startOfDay = new Date(now);
|
|
68
|
-
startOfDay.setUTCHours(0, 0, 0, 0);
|
|
69
|
-
|
|
70
|
-
const response = await request(gatewayApi, '/api/Trade/search', 'POST', {
|
|
71
|
-
accountId: id,
|
|
72
|
-
startTimestamp: startOfDay.toISOString(),
|
|
73
|
-
endTimestamp: now.toISOString()
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
if (response.statusCode === 200) {
|
|
77
|
-
const trades = response.data.trades || response.data || [];
|
|
78
|
-
return { success: true, trades: Array.isArray(trades) ? trades : [] };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return { success: true, trades: [] };
|
|
82
|
-
} catch (err) {
|
|
83
|
-
return { success: true, trades: [], error: err.message };
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Place an order
|
|
89
|
-
* @param {Function} request - Request function
|
|
90
|
-
* @param {string} gatewayApi - Gateway API host
|
|
91
|
-
* @param {Object} orderData - Order details
|
|
92
|
-
*/
|
|
93
|
-
async function placeOrder(request, gatewayApi, orderData) {
|
|
94
|
-
try {
|
|
95
|
-
const response = await request(gatewayApi, '/api/Order/place', 'POST', orderData, 'orders');
|
|
96
|
-
|
|
97
|
-
if (response.statusCode === 200 && response.data.success) {
|
|
98
|
-
log.info('Order placed', { orderId: response.data.orderId });
|
|
99
|
-
return { success: true, order: response.data };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return { success: false, error: response.data.errorMessage || 'Order failed' };
|
|
103
|
-
} catch (err) {
|
|
104
|
-
return { success: false, error: err.message };
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Cancel an order
|
|
110
|
-
* @param {Function} request - Request function
|
|
111
|
-
* @param {string} gatewayApi - Gateway API host
|
|
112
|
-
* @param {number|string} orderId - Order ID
|
|
113
|
-
*/
|
|
114
|
-
async function cancelOrder(request, gatewayApi, orderId) {
|
|
115
|
-
try {
|
|
116
|
-
const response = await request(gatewayApi, '/api/Order/cancel', 'POST', { orderId: parseInt(orderId, 10) }, 'orders');
|
|
117
|
-
return { success: response.statusCode === 200 && response.data.success };
|
|
118
|
-
} catch (err) {
|
|
119
|
-
return { success: false, error: err.message };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Cancel all open orders for an account
|
|
125
|
-
* @param {Function} request - Request function
|
|
126
|
-
* @param {string} gatewayApi - Gateway API host
|
|
127
|
-
* @param {number|string} accountId - Account ID
|
|
128
|
-
*/
|
|
129
|
-
async function cancelAllOrders(request, gatewayApi, accountId) {
|
|
130
|
-
try {
|
|
131
|
-
const id = validateAccountId(accountId);
|
|
132
|
-
const ordersResult = await getOrders(request, gatewayApi, id);
|
|
133
|
-
|
|
134
|
-
if (!ordersResult.success || !ordersResult.orders?.length) {
|
|
135
|
-
return { success: true, cancelled: 0 };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const pendingOrders = ordersResult.orders.filter(o =>
|
|
139
|
-
o.status === 'Working' || o.status === 'Pending' ||
|
|
140
|
-
o.status === 0 || o.status === 1
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
let cancelled = 0;
|
|
144
|
-
for (const order of pendingOrders) {
|
|
145
|
-
const result = await cancelOrder(request, gatewayApi, order.orderId || order.id);
|
|
146
|
-
if (result.success) cancelled++;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return { success: true, cancelled };
|
|
150
|
-
} catch (err) {
|
|
151
|
-
return { success: false, cancelled: 0, error: err.message };
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Close a position
|
|
157
|
-
* @param {Function} request - Request function
|
|
158
|
-
* @param {string} gatewayApi - Gateway API host
|
|
159
|
-
* @param {number|string} accountId - Account ID
|
|
160
|
-
* @param {number|string} contractId - Contract ID
|
|
161
|
-
*/
|
|
162
|
-
async function closePosition(request, gatewayApi, accountId, contractId) {
|
|
163
|
-
try {
|
|
164
|
-
const id = validateAccountId(accountId);
|
|
165
|
-
const response = await request(gatewayApi, '/api/Position/closeContract', 'POST', { accountId: id, contractId }, 'orders');
|
|
166
|
-
return { success: response.statusCode === 200 && response.data.success };
|
|
167
|
-
} catch (err) {
|
|
168
|
-
return { success: false, error: err.message };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
module.exports = {
|
|
173
|
-
getPositions,
|
|
174
|
-
getOrders,
|
|
175
|
-
getTrades,
|
|
176
|
-
placeOrder,
|
|
177
|
-
cancelOrder,
|
|
178
|
-
cancelAllOrders,
|
|
179
|
-
closePosition,
|
|
180
|
-
};
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rithmic Latency Tracking
|
|
3
|
-
* @module services/rithmic/latency-tracker
|
|
4
|
-
*
|
|
5
|
-
* High-precision order-to-fill latency tracking.
|
|
6
|
-
* Uses circular buffer and high-resolution timing.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Get high-resolution timestamp in nanoseconds
|
|
11
|
-
* @returns {bigint}
|
|
12
|
-
*/
|
|
13
|
-
const hrNow = () => process.hrtime.bigint();
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Convert nanoseconds to milliseconds with precision
|
|
17
|
-
* @param {bigint} ns
|
|
18
|
-
* @returns {number}
|
|
19
|
-
*/
|
|
20
|
-
const nsToMs = (ns) => Number(ns) / 1_000_000;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Latency tracker with circular buffer for O(1) operations
|
|
24
|
-
*/
|
|
25
|
-
const LatencyTracker = {
|
|
26
|
-
_pending: new Map(), // orderTag -> entryTime (bigint nanoseconds)
|
|
27
|
-
_samples: null, // Pre-allocated Float64Array circular buffer
|
|
28
|
-
_maxSamples: 100,
|
|
29
|
-
_head: 0, // Next write position
|
|
30
|
-
_count: 0, // Number of valid samples
|
|
31
|
-
_initialized: false,
|
|
32
|
-
|
|
33
|
-
/** Initialize circular buffer (lazy init) */
|
|
34
|
-
_init() {
|
|
35
|
-
if (this._initialized) return;
|
|
36
|
-
this._samples = new Float64Array(this._maxSamples);
|
|
37
|
-
this._initialized = true;
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
/** Record order sent time with high-resolution timestamp */
|
|
41
|
-
recordEntry(orderTag, entryTimeMs) {
|
|
42
|
-
this._pending.set(orderTag, hrNow());
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
/** Record fill received, calculate latency with sub-ms precision */
|
|
46
|
-
recordFill(orderTag) {
|
|
47
|
-
const entryTime = this._pending.get(orderTag);
|
|
48
|
-
if (!entryTime) return null;
|
|
49
|
-
|
|
50
|
-
this._pending.delete(orderTag);
|
|
51
|
-
const latencyNs = hrNow() - entryTime;
|
|
52
|
-
const latencyMs = nsToMs(latencyNs);
|
|
53
|
-
|
|
54
|
-
// Store in circular buffer (no shift, O(1))
|
|
55
|
-
this._init();
|
|
56
|
-
this._samples[this._head] = latencyMs;
|
|
57
|
-
this._head = (this._head + 1) % this._maxSamples;
|
|
58
|
-
if (this._count < this._maxSamples) this._count++;
|
|
59
|
-
|
|
60
|
-
return latencyMs;
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
/** Get average latency */
|
|
64
|
-
getAverage() {
|
|
65
|
-
if (this._count === 0) return null;
|
|
66
|
-
let sum = 0;
|
|
67
|
-
for (let i = 0; i < this._count; i++) {
|
|
68
|
-
sum += this._samples[i];
|
|
69
|
-
}
|
|
70
|
-
return sum / this._count;
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
/** Get min/max/avg stats with high precision */
|
|
74
|
-
getStats() {
|
|
75
|
-
if (this._count === 0) {
|
|
76
|
-
return { min: null, max: null, avg: null, p50: null, p99: null, samples: 0 };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const valid = [];
|
|
80
|
-
for (let i = 0; i < this._count; i++) {
|
|
81
|
-
valid.push(this._samples[i]);
|
|
82
|
-
}
|
|
83
|
-
valid.sort((a, b) => a - b);
|
|
84
|
-
|
|
85
|
-
const sum = valid.reduce((a, b) => a + b, 0);
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
min: valid[0],
|
|
89
|
-
max: valid[valid.length - 1],
|
|
90
|
-
avg: sum / valid.length,
|
|
91
|
-
p50: valid[Math.floor(valid.length * 0.5)],
|
|
92
|
-
p99: valid[Math.floor(valid.length * 0.99)] || valid[valid.length - 1],
|
|
93
|
-
samples: this._count,
|
|
94
|
-
};
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
/** Get last N latency samples */
|
|
98
|
-
getRecent(n = 10) {
|
|
99
|
-
if (this._count === 0) return [];
|
|
100
|
-
const result = [];
|
|
101
|
-
const start = this._count < this._maxSamples ? 0 : this._head;
|
|
102
|
-
for (let i = 0; i < Math.min(n, this._count); i++) {
|
|
103
|
-
const idx = (start + this._count - 1 - i + this._maxSamples) % this._maxSamples;
|
|
104
|
-
result.push(this._samples[idx]);
|
|
105
|
-
}
|
|
106
|
-
return result;
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
/** Clear all tracking data */
|
|
110
|
-
clear() {
|
|
111
|
-
this._pending.clear();
|
|
112
|
-
this._head = 0;
|
|
113
|
-
this._count = 0;
|
|
114
|
-
if (this._samples) {
|
|
115
|
-
this._samples.fill(0);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Pre-allocated fill info pool for zero-allocation in hot path
|
|
122
|
-
*/
|
|
123
|
-
const FillInfoPool = {
|
|
124
|
-
_template: {
|
|
125
|
-
orderTag: null,
|
|
126
|
-
basketId: null,
|
|
127
|
-
orderId: null,
|
|
128
|
-
status: null,
|
|
129
|
-
symbol: null,
|
|
130
|
-
exchange: null,
|
|
131
|
-
accountId: null,
|
|
132
|
-
fillQuantity: 0,
|
|
133
|
-
totalFillQuantity: 0,
|
|
134
|
-
remainingQuantity: 0,
|
|
135
|
-
avgFillPrice: 0,
|
|
136
|
-
lastFillPrice: 0,
|
|
137
|
-
transactionType: 0,
|
|
138
|
-
orderType: 0,
|
|
139
|
-
quantity: 0,
|
|
140
|
-
ssboe: 0,
|
|
141
|
-
usecs: 0,
|
|
142
|
-
localTimestamp: 0,
|
|
143
|
-
roundTripLatencyMs: null,
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
/** Fill template with notification data */
|
|
147
|
-
fill(notif, receiveTime, latency) {
|
|
148
|
-
const o = this._template;
|
|
149
|
-
o.orderTag = notif.userTag || null;
|
|
150
|
-
o.basketId = notif.basketId;
|
|
151
|
-
o.orderId = notif.exchangeOrderId || notif.orderId;
|
|
152
|
-
o.status = notif.status;
|
|
153
|
-
o.symbol = notif.symbol;
|
|
154
|
-
o.exchange = notif.exchange;
|
|
155
|
-
o.accountId = notif.accountId;
|
|
156
|
-
o.fillQuantity = notif.totalFillSize || notif.fillQuantity || 0;
|
|
157
|
-
o.totalFillQuantity = notif.totalFillSize || notif.totalFillQuantity || 0;
|
|
158
|
-
o.remainingQuantity = notif.totalUnfilledSize || notif.remainingQuantity || 0;
|
|
159
|
-
o.avgFillPrice = parseFloat(notif.avgFillPrice || 0);
|
|
160
|
-
o.lastFillPrice = parseFloat(notif.price || notif.fillPrice || 0);
|
|
161
|
-
o.transactionType = notif.transactionType;
|
|
162
|
-
o.orderType = notif.priceType || notif.orderType;
|
|
163
|
-
o.quantity = notif.quantity;
|
|
164
|
-
o.ssboe = notif.ssboe;
|
|
165
|
-
o.usecs = notif.usecs;
|
|
166
|
-
o.localTimestamp = receiveTime;
|
|
167
|
-
o.roundTripLatencyMs = latency;
|
|
168
|
-
return o;
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
/** Create a copy for async operations */
|
|
172
|
-
clone(fillInfo) {
|
|
173
|
-
return { ...fillInfo };
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
module.exports = {
|
|
178
|
-
hrNow,
|
|
179
|
-
nsToMs,
|
|
180
|
-
LatencyTracker,
|
|
181
|
-
FillInfoPool,
|
|
182
|
-
};
|