hedgequantx 2.5.35 → 2.5.37

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.
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  const { connections } = require('../session');
10
- const { analyzeTrading } = require('./client');
10
+ const { analyzeTrading, analyzePerformance, getMarketAdvice } = require('./client');
11
11
 
12
12
  let aiService = null;
13
13
 
@@ -298,6 +298,8 @@ class AISupervisor {
298
298
 
299
299
  /**
300
300
  * Calculate consensus from multiple agent decisions
301
+ * RULE: ALL agents must agree (100% unanimity) before taking action
302
+ *
301
303
  * @param {Array} decisions - Array of agent decisions
302
304
  * @returns {Object|null} Consensus result
303
305
  */
@@ -319,13 +321,16 @@ class AISupervisor {
319
321
  }
320
322
  }
321
323
 
322
- // Find majority action
323
- let majorityAction = null;
324
- let maxVotes = 0;
324
+ // Check for UNANIMITY - ALL agents must agree
325
+ let unanimousAction = null;
326
+ let isUnanimous = false;
327
+
325
328
  for (const [action, count] of Object.entries(votes)) {
326
- if (count > maxVotes) {
327
- maxVotes = count;
328
- majorityAction = action;
329
+ if (count === decisions.length) {
330
+ // All agents voted for this action
331
+ unanimousAction = action;
332
+ isUnanimous = true;
333
+ break;
329
334
  }
330
335
  }
331
336
 
@@ -335,13 +340,15 @@ class AISupervisor {
335
340
  : null;
336
341
 
337
342
  // Store consensus result
343
+ // If not unanimous, action is HOLD (no action taken)
338
344
  const consensus = {
339
345
  timestamp: Date.now(),
340
- action: majorityAction,
341
- confidence: avgConfidence,
346
+ action: isUnanimous ? unanimousAction : 'HOLD',
347
+ confidence: isUnanimous ? avgConfidence : null,
342
348
  votes,
343
349
  agentCount: decisions.length,
344
- agreement: maxVotes / decisions.length
350
+ isUnanimous,
351
+ agreement: isUnanimous ? 1.0 : 0
345
352
  };
346
353
 
347
354
  // Store consensus in first session for retrieval
@@ -560,6 +567,383 @@ class AISupervisor {
560
567
  static getSessionCount() {
561
568
  return supervisionSessions.size;
562
569
  }
570
+
571
+ /**
572
+ * Feed market tick to all agents (sync with strategy)
573
+ * Agents receive the same data as the strategy in real-time
574
+ *
575
+ * @param {Object} tick - Market tick data { price, bid, ask, volume, timestamp }
576
+ */
577
+ static feedTick(tick) {
578
+ if (supervisionSessions.size === 0) return;
579
+
580
+ // Store latest tick in all sessions
581
+ for (const [agentId, session] of supervisionSessions.entries()) {
582
+ if (!session.marketData) {
583
+ session.marketData = { ticks: [], lastTick: null };
584
+ }
585
+ session.marketData.lastTick = tick;
586
+ session.marketData.ticks.push(tick);
587
+
588
+ // Keep only last 1000 ticks to prevent memory bloat
589
+ if (session.marketData.ticks.length > 1000) {
590
+ session.marketData.ticks = session.marketData.ticks.slice(-1000);
591
+ }
592
+ }
593
+ }
594
+
595
+ /**
596
+ * Feed strategy signal to all agents (sync with strategy)
597
+ * Agents see every signal the strategy generates
598
+ *
599
+ * @param {Object} signal - Strategy signal { direction, entry, stopLoss, takeProfit, confidence }
600
+ */
601
+ static feedSignal(signal) {
602
+ if (supervisionSessions.size === 0) return;
603
+
604
+ const signalData = {
605
+ timestamp: Date.now(),
606
+ direction: signal.direction,
607
+ entry: signal.entry,
608
+ stopLoss: signal.stopLoss,
609
+ takeProfit: signal.takeProfit,
610
+ confidence: signal.confidence
611
+ };
612
+
613
+ // Store signal in all sessions
614
+ for (const [agentId, session] of supervisionSessions.entries()) {
615
+ if (!session.signals) {
616
+ session.signals = [];
617
+ }
618
+ session.signals.push(signalData);
619
+
620
+ // Keep only last 100 signals
621
+ if (session.signals.length > 100) {
622
+ session.signals = session.signals.slice(-100);
623
+ }
624
+ }
625
+ }
626
+
627
+ /**
628
+ * Feed trade execution to all agents (sync with strategy)
629
+ * Agents see every trade executed
630
+ *
631
+ * @param {Object} trade - Trade data { side, qty, price, pnl, symbol }
632
+ */
633
+ static feedTrade(trade) {
634
+ if (supervisionSessions.size === 0) return;
635
+
636
+ const tradeData = {
637
+ timestamp: Date.now(),
638
+ side: trade.side,
639
+ qty: trade.qty,
640
+ price: trade.price,
641
+ pnl: trade.pnl,
642
+ symbol: trade.symbol
643
+ };
644
+
645
+ // Store trade in all sessions
646
+ for (const [agentId, session] of supervisionSessions.entries()) {
647
+ if (!session.trades) {
648
+ session.trades = [];
649
+ }
650
+ session.trades.push(tradeData);
651
+ }
652
+ }
653
+
654
+ /**
655
+ * Update current position for all agents
656
+ *
657
+ * @param {Object} position - Position data { qty, side, entryPrice, pnl }
658
+ */
659
+ static updatePosition(position) {
660
+ if (supervisionSessions.size === 0) return;
661
+
662
+ for (const [agentId, session] of supervisionSessions.entries()) {
663
+ session.currentPosition = {
664
+ timestamp: Date.now(),
665
+ qty: position.qty,
666
+ side: position.side,
667
+ entryPrice: position.entryPrice,
668
+ pnl: position.pnl
669
+ };
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Update P&L for all agents
675
+ *
676
+ * @param {number} pnl - Current session P&L
677
+ * @param {number} balance - Account balance
678
+ */
679
+ static updatePnL(pnl, balance) {
680
+ if (supervisionSessions.size === 0) return;
681
+
682
+ for (const [agentId, session] of supervisionSessions.entries()) {
683
+ session.currentPnL = pnl;
684
+ session.currentBalance = balance;
685
+ }
686
+ }
687
+
688
+ /**
689
+ * Check if agents recommend intervention (PAUSE, REDUCE_SIZE, etc.)
690
+ * In CONSENSUS mode, ALL agents must agree to continue trading
691
+ *
692
+ * @returns {Object} { shouldContinue: boolean, action: string, reason: string }
693
+ */
694
+ static checkIntervention() {
695
+ if (supervisionSessions.size === 0) {
696
+ return { shouldContinue: true, action: 'CONTINUE', reason: 'No AI supervision active' };
697
+ }
698
+
699
+ // Get last consensus or individual decision
700
+ const consensus = this.getConsensus();
701
+
702
+ if (consensus && consensus.isUnanimous) {
703
+ if (consensus.action === 'PAUSE' || consensus.action === 'STOP') {
704
+ return { shouldContinue: false, action: consensus.action, reason: 'AI agents recommend pause' };
705
+ }
706
+ if (consensus.action === 'REDUCE_SIZE') {
707
+ return { shouldContinue: true, action: 'REDUCE_SIZE', reason: 'AI agents recommend reducing size' };
708
+ }
709
+ } else if (consensus && !consensus.isUnanimous) {
710
+ // Agents disagree - be conservative, don't take new trades
711
+ return { shouldContinue: false, action: 'HOLD', reason: 'AI agents disagree - waiting for consensus' };
712
+ }
713
+
714
+ return { shouldContinue: true, action: 'CONTINUE', reason: 'AI supervision active' };
715
+ }
716
+
717
+ /**
718
+ * Get real-time sync status for display
719
+ * Shows what data the agents are receiving
720
+ *
721
+ * @returns {Object} Sync status
722
+ */
723
+ static getSyncStatus() {
724
+ if (supervisionSessions.size === 0) {
725
+ return { synced: false, agents: 0 };
726
+ }
727
+
728
+ const firstSession = supervisionSessions.values().next().value;
729
+
730
+ return {
731
+ synced: true,
732
+ agents: supervisionSessions.size,
733
+ lastTick: firstSession?.marketData?.lastTick?.timestamp || null,
734
+ tickCount: firstSession?.marketData?.ticks?.length || 0,
735
+ signalCount: firstSession?.signals?.length || 0,
736
+ tradeCount: firstSession?.trades?.length || 0,
737
+ currentPnL: firstSession?.currentPnL || 0,
738
+ currentPosition: firstSession?.currentPosition || null
739
+ };
740
+ }
741
+
742
+ /**
743
+ * Request strategy optimization from all agents
744
+ * Agents analyze performance data and suggest improvements
745
+ * In CONSENSUS mode, only unanimous suggestions are applied
746
+ *
747
+ * @param {Object} performanceData - Strategy performance data
748
+ * @returns {Promise<Object|null>} Optimization suggestions (consensus)
749
+ */
750
+ static async requestOptimization(performanceData) {
751
+ if (supervisionSessions.size === 0) return null;
752
+
753
+ const allSessions = Array.from(supervisionSessions.values());
754
+ const suggestions = [];
755
+
756
+ // Get optimization suggestions from each agent
757
+ for (const session of allSessions) {
758
+ try {
759
+ const suggestion = await analyzePerformance(session.agent, performanceData);
760
+ if (suggestion) {
761
+ suggestions.push({
762
+ agentId: session.agentId,
763
+ agentName: session.agent.name,
764
+ ...suggestion
765
+ });
766
+ }
767
+ } catch (e) {
768
+ // Silent fail for individual agent
769
+ }
770
+ }
771
+
772
+ if (suggestions.length === 0) return null;
773
+
774
+ // If single agent, return its suggestion
775
+ if (suggestions.length === 1) {
776
+ return {
777
+ mode: 'INDIVIDUAL',
778
+ ...suggestions[0]
779
+ };
780
+ }
781
+
782
+ // CONSENSUS MODE: Find common optimizations
783
+ const consensusOptimizations = [];
784
+ const allOptimizations = suggestions.flatMap(s => s.optimizations || []);
785
+
786
+ // Group by parameter name
787
+ const paramGroups = {};
788
+ for (const opt of allOptimizations) {
789
+ if (!opt.param) continue;
790
+ if (!paramGroups[opt.param]) {
791
+ paramGroups[opt.param] = [];
792
+ }
793
+ paramGroups[opt.param].push(opt);
794
+ }
795
+
796
+ // Find unanimous suggestions (all agents agree on direction)
797
+ for (const [param, opts] of Object.entries(paramGroups)) {
798
+ if (opts.length === suggestions.length) {
799
+ // All agents suggested this param - check if they agree on direction
800
+ const directions = opts.map(o => {
801
+ const current = parseFloat(o.current) || 0;
802
+ const suggested = parseFloat(o.suggested) || 0;
803
+ return suggested > current ? 'increase' : suggested < current ? 'decrease' : 'same';
804
+ });
805
+
806
+ const allSame = directions.every(d => d === directions[0]);
807
+ if (allSame && directions[0] !== 'same') {
808
+ // Unanimous - use average of suggested values
809
+ const avgSuggested = opts.reduce((sum, o) => sum + (parseFloat(o.suggested) || 0), 0) / opts.length;
810
+ consensusOptimizations.push({
811
+ param,
812
+ current: opts[0].current,
813
+ suggested: avgSuggested.toFixed(2),
814
+ reason: `Unanimous (${suggestions.length} agents agree)`,
815
+ direction: directions[0]
816
+ });
817
+ }
818
+ }
819
+ }
820
+
821
+ // Calculate average confidence
822
+ const avgConfidence = Math.round(
823
+ suggestions.reduce((sum, s) => sum + (s.confidence || 0), 0) / suggestions.length
824
+ );
825
+
826
+ // Determine consensus market condition
827
+ const conditions = suggestions.map(s => s.marketCondition).filter(Boolean);
828
+ const conditionCounts = {};
829
+ for (const c of conditions) {
830
+ conditionCounts[c] = (conditionCounts[c] || 0) + 1;
831
+ }
832
+ const consensusCondition = Object.entries(conditionCounts)
833
+ .sort((a, b) => b[1] - a[1])[0]?.[0] || 'unknown';
834
+
835
+ return {
836
+ mode: 'CONSENSUS',
837
+ agentCount: suggestions.length,
838
+ isUnanimous: consensusOptimizations.length > 0,
839
+ optimizations: consensusOptimizations,
840
+ marketCondition: consensusCondition,
841
+ confidence: avgConfidence,
842
+ individualSuggestions: suggestions
843
+ };
844
+ }
845
+
846
+ /**
847
+ * Get real-time market advice from all agents
848
+ * Used for dynamic position sizing and risk adjustment
849
+ *
850
+ * @param {Object} marketData - Current market data
851
+ * @returns {Promise<Object|null>} Market advice (consensus)
852
+ */
853
+ static async getMarketAdvice(marketData) {
854
+ if (supervisionSessions.size === 0) return null;
855
+
856
+ const allSessions = Array.from(supervisionSessions.values());
857
+ const advices = [];
858
+
859
+ // Get advice from each agent
860
+ for (const session of allSessions) {
861
+ try {
862
+ const advice = await getMarketAdvice(session.agent, marketData);
863
+ if (advice) {
864
+ advices.push({
865
+ agentId: session.agentId,
866
+ agentName: session.agent.name,
867
+ ...advice
868
+ });
869
+ }
870
+ } catch (e) {
871
+ // Silent fail
872
+ }
873
+ }
874
+
875
+ if (advices.length === 0) return null;
876
+
877
+ // Single agent
878
+ if (advices.length === 1) {
879
+ return {
880
+ mode: 'INDIVIDUAL',
881
+ ...advices[0]
882
+ };
883
+ }
884
+
885
+ // CONSENSUS: All agents must agree on action
886
+ const actions = advices.map(a => a.action);
887
+ const allSameAction = actions.every(a => a === actions[0]);
888
+
889
+ if (allSameAction) {
890
+ // Unanimous action - average the size multiplier
891
+ const avgMultiplier = advices.reduce((sum, a) => sum + (a.sizeMultiplier || 1), 0) / advices.length;
892
+ const avgConfidence = Math.round(advices.reduce((sum, a) => sum + (a.confidence || 0), 0) / advices.length);
893
+
894
+ return {
895
+ mode: 'CONSENSUS',
896
+ isUnanimous: true,
897
+ action: actions[0],
898
+ sizeMultiplier: Math.round(avgMultiplier * 100) / 100,
899
+ confidence: avgConfidence,
900
+ reason: `${advices.length} agents unanimous`,
901
+ agentCount: advices.length
902
+ };
903
+ } else {
904
+ // Agents disagree - be conservative
905
+ return {
906
+ mode: 'CONSENSUS',
907
+ isUnanimous: false,
908
+ action: 'CAUTIOUS',
909
+ sizeMultiplier: 0.5,
910
+ confidence: 0,
911
+ reason: 'Agents disagree - reducing exposure',
912
+ agentCount: advices.length,
913
+ votes: actions.reduce((acc, a) => { acc[a] = (acc[a] || 0) + 1; return acc; }, {})
914
+ };
915
+ }
916
+ }
917
+
918
+ /**
919
+ * Apply optimization to strategy
920
+ * Called when agents have consensus on improvements
921
+ *
922
+ * @param {Object} strategy - Strategy instance (M1)
923
+ * @param {Object} optimization - Optimization to apply
924
+ * @returns {boolean} Success
925
+ */
926
+ static applyOptimization(strategy, optimization) {
927
+ if (!strategy || !optimization) return false;
928
+
929
+ try {
930
+ // Check if strategy has optimization method
931
+ if (typeof strategy.applyOptimization === 'function') {
932
+ strategy.applyOptimization(optimization);
933
+ return true;
934
+ }
935
+
936
+ // Fallback: try to set individual parameters
937
+ if (typeof strategy.setParameter === 'function' && optimization.param) {
938
+ strategy.setParameter(optimization.param, optimization.suggested);
939
+ return true;
940
+ }
941
+
942
+ return false;
943
+ } catch (e) {
944
+ return false;
945
+ }
946
+ }
563
947
  }
564
948
 
565
949
  module.exports = AISupervisor;