fix: PnL montant via entry_price fallback, Trades du cycle restructuré, Dernier Cycle trades ajoutés
- PnL simulated: Investi ~ et P&L € calculés avec entry_price quand capital_invested est null (label ~ pour indiquer estimation) - Dernier Cycle: rename trades loggés -> trades ajoutés + mini mention des non-loggés quand patternsAdded > tradesLogged - Trades du cycle: supprime le grand header +N Trades (info déplacée dans Dernier Cycle); layout inversé — trade en primaire, pattern en secondaire petit en dessous; trades non loggés affichés en gris Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -314,9 +314,12 @@ export default function Dashboard() {
|
||||
const losers = withPnl.filter((t: any) => t.pnl_pct < 0).length
|
||||
const closedTrades = trades.filter((t: any) => t.status === 'closed')
|
||||
const openTrades = trades.filter((t: any) => t.status !== 'closed')
|
||||
const totalCapital = trades.reduce((s: number, t: any) => s + (t.capital_invested ?? 0), 0)
|
||||
// Use capital_invested if set, otherwise entry_price as proxy cost
|
||||
const totalCapital = trades.reduce((s: number, t: any) =>
|
||||
s + (t.capital_invested ?? t.entry_price ?? 0), 0)
|
||||
const totalProfit = withPnl.reduce((s: number, t: any) =>
|
||||
s + ((t.capital_invested ?? 0) * t.pnl_pct / 100), 0)
|
||||
s + ((t.capital_invested ?? t.entry_price ?? 0) * t.pnl_pct / 100), 0)
|
||||
const isEstimated = trades.some((t: any) => t.capital_invested == null && t.entry_price != null)
|
||||
const targetHit = trades.filter((t: any) => t.alert_type === 'target_reached').length
|
||||
const stopHit = trades.filter((t: any) => t.alert_type === 'stop_loss').length
|
||||
|
||||
@@ -356,7 +359,9 @@ export default function Dashboard() {
|
||||
</div>
|
||||
<div className="mt-2 pt-2 border-t border-slate-700/30 flex justify-between items-end">
|
||||
<div>
|
||||
<div className="text-[10px] text-slate-500 mb-0.5">Investi</div>
|
||||
<div className="text-[10px] text-slate-500 mb-0.5">
|
||||
Investi{isEstimated ? ' ~' : ''}
|
||||
</div>
|
||||
<div className="text-2xl font-bold font-mono text-slate-300">
|
||||
{totalCapital > 0 ? `${totalCapital.toFixed(0)}€` : '—'}
|
||||
</div>
|
||||
@@ -467,6 +472,7 @@ export default function Dashboard() {
|
||||
const patternsAdded: number = last?.patterns_added ?? 0
|
||||
const patternsScored: number = last?.patterns_scored ?? 0
|
||||
const tradesLogged: number = cycleTrades.length
|
||||
const tradesNotLogged: number = Math.max(0, patternsAdded - tradesLogged)
|
||||
const tradesClosed = cycleTrades.filter((t: any) => t.status === 'closed').length
|
||||
const commentary = last?.commentary
|
||||
? (() => { try { return typeof last.commentary === 'string' ? JSON.parse(last.commentary) : last.commentary } catch { return null } })()
|
||||
@@ -486,8 +492,11 @@ export default function Dashboard() {
|
||||
<div className="text-[9px] text-slate-500 mt-0.5">patterns ajoutés</div>
|
||||
</div>
|
||||
<div className="bg-dark-700/60 rounded px-2 py-2">
|
||||
<div className="text-xl font-bold text-blue-400">{tradesLogged}</div>
|
||||
<div className="text-[9px] text-slate-500 mt-0.5">trades loggés</div>
|
||||
<div className="text-xl font-bold text-blue-400">+{tradesLogged}</div>
|
||||
<div className="text-[9px] text-slate-500 mt-0.5">trades ajoutés</div>
|
||||
{tradesNotLogged > 0 && (
|
||||
<div className="text-[8px] text-slate-700 mt-0.5">{tradesNotLogged} non-loggé{tradesNotLogged > 1 ? 's' : ''}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-dark-700/60 rounded px-2 py-1.5">
|
||||
<div className="text-base font-bold text-emerald-400">{patternsScored}</div>
|
||||
@@ -591,7 +600,6 @@ export default function Dashboard() {
|
||||
|
||||
{/* Trades du dernier cycle — détaillé */}
|
||||
{(() => {
|
||||
const tradeCount = cycleTrades.length
|
||||
// Build a map: pattern_id → trade for quick lookup
|
||||
const tradeByPattern: Record<string, any> = {}
|
||||
for (const t of cycleTrades) {
|
||||
@@ -607,11 +615,8 @@ export default function Dashboard() {
|
||||
<span className="section-title mb-0 text-[10px]">📥 Trades du cycle</span>
|
||||
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
||||
</div>
|
||||
<div className="text-xl font-bold text-emerald-400 font-mono">
|
||||
{tradeCount > 0 ? `+${tradeCount} Trade${tradeCount > 1 ? 's' : ''}` : lastCycle ? '+0 Trades' : '—'}
|
||||
</div>
|
||||
{rows.length > 0 ? (
|
||||
<div className="space-y-1.5 mt-1.5">
|
||||
<div className="space-y-2 mt-1">
|
||||
{rows.slice(0, 3).map((p: any, i: number) => {
|
||||
const t = tradeByPattern[p.id]
|
||||
const logged = !!t
|
||||
@@ -620,38 +625,41 @@ export default function Dashboard() {
|
||||
const pnl: number | null = t?.pnl_pct ?? null
|
||||
const isBear = t?.strategy?.toLowerCase().includes('put') || t?.strategy?.toLowerCase().includes('bear')
|
||||
return (
|
||||
<div key={i} className="text-[10px]">
|
||||
{/* Pattern name (origin) */}
|
||||
<div className="flex items-center gap-1 mb-0.5">
|
||||
<span className={clsx('text-[8px] font-bold shrink-0 w-3', logged ? 'text-emerald-400' : 'text-slate-600')}>
|
||||
{logged ? '✓' : '○'}
|
||||
</span>
|
||||
<span className="text-slate-400 truncate flex-1 italic">{p.name}</span>
|
||||
{score !== null && (
|
||||
<span className={clsx('font-mono shrink-0 text-[9px] font-bold', scoreColor(score))}>{score}</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Trade details if logged */}
|
||||
{logged && (
|
||||
<div className="flex items-center gap-1.5 ml-3">
|
||||
<span className="shrink-0 text-[9px]">{isBear ? '🐻' : '🐂'}</span>
|
||||
<span className="font-mono text-slate-300 font-semibold">{t.underlying ?? '—'}</span>
|
||||
<span className="text-slate-600 truncate flex-1">{t.strategy ?? ''}</span>
|
||||
{ev !== null && (
|
||||
<span className={clsx('font-mono shrink-0 text-[9px]', ev >= 0.5 ? 'text-emerald-400' : ev >= 0 ? 'text-yellow-400' : 'text-red-400')}>
|
||||
EV{ev >= 0 ? '+' : ''}{ev.toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
{pnl !== null && (
|
||||
<span className={clsx('font-mono font-bold shrink-0', pnl >= 0 ? 'text-emerald-400' : 'text-red-400')}>
|
||||
{pnl >= 0 ? '+' : ''}{pnl.toFixed(1)}%
|
||||
</span>
|
||||
<div key={i}>
|
||||
{logged ? (
|
||||
<>
|
||||
{/* Trade row — primary */}
|
||||
<div className="flex items-center gap-1.5 text-[10px]">
|
||||
<span className="shrink-0">{isBear ? '🐻' : '🐂'}</span>
|
||||
<span className="font-mono text-slate-200 font-bold w-10 truncate">{t.underlying ?? '—'}</span>
|
||||
<span className="text-slate-400 truncate flex-1">{t.strategy ?? ''}</span>
|
||||
{ev !== null && (
|
||||
<span className={clsx('font-mono shrink-0 text-[9px]', ev >= 0.5 ? 'text-emerald-400' : ev >= 0 ? 'text-yellow-400' : 'text-red-400')}>
|
||||
EV{ev >= 0 ? '+' : ''}{ev.toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
{score !== null && (
|
||||
<span className={clsx('font-mono shrink-0 font-bold', scoreColor(score))}>{score}</span>
|
||||
)}
|
||||
{pnl !== null && (
|
||||
<span className={clsx('font-mono font-bold shrink-0', pnl >= 0 ? 'text-emerald-400' : 'text-red-400')}>
|
||||
{pnl >= 0 ? '+' : ''}{pnl.toFixed(1)}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Pattern name — secondary */}
|
||||
<div className="text-[8px] text-slate-600 italic truncate mt-0.5 ml-4">{p.name}</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center gap-1.5 text-[10px]">
|
||||
<span className="text-slate-700 shrink-0">○</span>
|
||||
<span className="text-slate-600 truncate flex-1 italic">{p.name}</span>
|
||||
{score !== null && (
|
||||
<span className={clsx('font-mono shrink-0 text-[9px]', scoreColor(score))}>{score}</span>
|
||||
)}
|
||||
<span className="text-[8px] text-slate-700 shrink-0">non loggé</span>
|
||||
</div>
|
||||
)}
|
||||
{!logged && (
|
||||
<div className="ml-3 text-[9px] text-slate-700">score insuffisant — non loggé</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user