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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user