hedgequantx 2.5.37 → 2.5.39

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.5.37",
3
+ "version": "2.5.39",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -16,6 +16,7 @@ const { getLogoWidth, visibleLength, drawBoxHeader, drawBoxFooter, getColWidths,
16
16
  const { prompts } = require('../utils');
17
17
  const aiService = require('../services/ai');
18
18
  const AISupervisor = require('../services/ai/supervisor');
19
+ const StrategySupervisor = require('../services/ai/strategy-supervisor');
19
20
 
20
21
  /**
21
22
  * Show Stats Page
@@ -479,6 +480,141 @@ const showStats = async (service) => {
479
480
  }
480
481
 
481
482
  drawBoxFooter(boxWidth);
483
+
484
+ // ========== AI BEHAVIOR DIAGRAM ==========
485
+ const behaviorData = StrategySupervisor.getBehaviorHistory(100);
486
+ const learningStats = StrategySupervisor.getLearningStats();
487
+
488
+ console.log();
489
+ drawBoxHeader('AI AGENTS BEHAVIOR', boxWidth);
490
+
491
+ const behaviorInnerWidth = boxWidth - 2;
492
+
493
+ // Count behavior occurrences
494
+ const behaviorCounts = { AGGRESSIVE: 0, NORMAL: 0, CAUTIOUS: 0, PAUSE: 0 };
495
+ const valueToAction = { 3: 'AGGRESSIVE', 2: 'NORMAL', 1: 'CAUTIOUS', 0: 'PAUSE' };
496
+
497
+ if (behaviorData.values.length > 0) {
498
+ for (const val of behaviorData.values) {
499
+ const action = valueToAction[Math.round(val)] || 'NORMAL';
500
+ behaviorCounts[action]++;
501
+ }
502
+ } else {
503
+ behaviorCounts.NORMAL = 1; // Default
504
+ }
505
+
506
+ const total = Object.values(behaviorCounts).reduce((a, b) => a + b, 0) || 1;
507
+ const percentages = {
508
+ AGGRESSIVE: Math.round((behaviorCounts.AGGRESSIVE / total) * 100),
509
+ NORMAL: Math.round((behaviorCounts.NORMAL / total) * 100),
510
+ CAUTIOUS: Math.round((behaviorCounts.CAUTIOUS / total) * 100),
511
+ PAUSE: Math.round((behaviorCounts.PAUSE / total) * 100)
512
+ };
513
+
514
+ // Bar chart configuration
515
+ const labelWidth = 12;
516
+ const percentWidth = 6;
517
+ const barMaxWidth = behaviorInnerWidth - labelWidth - percentWidth - 6;
518
+
519
+ // Get current behavior
520
+ const currentValue = behaviorData.values.length > 0 ? behaviorData.values[behaviorData.values.length - 1] : 2;
521
+ const currentAction = valueToAction[Math.round(currentValue)] || 'NORMAL';
522
+
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
+ // Colors for each behavior
537
+ const barColors = {
538
+ AGGRESSIVE: chalk.green,
539
+ NORMAL: chalk.cyan,
540
+ CAUTIOUS: chalk.yellow,
541
+ PAUSE: chalk.red
542
+ };
543
+
544
+ // Draw bars from top to bottom
545
+ const barLabels = ['AGGRESSIVE', 'NORMAL', 'CAUTIOUS', 'PAUSE'];
546
+ const shortLabels = ['AGR', 'NOR', 'CAU', 'PAU'];
547
+
548
+ // Calculate left padding to center the chart
549
+ const totalBarWidth = (barWidth * 4) + 9; // 4 bars + 3 spaces of 3 chars
550
+ const leftPad = Math.floor((behaviorInnerWidth - totalBarWidth - 4) / 2);
551
+
552
+ for (let row = chartHeight; row >= 1; row--) {
553
+ let line = ' '.repeat(leftPad);
554
+
555
+ for (let i = 0; i < 4; i++) {
556
+ const label = barLabels[i];
557
+ const height = barHeights[label];
558
+ const color = barColors[label];
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
+ }
572
+
573
+ const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
574
+ line += ' '.repeat(Math.max(0, behaviorInnerWidth - lineLen));
575
+ console.log(chalk.cyan('\u2551') + line + chalk.cyan('\u2551'));
576
+ }
577
+
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
+ // Empty line
609
+ console.log(chalk.cyan('\u2551') + ' '.repeat(behaviorInnerWidth) + chalk.cyan('\u2551'));
610
+
611
+ // Stats line
612
+ const durationMin = behaviorData.duration ? Math.floor(behaviorData.duration / 60000) : 0;
613
+ const statsLine = ` CURRENT: ${barColors[currentAction](currentAction)} | PATTERNS: ${learningStats.patternsLearned.total} (${learningStats.patternsLearned.winning}W/${learningStats.patternsLearned.losing}L) | OPTIMIZATIONS: ${learningStats.optimizations}`;
614
+ const statsLen = statsLine.replace(/\x1b\[[0-9;]*m/g, '').length;
615
+ console.log(chalk.cyan('\u2551') + statsLine + ' '.repeat(Math.max(0, behaviorInnerWidth - statsLen)) + chalk.cyan('\u2551'));
616
+
617
+ drawBoxFooter(boxWidth);
482
618
  }
483
619
 
484
620
  // ========== EQUITY CURVE ==========
@@ -56,7 +56,12 @@ let supervisorState = {
56
56
  action: 'NORMAL',
57
57
  sizeMultiplier: 1.0,
58
58
  reason: 'Starting'
59
- }
59
+ },
60
+
61
+ // Behavior history for graph (action over time)
62
+ // Values: 0=PAUSE, 1=CAUTIOUS, 2=NORMAL, 3=AGGRESSIVE
63
+ behaviorHistory: [],
64
+ behaviorStartTime: null
60
65
  };
61
66
 
62
67
  // Analysis interval
@@ -66,6 +71,8 @@ let analysisInterval = null;
66
71
  * Initialize supervisor with strategy and agents
67
72
  */
68
73
  const initialize = (strategy, agents, service, accountId) => {
74
+ const now = Date.now();
75
+
69
76
  supervisorState = {
70
77
  ...supervisorState,
71
78
  active: true,
@@ -92,13 +99,23 @@ const initialize = (strategy, agents, service, accountId) => {
92
99
  maxLossStreak: 0
93
100
  },
94
101
  optimizations: [],
95
- lastOptimizationTime: Date.now()
102
+ lastOptimizationTime: now,
103
+ behaviorHistory: [{ timestamp: now, value: 2, action: 'NORMAL' }], // Start with NORMAL
104
+ behaviorStartTime: now,
105
+ currentAdvice: { action: 'NORMAL', sizeMultiplier: 1.0, reason: 'Starting' }
96
106
  };
97
107
 
98
108
  // Start continuous analysis loop
99
109
  if (analysisInterval) clearInterval(analysisInterval);
100
110
  analysisInterval = setInterval(analyzeAndOptimize, supervisorState.optimizationInterval);
101
111
 
112
+ // Also record behavior every 10 seconds to have smooth graph
113
+ setInterval(() => {
114
+ if (supervisorState.active) {
115
+ recordBehavior(supervisorState.currentAdvice.action);
116
+ }
117
+ }, 10000);
118
+
102
119
  return {
103
120
  success: true,
104
121
  agents: agents.length,
@@ -410,6 +427,7 @@ const analyzeAndOptimize = async () => {
410
427
  sizeMultiplier: consensusResult.sizeMultiplier || 1.0,
411
428
  reason: consensusResult.reason || 'Consensus recommendation'
412
429
  };
430
+ recordBehavior(consensusResult.action);
413
431
  }
414
432
  } else {
415
433
  // INDIVIDUAL MODE: Apply single agent's suggestions
@@ -427,12 +445,40 @@ const analyzeAndOptimize = async () => {
427
445
  sizeMultiplier: suggestion.sizeMultiplier || 1.0,
428
446
  reason: suggestion.reason || 'Agent recommendation'
429
447
  };
448
+ recordBehavior(suggestion.action);
430
449
  }
431
450
  }
