294 lines
10 KiB
TypeScript
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;
|