hedgequantx 2.9.32 → 2.9.34

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.32",
3
+ "version": "2.9.34",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/app.js CHANGED
@@ -77,7 +77,10 @@ const refreshStats = async () => {
77
77
 
78
78
  try {
79
79
  const allAccounts = await connections.getAllAccounts();
80
- const activeAccounts = allAccounts.filter(acc => acc.status === 0);
80
+ // Filter active accounts: status === 0 (ProjectX) OR status === 'active' (Rithmic) OR no status
81
+ const activeAccounts = allAccounts.filter(acc =>
82
+ acc.status === 0 || acc.status === 'active' || acc.status === undefined || acc.status === null
83
+ );
81
84
 
82
85
  let totalBalance = null;
83
86
  let totalPnl = null;
@@ -113,50 +113,75 @@ const testApiKeyConnection = async (agent) => {
113
113
 
114
114
  /**
115
115
  * Test a single agent connection and response format
116
+ * Includes retry logic for non-deterministic agents (like MiniMax)
116
117
  * @param {Object} agent - Agent config { id, provider, modelId, connectionType, apiKey, ... }
118
+ * @param {number} maxRetries - Maximum retry attempts (default 2)
117
119
  * @returns {Promise<Object>} { success, latency, formatValid, error }
118
120
  */
119
- const testAgentConnection = async (agent) => {
120
- const startTime = Date.now();
121
+ const testAgentConnection = async (agent, maxRetries = 2) => {
122
+ let lastResult = null;
121
123
 
122
- try {
123
- // Route based on connection type
124
- if (agent.connectionType === 'apikey') {
125
- return await testApiKeyConnection(agent);
126
- }
127
-
128
- // CLIProxy connection
129
- const result = await cliproxy.chat(agent.provider, agent.modelId, TEST_PROMPT, AGENT_TIMEOUT);
130
- const latency = Date.now() - startTime;
124
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
125
+ const startTime = Date.now();
131
126
 
132
- if (!result.success) {
133
- return {
127
+ try {
128
+ // Route based on connection type
129
+ if (agent.connectionType === 'apikey') {
130
+ lastResult = await testApiKeyConnection(agent);
131
+ } else {
132
+ // CLIProxy connection
133
+ const result = await cliproxy.chat(agent.provider, agent.modelId, TEST_PROMPT, AGENT_TIMEOUT);
134
+ const latency = Date.now() - startTime;
135
+
136
+ if (!result.success) {
137
+ lastResult = {
138
+ success: false,
139
+ latency,
140
+ formatValid: false,
141
+ error: result.error || 'No response from agent'
142
+ };
143
+ } else {
144
+ // Validate response format
145
+ const formatResult = validateResponseFormat(result.content);
146
+
147
+ lastResult = {
148
+ success: formatResult.valid,
149
+ latency,
150
+ formatValid: formatResult.valid,
151
+ error: formatResult.valid ? null : formatResult.error,
152
+ response: result.content
153
+ };
154
+ }
155
+ }
156
+
157
+ // If successful, return immediately
158
+ if (lastResult.success) {
159
+ return lastResult;
160
+ }
161
+
162
+ // Only retry on format errors (not connection errors)
163
+ if (lastResult.error && !lastResult.error.includes('valid JSON') &&
164
+ !lastResult.error.includes('Missing') && !lastResult.error.includes('Invalid')) {
165
+ // Connection error - don't retry
166
+ return lastResult;
167
+ }
168
+
169
+ // Wait a bit before retry
170
+ if (attempt < maxRetries) {
171
+ await new Promise(r => setTimeout(r, 500));
172
+ }
173
+
174
+ } catch (error) {
175
+ lastResult = {
134
176
  success: false,
135
- latency,
177
+ latency: Date.now() - startTime,
136
178
  formatValid: false,
137
- error: result.error || 'No response from agent'
179
+ error: error.message || 'Connection failed'
138
180
  };
139
181
  }
140
-
141
- // Validate response format
142
- const formatResult = validateResponseFormat(result.content);
143
-
144
- return {
145
- success: formatResult.valid,
146
- latency,
147
- formatValid: formatResult.valid,
148
- error: formatResult.valid ? null : formatResult.error,
149
- response: result.content
150
- };
151
-
152
- } catch (error) {
153
- return {
154
- success: false,
155
- latency: Date.now() - startTime,
156
- formatValid: false,
157
- error: error.message || 'Connection failed'
158
- };
159
182
  }
183
+
184
+ return lastResult;
160
185
  };
161
186
 
162
187
  /**
@@ -37,8 +37,13 @@ const extractJSON = (text) => {
37
37
  } catch (e) { /* continue */ }
38
38
  }
39
39
 
40
- // Remove <think>...</think> tags (MiniMax)
41
- let cleaned = text.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
40
+ // Remove <think>...</think> tags (MiniMax) - multiple patterns
41
+ let cleaned = text
42
+ .replace(/<think>[\s\S]*?<\/think>/gi, '')
43
+ .replace(/<thinking>[\s\S]*?<\/thinking>/gi, '')
44
+ .replace(/<reasoning>[\s\S]*?<\/reasoning>/gi, '')
45
+ .replace(/<analysis>[\s\S]*?<\/analysis>/gi, '')
46
+ .trim();
42
47
 
43
48
  // Try to parse cleaned text
44
49
  try {
@@ -50,14 +55,37 @@ const extractJSON = (text) => {
50
55
  if (startIdx !== -1) {
51
56
  let depth = 0;
52
57
  let endIdx = -1;
58
+ let inString = false;
59
+ let escapeNext = false;
60
+
53
61
  for (let i = startIdx; i < cleaned.length; i++) {
54
- if (cleaned[i] === '{') depth++;
55
- if (cleaned[i] === '}') depth--;
56
- if (depth === 0) {
57
- endIdx = i;
58
- break;
62
+ const char = cleaned[i];
63
+
64
+ if (escapeNext) {
65
+ escapeNext = false;
66
+ continue;
67
+ }
68
+
69
+ if (char === '\\') {
70
+ escapeNext = true;
71
+ continue;
72
+ }
73
+
74
+ if (char === '"') {
75
+ inString = !inString;
76
+ continue;
77
+ }
78
+
79
+ if (!inString) {
80
+ if (char === '{') depth++;
81
+ if (char === '}') depth--;
82
+ if (depth === 0) {
83
+ endIdx = i;
84
+ break;
85
+ }
59
86
  }
60
87
  }
88
+
61
89
  if (endIdx !== -1) {
62
90
  try {
63
91
  return JSON.parse(cleaned.substring(startIdx, endIdx + 1));
@@ -65,6 +93,19 @@ const extractJSON = (text) => {
65
93
  }
66
94
  }
67
95
 
96
+ // Try to extract just decision/confidence/reason pattern
97
+ const decisionMatch = cleaned.match(/"decision"\s*:\s*"(approve|reject|modify)"/i);
98
+ const confidenceMatch = cleaned.match(/"confidence"\s*:\s*(\d+)/);
99
+ const reasonMatch = cleaned.match(/"reason"\s*:\s*"([^"]+)"/);
100
+
101
+ if (decisionMatch && confidenceMatch) {
102
+ return {
103
+ decision: decisionMatch[1].toLowerCase(),
104
+ confidence: parseInt(confidenceMatch[1]),
105
+ reason: reasonMatch ? reasonMatch[1] : 'Extracted from partial response'
106
+ };
107
+ }
108
+
68
109
  // Last resort: try to find JSON object pattern with "decision"
69
110
  const jsonMatch = cleaned.match(/\{[^{}]*"decision"[^{}]*\}/);
70
111
  if (jsonMatch) {
@@ -292,33 +292,64 @@ const getOrderHistory = async (service, date) => {
292
292
  };
293
293
 
294
294
  /**
295
- * Get full trade history for multiple dates
295
+ * Get full trade history (fills) from ORDER_PLANT
296
296
  * @param {RithmicService} service - The Rithmic service instance
297
- * @param {number} days - Number of days to fetch (default 30)
297
+ * @param {number} days - Number of days to fetch (default 7, max 14)
298
298
  * @returns {Promise<{success: boolean, trades: Array}>}
299
299
  */
300
- const getTradeHistoryFull = async (service, days = 30) => {
300
+ const getTradeHistoryFull = async (service, days = 7) => {
301
301
  if (!service.orderConn || !service.loginInfo) {
302
302
  return { success: false, trades: [] };
303
303
  }
304
304
 
305
- // Get available dates
306
- const { dates } = await getOrderHistoryDates(service);
305
+ // Get available dates with timeout
306
+ let dates;
307
+ try {
308
+ const datesPromise = getOrderHistoryDates(service);
309
+ const timeoutPromise = new Promise((_, reject) =>
310
+ setTimeout(() => reject(new Error('Timeout')), 5000)
311
+ );
312
+ const result = await Promise.race([datesPromise, timeoutPromise]);
313
+ dates = result.dates;
314
+ } catch (e) {
315
+ return { success: true, trades: [] };
316
+ }
317
+
307
318
  if (!dates || dates.length === 0) {
308
319
  return { success: true, trades: [] };
309
320
  }
310
321
 
311
- // Sort dates descending and limit to requested days
312
- const sortedDates = dates.sort((a, b) => b.localeCompare(a)).slice(0, days);
322
+ // Filter to recent dates only (last N days from today)
323
+ const today = new Date();
324
+ const cutoffDate = new Date(today.getTime() - (Math.min(days, 14) * 24 * 60 * 60 * 1000));
325
+ const cutoffStr = cutoffDate.toISOString().slice(0, 10).replace(/-/g, '');
326
+
327
+ // Sort dates descending and filter to recent only
328
+ const recentDates = dates
329
+ .filter(d => d >= cutoffStr)
330
+ .sort((a, b) => b.localeCompare(a))
331
+ .slice(0, 7); // Max 7 dates to avoid long waits
332
+
333
+ if (recentDates.length === 0) {
334
+ return { success: true, trades: [] };
335
+ }
313
336
 
314
337
  const allTrades = [];
315
338
 
316
- // Fetch history for each date
317
- for (const date of sortedDates) {
318
- const { orders } = await getOrderHistory(service, date);
319
- // Filter only fills (notifyType 5)
320
- const fills = orders.filter(o => o.notifyType === 5 || o.fillPrice);
321
- allTrades.push(...fills);
339
+ // Fetch history for each date with short timeout
340
+ for (const date of recentDates) {
341
+ try {
342
+ const histPromise = getOrderHistory(service, date);
343
+ const timeoutPromise = new Promise((resolve) =>
344
+ setTimeout(() => resolve({ orders: [] }), 3000)
345
+ );
346
+ const { orders } = await Promise.race([histPromise, timeoutPromise]);
347
+ // Filter only fills (notifyType 5)
348
+ const fills = (orders || []).filter(o => o.notifyType === 5 || o.fillPrice);
349
+ allTrades.push(...fills);
350
+ } catch (e) {
351
+ // Skip failed dates
352
+ }
322
353
  }
323
354
 
324
355
  return { success: true, trades: allTrades };