@extends('layouts.trading') @section('title', 'Journal — ICT Performance Lab') @section('styles') .page { max-width: 1320px; margin: 0 auto; padding: 2rem 1.5rem 3rem; } .journal-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 1.25rem; margin-bottom: 1.6rem; flex-wrap: wrap; } .journal-title__eyebrow { display: block; margin-bottom: 0.35rem; font-size: 0.7rem; font-weight: 750; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent); } .journal-title h1 { margin: 0; font-size: clamp(2rem, 4vw, 2.7rem); font-weight: 800; letter-spacing: -0.045em; color: var(--text-primary); } .journal-title p { margin: 0.45rem 0 0; color: var(--text-secondary); font-size: 0.95rem; } .top-actions { display: flex; gap: 0.65rem; flex-wrap: wrap; } .action-btn { min-height: 42px; display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem; padding: 0.7rem 1rem; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-surface); color: var(--text-secondary); font-size: 0.82rem; font-weight: 750; text-decoration: none; transition: 0.15s ease; } .action-btn:hover { transform: translateY(-1px); color: var(--text-primary); border-color: var(--border-strong); } .action-btn--primary { color: #fff; border-color: transparent; background: linear-gradient(135deg, var(--accent-light), var(--accent)); box-shadow: 0 9px 24px var(--accent-glow); } .filters-bar { display: grid; grid-template-columns: auto minmax(260px, 1fr) auto; gap: 0.8rem; align-items: center; margin-bottom: 1rem; } .mode-switch { display: inline-flex; gap: 0.3rem; padding: 0.32rem; border: 1px solid var(--border); border-radius: 11px; background: var(--bg-surface); } .mode-switch a { min-width: 72px; padding: 0.58rem 0.8rem; border-radius: 8px; text-align: center; color: var(--text-secondary); font-size: 0.76rem; font-weight: 800; text-decoration: none; } .mode-switch a.active { color: #fff; background: linear-gradient(135deg, var(--accent-light), var(--accent)); box-shadow: 0 6px 18px var(--accent-glow); } .search-box { position: relative; } .search-box input { width: 100%; min-height: 42px; padding: 0.72rem 0.9rem 0.72rem 2.5rem; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-surface); color: var(--text-primary); outline: none; } .search-box input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); } .search-box__icon { position: absolute; left: 0.9rem; top: 50%; transform: translateY(-50%); color: var(--text-muted); font-size: 0.9rem; } .view-context { text-align: right; font-size: 0.75rem; color: var(--text-muted); white-space: nowrap; } .stats-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 0.9rem; margin-bottom: 1rem; } .stat-card { min-height: 118px; padding: 1rem 1.1rem; border: 1px solid var(--border); border-radius: 15px; background: var(--bg-surface); } .stat-card__label { margin-bottom: 0.4rem; font-size: 0.66rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); } .stat-card__value { font-family: var(--font-mono); font-size: 1.35rem; font-weight: 850; color: var(--text-primary); } .stat-card__sub { margin-top: 0.3rem; font-size: 0.72rem; color: var(--text-secondary); } .positive { color: var(--positive) !important; } .negative { color: var(--negative) !important; } .journal-card { overflow: hidden; border: 1px solid var(--border); border-radius: 17px; background: var(--bg-surface); } .journal-card__header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 1rem 1.1rem; border-bottom: 1px solid var(--border); background: var(--bg-elevated); flex-wrap: wrap; } .journal-card__title { margin: 0; font-size: 0.78rem; font-weight: 800; color: var(--text-primary); } .journal-card__count { font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-muted); } .table-wrapper { overflow-x: auto; } .trades-table { width: 100%; min-width: 1080px; border-collapse: collapse; } .trades-table th { padding: 0.78rem 0.9rem; border-bottom: 1px solid var(--border); background: var(--bg-surface); text-align: left; font-size: 0.64rem; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-muted); } .trades-table td { padding: 0.9rem; border-bottom: 1px solid var(--border); font-size: 0.8rem; color: var(--text-primary); vertical-align: middle; } .trade-row { cursor: pointer; transition: background 0.12s ease; } .trade-row:hover { background: var(--bg-hover); } .trade-row[data-search-hidden="1"] { display: none; } .trade-row--addin { background: color-mix(in srgb, var(--accent-glow) 18%, transparent); } .trade-row--addin td:first-child { padding-left: 1.6rem; } .trade-row--addin .asset-name::before { content: "↳ "; color: var(--accent); font-weight: 900; } .trade-row:last-child td { border-bottom: none; } .mono { font-family: var(--font-mono); } .muted { color: var(--text-muted); } .asset-cell { display: flex; align-items: center; gap: 0.55rem; } .asset-name { font-family: var(--font-mono); font-weight: 800; } .asset-meta { display: block; margin-top: 0.15rem; font-size: 0.67rem; color: var(--text-muted); } .idea-summary { display: inline-flex; align-items: center; gap: 0.35rem; margin-top: 0.28rem; padding: 0.2rem 0.42rem; border-radius: 6px; background: var(--bg-elevated); color: var(--text-secondary); font-size: 0.64rem; font-weight: 750; } .idea-summary strong { font-family: var(--font-mono); color: var(--text-primary); } .badge { display: inline-flex; align-items: center; justify-content: center; padding: 0.24rem 0.52rem; border-radius: 6px; font-size: 0.68rem; font-weight: 800; white-space: nowrap; } .badge--long { color: var(--long); background: var(--long-bg); } .badge--short { color: var(--short); background: var(--short-bg); } .badge--neutral { color: var(--text-secondary); background: rgba(100, 116, 139, 0.12); } .badge--positive { color: var(--positive); background: rgba(34, 197, 94, 0.10); } .badge--negative { color: var(--negative); background: rgba(239, 68, 68, 0.10); } .badge--paper { color: #8b5cf6; background: rgba(139, 92, 246, 0.10); } .badge--real { color: var(--accent); background: var(--accent-glow); } .badge--addin { color: #d97706; background: rgba(245, 158, 11, 0.12); } .discipline { display: flex; align-items: center; gap: 0.5rem; } .discipline__track { width: 62px; height: 5px; border-radius: 999px; overflow: hidden; background: var(--bg-elevated); } .discipline__fill { height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--accent), var(--accent-light)); } .discipline__score { min-width: 28px; font-family: var(--font-mono); font-size: 0.72rem; } .row-actions { display: flex; justify-content: flex-end; gap: 0.4rem; } .row-action { display: inline-flex; align-items: center; justify-content: center; min-height: 30px; padding: 0.35rem 0.55rem; border: 1px solid var(--border); border-radius: 7px; background: var(--bg-base); color: var(--text-secondary); font-size: 0.67rem; font-weight: 750; text-decoration: none; } .row-action:hover { color: var(--text-primary); border-color: var(--border-strong); } .empty-state { padding: 4rem 1.5rem; text-align: center; } .empty-state__icon { margin-bottom: 0.8rem; font-size: 2rem; color: var(--text-muted); } .empty-state__title { margin: 0 0 0.35rem; font-size: 1rem; font-weight: 800; color: var(--text-primary); } .empty-state__text { margin: 0 0 1.2rem; color: var(--text-secondary); font-size: 0.82rem; } .footer { margin-top: 1.5rem; text-align: center; color: var(--text-muted); font-size: 0.7rem; } @media (max-width: 980px) { .filters-bar { grid-template-columns: 1fr; } .view-context { text-align: left; } .stats-grid { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 620px) { .page { padding: 1.2rem 1rem 2rem; } .top-actions { width: 100%; } .action-btn { flex: 1; } .mode-switch { width: 100%; } .mode-switch a { flex: 1; } .stats-grid { grid-template-columns: 1fr; } } @endsection @section('content') @php $mode = $mode ?? request()->query('mode', 'real'); if (! in_array($mode, ['real', 'paper', 'all'], true)) { $mode = 'real'; } /* |-------------------------------------------------------------------------- | Exécutions visibles dans le journal |-------------------------------------------------------------------------- */ $executionCount = $trades->count(); /* |-------------------------------------------------------------------------- | Construction des idées de trade |-------------------------------------------------------------------------- | | Une idée = 1 trade principal + ses add-ins. | Les statistiques en haut du journal sont calculées PAR IDÉE. | | RR global pondéré : | Σ (risque utilisé × RR réalisé) / Σ risques utilisés | */ $mainTrades = $trades ->filter(fn ($trade) => empty($trade->parent_trade_id)) ->values(); $ideas = $mainTrades->map(function ($mainTrade) use ($trades) { $executions = $trades ->filter(function ($execution) use ($mainTrade) { return (int) $execution->id === (int) $mainTrade->id || (int) ($execution->parent_trade_id ?? 0) === (int) $mainTrade->id; }) ->values(); $isCompleted = $executions->isNotEmpty() && $executions->every( fn ($execution) => $execution->rr_realized !== null ); $totalRisk = (float) $executions->sum(function ($execution) { return (float) ( $execution->risk_used_percent ?? $execution->risk_level ?? $execution->risk_percent ?? 0 ); }); $weightedR = (float) $executions->sum(function ($execution) { $risk = (float) ( $execution->risk_used_percent ?? $execution->risk_level ?? $execution->risk_percent ?? 0 ); return $risk * (float) ($execution->rr_realized ?? 0); }); $globalR = $isCompleted && $totalRisk > 0 ? $weightedR / $totalRisk : null; /* * Compatibilité anciens trades clôturés sans donnée de risque. */ if ( $isCompleted && $globalR === null && $executions->count() === 1 ) { $globalR = (float) $mainTrade->rr_realized; } return (object) [ 'id' => $mainTrade->id, 'asset' => $mainTrade->asset, 'trade_date' => $mainTrade->trade_date, 'entry_time' => $mainTrade->entry_time, 'session' => $mainTrade->session, 'day' => $mainTrade->day, 'setup_grade' => $mainTrade->setup_grade, 'discipline_score' => $mainTrade->discipline_score, 'global_rr' => $globalR, 'risk_total' => $totalRisk, 'execution_count' => $executions->count(), 'is_completed' => $isCompleted, ]; })->values(); $ideaCount = $ideas->count(); $completedIdeas = $ideas ->filter( fn ($idea) => $idea->is_completed && $idea->global_rr !== null ) ->values(); $winningIdeas = $completedIdeas->filter( fn ($idea) => (float) $idea->global_rr > 0 ); $losingIdeas = $completedIdeas->filter( fn ($idea) => (float) $idea->global_rr < 0 ); $winRate = $completedIdeas->count() > 0 ? ($winningIdeas->count() / $completedIdeas->count()) * 100 : 0; $totalR = (float) $completedIdeas->sum( fn ($idea) => (float) $idea->global_rr ); /* |-------------------------------------------------------------------------- | Discipline — une note par idée |-------------------------------------------------------------------------- */ $averageDiscipline = $ideas ->whereNotNull('discipline_score') ->avg('discipline_score'); /* |-------------------------------------------------------------------------- | Libellés |-------------------------------------------------------------------------- */ $modeLabel = match ($mode) { 'paper' => 'Paper', 'all' => 'Total', default => 'Réel', }; $modeDescription = match ($mode) { 'paper' => 'Setups non exécutés', 'all' => 'Réel + Paper', default => 'Trades réellement exécutés', }; /* |-------------------------------------------------------------------------- | Ordre visuel principal + add-ins |-------------------------------------------------------------------------- */ $orphanAddins = $trades ->filter(function ($trade) use ($trades) { if (($trade->position_type ?? 'main') !== 'addin') { return false; } return ! $trades->contains( fn ($candidate) => (int) $candidate->id === (int) $trade->parent_trade_id ); }) ->values(); $displayTrades = collect(); foreach ($mainTrades as $mainTrade) { $displayTrades->push($mainTrade); $children = $trades ->filter( fn ($trade) => ($trade->position_type ?? 'main') === 'addin' && (int) $trade->parent_trade_id === (int) $mainTrade->id ) ->sortBy('addin_number'); foreach ($children as $child) { $displayTrades->push($child); } } foreach ($orphanAddins as $orphanAddin) { $displayTrades->push($orphanAddin); } @endphp
Toutes tes exécutions, tes setups et tes décisions au même endroit.
Documente ton premier setup ICT pour commencer ton journal.
+ Nouveau trade| Date | Actif | Mode | Direction | Session | Setup | Risque | Résultat | Discipline | |
|---|---|---|---|---|---|---|---|---|---|
| {{ $trade->trade_date?->format('d/m/Y') ?? '—' }} |
{{ $trade->asset }}
@if ($isAddin)
@else
@if ($ideaExecutionCount > 1)
Idée :
{{ $ideaExecutionCount }}
exécutions
·
{{ number_format($ideaRiskUsed, 2) }}%
risque cumulé
@if ($ideaR !== null)
·
{{ $ideaR > 0 ? '+' : '' }}{{ number_format($ideaR, 2) }}R
@endif
@endif
@endif
|
@if ($trade->trade_mode === 'paper') Paper @else Réel @endif @if ($isAddin) Add-in @endif | @if ($isLong) Long @elseif ($isShort) Short @else {{ $trade->direction ?? '—' }} @endif | {{ $trade->session ?? '—' }} | @if ($trade->setup_grade) {{ $trade->setup_grade }} @else — @endif | {{ $trade->risk_used_percent !== null ? number_format((float) $trade->risk_used_percent, 2) . '%' : ( $trade->risk_percent !== null ? number_format((float) $trade->risk_percent, 2) . '%' : '—' ) }} @if (! $isAddin && $ideaExecutionCount > 1) @endif | @if ($rr !== null) {{ $rr > 0 ? '+' : '' }}{{ number_format((float) $rr, 2) }}R @elseif ($trade->result) {{ strtoupper($trade->result) }} @else Ouvert @endif |
@if ($discipline !== null)
|