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
|
-
//
|
|
323
|
-
let
|
|
324
|
-
let
|
|
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
|
|
327
|
-
|
|
328
|
-
|
|
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:
|
|
341
|
-
confidence: avgConfidence,
|
|
346
|
+
action: isUnanimous ? unanimousAction : 'HOLD',
|
|
347
|
+
confidence: isUnanimous ? avgConfidence : null,
|
|
342
348
|
votes,
|
|
343
349
|
agentCount: decisions.length,
|
|
344
|
-
|
|
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;
|