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,
|
useGeoRiskScore, useAllQuotes,
|
||||||
useCalendar, usePortfolioSummary, useLastScores, useAllPatterns, useMacroRegime,
|
useCalendar, usePortfolioSummary, useLastScores, useAllPatterns, useMacroRegime,
|
||||||
useTradeMtm, useRiskDashboard, useGeoNews,
|
useTradeMtm, useRiskDashboard, useGeoNews,
|
||||||
useSimPortfolioRisk, useCycleStatus,
|
useSimPortfolioRisk, useCycleStatus, useClosedTrades,
|
||||||
} from '../hooks/useApi'
|
} from '../hooks/useApi'
|
||||||
import { Clock, Globe, ShieldAlert, ArrowUpRight, Brain, Newspaper } from 'lucide-react'
|
import { Clock, Globe, ShieldAlert, ArrowUpRight, Brain, Newspaper } from 'lucide-react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
@@ -87,6 +87,7 @@ export default function Dashboard() {
|
|||||||
const { data: allPatternsData } = useAllPatterns()
|
const { data: allPatternsData } = useAllPatterns()
|
||||||
const { data: macroData } = useMacroRegime()
|
const { data: macroData } = useMacroRegime()
|
||||||
const { data: tradeMtmData } = useTradeMtm(30)
|
const { data: tradeMtmData } = useTradeMtm(30)
|
||||||
|
const { data: closedTradesData } = useClosedTrades(90)
|
||||||
const { data: riskDashboard } = useRiskDashboard()
|
const { data: riskDashboard } = useRiskDashboard()
|
||||||
const { data: simRisk } = useSimPortfolioRisk()
|
const { data: simRisk } = useSimPortfolioRisk()
|
||||||
const { data: cycleStatusData } = useCycleStatus()
|
const { data: cycleStatusData } = useCycleStatus()
|
||||||
@@ -346,12 +347,9 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
{/* PnL */}
|
{/* PnL */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const trades: any[] = mtmTrades
|
// Unrealized — open trades only (get_trade_entry_prices excludes closed)
|
||||||
const openTrades = trades.filter((t: any) => t.status !== 'closed')
|
const openTrades = mtmTrades
|
||||||
const closedTrades = trades.filter((t: any) => t.status === 'closed')
|
const openWithPnl = openTrades.filter((t: any) => t.pnl_pct != null)
|
||||||
|
|
||||||
// Unrealized — open positions
|
|
||||||
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 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 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
|
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 targetHit = openTrades.filter((t: any) => t.alert_type === 'target_reached').length
|
||||||
const stopHit = openTrades.filter((t: any) => t.alert_type === 'stop_loss').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)
|
// Realized — from dedicated closed-trades endpoint (pnl_realized in %, capital for EUR)
|
||||||
const closedCapital = closedTrades.reduce((s: number, t: any) => s + (t.capital_invested ?? t.entry_price ?? 0), 0)
|
const closedTrades = (closedTradesData as any)?.trades ?? []
|
||||||
const closedProfit = closedTrades.reduce((s: number, t: any) => {
|
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
|
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)
|
}, 0)
|
||||||
const closedWinners = closedTrades.filter((t: any) => (t.pnl_pct ?? 0) > 0).length
|
const closedWithPnl = closedTrades.filter((t: any) => t.pnl_realized != null)
|
||||||
const closedLosers = closedTrades.filter((t: any) => (t.pnl_pct ?? 0) < 0).length
|
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 totalCapital = openCapital + closedCapital
|
||||||
const totalProfit = openProfit + closedProfit
|
const totalProfit = openProfit + closedProfitEur
|
||||||
|
|
||||||
const pf = portfolio as any
|
const pf = portfolio as any
|
||||||
const pfPnlPct = pf?.unrealized_pnl_pct ?? null
|
const pfPnlPct = pf?.unrealized_pnl_pct ?? null
|
||||||
@@ -438,14 +441,16 @@ export default function Dashboard() {
|
|||||||
{pnlView === 'simulated' ? (
|
{pnlView === 'simulated' ? (
|
||||||
<>
|
<>
|
||||||
<div className={clsx('text-xl font-bold font-mono leading-none',
|
<div className={clsx('text-xl font-bold font-mono leading-none',
|
||||||
closedTrades.length === 0 ? 'text-slate-600'
|
avgClosedPct === null ? 'text-slate-600'
|
||||||
: closedProfit >= 0 ? 'text-emerald-400' : 'text-red-400')}>
|
: avgClosedPct >= 0 ? 'text-emerald-400' : 'text-red-400')}>
|
||||||
{closedTrades.length === 0 ? '—'
|
{avgClosedPct !== null
|
||||||
: `${closedProfit >= 0 ? '+' : ''}${closedProfit.toFixed(0)}€`}
|
? `${avgClosedPct >= 0 ? '+' : ''}${avgClosedPct.toFixed(2)}%`
|
||||||
|
: closedTrades.length === 0 ? '—' : '+0.00%'}
|
||||||
</div>
|
</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
|
{closedCapital > 0
|
||||||
? `${(closedProfit / closedCapital * 100).toFixed(1)}%`
|
? `${closedProfitEur >= 0 ? '+' : ''}${closedProfitEur.toFixed(0)}€`
|
||||||
: closedTrades.length > 0 ? 'sans capital' : ''}
|
: closedTrades.length > 0 ? 'sans capital' : ''}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-slate-500 mt-1">
|
<div className="text-[10px] text-slate-500 mt-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user