fix: P&L Réalisé dashboard — source useClosedTrades au lieu de mtmTrades

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 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-20 11:34:07 +02:00
parent d4a016a535
commit fff0c15316

View File

@@ -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' ? (
<>
<div className={clsx('text-xl font-bold font-mono leading-none',
closedTrades.length === 0 ? 'text-slate-600'
: closedProfit >= 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%'}
</div>
<div className="text-xs font-mono mt-0.5 text-slate-500">
<div className={clsx('text-xs font-mono mt-0.5',
closedProfitEur >= 0 ? 'text-emerald-500' : 'text-red-400')}>
{closedCapital > 0
? `${(closedProfit / closedCapital * 100).toFixed(1)}%`
? `${closedProfitEur >= 0 ? '+' : ''}${closedProfitEur.toFixed(0)}`
: closedTrades.length > 0 ? 'sans capital' : ''}
</div>
<div className="text-[10px] text-slate-500 mt-1">