432
451
 
433
452
  supervisorState.lastOptimizationTime = Date.now();
434
453
  };
435
454
 
455
+ /**
456
+ * Record behavior for graph visualization
457
+ * Converts action to numeric value: PAUSE=0, CAUTIOUS=1, NORMAL=2, AGGRESSIVE=3
458
+ */
459
+ const recordBehavior = (action) => {
460
+ const actionToValue = {
461
+ 'PAUSE': 0,
462
+ 'CAUTIOUS': 1,
463
+ 'NORMAL': 2,
464
+ 'AGGRESSIVE': 3
465
+ };
466
+
467
+ const value = actionToValue[action] ?? 2; // Default to NORMAL
468
+ const now = Date.now();
469
+
470
+ supervisorState.behaviorHistory.push({
471
+ timestamp: now,
472
+ value,
473
+ action
474
+ });
475
+
476
+ // Keep last 200 data points
477
+ if (supervisorState.behaviorHistory.length > 200) {
478
+ supervisorState.behaviorHistory = supervisorState.behaviorHistory.slice(-200);
479
+ }
480
+ };
481
+
436
482
  /**
437
483
  * Analyze patterns in losing trades
438
484
  */
@@ -662,6 +708,66 @@ const shouldTrade = () => {
662
708
  };
663
709
  };
