diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts
index 776fca8..f703ac9 100644
--- a/frontend/src/hooks/useApi.ts
+++ b/frontend/src/hooks/useApi.ts
@@ -102,10 +102,10 @@ export interface FFEvent {
impact: 'high' | 'medium' | 'low'; event_name: string
actual_value: number | null; forecast_value: number | null; previous_value: number | null
}
-export const useEcoCalendar = (params: { period?: string; limit?: number } = {}) =>
+export const useEcoCalendar = (params: { period?: string; limit?: number; impacts?: string } = {}) =>
useQuery<{ events: FFEvent[] }>({
queryKey: ['eco-calendar', params],
- queryFn: () => api.get('/eco/calendar', { params: { period: 'recent', limit: 30, ...params } }).then(r => r.data),
+ queryFn: () => api.get('/eco/calendar', { params: { period: 'recent', limit: 50, impacts: 'high,medium', ...params } }).then(r => r.data),
staleTime: 5 * 60_000,
})
diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx
index fa06bc8..64eb644 100644
--- a/frontend/src/pages/Dashboard.tsx
+++ b/frontend/src/pages/Dashboard.tsx
@@ -164,7 +164,7 @@ export default function Dashboard() {
const topNews = useMemo(() =>
[...(geoNews ?? [])]
.sort((a, b) => b.impact_score - a.impact_score)
- .slice(0, 2)
+ .slice(0, 5)
, [geoNews])
// Watchlist radar: change_pct normalized to a 0-100 scale (50 = flat)
@@ -246,30 +246,40 @@ export default function Dashboard() {
) : riskScore && gauge ? (
<>
- {riskScore.score}
- {gauge.label}
+
+
+
{riskScore.score}
+
{gauge.label}
+
+
+ {riskScore.top_risks?.map(([cat, val]) => (
+
+ {(cat as string).replace('_', ' ')}
+ {Math.round((val as number) * 100)}%
+
+ ))}
+
+
-
- {riskScore.top_risks?.map(([cat, val]) => (
-
- {(cat as string).replace('_', ' ')}
- {Math.round((val as number) * 100)}%
-
- ))}
-
{topNews.length > 0 && (
-
+
Top news
- {topNews.map((n, i) => (
-
- {n.title}
- {Math.round(n.impact_score * 100)}
-
- ))}
+ {topNews.map((n, i) => {
+ const impact = Math.round(n.impact_score * 100)
+ const impactColor = impact >= 75 ? 'text-red-400' : impact >= 50 ? 'text-orange-400' : impact >= 25 ? 'text-yellow-400' : 'text-emerald-400'
+ const dotColor = impact >= 75 ? 'bg-red-500' : impact >= 50 ? 'bg-orange-500' : impact >= 25 ? 'bg-yellow-500' : 'bg-emerald-500'
+ return (
+
+
+ {n.title}
+ {impact}
+
+ )
+ })}
)}
>
@@ -294,7 +304,7 @@ export default function Dashboard() {
-
+
{((watchlistQuotesData as any)?.items ?? []).map((it: any) => (
{it.ticker}
@@ -794,7 +804,7 @@ export default function Dashboard() {
.map((w: any) => snapshots[w.ticker])
.filter(Boolean)
.sort((a: any, b: any) => Math.abs((b.iv_rank ?? 50) - 50) - Math.abs((a.iv_rank ?? 50) - 50))
- .slice(0, 5)
+ .slice(0, 6)
return (
@@ -811,13 +821,31 @@ export default function Dashboard() {
{highlights.map((h: any) => {
const rank = h.iv_rank
const skewPct = h.skew?.skew_pct
+ const ivCur = h.iv_current_pct
+ const ivPctl = h.iv_percentile
+ const flow: string | null = h.options_flow?.flow_bias ?? null
+ const pcRatio = h.options_flow?.pc_oi_ratio
return (
-
-
{rank != null && rank > 80 ? '🔴' : rank != null && rank < 20 ? '🟢' : '⚪'}
-
{h.ticker}
-
IVR {rank != null ? rank.toFixed(0) : '—'}
- {skewPct != null && Math.abs(skewPct) > 3 && (
-
skew {skewPct >= 0 ? '+' : ''}{skewPct.toFixed(1)}
+
+
+ {rank != null && rank > 80 ? '🔴' : rank != null && rank < 20 ? '🟢' : '⚪'}
+ {h.ticker}
+ IVR {rank != null ? rank.toFixed(0) : '—'}
+ {ivCur != null && · IV {ivCur.toFixed(1)}%}
+ {skewPct != null && Math.abs(skewPct) > 3 && (
+ 0 ? 'text-orange-400' : 'text-blue-400')}>
+ skew {skewPct >= 0 ? '+' : ''}{skewPct.toFixed(1)}
+
+ )}
+
+ {(ivPctl != null || flow != null || pcRatio != null) && (
+
+ {ivPctl != null && Pctl {ivPctl.toFixed(0)}}
+ {ivPctl != null && pcRatio != null && ' · '}
+ {pcRatio != null && P/C {pcRatio.toFixed(2)}}
+ {(ivPctl != null || pcRatio != null) && flow && ' · '}
+ {flow && {flow}}
+
)}
)