feat: show current position on company detail trends tab
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/finalize Pipeline was successful
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled

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.
This commit is contained in:
Celes Renata
2026-04-29 18:06:37 +00:00
parent 0665cef7e3
commit 4f7358f4e3
+48 -1
View File
@@ -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 <LoadingSpinner />;
@@ -83,7 +85,10 @@ export function CompanyDetailPage() {
</div>
{tab === 'trends' && (
<TrendHistoryChart trends={trendHistory ?? []} latestTrends={trends ?? []} ticker={company.ticker} marketPrices={marketPrices ?? []} />
<div className="space-y-4">
<PositionCard positions={positions ?? []} ticker={company.ticker} />
<TrendHistoryChart trends={trendHistory ?? []} latestTrends={trends ?? []} ticker={company.ticker} marketPrices={marketPrices ?? []} />
</div>
)}
{tab === 'sources' && (
@@ -615,6 +620,48 @@ function TrendTooltip({ active, payload, label }: Record<string, unknown>) {
);
}
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 (
<Card>
<div className="flex items-center justify-between">
<h2 className="text-sm font-medium text-gray-400">Open Position</h2>
<StatusBadge status="active" />
</div>
<dl className="mt-2 grid grid-cols-2 gap-x-8 gap-y-2 text-sm sm:grid-cols-5">
<div>
<dt className="text-gray-500">Shares</dt>
<dd className="font-mono text-gray-200">{pos.quantity}</dd>
</div>
<div>
<dt className="text-gray-500">Avg Entry</dt>
<dd className="font-mono text-gray-200">${pos.avg_entry_price.toFixed(2)}</dd>
</div>
<div>
<dt className="text-gray-500">Current Price</dt>
<dd className="font-mono text-gray-200">{pos.current_price ? `$${pos.current_price.toFixed(2)}` : '—'}</dd>
</div>
<div>
<dt className="text-gray-500">Market Value</dt>
<dd className="font-mono text-gray-200">{marketValue ? `$${marketValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '—'}</dd>
</div>
<div>
<dt className="text-gray-500">Unrealized P&L</dt>
<dd className={`font-mono font-semibold ${pnlColor}`}>
{pos.unrealized_pnl != null ? `${pnlSign}$${Math.abs(pos.unrealized_pnl).toFixed(2)}` : '—'}
</dd>
</div>
</dl>
</Card>
);
}
function TrendHistoryChart({ trends, latestTrends, ticker, marketPrices }: { trends: TrendSummary[]; latestTrends: TrendSummary[]; ticker: string; marketPrices: MarketPrice[] }) {
const [selectedWindow, setSelectedWindow] = useState('7d');