From fff0c15316df5af2081aed5bb8a08110ce5f195e Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Sat, 20 Jun 2026 11:34:07 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20P&L=20R=C3=A9alis=C3=A9=20dashboard=20?= =?UTF-8?q?=E2=80=94=20source=20useClosedTrades=20au=20lieu=20de=20mtmTrad?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get_trade_entry_prices filtre WHERE status='open', les trades fermés n'y apparaissaient jamais. Réalisé branche maintenant sur useClosedTrades(90) avec calcul EUR = capital * pnl_realized% / 100 et affichage avg %. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/pages/Dashboard.tsx | 47 ++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index b171488..6627ede 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -4,7 +4,7 @@ import { useGeoRiskScore, useAllQuotes, useCalendar, usePortfolioSummary, useLastScores, useAllPatterns, useMacroRegime, useTradeMtm, useRiskDashboard, useGeoNews, - useSimPortfolioRisk, useCycleStatus, + useSimPortfolioRisk, useCycleStatus, useClosedTrades, } from '../hooks/useApi' import { Clock, Globe, ShieldAlert, ArrowUpRight, Brain, Newspaper } from 'lucide-react' import { Link } from 'react-router-dom' @@ -87,6 +87,7 @@ export default function Dashboard() { const { data: allPatternsData } = useAllPatterns() const { data: macroData } = useMacroRegime() const { data: tradeMtmData } = useTradeMtm(30) + const { data: closedTradesData } = useClosedTrades(90) const { data: riskDashboard } = useRiskDashboard() const { data: simRisk } = useSimPortfolioRisk() const { data: cycleStatusData } = useCycleStatus() @@ -346,12 +347,9 @@ export default function Dashboard() { {/* PnL */} {(() => { - const trades: any[] = mtmTrades - const openTrades = trades.filter((t: any) => t.status !== 'closed') - const closedTrades = trades.filter((t: any) => t.status === 'closed') - - // Unrealized — open positions - const openWithPnl = openTrades.filter((t: any) => t.pnl_pct != null) + // Unrealized — open trades only (get_trade_entry_prices excludes closed) + const openTrades = mtmTrades + const openWithPnl = openTrades.filter((t: any) => t.pnl_pct != null) const openCapital = openTrades.reduce((s: number, t: any) => s + (t.capital_invested ?? t.entry_price ?? 0), 0) const openProfit = openWithPnl.reduce((s: number, t: any) => s + ((t.capital_invested ?? t.entry_price ?? 0) * t.pnl_pct / 100), 0) const openPnlPct = openCapital > 0 ? openProfit / openCapital * 100 : null @@ -360,18 +358,23 @@ export default function Dashboard() { const targetHit = openTrades.filter((t: any) => t.alert_type === 'target_reached').length const stopHit = openTrades.filter((t: any) => t.alert_type === 'stop_loss').length - // Realized — closed positions (pnl_pct locked from close_price/pnl_realized by backend) - const closedCapital = closedTrades.reduce((s: number, t: any) => s + (t.capital_invested ?? t.entry_price ?? 0), 0) - const closedProfit = closedTrades.reduce((s: number, t: any) => { + // Realized — from dedicated closed-trades endpoint (pnl_realized in %, capital for EUR) + const closedTrades = (closedTradesData as any)?.trades ?? [] + const closedCapital = closedTrades.reduce((s: number, t: any) => s + (t.capital_invested ?? t.entry_price ?? 0), 0) + const closedProfitEur = closedTrades.reduce((s: number, t: any) => { const cap = t.capital_invested ?? t.entry_price ?? 0 - return s + (t.pnl_pct != null ? cap * t.pnl_pct / 100 : 0) + return s + (cap * (t.pnl_realized ?? 0) / 100) }, 0) - const closedWinners = closedTrades.filter((t: any) => (t.pnl_pct ?? 0) > 0).length - const closedLosers = closedTrades.filter((t: any) => (t.pnl_pct ?? 0) < 0).length + const closedWithPnl = closedTrades.filter((t: any) => t.pnl_realized != null) + const avgClosedPct = closedWithPnl.length > 0 + ? closedWithPnl.reduce((s: number, t: any) => s + t.pnl_realized, 0) / closedWithPnl.length + : null + const closedWinners = closedTrades.filter((t: any) => (t.pnl_realized ?? 0) > 0).length + const closedLosers = closedTrades.filter((t: any) => (t.pnl_realized ?? 0) < 0).length - const isEstimated = trades.some((t: any) => t.capital_invested == null && t.entry_price != null) + const isEstimated = openTrades.some((t: any) => t.capital_invested == null && t.entry_price != null) const totalCapital = openCapital + closedCapital - const totalProfit = openProfit + closedProfit + const totalProfit = openProfit + closedProfitEur const pf = portfolio as any const pfPnlPct = pf?.unrealized_pnl_pct ?? null @@ -438,14 +441,16 @@ export default function Dashboard() { {pnlView === 'simulated' ? ( <>
= 0 ? 'text-emerald-400' : 'text-red-400')}> - {closedTrades.length === 0 ? '—' - : `${closedProfit >= 0 ? '+' : ''}${closedProfit.toFixed(0)}€`} + avgClosedPct === null ? 'text-slate-600' + : avgClosedPct >= 0 ? 'text-emerald-400' : 'text-red-400')}> + {avgClosedPct !== null + ? `${avgClosedPct >= 0 ? '+' : ''}${avgClosedPct.toFixed(2)}%` + : closedTrades.length === 0 ? '—' : '+0.00%'}
-
+
= 0 ? 'text-emerald-500' : 'text-red-400')}> {closedCapital > 0 - ? `${(closedProfit / closedCapital * 100).toFixed(1)}%` + ? `${closedProfitEur >= 0 ? '+' : ''}${closedProfitEur.toFixed(0)}€` : closedTrades.length > 0 ? 'sans capital' : ''}