hedgequantx 2.6.160 → 2.6.162
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/menus/ai-agent-connect.js +181 -0
- package/src/menus/ai-agent-models.js +219 -0
- package/src/menus/ai-agent-oauth.js +292 -0
- package/src/menus/ai-agent-ui.js +141 -0
- package/src/menus/ai-agent.js +88 -1489
- package/src/pages/algo/copy-engine.js +449 -0
- package/src/pages/algo/copy-trading.js +11 -543
- package/src/pages/algo/smart-logs-data.js +218 -0
- package/src/pages/algo/smart-logs.js +9 -214
- package/src/pages/algo/ui-constants.js +144 -0
- package/src/pages/algo/ui-summary.js +184 -0
- package/src/pages/algo/ui.js +42 -526
- package/src/pages/stats-calculations.js +191 -0
- package/src/pages/stats-ui.js +381 -0
- package/src/pages/stats.js +14 -507
- package/src/services/ai/client-analysis.js +194 -0
- package/src/services/ai/client-models.js +333 -0
- package/src/services/ai/client.js +6 -489
- package/src/services/ai/index.js +2 -257
- package/src/services/ai/proxy-install.js +249 -0
- package/src/services/ai/proxy-manager.js +29 -411
- package/src/services/ai/proxy-remote.js +161 -0
- package/src/services/ai/strategy-supervisor.js +10 -765
- package/src/services/ai/supervisor-data.js +195 -0
- package/src/services/ai/supervisor-optimize.js +215 -0
- package/src/services/ai/supervisor-sync.js +178 -0
- package/src/services/ai/supervisor-utils.js +158 -0
- package/src/services/ai/supervisor.js +50 -515
- package/src/services/ai/validation.js +250 -0
- package/src/services/hqx-server-events.js +110 -0
- package/src/services/hqx-server-handlers.js +217 -0
- package/src/services/hqx-server-latency.js +136 -0
- package/src/services/hqx-server.js +51 -403
- package/src/services/position-constants.js +28 -0
- package/src/services/position-manager.js +105 -554
- package/src/services/position-momentum.js +206 -0
- package/src/services/projectx/accounts.js +142 -0
- package/src/services/projectx/index.js +40 -289
- package/src/services/projectx/trading.js +180 -0
- package/src/services/rithmic/handlers.js +2 -208
- package/src/services/rithmic/index.js +32 -542
- package/src/services/rithmic/latency-tracker.js +182 -0
- package/src/services/rithmic/specs.js +146 -0
- package/src/services/rithmic/trade-history.js +254 -0
|
@@ -17,535 +17,19 @@ const fs = require('fs');
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const os = require('os');
|
|
19
19
|
const { analyzePerformance, getMarketAdvice, callAI } = require('./client');
|
|
20
|
+
const {
|
|
21
|
+
calculateTrendStrength, calculateRecentRange, calculatePriceVelocity,
|
|
22
|
+
getMarketSession, getMinutesSinceOpen, analyzePriceLevelInteraction,
|
|
23
|
+
determineLevelType, analyzePriceAction, analyzeVolume, calculateVolatility
|
|
24
|
+
} = require('./supervisor-utils');
|
|
20
25
|
|
|
21
26
|
// Path for persisted learning data
|
|
22
|
-
const DATA_DIR = path.join(os.homedir(), '.hqx');
|
|
23
|
-
const LEARNING_FILE = path.join(DATA_DIR, 'ai-learning.json');
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
if (!fs.existsSync(DATA_DIR)) {
|
|
32
|
-
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (fs.existsSync(LEARNING_FILE)) {
|
|
36
|
-
const data = JSON.parse(fs.readFileSync(LEARNING_FILE, 'utf8'));
|
|
37
|
-
|
|
38
|
-
// Clean old sessions (keep only last 1 month / 31 days)
|
|
39
|
-
const oneMonthAgo = Date.now() - (31 * 24 * 60 * 60 * 1000);
|
|
40
|
-
const sessions = (data.sessions || []).filter(s =>
|
|
41
|
-
new Date(s.date).getTime() > oneMonthAgo
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
// Pattern memory
|
|
46
|
-
winningPatterns: data.winningPatterns || [],
|
|
47
|
-
losingPatterns: data.losingPatterns || [],
|
|
48
|
-
|
|
49
|
-
// Optimization history
|
|
50
|
-
optimizations: data.optimizations || [],
|
|
51
|
-
|
|
52
|
-
// Symbol-specific data (NQ, ES, etc.)
|
|
53
|
-
symbols: data.symbols || {},
|
|
54
|
-
|
|
55
|
-
// Full session history (last 30 days)
|
|
56
|
-
sessions: sessions,
|
|
57
|
-
|
|
58
|
-
// Hourly performance analysis
|
|
59
|
-
hourlyStats: data.hourlyStats || {},
|
|
60
|
-
|
|
61
|
-
// Day of week analysis
|
|
62
|
-
dayOfWeekStats: data.dayOfWeekStats || {},
|
|
63
|
-
|
|
64
|
-
// Strategy behavior profile
|
|
65
|
-
strategyProfile: data.strategyProfile || {
|
|
66
|
-
bestHours: [],
|
|
67
|
-
worstHours: [],
|
|
68
|
-
avgWinStreak: 0,
|
|
69
|
-
avgLossStreak: 0,
|
|
70
|
-
preferredConditions: null
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
// Lifetime stats
|
|
74
|
-
totalSessions: data.totalSessions || 0,
|
|
75
|
-
totalTrades: data.totalTrades || 0,
|
|
76
|
-
totalWins: data.totalWins || 0,
|
|
77
|
-
totalLosses: data.totalLosses || 0,
|
|
78
|
-
lifetimePnL: data.lifetimePnL || 0,
|
|
79
|
-
|
|
80
|
-
lastUpdated: data.lastUpdated || null,
|
|
81
|
-
firstSession: data.firstSession || null
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
} catch (e) {
|
|
85
|
-
// Silent fail - start fresh
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
winningPatterns: [],
|
|
90
|
-
losingPatterns: [],
|
|
91
|
-
optimizations: [],
|
|
92
|
-
symbols: {},
|
|
93
|
-
sessions: [],
|
|
94
|
-
hourlyStats: {},
|
|
95
|
-
dayOfWeekStats: {},
|
|
96
|
-
strategyProfile: {
|
|
97
|
-
bestHours: [],
|
|
98
|
-
worstHours: [],
|
|
99
|
-
avgWinStreak: 0,
|
|
100
|
-
avgLossStreak: 0,
|
|
101
|
-
preferredConditions: null
|
|
102
|
-
},
|
|
103
|
-
totalSessions: 0,
|
|
104
|
-
totalTrades: 0,
|
|
105
|
-
totalWins: 0,
|
|
106
|
-
totalLosses: 0,
|
|
107
|
-
lifetimePnL: 0,
|
|
108
|
-
lastUpdated: null,
|
|
109
|
-
firstSession: null
|
|
110
|
-
};
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Get or create symbol data structure
|
|
115
|
-
*/
|
|
116
|
-
const getSymbolData = (symbolName) => {
|
|
117
|
-
const data = loadLearningData();
|
|
118
|
-
if (!data.symbols[symbolName]) {
|
|
119
|
-
return {
|
|
120
|
-
name: symbolName,
|
|
121
|
-
levels: [], // Key price levels
|
|
122
|
-
sessions: [], // Trading sessions history
|
|
123
|
-
patterns: [], // Symbol-specific patterns
|
|
124
|
-
stats: {
|
|
125
|
-
trades: 0,
|
|
126
|
-
wins: 0,
|
|
127
|
-
losses: 0,
|
|
128
|
-
pnl: 0
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
return data.symbols[symbolName];
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Record a key price level for a symbol
|
|
137
|
-
* Called when trades happen at significant levels
|
|
138
|
-
*/
|
|
139
|
-
const recordPriceLevel = (symbolName, price, type, outcome) => {
|
|
140
|
-
// type: 'support', 'resistance', 'breakout', 'rejection'
|
|
141
|
-
// outcome: 'win', 'loss', 'neutral'
|
|
142
|
-
|
|
143
|
-
const level = {
|
|
144
|
-
price: Math.round(price * 4) / 4, // Round to nearest 0.25
|
|
145
|
-
type,
|
|
146
|
-
outcome,
|
|
147
|
-
timestamp: Date.now(),
|
|
148
|
-
date: new Date().toISOString().split('T')[0],
|
|
149
|
-
hour: new Date().getHours()
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
if (!supervisorState.currentSymbol) {
|
|
153
|
-
supervisorState.currentSymbol = symbolName;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!supervisorState.symbolLevels) {
|
|
157
|
-
supervisorState.symbolLevels = [];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Check if level already exists (within 2 ticks)
|
|
161
|
-
const tickSize = symbolName.includes('NQ') ? 0.25 : 0.25;
|
|
162
|
-
const existing = supervisorState.symbolLevels.find(l =>
|
|
163
|
-
Math.abs(l.price - level.price) <= tickSize * 2
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
if (existing) {
|
|
167
|
-
// Update existing level
|
|
168
|
-
existing.touches = (existing.touches || 1) + 1;
|
|
169
|
-
existing.lastTouch = Date.now();
|
|
170
|
-
existing.outcomes = existing.outcomes || [];
|
|
171
|
-
existing.outcomes.push(outcome);
|
|
172
|
-
} else {
|
|
173
|
-
// New level
|
|
174
|
-
level.touches = 1;
|
|
175
|
-
level.outcomes = [outcome];
|
|
176
|
-
supervisorState.symbolLevels.push(level);
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Analyze current price against known levels
|
|
182
|
-
* Returns nearby important levels
|
|
183
|
-
*/
|
|
184
|
-
const analyzeNearbyLevels = (symbolName, currentPrice) => {
|
|
185
|
-
const data = loadLearningData();
|
|
186
|
-
const symbolData = data.symbols[symbolName];
|
|
187
|
-
|
|
188
|
-
if (!symbolData || !symbolData.levels || symbolData.levels.length === 0) {
|
|
189
|
-
return { nearbyLevels: [], message: 'No historical levels' };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const tickSize = symbolName.includes('NQ') ? 0.25 : 0.25;
|
|
193
|
-
const range = tickSize * 20; // Look within 20 ticks
|
|
194
|
-
|
|
195
|
-
const nearbyLevels = symbolData.levels
|
|
196
|
-
.filter(l => Math.abs(l.price - currentPrice) <= range)
|
|
197
|
-
.sort((a, b) => Math.abs(a.price - currentPrice) - Math.abs(b.price - currentPrice))
|
|
198
|
-
.slice(0, 5) // Top 5 nearest levels
|
|
199
|
-
.map(l => {
|
|
200
|
-
const winRate = l.outcomes ?
|
|
201
|
-
l.outcomes.filter(o => o === 'win').length / l.outcomes.length : 0;
|
|
202
|
-
return {
|
|
203
|
-
price: l.price,
|
|
204
|
-
distance: Math.round((l.price - currentPrice) / tickSize),
|
|
205
|
-
type: l.type,
|
|
206
|
-
touches: l.touches || 1,
|
|
207
|
-
winRate: Math.round(winRate * 100),
|
|
208
|
-
direction: l.price > currentPrice ? 'above' : 'below'
|
|
209
|
-
};
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
nearbyLevels,
|
|
214
|
-
message: nearbyLevels.length > 0 ?
|
|
215
|
-
`${nearbyLevels.length} known levels nearby` :
|
|
216
|
-
'No known levels nearby'
|
|
217
|
-
};
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Save learning data to disk
|
|
222
|
-
* Full memory of strategy behavior over 1 month
|
|
223
|
-
*/
|
|
224
|
-
const saveLearningData = () => {
|
|
225
|
-
try {
|
|
226
|
-
if (!fs.existsSync(DATA_DIR)) {
|
|
227
|
-
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Load existing data first
|
|
231
|
-
const existing = loadLearningData();
|
|
232
|
-
|
|
233
|
-
const now = new Date();
|
|
234
|
-
const currentHour = now.getHours();
|
|
235
|
-
const currentDay = now.getDay(); // 0=Sunday, 1=Monday, etc.
|
|
236
|
-
const dayNames = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
|
237
|
-
|
|
238
|
-
// Merge symbol data
|
|
239
|
-
const symbols = { ...existing.symbols };
|
|
240
|
-
if (supervisorState.currentSymbol) {
|
|
241
|
-
const sym = supervisorState.currentSymbol;
|
|
242
|
-
if (!symbols[sym]) {
|
|
243
|
-
symbols[sym] = {
|
|
244
|
-
name: sym,
|
|
245
|
-
levels: [],
|
|
246
|
-
sessions: [],
|
|
247
|
-
stats: { trades: 0, wins: 0, losses: 0, pnl: 0 },
|
|
248
|
-
hourlyStats: {},
|
|
249
|
-
dayOfWeekStats: {}
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Merge levels - keep last 50 most important
|
|
254
|
-
const existingLevels = symbols[sym].levels || [];
|
|
255
|
-
const newLevels = supervisorState.symbolLevels || [];
|
|
256
|
-
symbols[sym].levels = mergeLevels(existingLevels, newLevels, 50);
|
|
257
|
-
|
|
258
|
-
// Add current session summary
|
|
259
|
-
symbols[sym].sessions.push({
|
|
260
|
-
date: now.toISOString(),
|
|
261
|
-
hour: currentHour,
|
|
262
|
-
dayOfWeek: dayNames[currentDay],
|
|
263
|
-
trades: supervisorState.performance.trades,
|
|
264
|
-
wins: supervisorState.performance.wins,
|
|
265
|
-
losses: supervisorState.performance.losses,
|
|
266
|
-
pnl: supervisorState.performance.totalPnL,
|
|
267
|
-
levelsWorked: newLevels.length,
|
|
268
|
-
maxWinStreak: supervisorState.performance.maxWinStreak,
|
|
269
|
-
maxLossStreak: supervisorState.performance.maxLossStreak
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// Keep only last 31 days of sessions per symbol
|
|
273
|
-
const oneMonthAgo = Date.now() - (31 * 24 * 60 * 60 * 1000);
|
|
274
|
-
symbols[sym].sessions = symbols[sym].sessions.filter(s =>
|
|
275
|
-
new Date(s.date).getTime() > oneMonthAgo
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
// Update symbol stats
|
|
279
|
-
symbols[sym].stats.trades += supervisorState.performance.trades;
|
|
280
|
-
symbols[sym].stats.wins += supervisorState.performance.wins;
|
|
281
|
-
symbols[sym].stats.losses += supervisorState.performance.losses;
|
|
282
|
-
symbols[sym].stats.pnl += supervisorState.performance.totalPnL;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Update hourly stats (which hours perform best)
|
|
286
|
-
const hourlyStats = { ...existing.hourlyStats };
|
|
287
|
-
const hourKey = String(currentHour);
|
|
288
|
-
if (!hourlyStats[hourKey]) {
|
|
289
|
-
hourlyStats[hourKey] = { trades: 0, wins: 0, losses: 0, pnl: 0 };
|
|
290
|
-
}
|
|
291
|
-
hourlyStats[hourKey].trades += supervisorState.performance.trades;
|
|
292
|
-
hourlyStats[hourKey].wins += supervisorState.performance.wins;
|
|
293
|
-
hourlyStats[hourKey].losses += supervisorState.performance.losses;
|
|
294
|
-
hourlyStats[hourKey].pnl += supervisorState.performance.totalPnL;
|
|
295
|
-
|
|
296
|
-
// Update day of week stats
|
|
297
|
-
const dayOfWeekStats = { ...existing.dayOfWeekStats };
|
|
298
|
-
const dayKey = dayNames[currentDay];
|
|
299
|
-
if (!dayOfWeekStats[dayKey]) {
|
|
300
|
-
dayOfWeekStats[dayKey] = { trades: 0, wins: 0, losses: 0, pnl: 0 };
|
|
301
|
-
}
|
|
302
|
-
dayOfWeekStats[dayKey].trades += supervisorState.performance.trades;
|
|
303
|
-
dayOfWeekStats[dayKey].wins += supervisorState.performance.wins;
|
|
304
|
-
dayOfWeekStats[dayKey].losses += supervisorState.performance.losses;
|
|
305
|
-
dayOfWeekStats[dayKey].pnl += supervisorState.performance.totalPnL;
|
|
306
|
-
|
|
307
|
-
// Build strategy profile from data
|
|
308
|
-
const strategyProfile = buildStrategyProfile(hourlyStats, dayOfWeekStats, existing.sessions);
|
|
309
|
-
|
|
310
|
-
// Current session record
|
|
311
|
-
const currentSession = {
|
|
312
|
-
date: now.toISOString(),
|
|
313
|
-
symbol: supervisorState.currentSymbol,
|
|
314
|
-
hour: currentHour,
|
|
315
|
-
dayOfWeek: dayNames[currentDay],
|
|
316
|
-
trades: supervisorState.performance.trades,
|
|
317
|
-
wins: supervisorState.performance.wins,
|
|
318
|
-
losses: supervisorState.performance.losses,
|
|
319
|
-
pnl: supervisorState.performance.totalPnL,
|
|
320
|
-
maxWinStreak: supervisorState.performance.maxWinStreak,
|
|
321
|
-
maxLossStreak: supervisorState.performance.maxLossStreak,
|
|
322
|
-
optimizationsApplied: supervisorState.optimizations.length,
|
|
323
|
-
levelsLearned: (supervisorState.symbolLevels || []).length
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
// Merge sessions (keep 1 month)
|
|
327
|
-
const oneMonthAgo = Date.now() - (31 * 24 * 60 * 60 * 1000);
|
|
328
|
-
const sessions = [...existing.sessions, currentSession].filter(s =>
|
|
329
|
-
new Date(s.date).getTime() > oneMonthAgo
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
// Build data to save
|
|
333
|
-
const dataToSave = {
|
|
334
|
-
// Patterns - keep last 100 of each type
|
|
335
|
-
winningPatterns: mergePatterns(existing.winningPatterns, supervisorState.winningPatterns, 100),
|
|
336
|
-
losingPatterns: mergePatterns(existing.losingPatterns, supervisorState.losingPatterns, 100),
|
|
337
|
-
|
|
338
|
-
// Optimizations history - keep last 50
|
|
339
|
-
optimizations: [...existing.optimizations, ...supervisorState.optimizations].slice(-50),
|
|
340
|
-
|
|
341
|
-
// Symbol-specific data
|
|
342
|
-
symbols,
|
|
343
|
-
|
|
344
|
-
// Full session history (1 month)
|
|
345
|
-
sessions,
|
|
346
|
-
|
|
347
|
-
// Performance by hour and day
|
|
348
|
-
hourlyStats,
|
|
349
|
-
dayOfWeekStats,
|
|
350
|
-
|
|
351
|
-
// Strategy behavior profile
|
|
352
|
-
strategyProfile,
|
|
353
|
-
|
|
354
|
-
// Lifetime stats
|
|
355
|
-
totalSessions: existing.totalSessions + 1,
|
|
356
|
-
totalTrades: existing.totalTrades + supervisorState.performance.trades,
|
|
357
|
-
totalWins: existing.totalWins + supervisorState.performance.wins,
|
|
358
|
-
totalLosses: existing.totalLosses + supervisorState.performance.losses,
|
|
359
|
-
lifetimePnL: existing.lifetimePnL + supervisorState.performance.totalPnL,
|
|
360
|
-
|
|
361
|
-
// Metadata
|
|
362
|
-
firstSession: existing.firstSession || now.toISOString(),
|
|
363
|
-
lastUpdated: now.toISOString()
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
fs.writeFileSync(LEARNING_FILE, JSON.stringify(dataToSave, null, 2));
|
|
367
|
-
return true;
|
|
368
|
-
} catch (e) {
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Build strategy profile from historical data
|
|
375
|
-
* Identifies best/worst hours, days, and patterns
|
|
376
|
-
*/
|
|
377
|
-
const buildStrategyProfile = (hourlyStats, dayOfWeekStats, sessions) => {
|
|
378
|
-
// Find best and worst hours
|
|
379
|
-
const hours = Object.entries(hourlyStats)
|
|
380
|
-
.map(([hour, stats]) => ({
|
|
381
|
-
hour: parseInt(hour),
|
|
382
|
-
winRate: stats.trades > 0 ? (stats.wins / stats.trades) * 100 : 0,
|
|
383
|
-
pnl: stats.pnl,
|
|
384
|
-
trades: stats.trades
|
|
385
|
-
}))
|
|
386
|
-
.filter(h => h.trades >= 3) // Need at least 3 trades to be significant
|
|
387
|
-
.sort((a, b) => b.winRate - a.winRate);
|
|
388
|
-
|
|
389
|
-
const bestHours = hours.slice(0, 3).map(h => h.hour);
|
|
390
|
-
const worstHours = hours.slice(-3).map(h => h.hour);
|
|
391
|
-
|
|
392
|
-
// Find best and worst days
|
|
393
|
-
const days = Object.entries(dayOfWeekStats)
|
|
394
|
-
.map(([day, stats]) => ({
|
|
395
|
-
day,
|
|
396
|
-
winRate: stats.trades > 0 ? (stats.wins / stats.trades) * 100 : 0,
|
|
397
|
-
pnl: stats.pnl,
|
|
398
|
-
trades: stats.trades
|
|
399
|
-
}))
|
|
400
|
-
.filter(d => d.trades >= 3)
|
|
401
|
-
.sort((a, b) => b.winRate - a.winRate);
|
|
402
|
-
|
|
403
|
-
const bestDays = days.slice(0, 2).map(d => d.day);
|
|
404
|
-
const worstDays = days.slice(-2).map(d => d.day);
|
|
405
|
-
|
|
406
|
-
// Calculate average streaks from sessions
|
|
407
|
-
let totalWinStreaks = 0, totalLossStreaks = 0, streakCount = 0;
|
|
408
|
-
for (const session of sessions) {
|
|
409
|
-
if (session.maxWinStreak) totalWinStreaks += session.maxWinStreak;
|
|
410
|
-
if (session.maxLossStreak) totalLossStreaks += session.maxLossStreak;
|
|
411
|
-
streakCount++;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return {
|
|
415
|
-
bestHours,
|
|
416
|
-
worstHours,
|
|
417
|
-
bestDays,
|
|
418
|
-
worstDays,
|
|
419
|
-
avgWinStreak: streakCount > 0 ? Math.round(totalWinStreaks / streakCount * 10) / 10 : 0,
|
|
420
|
-
avgLossStreak: streakCount > 0 ? Math.round(totalLossStreaks / streakCount * 10) / 10 : 0,
|
|
421
|
-
totalSessionsAnalyzed: sessions.length
|
|
422
|
-
};
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Merge pattern arrays, keeping most recent unique entries
|
|
427
|
-
*/
|
|
428
|
-
const mergePatterns = (existing, current, maxCount) => {
|
|
429
|
-
const merged = [...existing, ...current];
|
|
430
|
-
|
|
431
|
-
// Sort by timestamp descending (most recent first)
|
|
432
|
-
merged.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
433
|
-
|
|
434
|
-
// Keep only maxCount most recent
|
|
435
|
-
return merged.slice(0, maxCount);
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Merge price levels, prioritizing most touched and most recent
|
|
440
|
-
*/
|
|
441
|
-
const mergeLevels = (existing, current, maxCount) => {
|
|
442
|
-
const merged = [...existing];
|
|
443
|
-
|
|
444
|
-
for (const level of current) {
|
|
445
|
-
const tickSize = 0.25;
|
|
446
|
-
const existingIdx = merged.findIndex(l =>
|
|
447
|
-
Math.abs(l.price - level.price) <= tickSize * 2
|
|
448
|
-
);
|
|
449
|
-
|
|
450
|
-
if (existingIdx >= 0) {
|
|
451
|
-
// Update existing level
|
|
452
|
-
merged[existingIdx].touches = (merged[existingIdx].touches || 1) + (level.touches || 1);
|
|
453
|
-
merged[existingIdx].lastTouch = Math.max(merged[existingIdx].lastTouch || 0, level.lastTouch || level.timestamp || 0);
|
|
454
|
-
merged[existingIdx].outcomes = [...(merged[existingIdx].outcomes || []), ...(level.outcomes || [])].slice(-20);
|
|
455
|
-
} else {
|
|
456
|
-
merged.push(level);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Sort by importance (touches * recency)
|
|
461
|
-
const now = Date.now();
|
|
462
|
-
merged.sort((a, b) => {
|
|
463
|
-
const scoreA = (a.touches || 1) * (1 / (1 + (now - (a.lastTouch || a.timestamp || 0)) / 86400000));
|
|
464
|
-
const scoreB = (b.touches || 1) * (1 / (1 + (now - (b.lastTouch || b.timestamp || 0)) / 86400000));
|
|
465
|
-
return scoreB - scoreA;
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
return merged.slice(0, maxCount);
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* DEPRECATED - moved inline
|
|
473
|
-
*/
|
|
474
|
-
const mergePatternsDEP = (existing, current, maxCount) => {
|
|
475
|
-
const merged = [...existing, ...current];
|
|
476
|
-
merged.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
477
|
-
|
|
478
|
-
// Keep only maxCount most recent
|
|
479
|
-
return merged.slice(0, maxCount);
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
// Singleton supervisor state
|
|
483
|
-
let supervisorState = {
|
|
484
|
-
active: false,
|
|
485
|
-
agents: [],
|
|
486
|
-
strategy: null,
|
|
487
|
-
service: null,
|
|
488
|
-
accountId: null,
|
|
489
|
-
|
|
490
|
-
// Current symbol being traded
|
|
491
|
-
currentSymbol: null,
|
|
492
|
-
|
|
493
|
-
// Real-time data (synced with strategy)
|
|
494
|
-
ticks: [],
|
|
495
|
-
signals: [],
|
|
496
|
-
trades: [],
|
|
497
|
-
|
|
498
|
-
// Symbol-specific levels learned this session
|
|
499
|
-
symbolLevels: [],
|
|
500
|
-
|
|
501
|
-
// Learning data
|
|
502
|
-
winningPatterns: [],
|
|
503
|
-
losingPatterns: [],
|
|
504
|
-
|
|
505
|
-
// Performance tracking
|
|
506
|
-
performance: {
|
|
507
|
-
trades: 0,
|
|
508
|
-
wins: 0,
|
|
509
|
-
losses: 0,
|
|
510
|
-
totalPnL: 0,
|
|
511
|
-
maxDrawdown: 0,
|
|
512
|
-
currentDrawdown: 0,
|
|
513
|
-
peakPnL: 0,
|
|
514
|
-
winStreak: 0,
|
|
515
|
-
lossStreak: 0,
|
|
516
|
-
maxWinStreak: 0,
|
|
517
|
-
maxLossStreak: 0
|
|
518
|
-
},
|
|
519
|
-
|
|
520
|
-
// Optimization state
|
|
521
|
-
optimizations: [],
|
|
522
|
-
lastOptimizationTime: 0,
|
|
523
|
-
optimizationInterval: 60000, // Analyze every 60 seconds
|
|
524
|
-
|
|
525
|
-
// Current recommendations
|
|
526
|
-
currentAdvice: {
|
|
527
|
-
action: 'NORMAL',
|
|
528
|
-
sizeMultiplier: 1.0,
|
|
529
|
-
reason: 'Starting'
|
|
530
|
-
},
|
|
531
|
-
|
|
532
|
-
// Behavior history for graph (action over time)
|
|
533
|
-
// Values: 0=PAUSE, 1=CAUTIOUS, 2=NORMAL, 3=AGGRESSIVE
|
|
534
|
-
behaviorHistory: [],
|
|
535
|
-
behaviorStartTime: null,
|
|
536
|
-
|
|
537
|
-
// Lifetime stats loaded from previous sessions
|
|
538
|
-
lifetimeStats: null,
|
|
539
|
-
|
|
540
|
-
// Previous sessions memory (loaded on init)
|
|
541
|
-
previousSessions: [],
|
|
542
|
-
|
|
543
|
-
// Hourly performance tracking
|
|
544
|
-
hourlyPerformance: {}
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
// Analysis interval
|
|
548
|
-
let analysisInterval = null;
|
|
28
|
+
const {
|
|
29
|
+
DATA_DIR, LEARNING_FILE, loadLearningData, getSymbolData,
|
|
30
|
+
recordPriceLevel, analyzeNearbyLevels, saveLearningData,
|
|
31
|
+
buildStrategyProfile, mergePatterns, mergeLevels, clearLearningData
|
|
32
|
+
} = require('./supervisor-data');
|
|
549
33
|
|
|
550
34
|
/**
|
|
551
35
|
* Initialize supervisor with strategy and agents
|
|
@@ -946,168 +430,6 @@ const feedTradeResult = (trade) => {
|
|
|
946
430
|
}
|
|
947
431
|
};
|
|
948
432
|
|
|
949
|
-
/**
|
|
950
|
-
* Calculate trend strength from ticks
|
|
951
|
-
* Compares short-term vs medium-term trend
|
|
952
|
-
*/
|
|
953
|
-
const calculateTrendStrength = (ticks) => {
|
|
954
|
-
if (!ticks || ticks.length < 20) return { strength: 0, direction: 'unknown' };
|
|
955
|
-
|
|
956
|
-
const prices = ticks.map(t => t.price).filter(Boolean);
|
|
957
|
-
if (prices.length < 20) return { strength: 0, direction: 'unknown' };
|
|
958
|
-
|
|
959
|
-
// Short-term trend (last 20)
|
|
960
|
-
const shortTerm = prices.slice(-20);
|
|
961
|
-
const shortChange = shortTerm[shortTerm.length - 1] - shortTerm[0];
|
|
962
|
-
|
|
963
|
-
// Medium-term trend (last 50 or all)
|
|
964
|
-
const mediumTerm = prices.slice(-50);
|
|
965
|
-
const mediumChange = mediumTerm[mediumTerm.length - 1] - mediumTerm[0];
|
|
966
|
-
|
|
967
|
-
// Strength = how aligned are short and medium trends
|
|
968
|
-
const aligned = (shortChange > 0 && mediumChange > 0) || (shortChange < 0 && mediumChange < 0);
|
|
969
|
-
const avgRange = Math.abs(Math.max(...prices) - Math.min(...prices)) || 1;
|
|
970
|
-
|
|
971
|
-
return {
|
|
972
|
-
strength: aligned ? Math.min(1, (Math.abs(shortChange) + Math.abs(mediumChange)) / avgRange) : 0,
|
|
973
|
-
direction: mediumChange > 0 ? 'bullish' : mediumChange < 0 ? 'bearish' : 'neutral',
|
|
974
|
-
shortTermDirection: shortChange > 0 ? 'up' : shortChange < 0 ? 'down' : 'flat',
|
|
975
|
-
aligned
|
|
976
|
-
};
|
|
977
|
-
};
|
|
978
|
-
|
|
979
|
-
/**
|
|
980
|
-
* Calculate recent price range
|
|
981
|
-
*/
|
|
982
|
-
const calculateRecentRange = (ticks, count) => {
|
|
983
|
-
if (!ticks || ticks.length === 0) return { high: 0, low: 0, range: 0 };
|
|
984
|
-
|
|
985
|
-
const prices = ticks.slice(-count).map(t => t.price).filter(Boolean);
|
|
986
|
-
if (prices.length === 0) return { high: 0, low: 0, range: 0 };
|
|
987
|
-
|
|
988
|
-
const high = Math.max(...prices);
|
|
989
|
-
const low = Math.min(...prices);
|
|
990
|
-
|
|
991
|
-
return { high, low, range: high - low };
|
|
992
|
-
};
|
|
993
|
-
|
|
994
|
-
/**
|
|
995
|
-
* Calculate price velocity (speed of movement)
|
|
996
|
-
*/
|
|
997
|
-
const calculatePriceVelocity = (ticks, count) => {
|
|
998
|
-
if (!ticks || ticks.length < 2) return { velocity: 0, acceleration: 0 };
|
|
999
|
-
|
|
1000
|
-
const recentTicks = ticks.slice(-count);
|
|
1001
|
-
if (recentTicks.length < 2) return { velocity: 0, acceleration: 0 };
|
|
1002
|
-
|
|
1003
|
-
const prices = recentTicks.map(t => t.price).filter(Boolean);
|
|
1004
|
-
if (prices.length < 2) return { velocity: 0, acceleration: 0 };
|
|
1005
|
-
|
|
1006
|
-
// Velocity = price change per tick
|
|
1007
|
-
const totalChange = prices[prices.length - 1] - prices[0];
|
|
1008
|
-
const velocity = totalChange / prices.length;
|
|
1009
|
-
|
|
1010
|
-
// Acceleration = change in velocity
|
|
1011
|
-
const midPoint = Math.floor(prices.length / 2);
|
|
1012
|
-
const firstHalfVelocity = (prices[midPoint] - prices[0]) / midPoint;
|
|
1013
|
-
const secondHalfVelocity = (prices[prices.length - 1] - prices[midPoint]) / (prices.length - midPoint);
|
|
1014
|
-
const acceleration = secondHalfVelocity - firstHalfVelocity;
|
|
1015
|
-
|
|
1016
|
-
return { velocity, acceleration };
|
|
1017
|
-
};
|
|
1018
|
-
|
|
1019
|
-
/**
|
|
1020
|
-
* Get current market session
|
|
1021
|
-
*/
|
|
1022
|
-
const getMarketSession = (hour) => {
|
|
1023
|
-
// US Eastern Time sessions (adjust if needed)
|
|
1024
|
-
if (hour >= 9 && hour < 12) return 'morning';
|
|
1025
|
-
if (hour >= 12 && hour < 14) return 'midday';
|
|
1026
|
-
if (hour >= 14 && hour < 16) return 'afternoon';
|
|
1027
|
-
if (hour >= 16 && hour < 18) return 'close';
|
|
1028
|
-
if (hour >= 18 || hour < 9) return 'overnight';
|
|
1029
|
-
return 'unknown';
|
|
1030
|
-
};
|
|
1031
|
-
|
|
1032
|
-
/**
|
|
1033
|
-
* Get minutes since market open (9:30 AM ET)
|
|
1034
|
-
*/
|
|
1035
|
-
const getMinutesSinceOpen = (hour) => {
|
|
1036
|
-
const marketOpenHour = 9;
|
|
1037
|
-
const marketOpenMinute = 30;
|
|
1038
|
-
const now = new Date();
|
|
1039
|
-
const currentMinutes = hour * 60 + now.getMinutes();
|
|
1040
|
-
const openMinutes = marketOpenHour * 60 + marketOpenMinute;
|
|
1041
|
-
return Math.max(0, currentMinutes - openMinutes);
|
|
1042
|
-
};
|
|
1043
|
-
|
|
1044
|
-
/**
|
|
1045
|
-
* Analyze price level interaction
|
|
1046
|
-
* Determines if entry was near support/resistance
|
|
1047
|
-
*/
|
|
1048
|
-
const analyzePriceLevelInteraction = (entryPrice, ticks) => {
|
|
1049
|
-
if (!ticks || ticks.length < 20) return null;
|
|
1050
|
-
|
|
1051
|
-
const prices = ticks.map(t => t.price).filter(Boolean);
|
|
1052
|
-
if (prices.length < 20) return null;
|
|
1053
|
-
|
|
1054
|
-
const high = Math.max(...prices);
|
|
1055
|
-
const low = Math.min(...prices);
|
|
1056
|
-
const range = high - low || 1;
|
|
1057
|
-
|
|
1058
|
-
// Position within range (0 = at low, 1 = at high)
|
|
1059
|
-
const positionInRange = (entryPrice - low) / range;
|
|
1060
|
-
|
|
1061
|
-
// Distance to recent high/low
|
|
1062
|
-
const distanceToHigh = high - entryPrice;
|
|
1063
|
-
const distanceToLow = entryPrice - low;
|
|
1064
|
-
|
|
1065
|
-
// Determine level type
|
|
1066
|
-
let levelType = 'middle';
|
|
1067
|
-
if (positionInRange > 0.8) levelType = 'near_high';
|
|
1068
|
-
else if (positionInRange < 0.2) levelType = 'near_low';
|
|
1069
|
-
else if (positionInRange > 0.6) levelType = 'upper_middle';
|
|
1070
|
-
else if (positionInRange < 0.4) levelType = 'lower_middle';
|
|
1071
|
-
|
|
1072
|
-
return {
|
|
1073
|
-
positionInRange: Math.round(positionInRange * 100) / 100,
|
|
1074
|
-
distanceToHigh,
|
|
1075
|
-
distanceToLow,
|
|
1076
|
-
recentHigh: high,
|
|
1077
|
-
recentLow: low,
|
|
1078
|
-
levelType
|
|
1079
|
-
};
|
|
1080
|
-
};
|
|
1081
|
-
|
|
1082
|
-
/**
|
|
1083
|
-
* Determine what type of level this price represents
|
|
1084
|
-
*/
|
|
1085
|
-
const determineLevelType = (price, ticks) => {
|
|
1086
|
-
if (!ticks || ticks.length < 10) return 'unknown';
|
|
1087
|
-
|
|
1088
|
-
const prices = ticks.map(t => t.price).filter(Boolean);
|
|
1089
|
-
if (prices.length < 10) return 'unknown';
|
|
1090
|
-
|
|
1091
|
-
const high = Math.max(...prices);
|
|
1092
|
-
const low = Math.min(...prices);
|
|
1093
|
-
const range = high - low || 1;
|
|
1094
|
-
const tickSize = 0.25;
|
|
1095
|
-
|
|
1096
|
-
// Check if near recent high (potential resistance)
|
|
1097
|
-
if (Math.abs(price - high) <= tickSize * 4) return 'resistance';
|
|
1098
|
-
|
|
1099
|
-
// Check if near recent low (potential support)
|
|
1100
|
-
if (Math.abs(price - low) <= tickSize * 4) return 'support';
|
|
1101
|
-
|
|
1102
|
-
// Check for breakout (above recent high)
|
|
1103
|
-
if (price > high) return 'breakout_high';
|
|
1104
|
-
|
|
1105
|
-
// Check for breakdown (below recent low)
|
|
1106
|
-
if (price < low) return 'breakout_low';
|
|
1107
|
-
|
|
1108
|
-
return 'middle';
|
|
1109
|
-
};
|
|
1110
|
-
|
|
1111
433
|
/**
|
|
1112
434
|
* Learn from a completed trade
|
|
1113
435
|
* Extracts patterns from winning and losing trades
|
|
@@ -1215,69 +537,6 @@ const getHourlyWinRate = (hour) => {
|
|
|
1215
537
|
return null;
|
|
1216
538
|
};
|
|
1217
539
|
|
|
1218
|
-
/**
|
|
1219
|
-
* Analyze price action from ticks
|
|
1220
|
-
*/
|
|
1221
|
-
const analyzePriceAction = (ticks) => {
|
|
1222
|
-
if (!ticks || ticks.length < 2) return { trend: 'unknown', strength: 0 };
|
|
1223
|
-
|
|
1224
|
-
const prices = ticks.map(t => t.price).filter(Boolean);
|
|
1225
|
-
if (prices.length < 2) return { trend: 'unknown', strength: 0 };
|
|
1226
|
-
|
|
1227
|
-
const first = prices[0];
|
|
1228
|
-
const last = prices[prices.length - 1];
|
|
1229
|
-
const change = last - first;
|
|
1230
|
-
const range = Math.max(...prices) - Math.min(...prices);
|
|
1231
|
-
|
|
1232
|
-
return {
|
|
1233
|
-
trend: change > 0 ? 'up' : change < 0 ? 'down' : 'flat',
|
|
1234
|
-
strength: range > 0 ? Math.abs(change) / range : 0,
|
|
1235
|
-
range,
|
|
1236
|
-
change
|
|
1237
|
-
};
|
|
1238
|
-
};
|
|
1239
|
-
|
|
1240
|
-
/**
|
|
1241
|
-
* Analyze volume from ticks
|
|
1242
|
-
*/
|
|
1243
|
-
const analyzeVolume = (ticks) => {
|
|
1244
|
-
if (!ticks || ticks.length === 0) return { total: 0, avg: 0, trend: 'unknown' };
|
|
1245
|
-
|
|
1246
|
-
const volumes = ticks.map(t => t.volume || 0);
|
|
1247
|
-
const total = volumes.reduce((a, b) => a + b, 0);
|
|
1248
|
-
const avg = total / volumes.length;
|
|
1249
|
-
|
|
1250
|
-
// Compare first half vs second half
|
|
1251
|
-
const mid = Math.floor(volumes.length / 2);
|
|
1252
|
-
const firstHalf = volumes.slice(0, mid).reduce((a, b) => a + b, 0);
|
|
1253
|
-
const secondHalf = volumes.slice(mid).reduce((a, b) => a + b, 0);
|
|
1254
|
-
|
|
1255
|
-
return {
|
|
1256
|
-
total,
|
|
1257
|
-
avg,
|
|
1258
|
-
trend: secondHalf > firstHalf * 1.2 ? 'increasing' : secondHalf < firstHalf * 0.8 ? 'decreasing' : 'stable'
|
|
1259
|
-
};
|
|
1260
|
-
};
|
|
1261
|
-
|
|
1262
|
-
/**
|
|
1263
|
-
* Calculate volatility from ticks
|
|
1264
|
-
*/
|
|
1265
|
-
const calculateVolatility = (ticks) => {
|
|
1266
|
-
if (!ticks || ticks.length < 2) return 0;
|
|
1267
|
-
|
|
1268
|
-
const prices = ticks.map(t => t.price).filter(Boolean);
|
|
1269
|
-
if (prices.length < 2) return 0;
|
|
1270
|
-
|
|
1271
|
-
const returns = [];
|
|
1272
|
-
for (let i = 1; i < prices.length; i++) {
|
|
1273
|
-
returns.push((prices[i] - prices[i-1]) / prices[i-1]);
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
|
|
1277
|
-
const variance = returns.reduce((sum, r) => sum + Math.pow(r - mean, 2), 0) / returns.length;
|
|
1278
|
-
|
|
1279
|
-
return Math.sqrt(variance);
|
|
1280
|
-
};
|
|
1281
540
|
|
|
1282
541
|
/**
|
|
1283
542
|
* Main analysis and optimization loop
|
|
@@ -2019,20 +1278,6 @@ const getLifetimeStats = () => {
|
|
|
2019
1278
|
};
|
|
2020
1279
|
};
|
|
2021
1280
|
|
|
2022
|
-
/**
|
|
2023
|
-
* Clear all learned data (reset AI memory)
|
|
2024
|
-
*/
|
|
2025
|
-
const clearLearningData = () => {
|
|
2026
|
-
try {
|
|
2027
|
-
if (fs.existsSync(LEARNING_FILE)) {
|
|
2028
|
-
fs.unlinkSync(LEARNING_FILE);
|
|
2029
|
-
}
|
|
2030
|
-
return true;
|
|
2031
|
-
} catch (e) {
|
|
2032
|
-
return false;
|
|
2033
|
-
}
|
|
2034
|
-
};
|
|
2035
|
-
|
|
2036
1281
|
module.exports = {
|
|
2037
1282
|
// Core lifecycle
|
|
2038
1283
|
initialize,
|