Simplify top setups table
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m17s
Deploy / deploy (push) Successful in 37s

This commit is contained in:
2026-07-04 13:57:42 +02:00
parent 65335cf1f3
commit 61156684ff
+25 -16
View File
@@ -13,7 +13,6 @@ import { OpenTradesPanel } from '../components/dashboard/OpenTradesPanel';
import { SkeletonCard, SkeletonTable } from '../components/ui/Skeleton';
import { formatPrice } from '../lib/format';
import { tradePnl } from '../lib/paperTrade';
import { recommendationActionLabel } from '../lib/recommendation';
import { qualifiesSetup, activationSummary, primaryTargetProbability } from '../lib/qualification';
import type { TradeSetup } from '../lib/types';
@@ -60,6 +59,19 @@ function DirectionTag({ direction }: { direction: string }) {
);
}
function convictionLabel(action: TradeSetup['recommended_action']): string {
if (!action || action === 'NEUTRAL') return '—';
if (action.endsWith('_HIGH')) return 'High';
if (action.endsWith('_MODERATE')) return 'Moderate';
return '—';
}
function convictionClass(action: TradeSetup['recommended_action']): string {
if (action?.endsWith('_HIGH')) return 'text-emerald-300';
if (action?.endsWith('_MODERATE')) return 'text-amber-300';
return 'text-gray-400';
}
export default function DashboardPage() {
const trades = useTrades();
const watchlist = useWatchlist();
@@ -200,9 +212,9 @@ export default function DashboardPage() {
<div className="xl:col-span-3">
<Section
title="Top Setups"
hint="qualified by residual momentum, ranked by production score"
hint="current qualified signals"
>
{trades.isLoading && <SkeletonTable rows={5} cols={5} />}
{trades.isLoading && <SkeletonTable rows={5} cols={7} />}
{trades.isError && <Callout variant="error">Failed to load setups</Callout>}
{trades.data && topSetups.length === 0 && (
<Callout variant="empty">No qualified actionable setups right now.</Callout>
@@ -214,17 +226,17 @@ export default function DashboardPage() {
<tr className="border-b border-white/[0.06] text-left text-xs uppercase tracking-wider text-gray-500">
<th className="px-4 py-3">Ticker</th>
<th className="px-4 py-3">Dir</th>
<th className="px-4 py-3 text-right">Entry</th>
<th className="px-4 py-3 text-right">Current / Target</th>
<th className="px-4 py-3 text-right">R:R</th>
<th className="px-4 py-3 text-right">Target&nbsp;Prob</th>
<th className="px-4 py-3 text-right">Prod. Rank</th>
<th className="px-4 py-3 text-right">Residual</th>
<th className="hidden px-4 py-3 md:table-cell">Action</th>
<th className="hidden px-4 py-3 md:table-cell">Conviction</th>
</tr>
</thead>
<tbody>
{topSetups.map((setup, i) => {
const isTopPick = i === 0;
const displayedPrice = setup.current_price ?? setup.entry_price;
return (
<tr
key={setup.id}
@@ -250,7 +262,10 @@ export default function DashboardPage() {
)}
</td>
<td className="px-4 py-3"><DirectionTag direction={setup.direction} /></td>
<td className="num px-4 py-3 text-right text-gray-200">{formatPrice(setup.entry_price)}</td>
<td className="num px-4 py-3 text-right">
<div className="text-gray-200">{formatPrice(displayedPrice)}</div>
<div className="text-[11px] text-gray-500">target {formatPrice(setup.target)}</div>
</td>
<td className="num px-4 py-3 text-right text-gray-200">{setup.rr_ratio.toFixed(1)}:1</td>
<td className="num px-4 py-3 text-right text-gray-200">
{(() => {
@@ -258,24 +273,18 @@ export default function DashboardPage() {
return p != null ? `${Math.round(p)}%` : '—';
})()}
</td>
<td className="num px-4 py-3 text-right font-semibold text-gray-200">
{setup.strategy_rank != null ? `${Math.round(setup.strategy_rank)}%ile` : '—'}
</td>
<td className="num px-4 py-3 text-right text-gray-400">
{setup.momentum_percentile != null ? `${Math.round(setup.momentum_percentile)}%ile` : '—'}
</td>
<td className="hidden px-4 py-3 text-xs text-gray-400 md:table-cell">
{recommendationActionLabel(setup.recommended_action)}
<td className={`hidden px-4 py-3 text-xs font-semibold md:table-cell ${convictionClass(setup.recommended_action)}`}>
{convictionLabel(setup.recommended_action)}
</td>
</tr>
);
})}
</tbody>
</table>
<div className="flex items-center justify-between border-t border-white/[0.04] px-4 py-2.5">
<span className="text-[11px] text-gray-500">
Production rank = 80% residual momentum + 20% realized volatility; residual still gates qualification.
</span>
<div className="flex justify-end border-t border-white/[0.04] px-4 py-2.5">
<Link to="/signals" className="text-xs font-medium text-blue-300 hover:text-blue-200 transition-colors">
All setups
</Link>