diff --git a/app/routes/analyze.tsx b/app/routes/analyze.tsx index 1a9a672..4335626 100644 --- a/app/routes/analyze.tsx +++ b/app/routes/analyze.tsx @@ -3,112 +3,213 @@ import { Link } from "react-router"; import Navbar from "../components/Navbar"; import type { TradingDecision } from "../types/agents"; +interface Indicators { + rsi: number | null; + sma20: number | null; + sma50: number | null; + ema12: number | null; + ema26: number | null; + macd: number | null; + bbUpper: number | null; + bbMiddle: number | null; + bbLower: number | null; + atr: number | null; + avgVolume: number | null; +} + interface StockRow { id: string; ticker: string; currentPrice: number | null; position: number; - rsi: number | null; + indicators: Indicators; analysis: TradingDecision | null; loading: boolean; + indicatorsLoading: boolean; } -export const meta = () => { - return [ - { title: "Portfolio Analysis - AITrader" }, - { name: "description", content: "Analyze your stock portfolio with AI trading insights" }, +export const meta = () => [ + { title: "Portfolio Analysis - AITrader" }, + { name: "description", content: "Analyze your stock portfolio with AI trading insights" }, +]; + +function RsiBadge({ value }: { value: number }) { + const color = value > 70 ? "bg-red-100 text-red-700" : value < 30 ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-700"; + const label = value > 70 ? "Overbought" : value < 30 ? "Oversold" : "Neutral"; + return {value.toFixed(0)} {label}; +} + +function MacdBadge({ value }: { value: number }) { + const color = value > 0 ? "text-green-600" : "text-red-600"; + return {value > 0 ? "▲" : "▼"} {value.toFixed(2)}; +} + +function PriceVsSma({ price, sma, label }: { price: number; sma: number; label: string }) { + if (!price || !sma) return -; + const above = price > sma; + const pct = ((price - sma) / sma * 100).toFixed(1); + return ( + + {above ? "▲" : "▼"} {pct}% + + ); +} + +function SignalSummary({ price, indicators }: { price: number | null; indicators: Indicators }) { + if (!price) return No data; + + const signals: string[] = []; + + if (indicators.rsi != null) { + if (indicators.rsi > 70) signals.push("RSI overbought"); + else if (indicators.rsi < 30) signals.push("RSI oversold"); + } + + if (indicators.sma20 != null && indicators.sma50 != null) { + if (indicators.sma20 > indicators.sma50) signals.push("SMA bullish cross"); + else signals.push("SMA bearish cross"); + } + + if (indicators.macd != null) { + if (indicators.macd > 0) signals.push("MACD positive"); + else signals.push("MACD negative"); + } + + if (indicators.bbUpper != null && indicators.bbLower != null) { + if (price > indicators.bbUpper) signals.push("Above BB upper"); + else if (price < indicators.bbLower) signals.push("Below BB lower"); + } + + if (signals.length === 0) return -; + + const bullish = signals.filter(s => s.includes("oversold") || s.includes("bullish") || s.includes("positive") || s.includes("Below BB")).length; + const bearish = signals.filter(s => s.includes("overbought") || s.includes("bearish") || s.includes("negative") || s.includes("Above BB")).length; + const net = bullish - bearish; + const bias = net > 0 ? "bullish" : net < 0 ? "bearish" : "neutral"; + const biasColor = bias === "bullish" ? "text-green-600" : bias === "bearish" ? "text-red-600" : "text-gray-500"; + + return ( +
No stocks added. Add a ticker to get started.
) : ( @@ -310,80 +437,112 @@ export default function Analyze() {| Ticker | -Price | -Position | -RSI | -Analysis | -Actions | +Ticker | +Price | +Position | +Technical Summary | +RSI | +MACD | +SMA 20/50 | +AI Analysis | +Actions |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| - + | ||||||||||||||
| + {stock.ticker} | -+ | {stock.currentPrice ? `$${stock.currentPrice.toFixed(2)}` : "-"} | -- {stock.position} + | + {stock.position > 0 ? ( + {stock.position} shares + ) : ( + - + )} | -- {stock.rsi ? ( - 70 ? "text-red-600" : - stock.rsi < 30 ? "text-green-600" : "text-gray-900" - }> - {stock.rsi.toFixed(2)} - + |
+
+ {stock.indicatorsLoading ? (
+ Loading...
+ ) : (
+ <>
+
+ |
+
+ {stock.indicators.rsi != null ? (
+ |
- + |
+ {stock.indicators.macd != null ? (
+ |
+
+ {stock.currentPrice && stock.indicators.sma20 && stock.indicators.sma50 ? (
+
+
+ ) : "-"}
+ |
+
{stock.analysis ? (
-
{stock.analysis.action.toUpperCase()}
) : stock.loading ? (
- Analyzing...
+ Analyzing...
) : "-"}
- {stock.analysis.confidence ? `Confidence: ${(stock.analysis.confidence * 100).toFixed(0)}%` : "Saved suggestion"}
+ {(stock.analysis.confidence ?? 0 * 100).toFixed(0)}%
- {stock.analysis.executionPlan && (
-
- {stock.analysis.executionPlan.amount != null && (
- )}
Amount: {stock.analysis.executionPlan.amount} )}
- {stock.analysis.executionPlan.takeProfit != null && (Take profit: ${stock.analysis.executionPlan.takeProfit} )}
- |
-
-
+
+ |
+
-
|