From 4f7358f4e39a7a771dcad89467c1bbf735e73c43 Mon Sep 17 00:00:00 2001 From: Celes Renata Date: Wed, 29 Apr 2026 18:06:37 +0000 Subject: [PATCH] feat: show current position on company detail trends tab Displays an 'Open Position' card above the trend charts when we hold a position in that ticker. Shows shares, avg entry price, current price, market value, and unrealized P&L with green/red coloring. Card is hidden when no position exists for the ticker. --- frontend/src/pages/CompanyDetail.tsx | 49 +++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/CompanyDetail.tsx b/frontend/src/pages/CompanyDetail.tsx index 70ce5c6..7e90005 100644 --- a/frontend/src/pages/CompanyDetail.tsx +++ b/frontend/src/pages/CompanyDetail.tsx @@ -15,6 +15,7 @@ import { useTrendHistory, useMarketPrices, useDocument, + usePositions, } from '../api/hooks'; import { StatusBadge, ConfidenceBar, LoadingSpinner, Card } from '../components/ui'; import { DataTable, type Column } from '../components/DataTable'; @@ -45,6 +46,7 @@ export function CompanyDetailPage() { const { data: trends } = useTrends({ ticker: company?.ticker, limit: 200 }); const { data: trendHistory } = useTrendHistory({ ticker: company?.ticker, limit: 500 }); const { data: marketPrices } = useMarketPrices(company?.ticker, 200); + const { data: positions } = usePositions(company?.ticker); const [tab, setTab] = useState<'trends' | 'sources' | 'aliases' | 'macro' | 'competitors' | 'patterns' | 'signals' | 'decisions'>('trends'); if (isLoading || !company) return ; @@ -83,7 +85,10 @@ export function CompanyDetailPage() { {tab === 'trends' && ( - +
+ + +
)} {tab === 'sources' && ( @@ -615,6 +620,48 @@ function TrendTooltip({ active, payload, label }: Record) { ); } +function PositionCard({ positions, ticker }: { positions: import('../api/hooks').Position[]; ticker: string }) { + const pos = positions.find((p) => p.ticker === ticker && p.quantity > 0); + if (!pos) return null; + + const marketValue = pos.current_price ? pos.quantity * pos.current_price : null; + const pnlColor = (pos.unrealized_pnl ?? 0) >= 0 ? 'text-green-400' : 'text-red-400'; + const pnlSign = (pos.unrealized_pnl ?? 0) >= 0 ? '+' : ''; + + return ( + +
+

Open Position

+ +
+
+
+
Shares
+
{pos.quantity}
+
+
+
Avg Entry
+
${pos.avg_entry_price.toFixed(2)}
+
+
+
Current Price
+
{pos.current_price ? `$${pos.current_price.toFixed(2)}` : '—'}
+
+
+
Market Value
+
{marketValue ? `$${marketValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '—'}
+
+
+
Unrealized P&L
+
+ {pos.unrealized_pnl != null ? `${pnlSign}$${Math.abs(pos.unrealized_pnl).toFixed(2)}` : '—'} +
+
+
+
+ ); +} + function TrendHistoryChart({ trends, latestTrends, ticker, marketPrices }: { trends: TrendSummary[]; latestTrends: TrendSummary[]; ticker: string; marketPrices: MarketPrice[] }) { const [selectedWindow, setSelectedWindow] = useState('7d');