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 losers = withPnl.filter((t: any) => t.pnl_pct < 0).length
|
||||||
const closedTrades = trades.filter((t: any) => t.status === 'closed')
|
const closedTrades = trades.filter((t: any) => t.status === 'closed')
|
||||||
const openTrades = 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) =>
|
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 targetHit = trades.filter((t: any) => t.alert_type === 'target_reached').length
|
||||||
const stopHit = trades.filter((t: any) => t.alert_type === 'stop_loss').length
|
const stopHit = trades.filter((t: any) => t.alert_type === 'stop_loss').length
|
||||||
|
|
||||||
@@ -356,7 +359,9 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-2 pt-2 border-t border-slate-700/30 flex justify-between items-end">
|
<div className="mt-2 pt-2 border-t border-slate-700/30 flex justify-between items-end">
|
||||||
<div>
|
<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">
|
<div className="text-2xl font-bold font-mono text-slate-300">
|
||||||
{totalCapital > 0 ? `${totalCapital.toFixed(0)}€` : '—'}
|
{totalCapital > 0 ? `${totalCapital.toFixed(0)}€` : '—'}
|
||||||
</div>
|
</div>
|
||||||
@@ -467,6 +472,7 @@ export default function Dashboard() {
|
|||||||
const patternsAdded: number = last?.patterns_added ?? 0
|
const patternsAdded: number = last?.patterns_added ?? 0
|
||||||
const patternsScored: number = last?.patterns_scored ?? 0
|
const patternsScored: number = last?.patterns_scored ?? 0
|
||||||
const tradesLogged: number = cycleTrades.length
|
const tradesLogged: number = cycleTrades.length
|
||||||
|
const tradesNotLogged: number = Math.max(0, patternsAdded - tradesLogged)
|
||||||
const tradesClosed = cycleTrades.filter((t: any) => t.status === 'closed').length
|
const tradesClosed = cycleTrades.filter((t: any) => t.status === 'closed').length
|
||||||
const commentary = last?.commentary
|
const commentary = last?.commentary
|
||||||
? (() => { try { return typeof last.commentary === 'string' ? JSON.parse(last.commentary) : last.commentary } catch { return null } })()
|
? (() => { 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 className="text-[9px] text-slate-500 mt-0.5">patterns ajoutés</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-dark-700/60 rounded px-2 py-2">
|
<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-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-[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>
|
||||||
<div className="bg-dark-700/60 rounded px-2 py-1.5">
|
<div className="bg-dark-700/60 rounded px-2 py-1.5">
|
||||||
<div className="text-base font-bold text-emerald-400">{patternsScored}</div>
|
<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é */}
|
{/* Trades du dernier cycle — détaillé */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const tradeCount = cycleTrades.length
|
|
||||||
// Build a map: pattern_id → trade for quick lookup
|
// Build a map: pattern_id → trade for quick lookup
|
||||||
const tradeByPattern: Record<string, any> = {}
|
const tradeByPattern: Record<string, any> = {}
|
||||||
for (const t of cycleTrades) {
|
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>
|
<span className="section-title mb-0 text-[10px]">📥 Trades du cycle</span>
|
||||||
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
||||||
</div>
|
</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 ? (
|
{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) => {
|
{rows.slice(0, 3).map((p: any, i: number) => {
|
||||||
const t = tradeByPattern[p.id]
|
const t = tradeByPattern[p.id]
|
||||||
const logged = !!t
|
const logged = !!t
|
||||||
@@ -620,38 +625,41 @@ export default function Dashboard() {
|
|||||||
const pnl: number | null = t?.pnl_pct ?? null
|
const pnl: number | null = t?.pnl_pct ?? null
|
||||||
const isBear = t?.strategy?.toLowerCase().includes('put') || t?.strategy?.toLowerCase().includes('bear')
|
const isBear = t?.strategy?.toLowerCase().includes('put') || t?.strategy?.toLowerCase().includes('bear')
|
||||||
return (
|
return (
|
||||||
<div key={i} className="text-[10px]">
|
<div key={i}>
|
||||||
{/* Pattern name (origin) */}
|
{logged ? (
|
||||||
<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')}>
|
{/* Trade row — primary */}
|
||||||
{logged ? '✓' : '○'}
|
<div className="flex items-center gap-1.5 text-[10px]">
|
||||||
</span>
|
<span className="shrink-0">{isBear ? '🐻' : '🐂'}</span>
|
||||||
<span className="text-slate-400 truncate flex-1 italic">{p.name}</span>
|
<span className="font-mono text-slate-200 font-bold w-10 truncate">{t.underlying ?? '—'}</span>
|
||||||
{score !== null && (
|
<span className="text-slate-400 truncate flex-1">{t.strategy ?? ''}</span>
|
||||||
<span className={clsx('font-mono shrink-0 text-[9px] font-bold', scoreColor(score))}>{score}</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')}>
|
||||||
</div>
|
EV{ev >= 0 ? '+' : ''}{ev.toFixed(2)}
|
||||||
{/* Trade details if logged */}
|
</span>
|
||||||
{logged && (
|
)}
|
||||||
<div className="flex items-center gap-1.5 ml-3">
|
{score !== null && (
|
||||||
<span className="shrink-0 text-[9px]">{isBear ? '🐻' : '🐂'}</span>
|
<span className={clsx('font-mono shrink-0 font-bold', scoreColor(score))}>{score}</span>
|
||||||
<span className="font-mono text-slate-300 font-semibold">{t.underlying ?? '—'}</span>
|
)}
|
||||||
<span className="text-slate-600 truncate flex-1">{t.strategy ?? ''}</span>
|
{pnl !== null && (
|
||||||
{ev !== null && (
|
<span className={clsx('font-mono font-bold shrink-0', pnl >= 0 ? 'text-emerald-400' : 'text-red-400')}>
|
||||||
<span className={clsx('font-mono shrink-0 text-[9px]', ev >= 0.5 ? 'text-emerald-400' : ev >= 0 ? 'text-yellow-400' : 'text-red-400')}>
|
{pnl >= 0 ? '+' : ''}{pnl.toFixed(1)}%
|
||||||
EV{ev >= 0 ? '+' : ''}{ev.toFixed(2)}
|
</span>
|
||||||
</span>
|
)}
|
||||||
)}
|
</div>
|
||||||
{pnl !== null && (
|
{/* Pattern name — secondary */}
|
||||||
<span className={clsx('font-mono font-bold shrink-0', pnl >= 0 ? 'text-emerald-400' : 'text-red-400')}>
|
<div className="text-[8px] text-slate-600 italic truncate mt-0.5 ml-4">{p.name}</div>
|
||||||
{pnl >= 0 ? '+' : ''}{pnl.toFixed(1)}%
|
</>
|
||||||
</span>
|
) : (
|
||||||
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!logged && (
|
|
||||||
<div className="ml-3 text-[9px] text-slate-700">score insuffisant — non loggé</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user