feat: implement stock evaluation filters, fundamentals styling, and AI trading profiles
Build Institutional Trader / build-and-deploy (push) Successful in 2m1s
Details
Build Institutional Trader / build-and-deploy (push) Successful in 2m1s
Details
This commit is contained in:
parent
c981faf3a6
commit
f91534e976
|
|
@ -49,6 +49,8 @@ router.get('/universe', async (req, res) => {
|
|||
change_pct: quote?.change_pct ?? null,
|
||||
market_cap: quote?.market_cap ?? null,
|
||||
volume: quote?.volume ?? null,
|
||||
day_high: quote?.high ?? null,
|
||||
day_low: quote?.low ?? null,
|
||||
high_52w: null, // populated by quoteSummary if needed
|
||||
low_52w: null,
|
||||
market_state: quote?.market_state ?? null,
|
||||
|
|
|
|||
|
|
@ -303,20 +303,26 @@ ${yahooData ? `STOCK DATA (Yahoo Finance):
|
|||
- Volume: ${yahooData.volume?.toLocaleString() || 'N/A'}
|
||||
` : ''}
|
||||
|
||||
Provide a concise analysis (2-3 sentences max) with:
|
||||
1. Overall assessment (Bullish/Bearish/Neutral)
|
||||
2. Key risk factors or opportunities
|
||||
3. Clear action recommendation (Enter/Wait/Avoid)
|
||||
Provide a detailed analysis categorized into three distinct trader profiles based on the data.
|
||||
|
||||
Format as JSON:
|
||||
{
|
||||
"assessment": "BULLISH|BEARISH|NEUTRAL",
|
||||
"summary": "Brief 1-2 sentence summary",
|
||||
"keyFactors": ["factor1", "factor2", "factor3"],
|
||||
"recommendation": "ENTER|WAIT|AVOID",
|
||||
"reasoning": "Brief explanation",
|
||||
"riskLevel": "LOW|MEDIUM|HIGH",
|
||||
"timeHorizon": "INTRADAY|SWING|POSITION"
|
||||
"longTerm": {
|
||||
"assessment": "BULLISH|BEARISH|NEUTRAL",
|
||||
"moatAndFundamentals": "Brief analysis of economic moat, EPS growth, and fundamental ratios (P/E, P/B)",
|
||||
"recommendation": "ENTER|WAIT|AVOID"
|
||||
},
|
||||
"swing": {
|
||||
"assessment": "BULLISH|BEARISH|NEUTRAL",
|
||||
"technicalSetup": "Analysis of technical setups, consolidation, moving averages, and RSI momentum",
|
||||
"recommendation": "ENTER|WAIT|AVOID"
|
||||
},
|
||||
"dayTrade": {
|
||||
"assessment": "BULLISH|BEARISH|NEUTRAL",
|
||||
"volatilityAndLiquidity": "Analysis of immediate catalysts, intraday liquidity/volume, and volatility risks",
|
||||
"recommendation": "ENTER|WAIT|AVOID"
|
||||
},
|
||||
"summary": "Overall 1-2 sentence conclusion covering the most viable approach"
|
||||
}`;
|
||||
|
||||
// Use model from env or default to stable version
|
||||
|
|
@ -346,13 +352,22 @@ Format as JSON:
|
|||
// Fallback: create structured response from text
|
||||
console.warn('Failed to parse AI response as JSON, using fallback');
|
||||
analysis = {
|
||||
assessment: direction,
|
||||
summary: responseText.substring(0, 200),
|
||||
keyFactors: [],
|
||||
recommendation: score >= 2.0 ? 'ENTER' : 'WAIT',
|
||||
reasoning: responseText,
|
||||
riskLevel: score < 2.0 ? 'HIGH' : score < 5.0 ? 'MEDIUM' : 'LOW',
|
||||
timeHorizon: 'INTRADAY'
|
||||
longTerm: {
|
||||
assessment: direction,
|
||||
moatAndFundamentals: 'Unable to perform deep fundamental analysis. Please review ratios manually.',
|
||||
recommendation: score >= 5.0 ? 'ENTER' : 'WAIT'
|
||||
},
|
||||
swing: {
|
||||
assessment: direction,
|
||||
technicalSetup: 'Technical analysis currently unavailable.',
|
||||
recommendation: score >= 3.0 ? 'ENTER' : 'WAIT'
|
||||
},
|
||||
dayTrade: {
|
||||
assessment: direction,
|
||||
volatilityAndLiquidity: 'Analysis of intraday volume and catalysts unavailable.',
|
||||
recommendation: score >= 2.0 ? 'ENTER' : 'WAIT'
|
||||
},
|
||||
summary: responseText.substring(0, 200) || 'Analysis unavailable'
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -369,13 +384,10 @@ Format as JSON:
|
|||
success: false,
|
||||
error: error.message,
|
||||
analysis: {
|
||||
assessment: 'NEUTRAL',
|
||||
summary: 'Analysis unavailable',
|
||||
keyFactors: [],
|
||||
recommendation: 'WAIT',
|
||||
reasoning: 'Unable to generate analysis',
|
||||
riskLevel: 'MEDIUM',
|
||||
timeHorizon: 'INTRADAY'
|
||||
longTerm: { assessment: 'NEUTRAL', moatAndFundamentals: 'Analysis unavailable', recommendation: 'WAIT' },
|
||||
swing: { assessment: 'NEUTRAL', technicalSetup: 'Analysis unavailable', recommendation: 'WAIT' },
|
||||
dayTrade: { assessment: 'NEUTRAL', volatilityAndLiquidity: 'Analysis unavailable', recommendation: 'WAIT' },
|
||||
summary: 'Analysis unavailable'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ function CapBadge({ tier }) {
|
|||
|
||||
export default function MarketScreenerPanel({ onSelectSymbol }) {
|
||||
const [activeTab, setActiveTab] = useState('all');
|
||||
const [setupFilter, setSetupFilter] = useState('all');
|
||||
const [sortBy, setSortBy] = useState('market_cap');
|
||||
const [sortDir, setSortDir] = useState('desc');
|
||||
const [search, setSearch] = useState('');
|
||||
|
|
@ -98,11 +99,30 @@ export default function MarketScreenerPanel({ onSelectSymbol }) {
|
|||
}, [fetchData]);
|
||||
|
||||
const processed = [...data]
|
||||
.map(r => {
|
||||
// Calculate intraday volatility if we have high/low
|
||||
const volPct = (r.day_high && r.day_low && r.day_low > 0)
|
||||
? ((r.day_high - r.day_low) / r.day_low) * 100
|
||||
: null;
|
||||
return { ...r, volatility_pct: volPct };
|
||||
})
|
||||
.filter(r => {
|
||||
if (!search) return true;
|
||||
const q = search.toUpperCase();
|
||||
return r.symbol.includes(q) || r.name.toUpperCase().includes(q) || r.sector?.toUpperCase().includes(q);
|
||||
})
|
||||
.filter(r => {
|
||||
if (setupFilter === 'all') return true;
|
||||
if (setupFilter === 'day') {
|
||||
// Day Trade: high volume (>1M), high volatility (>3%)
|
||||
return (r.volume >= 1000000) && (r.volatility_pct > 3 || Math.abs(r.change_pct || 0) > 3);
|
||||
}
|
||||
if (setupFilter === 'swing') {
|
||||
// Swing Trade: potential reversal (massive drop > 5% or massive gain > 5% on high volume)
|
||||
return (r.volume >= 500000) && (Math.abs(r.change_pct || 0) > 5);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const av = a[sortBy] ?? -Infinity;
|
||||
const bv = b[sortBy] ?? -Infinity;
|
||||
|
|
@ -161,20 +181,38 @@ export default function MarketScreenerPanel({ onSelectSymbol }) {
|
|||
</div>
|
||||
|
||||
{/* Cap tier tabs */}
|
||||
<div className="flex border-b border-slate-700/50 bg-slate-950/30">
|
||||
{CAP_TABS.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`px-4 py-2.5 text-sm font-medium transition-all ${
|
||||
activeTab === tab.id
|
||||
? `border-b-2 border-blue-500 ${tab.color} bg-blue-500/5`
|
||||
: 'text-slate-500 hover:text-slate-300'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex flex-col sm:flex-row justify-between border-b border-slate-700/50 bg-slate-950/30">
|
||||
<div className="flex">
|
||||
{CAP_TABS.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`px-4 py-2.5 text-sm font-medium transition-all ${
|
||||
activeTab === tab.id
|
||||
? `border-b-2 border-blue-500 ${tab.color} bg-blue-500/5`
|
||||
: 'text-slate-500 hover:text-slate-300'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center px-4 space-x-2 border-t sm:border-t-0 border-slate-700/50 py-2 sm:py-0">
|
||||
<span className="text-xs text-slate-500 uppercase font-semibold mr-2">Setups:</span>
|
||||
{['all', 'day', 'swing'].map(f => (
|
||||
<button
|
||||
key={f}
|
||||
onClick={() => setSetupFilter(f)}
|
||||
className={`px-3 py-1 text-xs rounded transition-all ${
|
||||
setupFilter === f
|
||||
? 'bg-blue-500/20 text-blue-400 border border-blue-500/30'
|
||||
: 'bg-slate-800 text-slate-400 border border-slate-700 hover:bg-slate-700'
|
||||
}`}
|
||||
>
|
||||
{f === 'all' ? 'None' : f === 'day' ? '⚡ Day Trade' : '🌊 Swing Trade'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
|
|
@ -207,6 +245,10 @@ export default function MarketScreenerPanel({ onSelectSymbol }) {
|
|||
className="text-right px-4 py-3 font-medium cursor-pointer hover:text-white transition-colors"
|
||||
onClick={() => toggleSort('volume')}
|
||||
>Volume <SortIcon col="volume" /></th>
|
||||
<th
|
||||
className="text-right px-4 py-3 font-medium cursor-pointer hover:text-white transition-colors"
|
||||
onClick={() => toggleSort('volatility_pct')}
|
||||
>Volatility <SortIcon col="volatility_pct" /></th>
|
||||
<th className="text-center px-4 py-3 font-medium">Cap Tier</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -214,7 +256,7 @@ export default function MarketScreenerPanel({ onSelectSymbol }) {
|
|||
{loading && data.length === 0 ? (
|
||||
Array.from({ length: 10 }).map((_, i) => (
|
||||
<tr key={i} className="animate-pulse">
|
||||
{Array.from({ length: 8 }).map((_, j) => (
|
||||
{Array.from({ length: 9 }).map((_, j) => (
|
||||
<td key={j} className="px-4 py-3">
|
||||
<div className="h-4 bg-slate-800 rounded w-full" />
|
||||
</td>
|
||||
|
|
@ -222,7 +264,7 @@ export default function MarketScreenerPanel({ onSelectSymbol }) {
|
|||
</tr>
|
||||
))
|
||||
) : processed.length === 0 ? (
|
||||
<tr><td colSpan={8} className="px-4 py-12 text-center text-slate-500">No results found</td></tr>
|
||||
<tr><td colSpan={9} className="px-4 py-12 text-center text-slate-500">No results found</td></tr>
|
||||
) : (
|
||||
processed.map(row => (
|
||||
<tr
|
||||
|
|
@ -255,6 +297,13 @@ export default function MarketScreenerPanel({ onSelectSymbol }) {
|
|||
<td className="px-4 py-3 text-right text-slate-400 tabular-nums">
|
||||
{fmtVolume(row.volume)}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right tabular-nums">
|
||||
{row.volatility_pct != null ? (
|
||||
<span className={`text-sm ${row.volatility_pct > 3 ? 'text-emerald-400 font-medium' : 'text-slate-400'}`}>
|
||||
{row.volatility_pct.toFixed(2)}%
|
||||
</span>
|
||||
) : '—'}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-center">
|
||||
<CapBadge tier={row.capTier} />
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -241,9 +241,9 @@ function FundamentalsTab({ f }) {
|
|||
{
|
||||
title: '💰 Valuation',
|
||||
metrics: [
|
||||
{ label: 'P/E Ratio (TTM)', value: fmt(f.pe_ttm, 1) },
|
||||
{ label: 'P/E Ratio (Fwd)', value: fmt(f.pe_forward, 1) },
|
||||
{ label: 'Price / Book', value: fmt(f.price_to_book, 2) },
|
||||
{ label: 'P/E Ratio (TTM)', value: fmt(f.pe_ttm, 1), h: (f.pe_ttm >= 15 && f.pe_ttm <= 25) ? 'positive' : (f.pe_ttm < 0 || f.pe_ttm > 50) ? 'negative' : undefined },
|
||||
{ label: 'P/E Ratio (Fwd)', value: fmt(f.pe_forward, 1), h: (f.pe_forward >= 15 && f.pe_forward <= 25) ? 'positive' : (f.pe_forward < 0 || f.pe_forward > 50) ? 'negative' : undefined },
|
||||
{ label: 'Price / Book', value: fmt(f.price_to_book, 2), h: (f.price_to_book >= 1 && f.price_to_book <= 3) ? 'positive' : (f.price_to_book > 10) ? 'negative' : undefined },
|
||||
{ label: 'Price / Sales', value: fmt(f.price_to_sales, 2) },
|
||||
{ label: 'EV / EBITDA', value: fmt(f.ev_ebitda, 1) },
|
||||
{ label: 'Market Cap', value: fmtLarge(f.market_cap) },
|
||||
|
|
@ -267,7 +267,7 @@ function FundamentalsTab({ f }) {
|
|||
{ label: 'EBITDA Margin', value: fmtPct(f.ebitda_margins) },
|
||||
{ label: 'Operating Margin', value: fmtPct(f.operating_margins) },
|
||||
{ label: 'Profit Margin', value: fmtPct(f.profit_margins) },
|
||||
{ label: 'Return on Equity', value: fmtPct(f.return_on_equity), h: f.return_on_equity > 0.15 ? 'positive' : f.return_on_equity < 0 ? 'negative' : undefined },
|
||||
{ label: 'Return on Equity', value: fmtPct(f.return_on_equity), h: (f.return_on_equity >= 0.1 && f.return_on_equity <= 0.2) ? 'positive' : f.return_on_equity < 0 ? 'negative' : undefined },
|
||||
{ label: 'Return on Assets', value: fmtPct(f.return_on_assets) },
|
||||
]
|
||||
},
|
||||
|
|
@ -303,7 +303,7 @@ function FundamentalsTab({ f }) {
|
|||
{ label: 'Inst. Ownership', value: fmtPct(f.held_pct_institutions) },
|
||||
{ label: 'Insider Ownership', value: fmtPct(f.held_pct_insiders) },
|
||||
{ label: 'Dividend Yield', value: fmtPct(f.dividend_yield), h: f.dividend_yield > 0 ? 'positive' : undefined },
|
||||
{ label: 'Payout Ratio', value: fmtPct(f.payout_ratio) },
|
||||
{ label: 'Payout Ratio', value: fmtPct(f.payout_ratio), h: f.payout_ratio < 0.6 ? 'positive' : f.payout_ratio > 0.7 ? 'negative' : undefined },
|
||||
]
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -495,49 +495,60 @@ export function TradeAnalysisModal({ row, isOpen, onClose }) {
|
|||
)}
|
||||
|
||||
{aiAnalysis && aiAnalysis.analysis && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
variant={
|
||||
aiAnalysis.analysis.assessment === 'BULLISH' ? 'success' :
|
||||
aiAnalysis.analysis.assessment === 'BEARISH' ? 'destructive' : 'secondary'
|
||||
}
|
||||
className="text-xs"
|
||||
>
|
||||
{aiAnalysis.analysis.assessment}
|
||||
</Badge>
|
||||
<Badge
|
||||
variant={
|
||||
aiAnalysis.analysis.recommendation === 'ENTER' ? 'success' :
|
||||
aiAnalysis.analysis.recommendation === 'WAIT' ? 'warning' : 'destructive'
|
||||
}
|
||||
className="text-xs"
|
||||
>
|
||||
{aiAnalysis.analysis.recommendation}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{aiAnalysis.analysis.riskLevel} Risk
|
||||
</Badge>
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm text-slate-200 bg-slate-700/30 p-3 rounded border border-slate-600/50">
|
||||
<strong>Summary:</strong> {aiAnalysis.analysis.summary}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{/* Long Term */}
|
||||
{aiAnalysis.analysis.longTerm && (
|
||||
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
|
||||
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Long-Term Investor</div>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<Badge variant={aiAnalysis.analysis.longTerm.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.longTerm.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.longTerm.assessment}
|
||||
</Badge>
|
||||
<Badge variant={aiAnalysis.analysis.longTerm.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.longTerm.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.longTerm.recommendation}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.longTerm.moatAndFundamentals}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Swing Trade */}
|
||||
{aiAnalysis.analysis.swing && (
|
||||
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
|
||||
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Swing Trader</div>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<Badge variant={aiAnalysis.analysis.swing.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.swing.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.swing.assessment}
|
||||
</Badge>
|
||||
<Badge variant={aiAnalysis.analysis.swing.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.swing.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.swing.recommendation}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.swing.technicalSetup}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Day Trade */}
|
||||
{aiAnalysis.analysis.dayTrade && (
|
||||
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
|
||||
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Day Trader</div>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<Badge variant={aiAnalysis.analysis.dayTrade.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.dayTrade.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.dayTrade.assessment}
|
||||
</Badge>
|
||||
<Badge variant={aiAnalysis.analysis.dayTrade.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.dayTrade.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.dayTrade.recommendation}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.dayTrade.volatilityAndLiquidity}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-slate-200">{aiAnalysis.analysis.summary}</p>
|
||||
|
||||
{aiAnalysis.analysis.keyFactors && aiAnalysis.analysis.keyFactors.length > 0 && (
|
||||
<div>
|
||||
<div className="text-xs text-slate-500 mb-1">Key Factors:</div>
|
||||
<ul className="text-xs text-slate-300 space-y-1 list-disc list-inside">
|
||||
{aiAnalysis.analysis.keyFactors.map((factor, idx) => (
|
||||
<li key={idx}>{factor}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{aiAnalysis.analysis.reasoning && (
|
||||
<div>
|
||||
<div className="text-xs text-slate-500 mb-1">Reasoning:</div>
|
||||
<p className="text-xs text-slate-300">{aiAnalysis.analysis.reasoning}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -60,56 +60,64 @@ export function AnalysisSection({ row, hasPhase1Data, onPhase1Click }) {
|
|||
)}
|
||||
|
||||
{aiAnalysis && aiAnalysis.analysis && (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Badge
|
||||
variant={
|
||||
aiAnalysis.analysis.assessment === 'BULLISH' ? 'success' :
|
||||
aiAnalysis.analysis.assessment === 'BEARISH' ? 'destructive' : 'secondary'
|
||||
}
|
||||
className="text-xs"
|
||||
>
|
||||
{aiAnalysis.analysis.assessment}
|
||||
</Badge>
|
||||
<Badge
|
||||
variant={
|
||||
aiAnalysis.analysis.recommendation === 'ENTER' ? 'success' :
|
||||
aiAnalysis.analysis.recommendation === 'WAIT' ? 'warning' : 'destructive'
|
||||
}
|
||||
className="text-xs"
|
||||
>
|
||||
{aiAnalysis.analysis.recommendation}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{aiAnalysis.analysis.riskLevel} Risk
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-slate-200">{aiAnalysis.analysis.summary}</p>
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm text-slate-200 bg-slate-700/30 p-2 rounded border border-slate-600/50">
|
||||
<strong>Summary:</strong> {aiAnalysis.analysis.summary}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
{/* Long Term */}
|
||||
{aiAnalysis.analysis.longTerm && (
|
||||
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
|
||||
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Long-Term Investor</div>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<Badge variant={aiAnalysis.analysis.longTerm.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.longTerm.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.longTerm.assessment}
|
||||
</Badge>
|
||||
<Badge variant={aiAnalysis.analysis.longTerm.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.longTerm.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.longTerm.recommendation}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.longTerm.moatAndFundamentals}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Swing Trade */}
|
||||
{aiAnalysis.analysis.swing && (
|
||||
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
|
||||
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Swing Trader</div>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<Badge variant={aiAnalysis.analysis.swing.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.swing.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.swing.assessment}
|
||||
</Badge>
|
||||
<Badge variant={aiAnalysis.analysis.swing.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.swing.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.swing.recommendation}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.swing.technicalSetup}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Day Trade */}
|
||||
{aiAnalysis.analysis.dayTrade && (
|
||||
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
|
||||
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Day Trader</div>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<Badge variant={aiAnalysis.analysis.dayTrade.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.dayTrade.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.dayTrade.assessment}
|
||||
</Badge>
|
||||
<Badge variant={aiAnalysis.analysis.dayTrade.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.dayTrade.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
|
||||
{aiAnalysis.analysis.dayTrade.recommendation}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.dayTrade.volatilityAndLiquidity}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{aiAnalysis.analysis.keyFactors && aiAnalysis.analysis.keyFactors.length > 0 && (
|
||||
<div>
|
||||
<div className="text-xs text-slate-500 mb-1">Key Factors:</div>
|
||||
<ul className="text-xs text-slate-300 space-y-1">
|
||||
{aiAnalysis.analysis.keyFactors.map((factor, idx) => (
|
||||
<li key={idx}>• {factor}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{aiAnalysis.analysis.reasoning && (
|
||||
<div>
|
||||
<div className="text-xs text-slate-500 mb-1">Reasoning:</div>
|
||||
<p className="text-xs text-slate-300">{aiAnalysis.analysis.reasoning}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-4 text-xs text-slate-400 pt-2 border-t border-slate-700">
|
||||
<span>Time Horizon: {aiAnalysis.analysis.timeHorizon}</span>
|
||||
<div className="flex items-center gap-4 text-xs text-slate-500 pt-2 border-t border-slate-700">
|
||||
{aiAnalysis.timestamp && (
|
||||
<span>• {new Date(aiAnalysis.timestamp).toLocaleTimeString()}</span>
|
||||
<span>Generated: {new Date(aiAnalysis.timestamp).toLocaleTimeString()}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue