trading-platform-frontend/src/modules/ml/components/ICTAnalysisCard.tsx

294 lines
10 KiB
TypeScript

/**
* ICT Analysis Card Component
* Displays Smart Money Concepts analysis in a visual format
*/
import React from 'react';
import {
ArrowTrendingUpIcon,
ArrowTrendingDownIcon,
MinusIcon,
ChartBarIcon,
ExclamationTriangleIcon,
CheckCircleIcon,
XCircleIcon,
} from '@heroicons/react/24/solid';
interface OrderBlock {
type: 'bullish' | 'bearish';
high: number;
low: number;
midpoint: number;
strength: number;
valid: boolean;
touched: boolean;
}
interface FairValueGap {
type: 'bullish' | 'bearish';
high: number;
low: number;
midpoint: number;
size_percent: number;
filled: boolean;
}
interface ICTAnalysis {
symbol: string;
timeframe: string;
market_bias: 'bullish' | 'bearish' | 'neutral';
bias_confidence: number;
current_trend: string;
order_blocks: OrderBlock[];
fair_value_gaps: FairValueGap[];
entry_zone?: { low: number; high: number };
stop_loss?: number;
take_profits: { tp1?: number; tp2?: number; tp3?: number };
risk_reward?: number;
signals: string[];
score: number;
premium_zone: { low: number; high: number };
discount_zone: { low: number; high: number };
equilibrium: number;
}
interface ICTAnalysisCardProps {
analysis: ICTAnalysis;
onExecuteTrade?: (direction: 'buy' | 'sell', analysis: ICTAnalysis) => void;
className?: string;
}
export const ICTAnalysisCard: React.FC<ICTAnalysisCardProps> = ({
analysis,
onExecuteTrade,
className = '',
}) => {
const getBiasIcon = () => {
switch (analysis.market_bias) {
case 'bullish':
return <ArrowTrendingUpIcon className="w-6 h-6 text-green-400" />;
case 'bearish':
return <ArrowTrendingDownIcon className="w-6 h-6 text-red-400" />;
default:
return <MinusIcon className="w-6 h-6 text-gray-400" />;
}
};
const getBiasColor = () => {
switch (analysis.market_bias) {
case 'bullish':
return 'bg-green-500/20 text-green-400 border-green-500/30';
case 'bearish':
return 'bg-red-500/20 text-red-400 border-red-500/30';
default:
return 'bg-gray-500/20 text-gray-400 border-gray-500/30';
}
};
const getScoreColor = (score: number) => {
if (score >= 70) return 'text-green-400';
if (score >= 50) return 'text-yellow-400';
return 'text-red-400';
};
const validOrderBlocks = analysis.order_blocks.filter(ob => ob.valid);
const unfilledFVGs = analysis.fair_value_gaps.filter(fvg => !fvg.filled);
return (
<div className={`bg-gray-900 border border-gray-800 rounded-xl p-5 ${className}`}>
{/* Header */}
<div className="flex items-start justify-between mb-4">
<div>
<div className="flex items-center gap-2">
<h3 className="text-xl font-bold text-white">{analysis.symbol}</h3>
<span className="text-xs px-2 py-1 bg-gray-800 rounded text-gray-400">
{analysis.timeframe}
</span>
</div>
<p className="text-sm text-gray-500 mt-1">ICT/SMC Analysis</p>
</div>
{/* Score Badge */}
<div className="text-right">
<div className={`text-3xl font-bold ${getScoreColor(analysis.score)}`}>
{analysis.score}
</div>
<p className="text-xs text-gray-500">Setup Score</p>
</div>
</div>
{/* Market Bias */}
<div className={`flex items-center gap-3 p-3 rounded-lg border mb-4 ${getBiasColor()}`}>
{getBiasIcon()}
<div>
<p className="font-semibold uppercase">{analysis.market_bias} Bias</p>
<p className="text-sm opacity-80">
{Math.round(analysis.bias_confidence * 100)}% confidence {analysis.current_trend}
</p>
</div>
</div>
{/* Key Levels Grid */}
{analysis.entry_zone && (
<div className="mb-4">
<h4 className="text-sm font-semibold text-gray-400 mb-2">Trade Setup</h4>
<div className="grid grid-cols-2 gap-2">
<div className="p-3 bg-blue-900/20 border border-blue-800/30 rounded-lg">
<p className="text-xs text-blue-400">Entry Zone</p>
<p className="font-mono text-white">
{analysis.entry_zone.low.toFixed(5)} - {analysis.entry_zone.high.toFixed(5)}
</p>
</div>
{analysis.stop_loss && (
<div className="p-3 bg-red-900/20 border border-red-800/30 rounded-lg">
<p className="text-xs text-red-400">Stop Loss</p>
<p className="font-mono text-white">{analysis.stop_loss.toFixed(5)}</p>
</div>
)}
{analysis.take_profits.tp1 && (
<div className="p-3 bg-green-900/20 border border-green-800/30 rounded-lg">
<p className="text-xs text-green-400">Take Profit 1</p>
<p className="font-mono text-white">{analysis.take_profits.tp1.toFixed(5)}</p>
</div>
)}
{analysis.take_profits.tp2 && (
<div className="p-3 bg-green-900/20 border border-green-800/30 rounded-lg">
<p className="text-xs text-green-400">Take Profit 2</p>
<p className="font-mono text-white">{analysis.take_profits.tp2.toFixed(5)}</p>
</div>
)}
</div>
{analysis.risk_reward && (
<div className="mt-2 text-center">
<span className="text-sm text-gray-400">Risk:Reward</span>
<span className="ml-2 font-bold text-white">1:{analysis.risk_reward}</span>
</div>
)}
</div>
)}
{/* Order Blocks */}
{validOrderBlocks.length > 0 && (
<div className="mb-4">
<h4 className="text-sm font-semibold text-gray-400 mb-2 flex items-center gap-2">
<ChartBarIcon className="w-4 h-4" />
Order Blocks ({validOrderBlocks.length})
</h4>
<div className="space-y-2">
{validOrderBlocks.slice(0, 3).map((ob, idx) => (
<div
key={idx}
className={`flex items-center justify-between p-2 rounded ${
ob.type === 'bullish' ? 'bg-green-900/20' : 'bg-red-900/20'
}`}
>
<div className="flex items-center gap-2">
{ob.type === 'bullish' ? (
<ArrowTrendingUpIcon className="w-4 h-4 text-green-400" />
) : (
<ArrowTrendingDownIcon className="w-4 h-4 text-red-400" />
)}
<span className="text-sm text-white">
{ob.low.toFixed(5)} - {ob.high.toFixed(5)}
</span>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-400">
{Math.round(ob.strength * 100)}%
</span>
{ob.touched ? (
<ExclamationTriangleIcon className="w-4 h-4 text-yellow-400" title="Touched" />
) : (
<CheckCircleIcon className="w-4 h-4 text-green-400" title="Fresh" />
)}
</div>
</div>
))}
</div>
</div>
)}
{/* Fair Value Gaps */}
{unfilledFVGs.length > 0 && (
<div className="mb-4">
<h4 className="text-sm font-semibold text-gray-400 mb-2">
Fair Value Gaps ({unfilledFVGs.length} unfilled)
</h4>
<div className="space-y-2">
{unfilledFVGs.slice(0, 3).map((fvg, idx) => (
<div
key={idx}
className={`flex items-center justify-between p-2 rounded ${
fvg.type === 'bullish' ? 'bg-green-900/10' : 'bg-red-900/10'
}`}
>
<span className="text-sm text-white">
{fvg.low.toFixed(5)} - {fvg.high.toFixed(5)}
</span>
<span className="text-xs text-gray-400">
{fvg.size_percent.toFixed(2)}%
</span>
</div>
))}
</div>
</div>
)}
{/* Signals */}
{analysis.signals.length > 0 && (
<div className="mb-4">
<h4 className="text-sm font-semibold text-gray-400 mb-2">Active Signals</h4>
<div className="flex flex-wrap gap-1">
{analysis.signals.slice(0, 6).map((signal, idx) => (
<span
key={idx}
className="text-xs px-2 py-1 bg-gray-800 rounded text-gray-300"
>
{signal.replace(/_/g, ' ')}
</span>
))}
</div>
</div>
)}
{/* Premium/Discount Zones */}
<div className="mb-4 p-3 bg-gray-800/50 rounded-lg">
<h4 className="text-sm font-semibold text-gray-400 mb-2">Fibonacci Zones</h4>
<div className="grid grid-cols-3 gap-2 text-center text-xs">
<div>
<p className="text-red-400">Premium</p>
<p className="text-white font-mono">{analysis.premium_zone.low.toFixed(5)}</p>
</div>
<div>
<p className="text-gray-400">Equilibrium</p>
<p className="text-white font-mono">{analysis.equilibrium.toFixed(5)}</p>
</div>
<div>
<p className="text-green-400">Discount</p>
<p className="text-white font-mono">{analysis.discount_zone.high.toFixed(5)}</p>
</div>
</div>
</div>
{/* Action Buttons */}
{onExecuteTrade && analysis.score >= 50 && analysis.market_bias !== 'neutral' && (
<button
onClick={() => onExecuteTrade(
analysis.market_bias === 'bullish' ? 'buy' : 'sell',
analysis
)}
className={`w-full py-3 rounded-lg font-semibold transition-colors ${
analysis.market_bias === 'bullish'
? 'bg-green-600 hover:bg-green-700 text-white'
: 'bg-red-600 hover:bg-red-700 text-white'
}`}
>
{analysis.market_bias === 'bullish' ? 'Execute Buy' : 'Execute Sell'}
</button>
)}
</div>
);
};
export default ICTAnalysisCard;