diff --git a/backend/src/routes/marketAnalysis.js b/backend/src/routes/marketAnalysis.js index a01f47f..1062383 100644 --- a/backend/src/routes/marketAnalysis.js +++ b/backend/src/routes/marketAnalysis.js @@ -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, diff --git a/backend/src/services/aiAnalysisService.js b/backend/src/services/aiAnalysisService.js index 21ca959..2759604 100644 --- a/backend/src/services/aiAnalysisService.js +++ b/backend/src/services/aiAnalysisService.js @@ -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' } }; } diff --git a/frontend/src/components/dashboard/MarketScreenerPanel.jsx b/frontend/src/components/dashboard/MarketScreenerPanel.jsx index 29ea36d..e32ba44 100644 --- a/frontend/src/components/dashboard/MarketScreenerPanel.jsx +++ b/frontend/src/components/dashboard/MarketScreenerPanel.jsx @@ -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 }) { {/* Cap tier tabs */} -
- {CAP_TABS.map(tab => ( - - ))} +
+
+ {CAP_TABS.map(tab => ( + + ))} +
+
+ Setups: + {['all', 'day', 'swing'].map(f => ( + + ))} +
{/* 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 + toggleSort('volatility_pct')} + >Volatility Cap Tier @@ -214,7 +256,7 @@ export default function MarketScreenerPanel({ onSelectSymbol }) { {loading && data.length === 0 ? ( Array.from({ length: 10 }).map((_, i) => ( - {Array.from({ length: 8 }).map((_, j) => ( + {Array.from({ length: 9 }).map((_, j) => (
@@ -222,7 +264,7 @@ export default function MarketScreenerPanel({ onSelectSymbol }) { )) ) : processed.length === 0 ? ( - No results found + No results found ) : ( processed.map(row => ( {fmtVolume(row.volume)} + + {row.volatility_pct != null ? ( + 3 ? 'text-emerald-400 font-medium' : 'text-slate-400'}`}> + {row.volatility_pct.toFixed(2)}% + + ) : '—'} + diff --git a/frontend/src/components/dashboard/StockDetailPanel.jsx b/frontend/src/components/dashboard/StockDetailPanel.jsx index 4f04c03..7c1eabd 100644 --- a/frontend/src/components/dashboard/StockDetailPanel.jsx +++ b/frontend/src/components/dashboard/StockDetailPanel.jsx @@ -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 }, ] }, ]; diff --git a/frontend/src/components/tables/TradeAnalysisModal.jsx b/frontend/src/components/tables/TradeAnalysisModal.jsx index a181b72..e83e633 100644 --- a/frontend/src/components/tables/TradeAnalysisModal.jsx +++ b/frontend/src/components/tables/TradeAnalysisModal.jsx @@ -495,49 +495,60 @@ export function TradeAnalysisModal({ row, isOpen, onClose }) { )} {aiAnalysis && aiAnalysis.analysis && ( -
-
- - {aiAnalysis.analysis.assessment} - - - {aiAnalysis.analysis.recommendation} - - - {aiAnalysis.analysis.riskLevel} Risk - +
+
+ Summary: {aiAnalysis.analysis.summary} +
+ +
+ {/* Long Term */} + {aiAnalysis.analysis.longTerm && ( +
+
Long-Term Investor
+
+ + {aiAnalysis.analysis.longTerm.assessment} + + + {aiAnalysis.analysis.longTerm.recommendation} + +
+

{aiAnalysis.analysis.longTerm.moatAndFundamentals}

+
+ )} + + {/* Swing Trade */} + {aiAnalysis.analysis.swing && ( +
+
Swing Trader
+
+ + {aiAnalysis.analysis.swing.assessment} + + + {aiAnalysis.analysis.swing.recommendation} + +
+

{aiAnalysis.analysis.swing.technicalSetup}

+
+ )} + + {/* Day Trade */} + {aiAnalysis.analysis.dayTrade && ( +
+
Day Trader
+
+ + {aiAnalysis.analysis.dayTrade.assessment} + + + {aiAnalysis.analysis.dayTrade.recommendation} + +
+

{aiAnalysis.analysis.dayTrade.volatilityAndLiquidity}

+
+ )}
-

{aiAnalysis.analysis.summary}

- - {aiAnalysis.analysis.keyFactors && aiAnalysis.analysis.keyFactors.length > 0 && ( -
-
Key Factors:
-
    - {aiAnalysis.analysis.keyFactors.map((factor, idx) => ( -
  • {factor}
  • - ))} -
-
- )} - - {aiAnalysis.analysis.reasoning && ( -
-
Reasoning:
-

{aiAnalysis.analysis.reasoning}

-
- )}
)}
diff --git a/frontend/src/components/tables/expandedRowDetails/AnalysisSection.jsx b/frontend/src/components/tables/expandedRowDetails/AnalysisSection.jsx index bea697f..4bbfde4 100644 --- a/frontend/src/components/tables/expandedRowDetails/AnalysisSection.jsx +++ b/frontend/src/components/tables/expandedRowDetails/AnalysisSection.jsx @@ -60,56 +60,64 @@ export function AnalysisSection({ row, hasPhase1Data, onPhase1Click }) { )} {aiAnalysis && aiAnalysis.analysis && ( -
-
-
- - {aiAnalysis.analysis.assessment} - - - {aiAnalysis.analysis.recommendation} - - - {aiAnalysis.analysis.riskLevel} Risk - -
-

{aiAnalysis.analysis.summary}

+
+
+ Summary: {aiAnalysis.analysis.summary} +
+ +
+ {/* Long Term */} + {aiAnalysis.analysis.longTerm && ( +
+
Long-Term Investor
+
+ + {aiAnalysis.analysis.longTerm.assessment} + + + {aiAnalysis.analysis.longTerm.recommendation} + +
+

{aiAnalysis.analysis.longTerm.moatAndFundamentals}

+
+ )} + + {/* Swing Trade */} + {aiAnalysis.analysis.swing && ( +
+
Swing Trader
+
+ + {aiAnalysis.analysis.swing.assessment} + + + {aiAnalysis.analysis.swing.recommendation} + +
+

{aiAnalysis.analysis.swing.technicalSetup}

+
+ )} + + {/* Day Trade */} + {aiAnalysis.analysis.dayTrade && ( +
+
Day Trader
+
+ + {aiAnalysis.analysis.dayTrade.assessment} + + + {aiAnalysis.analysis.dayTrade.recommendation} + +
+

{aiAnalysis.analysis.dayTrade.volatilityAndLiquidity}

+
+ )}
- {aiAnalysis.analysis.keyFactors && aiAnalysis.analysis.keyFactors.length > 0 && ( -
-
Key Factors:
-
    - {aiAnalysis.analysis.keyFactors.map((factor, idx) => ( -
  • • {factor}
  • - ))} -
-
- )} - - {aiAnalysis.analysis.reasoning && ( -
-
Reasoning:
-

{aiAnalysis.analysis.reasoning}

-
- )} - -
- Time Horizon: {aiAnalysis.analysis.timeHorizon} +
{aiAnalysis.timestamp && ( - • {new Date(aiAnalysis.timestamp).toLocaleTimeString()} + Generated: {new Date(aiAnalysis.timestamp).toLocaleTimeString()} )}