From a662d81f007399af77b5aeff39962cd559eb0f24 Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Fri, 19 Jun 2026 22:40:08 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20news=20filters=20(p=C3=A9riode=20+=20tr?= =?UTF-8?q?i),=20Top=20Trades=20cockpit,=20date=20ajout=20patterns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GeoRadar: - Filtre période : Aujourd'hui / 7j / 30j / Toutes - Tri : Impact ↓/↑ · Récentes/Anciennes - Compteur de news filtrées Dashboard: - Remplace Super Contexte par "Top Trades" (top 5 par PnL%) avec ticker, stratégie, date d'entrée, PnL% - Card "Scores Patterns" : ajoute la date d'ajout (created_at) sous le nom de chaque pattern Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/pages/Dashboard.tsx | 62 +++++++++++++-------- frontend/src/pages/GeoRadar.tsx | 96 +++++++++++++++++++++++++++----- 2 files changed, 123 insertions(+), 35 deletions(-) diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index f7776aa..e96e4b9 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -3,7 +3,7 @@ import { useGeoRiskScore, useAllQuotes, useCalendar, usePortfolioSummary, useLastScores, useAllPatterns, useMacroRegime, useTradeMtm, useRiskDashboard, useGeoNews, - useSimPortfolioRisk, useCycleStatus, useKnowledgeState, + useSimPortfolioRisk, useCycleStatus, } from '../hooks/useApi' import { Clock, Globe, ShieldAlert, ArrowUpRight, Brain, Newspaper } from 'lucide-react' import { Link } from 'react-router-dom' @@ -86,7 +86,6 @@ export default function Dashboard() { const { data: riskDashboard } = useRiskDashboard() const { data: simRisk } = useSimPortfolioRisk() const { data: cycleStatusData } = useCycleStatus() - const { data: knowledgeState } = useKnowledgeState() const { data: geoNews } = useGeoNews() const [pnlView, setPnlView] = useState<'simulated' | 'portfolio'>(() => @@ -262,9 +261,13 @@ export default function Dashboard() { ? Math.max(rawScore ?? 0, ...(sp.trade_rankings ?? []).map((r: any) => Math.max(0, Math.min(100, (rawScore ?? 0) + (r.score_delta ?? 0))))) : null + const addedDate = p.created_at ? p.created_at.slice(0, 10) : null return ( -
- {p.name} +
+
+ {p.name} + {addedDate && {addedDate}} +
{bestEff !== null ? ( {bestEff} ) : ( @@ -571,29 +574,44 @@ export default function Dashboard() { {/* ── Command Center: Intelligence & Contexte ── */}
- {/* Super Contexte IA */} + {/* Top 5 trades loggés */} {(() => { - const state = (knowledgeState as any)?.state - const synthesis = state?.synthesis - const insights = (synthesis?.regime_insights?.length ?? 0) - + (synthesis?.pattern_insights?.length ?? 0) - + (synthesis?.recurring_mistakes?.length ?? 0) - const priority = synthesis?.strategic_priorities?.[0] - const excerpt = typeof priority === 'string' ? priority - : typeof state?.narrative === 'string' ? state.narrative.slice(0, 90) - : null + const top5 = [...mtmTrades] + .sort((a, b) => (b.pnl_pct ?? -999) - (a.pnl_pct ?? -999)) + .slice(0, 5) return ( - +
- 🧠 Super Contexte + 🏆 Top Trades
-
- {state ? `${insights} insights actifs` : 'Aucune synthèse'} -
-
- {excerpt ?? 'Lancer une synthèse dans Super Contexte'} -
+ {top5.length > 0 ? ( +
+ {top5.map((t: any, i: number) => { + const pnl: number | null = t.pnl_pct ?? null + const isBear = t.strategy?.toLowerCase().includes('put') || t.strategy?.toLowerCase().includes('bear') + const entryDate = t.entry_date ? t.entry_date.slice(0, 10) : null + return ( +
+ {i + 1} + {isBear ? '🐻' : '🐂'} + {t.underlying ?? '—'} + {t.strategy ?? ''} + {entryDate && {entryDate}} + {pnl !== null ? ( + = 0 ? 'text-emerald-400' : 'text-red-400')}> + {pnl >= 0 ? '+' : ''}{pnl.toFixed(1)}% + + ) : ( + + )} +
+ ) + })} +
+ ) : ( +
Aucun trade loggé
+ )} ) })()} diff --git a/frontend/src/pages/GeoRadar.tsx b/frontend/src/pages/GeoRadar.tsx index d599f76..327e015 100644 --- a/frontend/src/pages/GeoRadar.tsx +++ b/frontend/src/pages/GeoRadar.tsx @@ -252,17 +252,49 @@ function PatternRelevanceCard({ p }: { p: any }) { ) } +type SortMode = 'impact_desc' | 'impact_asc' | 'date_desc' | 'date_asc' +type DateRange = 'all' | 'today' | '7d' | '30d' + +const DATE_RANGE_LABELS: Record = { + all: 'Toutes', today: "Aujourd'hui", '7d': '7 jours', '30d': '30 jours', +} +const SORT_LABELS: Record = { + impact_desc: 'Impact ↓', impact_asc: 'Impact ↑', date_desc: 'Récentes ↓', date_asc: 'Anciennes ↑', +} + +function isWithinRange(dateStr: string, range: DateRange): boolean { + if (range === 'all') return true + const d = new Date(dateStr) + const now = new Date() + const diffMs = now.getTime() - d.getTime() + if (range === 'today') return diffMs < 86_400_000 + if (range === '7d') return diffMs < 7 * 86_400_000 + if (range === '30d') return diffMs < 30 * 86_400_000 + return true +} + export default function GeoRadar() { const { data: news, isLoading } = useGeoNews() const { data: riskScore } = useGeoRiskScore() const [filterCat, setFilterCat] = useState('all') + const [sortMode, setSortMode] = useState('impact_desc') + const [dateRange, setDateRange] = useState('all') const [tab, setTab] = useState<'news' | 'patterns'>('news') const [days, setDays] = useState(2) const { data: patternRelevance, isLoading: relLoading } = usePatternRelevance(days) const categories = ['all', ...Array.from(new Set(news?.map(n => n.category) ?? []))] - const filtered = filterCat === 'all' ? news : news?.filter(n => n.category === filterCat) + + const filtered = (news ?? []) + .filter(n => filterCat === 'all' || n.category === filterCat) + .filter(n => isWithinRange(n.date, dateRange)) + .sort((a, b) => { + if (sortMode === 'impact_desc') return b.impact_score - a.impact_score + if (sortMode === 'impact_asc') return a.impact_score - b.impact_score + if (sortMode === 'date_desc') return new Date(b.date).getTime() - new Date(a.date).getTime() + return new Date(a.date).getTime() - new Date(b.date).getTime() + }) const patternsWithNews = (patternRelevance ?? []).filter((p: any) => p.matching_news?.length > 0).length const totalPatterns = patternRelevance?.length ?? 0 @@ -333,25 +365,63 @@ export default function GeoRadar() { {/* News tab */} {tab === 'news' && ( <> -
- {categories.slice(0, 8).map(cat => ( - - ))} + {/* Filters row */} +
+ {/* Category filter */} +
+ {categories.slice(0, 8).map(cat => ( + + ))} +
+ + {/* Sort + date range row */} +
+
+ Période : +
+ {(['today', '7d', '30d', 'all'] as DateRange[]).map(r => ( + + ))} +
+
+
+ Tri : +
+ {(['impact_desc', 'impact_asc', 'date_desc', 'date_asc'] as SortMode[]).map(s => ( + + ))} +
+
+ {filtered.length} news +
+ {isLoading ? (
{[1,2,3,4].map(i =>
)}
) : (
- {filtered?.map((n, i) => )} - {(!filtered || filtered.length === 0) && ( + {filtered.map((n, i) => )} + {filtered.length === 0 && (
Démarrer le backend pour charger les actualités