hedgequantx 2.5.41 → 2.5.43
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/pages/stats.js +35 -73
- package/src/services/ai/index.js +24 -2
- package/src/services/ai/strategy-supervisor.js +129 -0
package/package.json
CHANGED
package/src/pages/stats.js
CHANGED
|
@@ -481,7 +481,7 @@ const showStats = async (service) => {
|
|
|
481
481
|
|
|
482
482
|
drawBoxFooter(boxWidth);
|
|
483
483
|
|
|
484
|
-
// ========== AI BEHAVIOR DIAGRAM ==========
|
|
484
|
+
// ========== AI BEHAVIOR DIAGRAM (HORIZONTAL BARS) ==========
|
|
485
485
|
const behaviorData = StrategySupervisor.getBehaviorHistory(100);
|
|
486
486
|
const learningStats = StrategySupervisor.getLearningStats();
|
|
487
487
|
|
|
@@ -511,28 +511,10 @@ const showStats = async (service) => {
|
|
|
511
511
|
PAUSE: Math.round((behaviorCounts.PAUSE / total) * 100)
|
|
512
512
|
};
|
|
513
513
|
|
|
514
|
-
// Bar chart configuration
|
|
515
|
-
const labelWidth = 12;
|
|
516
|
-
const percentWidth = 6;
|
|
517
|
-
const barMaxWidth = behaviorInnerWidth - labelWidth - percentWidth - 6;
|
|
518
|
-
|
|
519
514
|
// Get current behavior
|
|
520
515
|
const currentValue = behaviorData.values.length > 0 ? behaviorData.values[behaviorData.values.length - 1] : 2;
|
|
521
516
|
const currentAction = valueToAction[Math.round(currentValue)] || 'NORMAL';
|
|
522
517
|
|
|
523
|
-
// Draw vertical bar chart (bars going up)
|
|
524
|
-
const chartHeight = 8;
|
|
525
|
-
const barWidth = Math.floor((barMaxWidth - 12) / 4); // 4 bars with spacing
|
|
526
|
-
|
|
527
|
-
// Calculate bar heights (max height = chartHeight)
|
|
528
|
-
const maxPercent = Math.max(...Object.values(percentages), 1);
|
|
529
|
-
const barHeights = {
|
|
530
|
-
AGGRESSIVE: Math.round((percentages.AGGRESSIVE / 100) * chartHeight),
|
|
531
|
-
NORMAL: Math.round((percentages.NORMAL / 100) * chartHeight),
|
|
532
|
-
CAUTIOUS: Math.round((percentages.CAUTIOUS / 100) * chartHeight),
|
|
533
|
-
PAUSE: Math.round((percentages.PAUSE / 100) * chartHeight)
|
|
534
|
-
};
|
|
535
|
-
|
|
536
518
|
// Colors for each behavior
|
|
537
519
|
const barColors = {
|
|
538
520
|
AGGRESSIVE: chalk.green,
|
|
@@ -541,70 +523,50 @@ const showStats = async (service) => {
|
|
|
541
523
|
PAUSE: chalk.red
|
|
542
524
|
};
|
|
543
525
|
|
|
544
|
-
//
|
|
526
|
+
// Horizontal bar chart configuration
|
|
545
527
|
const barLabels = ['AGGRESSIVE', 'NORMAL', 'CAUTIOUS', 'PAUSE'];
|
|
546
528
|
const shortLabels = ['AGR', 'NOR', 'CAU', 'PAU'];
|
|
529
|
+
const labelWidth = 6; // "AGR " etc.
|
|
530
|
+
const pctWidth = 6; // "100% "
|
|
531
|
+
const spacing = 4; // spaces between bars
|
|
532
|
+
|
|
533
|
+
// Calculate bar width for each category (total 4 bars with spacing)
|
|
534
|
+
const availableWidth = behaviorInnerWidth - (labelWidth * 4) - (pctWidth * 4) - (spacing * 3) - 4;
|
|
535
|
+
const maxBarWidth = Math.floor(availableWidth / 4);
|
|
536
|
+
|
|
537
|
+
// Calculate bar widths based on percentage (max 100% = maxBarWidth)
|
|
538
|
+
const barWidths = {
|
|
539
|
+
AGGRESSIVE: Math.round((percentages.AGGRESSIVE / 100) * maxBarWidth),
|
|
540
|
+
NORMAL: Math.round((percentages.NORMAL / 100) * maxBarWidth),
|
|
541
|
+
CAUTIOUS: Math.round((percentages.CAUTIOUS / 100) * maxBarWidth),
|
|
542
|
+
PAUSE: Math.round((percentages.PAUSE / 100) * maxBarWidth)
|
|
543
|
+
};
|
|
547
544
|
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
545
|
+
// Draw horizontal bars (each bar is a row)
|
|
546
|
+
for (let i = 0; i < 4; i++) {
|
|
547
|
+
const label = barLabels[i];
|
|
548
|
+
const shortLabel = shortLabels[i];
|
|
549
|
+
const pct = percentages[label];
|
|
550
|
+
const barWidth = barWidths[label];
|
|
551
|
+
const color = barColors[label];
|
|
552
|
+
const isCurrent = label === currentAction;
|
|
554
553
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const isCurrent = label === currentAction;
|
|
560
|
-
|
|
561
|
-
if (row <= height) {
|
|
562
|
-
// Draw filled bar
|
|
563
|
-
const block = isCurrent ? '█' : '▓';
|
|
564
|
-
line += color(block.repeat(barWidth));
|
|
565
|
-
} else {
|
|
566
|
-
// Empty space
|
|
567
|
-
line += ' '.repeat(barWidth);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
if (i < 3) line += ' '; // Space between bars
|
|
571
|
-
}
|
|
554
|
+
// Build the bar
|
|
555
|
+
const block = isCurrent ? '█' : '▓';
|
|
556
|
+
const bar = barWidth > 0 ? color(block.repeat(barWidth)) : '';
|
|
557
|
+
const emptySpace = ' '.repeat(Math.max(0, maxBarWidth - barWidth));
|
|
572
558
|
|
|
559
|
+
// Format: " AGR ████████████████ 100% "
|
|
560
|
+
const labelPart = ' ' + color(shortLabel.padEnd(labelWidth));
|
|
561
|
+
const barPart = bar + emptySpace;
|
|
562
|
+
const pctPart = chalk.white((pct + '%').padStart(pctWidth));
|
|
563
|
+
|
|
564
|
+
let line = labelPart + barPart + pctPart;
|
|
573
565
|
const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
574
566
|
line += ' '.repeat(Math.max(0, behaviorInnerWidth - lineLen));
|
|
575
567
|
console.log(chalk.cyan('\u2551') + line + chalk.cyan('\u2551'));
|
|
576
568
|
}
|
|
577
569
|
|
|
578
|
-
// Draw baseline
|
|
579
|
-
let baseLine = ' '.repeat(leftPad) + '─'.repeat(totalBarWidth);
|
|
580
|
-
const baseLen = baseLine.length;
|
|
581
|
-
baseLine += ' '.repeat(Math.max(0, behaviorInnerWidth - baseLen));
|
|
582
|
-
console.log(chalk.cyan('\u2551') + chalk.white(baseLine) + chalk.cyan('\u2551'));
|
|
583
|
-
|
|
584
|
-
// Draw labels
|
|
585
|
-
let labelLine = ' '.repeat(leftPad);
|
|
586
|
-
for (let i = 0; i < 4; i++) {
|
|
587
|
-
const lbl = shortLabels[i];
|
|
588
|
-
const pad = Math.floor((barWidth - lbl.length) / 2);
|
|
589
|
-
labelLine += ' '.repeat(pad) + barColors[barLabels[i]](lbl) + ' '.repeat(barWidth - pad - lbl.length);
|
|
590
|
-
if (i < 3) labelLine += ' ';
|
|
591
|
-
}
|
|
592
|
-
const lblLen = labelLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
593
|
-
labelLine += ' '.repeat(Math.max(0, behaviorInnerWidth - lblLen));
|
|
594
|
-
console.log(chalk.cyan('\u2551') + labelLine + chalk.cyan('\u2551'));
|
|
595
|
-
|
|
596
|
-
// Draw percentages
|
|
597
|
-
let pctLine = ' '.repeat(leftPad);
|
|
598
|
-
for (let i = 0; i < 4; i++) {
|
|
599
|
-
const pct = percentages[barLabels[i]] + '%';
|
|
600
|
-
const pad = Math.floor((barWidth - pct.length) / 2);
|
|
601
|
-
pctLine += ' '.repeat(pad) + chalk.white(pct) + ' '.repeat(barWidth - pad - pct.length);
|
|
602
|
-
if (i < 3) pctLine += ' ';
|
|
603
|
-
}
|
|
604
|
-
const pctLen = pctLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
605
|
-
pctLine += ' '.repeat(Math.max(0, behaviorInnerWidth - pctLen));
|
|
606
|
-
console.log(chalk.cyan('\u2551') + pctLine + chalk.cyan('\u2551'));
|
|
607
|
-
|
|
608
570
|
// Empty line
|
|
609
571
|
console.log(chalk.cyan('\u2551') + ' '.repeat(behaviorInnerWidth) + chalk.cyan('\u2551'));
|
|
610
572
|
|
package/src/services/ai/index.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
const { getProviders, getProvider } = require('./providers');
|
|
7
7
|
const { storage } = require('../session');
|
|
8
8
|
const AISupervisor = require('./supervisor');
|
|
9
|
+
const StrategySupervisor = require('./strategy-supervisor');
|
|
9
10
|
|
|
10
11
|
// In-memory cache of connections
|
|
11
12
|
let connectionsCache = null;
|
|
@@ -228,10 +229,17 @@ const addAgent = async (providerId, optionId, credentials, model = null, customN
|
|
|
228
229
|
|
|
229
230
|
saveAISettings(aiSettings);
|
|
230
231
|
|
|
231
|
-
//
|
|
232
|
-
// Supervision requires a real service connection and account
|
|
232
|
+
// Get the full agent object
|
|
233
233
|
const agent = getAgent(agentId);
|
|
234
234
|
|
|
235
|
+
// Notify StrategySupervisor if algo is running
|
|
236
|
+
// This ensures new agents are immediately connected to live trading
|
|
237
|
+
try {
|
|
238
|
+
StrategySupervisor.addAgent(agent);
|
|
239
|
+
} catch (e) {
|
|
240
|
+
// Supervisor might not be active - that's OK
|
|
241
|
+
}
|
|
242
|
+
|
|
235
243
|
return agent;
|
|
236
244
|
};
|
|
237
245
|
|
|
@@ -253,6 +261,13 @@ const removeAgent = (agentId) => {
|
|
|
253
261
|
// Stop AI supervision for this agent
|
|
254
262
|
AISupervisor.stop(agentId);
|
|
255
263
|
|
|
264
|
+
// Notify StrategySupervisor to remove agent from live trading
|
|
265
|
+
try {
|
|
266
|
+
StrategySupervisor.removeAgent(agentId);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
// Supervisor might not be active - that's OK
|
|
269
|
+
}
|
|
270
|
+
|
|
256
271
|
// If removed agent was active, set new active
|
|
257
272
|
if (aiSettings.activeAgentId === agentId) {
|
|
258
273
|
aiSettings.activeAgentId = agents.length > 0 ? agents[0].id : null;
|
|
@@ -289,6 +304,13 @@ const disconnectAll = () => {
|
|
|
289
304
|
// Stop all AI supervision sessions
|
|
290
305
|
AISupervisor.stopAll();
|
|
291
306
|
|
|
307
|
+
// Refresh StrategySupervisor to clear agents
|
|
308
|
+
try {
|
|
309
|
+
StrategySupervisor.refreshAgents();
|
|
310
|
+
} catch (e) {
|
|
311
|
+
// Supervisor might not be active
|
|
312
|
+
}
|
|
313
|
+
|
|
292
314
|
saveAISettings({ agents: [] });
|
|
293
315
|
};
|
|
294
316
|
|
|
@@ -609,6 +609,11 @@ const initialize = (strategy, agents, service, accountId) => {
|
|
|
609
609
|
}
|
|
610
610
|
}, 10000);
|
|
611
611
|
|
|
612
|
+
// Start agent sync interval - ensures new agents are picked up dynamically
|
|
613
|
+
if (!supervisorState.agentSyncInterval) {
|
|
614
|
+
supervisorState.agentSyncInterval = setInterval(syncAgents, 5000);
|
|
615
|
+
}
|
|
616
|
+
|
|
612
617
|
return {
|
|
613
618
|
success: true,
|
|
614
619
|
agents: agents.length,
|
|
@@ -616,6 +621,108 @@ const initialize = (strategy, agents, service, accountId) => {
|
|
|
616
621
|
};
|
|
617
622
|
};
|
|
618
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Sync agents with AI service
|
|
626
|
+
* Called periodically to pick up newly added/removed agents
|
|
627
|
+
* Ensures all agents are always connected to the supervisor
|
|
628
|
+
*/
|
|
629
|
+
const syncAgents = () => {
|
|
630
|
+
if (!supervisorState.active) return;
|
|
631
|
+
|
|
632
|
+
try {
|
|
633
|
+
// Dynamic import to avoid circular dependency
|
|
634
|
+
const aiService = require('./index');
|
|
635
|
+
const currentAgents = aiService.getAgents();
|
|
636
|
+
|
|
637
|
+
if (!currentAgents) return;
|
|
638
|
+
|
|
639
|
+
const currentIds = new Set(currentAgents.map(a => a.id));
|
|
640
|
+
const supervisorIds = new Set(supervisorState.agents.map(a => a.id));
|
|
641
|
+
|
|
642
|
+
// Check for new agents
|
|
643
|
+
const newAgents = currentAgents.filter(a => !supervisorIds.has(a.id));
|
|
644
|
+
|
|
645
|
+
// Check for removed agents
|
|
646
|
+
const removedIds = [...supervisorIds].filter(id => !currentIds.has(id));
|
|
647
|
+
|
|
648
|
+
// Add new agents
|
|
649
|
+
if (newAgents.length > 0) {
|
|
650
|
+
supervisorState.agents = [...supervisorState.agents, ...newAgents];
|
|
651
|
+
// Log would go here if we had UI access
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Remove agents that were disconnected
|
|
655
|
+
if (removedIds.length > 0) {
|
|
656
|
+
supervisorState.agents = supervisorState.agents.filter(a => !removedIds.includes(a.id));
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Update mode based on current agent count
|
|
660
|
+
supervisorState.mode = supervisorState.agents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL';
|
|
661
|
+
|
|
662
|
+
} catch (e) {
|
|
663
|
+
// Silent fail - aiService might not be ready
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Force refresh agents from AI service
|
|
669
|
+
* Call this when you know agents have changed
|
|
670
|
+
*/
|
|
671
|
+
const refreshAgents = () => {
|
|
672
|
+
syncAgents();
|
|
673
|
+
return {
|
|
674
|
+
agents: supervisorState.agents.length,
|
|
675
|
+
mode: supervisorState.agents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL'
|
|
676
|
+
};
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Add a single agent to the supervisor (called when agent is added)
|
|
681
|
+
*/
|
|
682
|
+
const addAgent = (agent) => {
|
|
683
|
+
if (!supervisorState.active) return false;
|
|
684
|
+
|
|
685
|
+
// Check if already exists
|
|
686
|
+
if (supervisorState.agents.some(a => a.id === agent.id)) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
supervisorState.agents.push(agent);
|
|
691
|
+
supervisorState.mode = supervisorState.agents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL';
|
|
692
|
+
|
|
693
|
+
return true;
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Remove an agent from the supervisor (called when agent is removed)
|
|
698
|
+
*/
|
|
699
|
+
const removeAgent = (agentId) => {
|
|
700
|
+
if (!supervisorState.active) return false;
|
|
701
|
+
|
|
702
|
+
const index = supervisorState.agents.findIndex(a => a.id === agentId);
|
|
703
|
+
if (index === -1) return false;
|
|
704
|
+
|
|
705
|
+
supervisorState.agents.splice(index, 1);
|
|
706
|
+
supervisorState.mode = supervisorState.agents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL';
|
|
707
|
+
|
|
708
|
+
return true;
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Get current agent count and mode
|
|
713
|
+
*/
|
|
714
|
+
const getAgentInfo = () => {
|
|
715
|
+
return {
|
|
716
|
+
count: supervisorState.agents.length,
|
|
717
|
+
mode: supervisorState.agents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL',
|
|
718
|
+
agents: supervisorState.agents.map(a => ({
|
|
719
|
+
id: a.id,
|
|
720
|
+
name: a.name,
|
|
721
|
+
provider: a.providerId
|
|
722
|
+
}))
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
|
|
619
726
|
/**
|
|
620
727
|
* Stop supervisor and save learned data
|
|
621
728
|
*/
|
|
@@ -625,6 +732,12 @@ const stop = () => {
|
|
|
625
732
|
analysisInterval = null;
|
|
626
733
|
}
|
|
627
734
|
|
|
735
|
+
// Stop agent sync
|
|
736
|
+
if (supervisorState.agentSyncInterval) {
|
|
737
|
+
clearInterval(supervisorState.agentSyncInterval);
|
|
738
|
+
supervisorState.agentSyncInterval = null;
|
|
739
|
+
}
|
|
740
|
+
|
|
628
741
|
// Save all learned data before stopping
|
|
629
742
|
const saved = saveLearningData();
|
|
630
743
|
|
|
@@ -1921,18 +2034,34 @@ const clearLearningData = () => {
|
|
|
1921
2034
|
};
|
|
1922
2035
|
|
|
1923
2036
|
module.exports = {
|
|
2037
|
+
// Core lifecycle
|
|
1924
2038
|
initialize,
|
|
1925
2039
|
stop,
|
|
2040
|
+
|
|
2041
|
+
// Data feeds (from algo)
|
|
1926
2042
|
feedTick,
|
|
1927
2043
|
feedSignal,
|
|
1928
2044
|
feedTradeResult,
|
|
2045
|
+
|
|
2046
|
+
// Trading decisions
|
|
1929
2047
|
getCurrentAdvice,
|
|
1930
2048
|
shouldTrade,
|
|
2049
|
+
|
|
2050
|
+
// Agent management (dynamic sync)
|
|
2051
|
+
syncAgents,
|
|
2052
|
+
refreshAgents,
|
|
2053
|
+
addAgent,
|
|
2054
|
+
removeAgent,
|
|
2055
|
+
getAgentInfo,
|
|
2056
|
+
|
|
2057
|
+
// Status & stats
|
|
1931
2058
|
getStatus,
|
|
1932
2059
|
analyzeAndOptimize,
|
|
1933
2060
|
getBehaviorHistory,
|
|
1934
2061
|
getLearningStats,
|
|
1935
2062
|
getLifetimeStats,
|
|
2063
|
+
|
|
2064
|
+
// Data management
|
|
1936
2065
|
clearLearningData,
|
|
1937
2066
|
loadLearningData
|
|
1938
2067
|
};
|