664
710
 
711
+ /**
712
+ * Get behavior history for graph visualization
713
+ * Returns array of numeric values (0-3) representing agent behavior over time
714
+ *
715
+ * @param {number} maxPoints - Maximum data points to return
716
+ * @returns {Object} { values: number[], labels: string[], startTime: number }
717
+ */
718
+ const getBehaviorHistory = (maxPoints = 50) => {
719
+ if (!supervisorState.active || supervisorState.behaviorHistory.length === 0) {
720
+ return { values: [], labels: [], startTime: null };
721
+ }
722
+
723
+ let history = [...supervisorState.behaviorHistory];
724
+
725
+ // Downsample if too many points
726
+ if (history.length > maxPoints) {
727
+ const step = Math.ceil(history.length / maxPoints);
728
+ history = history.filter((_, i) => i % step === 0);
729
+ }
730
+
731
+ // If too few points, interpolate to make smooth curve
732
+ if (history.length < 10 && history.length > 1) {
733
+ const interpolated = [];
734
+ for (let i = 0; i < history.length - 1; i++) {
735
+ interpolated.push(history[i]);
736
+ // Add intermediate points
737
+ const curr = history[i].value;
738
+ const next = history[i + 1].value;
739
+ const mid = (curr + next) / 2;
740
+ interpolated.push({ value: mid, action: 'interpolated' });
741
+ }
742
+ interpolated.push(history[history.length - 1]);
743
+ history = interpolated;
744
+ }
745
+
746
+ return {
747
+ values: history.map(h => h.value),
748
+ actions: history.map(h => h.action),
749
+ startTime: supervisorState.behaviorStartTime,
750
+ duration: Date.now() - supervisorState.behaviorStartTime
751
+ };
752
+ };
753
+
754
+ /**
755
+ * Get learning statistics for display
756
+ */
757
+ const getLearningStats = () => {
758
+ return {
759
+ patternsLearned: {
760
+ winning: supervisorState.winningPatterns.length,
761
+ losing: supervisorState.losingPatterns.length,
762
+ total: supervisorState.winningPatterns.length + supervisorState.losingPatterns.length
763
+ },
764
+ optimizations: supervisorState.optimizations.length,
765
+ tradesAnalyzed: supervisorState.trades.length,
766
+ ticksProcessed: supervisorState.ticks.length,
767
+ signalsObserved: supervisorState.signals.length
768
+ };
769
+ };
770
+
665
771
  module.exports = {
666
772
  initialize,
667
773
  stop,
@@ -671,5 +777,7 @@ module.exports = {
671
777
  getCurrentAdvice,
672
778
  shouldTrade,
673
779
  getStatus,
674
- analyzeAndOptimize
780
+ analyzeAndOptimize,
781
+ getBehaviorHistory,
782
+ getLearningStats
675
783
  };