diff --git a/src/modules/ml/pages/MLDashboard.tsx b/src/modules/ml/pages/MLDashboard.tsx index 9a78360..9fca8db 100644 --- a/src/modules/ml/pages/MLDashboard.tsx +++ b/src/modules/ml/pages/MLDashboard.tsx @@ -32,6 +32,7 @@ import { SignalsTimeline } from '../components/SignalsTimeline'; import { AccuracyMetrics } from '../components/AccuracyMetrics'; import { ICTAnalysisCard } from '../components/ICTAnalysisCard'; import { EnsembleSignalCard } from '../components/EnsembleSignalCard'; +import EnsemblePanel, { type EnsembleConfig } from '../components/EnsemblePanel'; // Mock accuracy metrics (replace with API call) const mockMetrics = { @@ -47,10 +48,33 @@ const mockMetrics = { profit_factor: 1.7, }; -// Available symbols and timeframes -const SYMBOLS = ['EURUSD', 'GBPUSD', 'USDJPY', 'XAUUSD', 'BTCUSD', 'ETHUSD']; +// Available symbols and timeframes - Configurable list +// Includes Forex majors, commodities, and crypto +const SYMBOLS = [ + 'EURUSD', // EUR/USD - Forex major + 'GBPUSD', // GBP/USD - Forex major + 'USDJPY', // USD/JPY - Forex major + 'XAUUSD', // XAU/USD - Gold + 'BTCUSD', // BTC/USD - Bitcoin + 'ETHUSD', // ETH/USD - Ethereum + 'USDCHF', // USD/CHF - Forex major + 'AUDUSD', // AUD/USD - Forex major +]; const TIMEFRAMES = ['15M', '30M', '1H', '4H', '1D']; +// Default ensemble configuration for model weights +const DEFAULT_ENSEMBLE_CONFIG: EnsembleConfig = { + votingMethod: 'weighted', + minimumAgreement: 2, + confidenceThreshold: 60, + weights: [ + { modelId: 'amd', modelName: 'AMD Phase Detector', weight: 25, accuracy: 72 }, + { modelId: 'ict', modelName: 'ICT/SMC Analyzer', weight: 30, accuracy: 75 }, + { modelId: 'range', modelName: 'Range Predictor', weight: 25, accuracy: 68 }, + { modelId: 'tpsl', modelName: 'TP/SL Optimizer', weight: 20, accuracy: 70 }, + ], +}; + export default function MLDashboard() { const [signals, setSignals] = useState([]); const [amdPhases, setAmdPhases] = useState>(new Map()); @@ -69,6 +93,10 @@ export default function MLDashboard() { const [selectedTimeframe, setSelectedTimeframe] = useState('1H'); const [showOnlyActive, setShowOnlyActive] = useState(true); + // Ensemble configuration state + const [ensembleConfig, setEnsembleConfig] = useState(DEFAULT_ENSEMBLE_CONFIG); + const [showEnsembleConfig, setShowEnsembleConfig] = useState(false); + // Fetch all ML data const fetchMLData = useCallback(async () => { setLoading(true); @@ -174,6 +202,67 @@ export default function MLDashboard() { alert(`Would execute ${direction.toUpperCase()} trade for ${selectedSymbol}`); }; + // Handle ensemble config changes + const handleEnsembleConfigChange = (newConfig: EnsembleConfig) => { + setEnsembleConfig(newConfig); + }; + + const handleSaveEnsembleConfig = () => { + // Save ensemble config to localStorage for persistence + localStorage.setItem('ensembleConfig', JSON.stringify(ensembleConfig)); + setShowEnsembleConfig(false); + // Refetch ensemble signal with new weights + fetchAdvancedAnalysis(); + }; + + const handleResetEnsembleConfig = () => { + setEnsembleConfig(DEFAULT_ENSEMBLE_CONFIG); + }; + + // Calculate combined ensemble prediction based on current weights + const getWeightedEnsemblePrediction = useCallback(() => { + if (!ensembleSignal) return null; + + const strategies = ensembleSignal.strategy_signals; + const weights = ensembleConfig.weights; + + let totalWeightedScore = 0; + let totalWeight = 0; + let agreementCount = 0; + const action = ensembleSignal.action; + + // Calculate weighted score based on user-configured weights + weights.forEach((w) => { + const strategyKey = w.modelId as keyof typeof strategies; + const strategy = strategies[strategyKey]; + if (strategy) { + const normalizedWeight = w.weight / 100; + totalWeightedScore += strategy.score * normalizedWeight; + totalWeight += normalizedWeight; + + // Count agreement + if ( + (action === 'BUY' && strategy.action === 'BUY') || + (action === 'SELL' && strategy.action === 'SELL') + ) { + agreementCount++; + } + } + }); + + const adjustedConfidence = totalWeight > 0 ? totalWeightedScore / totalWeight : 0; + const meetsAgreement = agreementCount >= ensembleConfig.minimumAgreement; + const meetsConfidence = Math.abs(adjustedConfidence) * 100 >= ensembleConfig.confidenceThreshold; + + return { + adjustedScore: adjustedConfidence, + agreementCount, + meetsAgreement, + meetsConfidence, + isValidSignal: meetsAgreement && meetsConfidence, + }; + }, [ensembleSignal, ensembleConfig]); + return (
{/* Header */} @@ -364,48 +453,117 @@ export default function MLDashboard() { {/* Tab Content - Ensemble Signal */} {activeTab === 'ensemble' && ( -
- {ensembleSignal ? ( - - ) : ( -
-
Loading ensemble signal...
-
- )} +
+ {/* Ensemble Controls */} +
+

Ensemble Analysis

+ +
- {/* Quick comparison of all symbols */} -
-

All Symbols Overview

-
- {scanResults.map((result, idx) => ( -
-
- {result.symbol} - +
+
+

Weighted Ensemble Prediction

+
+ - {result.signal.action} + {ensembleSignal.action} -
-
-
-
0 ? 'bg-green-500' : 'bg-red-500' - }`} - style={{ width: `${Math.abs(result.signal.net_score) * 50 + 50}%` }} - /> -
- - {result.signal.net_score >= 0 ? '+' : ''}{result.signal.net_score.toFixed(2)} + + {Math.round(ensembleSignal.confidence * 100)}% confidence
- ))} + {(() => { + const prediction = getWeightedEnsemblePrediction(); + if (!prediction) return null; + return ( +
+
+ {prediction.isValidSignal ? 'Signal Valid' : 'Signal Weak'} +
+
+ {prediction.agreementCount}/{ensembleConfig.weights.length} models agree +
+
+ ); + })()} +
+
+ )} + + {/* Ensemble Configuration Panel */} + {showEnsembleConfig && ( + + )} + +
+ {ensembleSignal ? ( + + ) : ( +
+
Loading ensemble signal...
+
+ )} + + {/* Quick comparison of all symbols */} +
+

All Symbols Overview

+
+ {scanResults.map((result, idx) => ( + + ))} +