diff --git a/src/modules/trading/components/CandlestickChartWithML.tsx b/src/modules/trading/components/CandlestickChartWithML.tsx index 5617774..2cd9ed7 100644 --- a/src/modules/trading/components/CandlestickChartWithML.tsx +++ b/src/modules/trading/components/CandlestickChartWithML.tsx @@ -19,8 +19,8 @@ import { SeriesMarker, } from 'lightweight-charts'; import type { Candle, CandlestickChartProps } from '../../../types/trading.types'; -import type { MLSignal, RangePrediction, AMDPhase } from '../../../services/mlService'; -import { getLatestSignal, getRangePrediction, getAMDPhase } from '../../../services/mlService'; +import type { MLSignal, RangePrediction, AMDPhase, AMDZone } from '../../../services/mlService'; +import { getLatestSignal, getRangePrediction, getAMDPhase, getAMDZones } from '../../../services/mlService'; // ============================================================================ // Types @@ -51,6 +51,7 @@ interface MLOverlays { signal?: MLSignal | null; rangePrediction?: RangePrediction | null; amdPhase?: AMDPhase | null; + amdZones?: AMDZone[]; orderBlocks?: OrderBlock[]; fairValueGaps?: FairValueGap[]; } @@ -62,6 +63,7 @@ interface CandlestickChartWithMLProps extends CandlestickChartProps { showOrderBlocks?: boolean; showFairValueGaps?: boolean; showAMDPhase?: boolean; + showAMDZones?: boolean; autoRefreshML?: boolean; refreshInterval?: number; } @@ -146,6 +148,7 @@ export const CandlestickChartWithML: React.FC = ({ showOrderBlocks = false, showFairValueGaps = false, showAMDPhase = true, + showAMDZones = true, autoRefreshML = true, refreshInterval = 30000, }) => { @@ -157,6 +160,7 @@ export const CandlestickChartWithML: React.FC = ({ const rangeLowSeriesRef = useRef | null>(null); const resizeObserverRef = useRef(null); const priceLinesRef = useRef['createPriceLine']>[]>([]); + const amdZoneSeriesRef = useRef>>(new Map()); const [mlOverlays, setMlOverlays] = useState({}); const [isLoadingML, setIsLoadingML] = useState(false); @@ -169,23 +173,25 @@ export const CandlestickChartWithML: React.FC = ({ setIsLoadingML(true); try { - const [signal, rangePred, amd] = await Promise.all([ + const [signal, rangePred, amd, zones] = await Promise.all([ showSignalLevels ? getLatestSignal(symbol) : Promise.resolve(null), showRangePrediction ? getRangePrediction(symbol, interval) : Promise.resolve(null), showAMDPhase ? getAMDPhase(symbol) : Promise.resolve(null), + showAMDZones ? getAMDZones(symbol, interval, 10) : Promise.resolve([]), ]); setMlOverlays({ signal, rangePrediction: rangePred, amdPhase: amd, + amdZones: zones, }); } catch (error) { console.error('Error fetching ML data:', error); } finally { setIsLoadingML(false); } - }, [symbol, interval, enableMLOverlays, showSignalLevels, showRangePrediction, showAMDPhase]); + }, [symbol, interval, enableMLOverlays, showSignalLevels, showRangePrediction, showAMDPhase, showAMDZones]); // Auto-refresh ML data useEffect(() => { @@ -415,6 +421,84 @@ export const CandlestickChartWithML: React.FC = ({ } }, [mlOverlays, showSignalLevels, showRangePrediction]); + // Update AMD zones visualization + useEffect(() => { + if (!chartRef.current || !showAMDZones) return; + + const chart = chartRef.current; + + // Clear existing AMD zone series + amdZoneSeriesRef.current.forEach((series) => { + try { + chart.removeSeries(series); + } catch { + // Series might already be removed + } + }); + amdZoneSeriesRef.current.clear(); + + // Render new AMD zones + const zones = mlOverlays.amdZones || []; + if (zones.length === 0) return; + + zones.forEach((zone, index) => { + const zoneId = `amd-zone-${index}-${zone.phase}`; + + // Get color based on phase + let zoneColor: string; + switch (zone.phase) { + case 'accumulation': + zoneColor = ML_COLORS.amdAccumulation; + break; + case 'manipulation': + zoneColor = ML_COLORS.amdManipulation; + break; + case 'distribution': + zoneColor = ML_COLORS.amdDistribution; + break; + default: + zoneColor = 'rgba(107, 114, 128, 0.1)'; + } + + // Adjust opacity based on confidence + const opacity = Math.max(0.05, 0.15 * zone.confidence); + const adjustedColor = zoneColor.replace(/[\d.]+\)$/, `${opacity})`); + + // Create area series for zone + const areaSeries = chart.addAreaSeries({ + topColor: adjustedColor, + bottomColor: 'transparent', + lineColor: 'transparent', + lineWidth: 1, + crosshairMarkerVisible: false, + lastValueVisible: false, + priceLineVisible: false, + }); + + // Set zone data + const startTime = (zone.startTime / 1000) as Time; + const endTime = (zone.endTime / 1000) as Time; + + areaSeries.setData([ + { time: startTime, value: zone.priceHigh }, + { time: endTime, value: zone.priceHigh }, + ]); + + amdZoneSeriesRef.current.set(zoneId, areaSeries); + }); + + return () => { + amdZoneSeriesRef.current.forEach((series) => { + try { + chart.removeSeries(series); + } catch { + // Series might already be removed + } + }); + amdZoneSeriesRef.current.clear(); + }; + }, [mlOverlays.amdZones, showAMDZones]); + // Update data method const updateData = useCallback( (candles: Candle[]) => { diff --git a/src/modules/trading/components/TradingScreener.tsx b/src/modules/trading/components/TradingScreener.tsx index dd3033c..b8b695c 100644 --- a/src/modules/trading/components/TradingScreener.tsx +++ b/src/modules/trading/components/TradingScreener.tsx @@ -40,6 +40,7 @@ export interface ScreenerResult { low52w: number; rsi?: number; macdSignal?: 'bullish' | 'bearish' | 'neutral'; + trend?: 'bullish' | 'bearish' | 'sideways'; sma20?: number; sma50?: number; sma200?: number; @@ -47,6 +48,9 @@ export interface ScreenerResult { beta?: number; dividendYield?: number; isFavorite?: boolean; + volumeRatio?: number; + mlSignal?: 'buy' | 'sell' | 'hold' | null; + mlConfidence?: number; } export interface ScreenerFilter { @@ -111,12 +115,29 @@ const FILTER_PRESETS: { name: string; filters: Omit[] }[] name: 'Near 52W High', filters: [{ field: 'changePercent', operator: 'gt', value: 5, enabled: true }], }, + { + name: 'Bullish Trend', + filters: [{ field: 'trend', operator: 'eq', value: 'bullish', enabled: true }], + }, + { + name: 'Bearish Trend', + filters: [{ field: 'trend', operator: 'eq', value: 'bearish', enabled: true }], + }, + { + name: 'Change > 2%', + filters: [{ field: 'changePercent', operator: 'gt', value: 2, enabled: true }], + }, + { + name: 'ML Buy Signal', + filters: [{ field: 'mlSignal', operator: 'eq', value: 'buy', enabled: true }], + }, ]; const FILTERABLE_FIELDS: { field: keyof ScreenerResult; label: string; type: 'number' | 'string' }[] = [ { field: 'price', label: 'Price', type: 'number' }, { field: 'changePercent', label: 'Change %', type: 'number' }, { field: 'volume', label: 'Volume', type: 'number' }, + { field: 'volumeRatio', label: 'Vol Ratio', type: 'number' }, { field: 'marketCap', label: 'Market Cap', type: 'number' }, { field: 'pe', label: 'P/E Ratio', type: 'number' }, { field: 'rsi', label: 'RSI', type: 'number' }, @@ -124,6 +145,9 @@ const FILTERABLE_FIELDS: { field: keyof ScreenerResult; label: string; type: 'nu { field: 'dividendYield', label: 'Dividend Yield', type: 'number' }, { field: 'sector', label: 'Sector', type: 'string' }, { field: 'exchange', label: 'Exchange', type: 'string' }, + { field: 'trend', label: 'Trend', type: 'string' }, + { field: 'mlSignal', label: 'ML Signal', type: 'string' }, + { field: 'mlConfidence', label: 'ML Confidence', type: 'number' }, ]; const TradingScreener: React.FC = ({ @@ -152,7 +176,11 @@ const TradingScreener: React.FC = ({ // Apply filters and sorting const filteredResults = useMemo(() => { - let filtered = [...results]; + // Enrich results with computed fields if not present + let filtered = results.map((r) => ({ + ...r, + volumeRatio: r.volumeRatio ?? (r.avgVolume > 0 ? r.volume / r.avgVolume : undefined), + })); // Search filter if (searchQuery) { @@ -529,6 +557,16 @@ const TradingScreener: React.FC = ({ RSI{renderSortIndicator('rsi')} + + + + + + Actions @@ -581,6 +619,37 @@ const TradingScreener: React.FC = ({ )} + + {result.volumeRatio !== undefined && ( + 2 ? 'text-amber-400' : result.volumeRatio > 1 ? 'text-green-400' : 'text-gray-300' + }`} + > + {result.volumeRatio.toFixed(2)}x + + )} + + + {result.mlSignal && ( + + {result.mlSignal.toUpperCase()} + {result.mlConfidence !== undefined && ( + + {(result.mlConfidence * 100).toFixed(0)}% + + )} + + )} +