Simplify top setups table
This commit is contained in:
@@ -13,7 +13,6 @@ import { OpenTradesPanel } from '../components/dashboard/OpenTradesPanel';
|
|||||||
import { SkeletonCard, SkeletonTable } from '../components/ui/Skeleton';
|
import { SkeletonCard, SkeletonTable } from '../components/ui/Skeleton';
|
||||||
import { formatPrice } from '../lib/format';
|
import { formatPrice } from '../lib/format';
|
||||||
import { tradePnl } from '../lib/paperTrade';
|
import { tradePnl } from '../lib/paperTrade';
|
||||||
import { recommendationActionLabel } from '../lib/recommendation';
|
|
||||||
import { qualifiesSetup, activationSummary, primaryTargetProbability } from '../lib/qualification';
|
import { qualifiesSetup, activationSummary, primaryTargetProbability } from '../lib/qualification';
|
||||||
import type { TradeSetup } from '../lib/types';
|
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() {
|
export default function DashboardPage() {
|
||||||
const trades = useTrades();
|
const trades = useTrades();
|
||||||
const watchlist = useWatchlist();
|
const watchlist = useWatchlist();
|
||||||
@@ -200,9 +212,9 @@ export default function DashboardPage() {
|
|||||||
<div className="xl:col-span-3">
|
<div className="xl:col-span-3">
|
||||||
<Section
|
<Section
|
||||||
title="Top Setups"
|
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.isError && <Callout variant="error">Failed to load setups</Callout>}
|
||||||
{trades.data && topSetups.length === 0 && (
|
{trades.data && topSetups.length === 0 && (
|
||||||
<Callout variant="empty">No qualified actionable setups right now.</Callout>
|
<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">
|
<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">Ticker</th>
|
||||||
<th className="px-4 py-3">Dir</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">R:R</th>
|
||||||
<th className="px-4 py-3 text-right">Target Prob</th>
|
<th className="px-4 py-3 text-right">Target 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="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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{topSetups.map((setup, i) => {
|
{topSetups.map((setup, i) => {
|
||||||
const isTopPick = i === 0;
|
const isTopPick = i === 0;
|
||||||
|
const displayedPrice = setup.current_price ?? setup.entry_price;
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={setup.id}
|
key={setup.id}
|
||||||
@@ -250,7 +262,10 @@ export default function DashboardPage() {
|
|||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3"><DirectionTag direction={setup.direction} /></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">{setup.rr_ratio.toFixed(1)}:1</td>
|
||||||
<td className="num px-4 py-3 text-right text-gray-200">
|
<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)}%` : '—';
|
return p != null ? `${Math.round(p)}%` : '—';
|
||||||
})()}
|
})()}
|
||||||
</td>
|
</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">
|
<td className="num px-4 py-3 text-right text-gray-400">
|
||||||
{setup.momentum_percentile != null ? `${Math.round(setup.momentum_percentile)}%ile` : '—'}
|
{setup.momentum_percentile != null ? `${Math.round(setup.momentum_percentile)}%ile` : '—'}
|
||||||
</td>
|
</td>
|
||||||
<td className="hidden px-4 py-3 text-xs text-gray-400 md:table-cell">
|
<td className={`hidden px-4 py-3 text-xs font-semibold md:table-cell ${convictionClass(setup.recommended_action)}`}>
|
||||||
{recommendationActionLabel(setup.recommended_action)}
|
{convictionLabel(setup.recommended_action)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div className="flex items-center justify-between border-t border-white/[0.04] px-4 py-2.5">
|
<div className="flex justify-end 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>
|
|
||||||
<Link to="/signals" className="text-xs font-medium text-blue-300 hover:text-blue-200 transition-colors">
|
<Link to="/signals" className="text-xs font-medium text-blue-300 hover:text-blue-200 transition-colors">
|
||||||
All setups →
|
All setups →
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
Reference in New Issue
Block a user