@php use App\Support\SymbolPresentation; @endphp @extends('portal.layouts.master') @section('title', 'Portfolio') @section('page-title', 'Portfolio') @section('extra-css') /* ── Stat cards ── */ .pf-cards { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: .75rem; margin-bottom: 1.1rem; } @media (max-width: 1400px) { .pf-cards { grid-template-columns: repeat(3, 1fr); } } @media (max-width: 1100px) { .pf-cards { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 600px) { .pf-cards { grid-template-columns: 1fr; } } .pf-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; padding: 1rem 1.1rem .65rem; display: flex; flex-direction: column; min-height: 120px; position: relative; overflow: hidden; } .pf-card::before { content: ''; position: absolute; inset: 0; background: var(--pc-grad, transparent); opacity: .18; pointer-events: none; border-radius: 10px; } .pf-card--blue { --pc-grad: linear-gradient(135deg,#3b82f6,transparent 65%); } .pf-card--teal { --pc-grad: linear-gradient(135deg,#0d9488,transparent 65%); } .pf-card--green { --pc-grad: linear-gradient(135deg,#0ecb81,transparent 65%); } .pf-card--purple { --pc-grad: linear-gradient(135deg,#8b5cf6,transparent 65%); } .pf-card--amber { --pc-grad: linear-gradient(135deg,#f59e0b,transparent 65%); } .pf-card--cyan { --pc-grad: linear-gradient(135deg,#06b6d4,transparent 65%); } .pf-position-summary { display:flex; align-items:center; gap:1.25rem; flex-wrap:nowrap; white-space:nowrap; overflow-x:auto; padding:.75rem .85rem; font-size:.78rem; color:var(--text-secondary); } .pf-position-summary span { display:inline-flex; align-items:center; gap:.28rem; } .pf-position-summary strong { color:var(--text-muted); font-weight:500; } .pf-position-summary b { color:var(--text-primary); font-weight:600; font-variant-numeric:tabular-nums; } @media (max-width: 768px) { .pf-position-summary { gap:.85rem; } } .pf-card .pc-label { font-size: .6rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--text-secondary); margin-bottom: .3rem; } .pf-card .pc-value { font-size: 1.75rem; font-weight: 800; line-height: 1; letter-spacing: -.02em; color: var(--text-primary); margin-bottom: .1rem; } .pf-card--green .pc-value { color: #0ecb81; } .pf-card--purple .pc-value { color: #a78bfa; } .pf-card--amber .pc-value { color: #fbbf24; } .pf-card--cyan .pc-value { color: #22d3ee; } .pf-card .pc-sub { font-size: .68rem; color: var(--text-muted); margin-bottom: auto; } .pf-card .pc-sub.profit { color: #0ecb81; } .pf-card .pc-sub.loss { color: #f6465d; } .pc-latest { display:flex; flex-direction:column; justify-content:center; flex:1; min-width:0; } .pc-latest-name { color:var(--text-primary); font-size:1rem; font-weight:800; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .pc-latest-meta { color:var(--text-secondary); font-size:.68rem; margin-top:.25rem; } .pc-latest-amount { color:#22d3ee; font-size:.82rem; font-weight:700; margin-top:.25rem; } /* mini charts */ .pc-chart { margin-top: .6rem; height: 42px; overflow: hidden; } .pc-chart svg { width: 100%; height: 100%; display: block; } /* donut gauge */ .pc-gauge { display: flex; align-items: center; justify-content: center; flex: 1; padding: .2rem 0; } .pc-gauge svg { width: 68px; height: 68px; } .pc-gauge-track { fill: none; stroke: rgba(139,92,246,.2); stroke-width: 7; } .pc-gauge-fill { fill: none; stroke: #8b5cf6; stroke-width: 7; stroke-linecap: round; transform: rotate(-90deg); transform-origin: 50% 50%; } .pc-gauge-num { font-size: 1.1rem; font-weight: 800; fill: #a78bfa; } /* table */ .symbol-cell { display: flex; align-items: center; gap: .6rem; } .symbol-icon { width: 36px; height: 36px; border-radius: 50%; background: var(--bg-tertiary); display: flex; align-items: center; justify-content: center; font-size: .85rem; color: var(--text-secondary); font-weight: 600; flex-shrink: 0; overflow: hidden; } .symbol-icon img { width: 20px; height: 20px; object-fit: contain; border-radius: 50%; } .cat-badge { font-size: .62rem; font-weight: 600; padding: .18rem .5rem; border-radius: 4px; text-transform: uppercase; letter-spacing: .03em; } .cat-crypto { background: rgba(245,158,11,.15); color: #f59e0b; } .cat-forex { background: rgba(14,203,129,.15); color: #0ecb81; } .cat-stocks { background: rgba(59,130,246,.15); color: #3b82f6; } .cat-commodities { background: rgba(168,85,247,.15); color: #a855f7; } .pf-breakdown { display:flex; gap:1rem; flex-wrap:wrap; margin:-.25rem 0 1rem; padding:.75rem 1rem; background:var(--bg-card); border:1px solid var(--border); border-radius:8px; } .pf-breakdown-item { min-width:160px; } .pf-breakdown-item span { display:block; color:var(--text-muted); font-size:.67rem; text-transform:uppercase; letter-spacing:.05em; } .pf-breakdown-item strong { font-size:.92rem; } .pf-warning { margin-bottom:1rem; padding:.65rem .85rem; border:1px solid rgba(245,158,11,.35); border-radius:7px; background:rgba(245,158,11,.08); color:#fbbf24; font-size:.77rem; } .pf-section { margin-top:1rem; } .pf-status { font-size:.64rem; padding:.14rem .4rem; border-radius:4px; text-transform:uppercase; } .pf-status.current { color:#0ecb81; background:rgba(14,203,129,.12); } .pf-status.delayed,.pf-status.unavailable { color:#f59e0b; background:rgba(245,158,11,.12); } .pf-position-sections { display:flex; flex-direction:column; } #portfolioLeveragedPositions { order:1; } #portfolioPositionSummary { order:2; } #portfolioPredictionPositions { order:3; } #portfolioBinaryPositions { order:4; } #portfolioMarketPositions { order:5; margin-top:1rem; } /* close-position toast */ .cp-toast { position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 9999; display: flex; align-items: flex-start; gap: .75rem; min-width: 280px; max-width: 420px; padding: .9rem 1rem; border-radius: 10px; background: #0f1923; border: 1px solid rgba(255,255,255,.12); box-shadow: 0 8px 32px rgba(0,0,0,.45); font-size: .84rem; line-height: 1.45; animation: cpToastIn .25s ease; } .cp-toast.success { border-left: 3px solid #0ecb81; } .cp-toast.error { border-left: 3px solid #f6465d; } .cp-toast-icon { font-size: 1.1rem; flex-shrink: 0; margin-top: .05rem; } .cp-toast.success .cp-toast-icon { color: #0ecb81; } .cp-toast.error .cp-toast-icon { color: #f6465d; } .cp-toast-close { margin-left: auto; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 1.1rem; line-height: 1; padding: 0 0 0 .5rem; flex-shrink: 0; } .partial-close-modal { position:fixed; inset:0; z-index:10000; display:grid; place-items:center; padding:1rem; background:rgba(0,0,0,.65); } .partial-close-modal[hidden] { display:none; } .partial-close-dialog { position:relative; width:min(420px,100%); padding:1.25rem; border:1px solid var(--border); border-radius:12px; background:var(--bg-card); box-shadow:0 18px 60px rgba(0,0,0,.5); } .partial-close-dialog h3 { margin:0 0 .35rem; font-size:1.05rem; } .partial-close-x { position:absolute; top:.65rem; right:.75rem; border:0; background:none; color:var(--text-muted); font-size:1.5rem; cursor:pointer; } .partial-close-available { margin-top:.45rem; color:var(--text-muted); font-size:.78rem; } .partial-close-error { margin-top:.65rem; color:#f6465d; font-size:.8rem; } .partial-close-actions { display:flex; justify-content:flex-end; gap:.5rem; margin-top:1rem; } @keyframes cpToastIn { from { opacity:0; transform: translateY(12px); } to { opacity:1; transform: translateY(0); } } @endsection @section('content') @php $mainCurrency = $valuation['main_currency']; $posCount = $valuation['counts']['total']; $totalPnl = $valuation['total_pnl']; $totalPnlPct = $valuation['total_pnl_pct']; $pnlSign = $totalPnl >= 0 ? '+' : ''; $pnlColor = $totalPnl >= 0 ? '#0ecb81' : '#f6465d'; /* gauge — fill circumference based on capped position count (max visual 10) */ $gaugeR = 24; $gaugeCircum = 2 * M_PI * $gaugeR; $gaugeFill = $posCount > 0 ? min(1, $posCount / max(1, $posCount)) : 0; // always full when >0 $gaugeDash = $gaugeCircum * 0.75; // fixed 75% arc to look nice when count >=1 if ($posCount === 0) { $gaugeDash = 0; } @endphp
{{-- 1. Net Funded --}}
Net Funded
{{ $mainCurrency }} {{ number_format($valuation['net_funded'], 2) }}
Approved deposits minus withdrawals
{{-- Dotted sparkline --}} @php $dotPts = [38,35,34,36,33,30,28,31,26,24,22,25,20,18,16,19,14,12,10,8]; @endphp @foreach($dotPts as $di => $dv) @endforeach
{{-- 2. Portfolio Equity --}}
Portfolio Equity
{{ $mainCurrency }} {{ number_format($valuation['equity'], 2) }}
Cash plus open-position value
{{-- 3. Total P&L --}}
Total P&L
{{ $pnlSign }}{{ $mainCurrency }} {{ number_format(abs($totalPnl), 2) }}
{{ $totalPnlPct >= 0 ? '+' : '' }}{{ number_format($totalPnlPct, 2) }}%
@php $bars = [10,14,8,18,12,22,16,28,20,32,24,36,28,34,30,38,32,36,34,40]; @endphp @foreach($bars as $bi => $bh) @endforeach
{{-- 4. Open Positions --}}
Open Positions
{{ $posCount }}
Active instruments
{{ $posCount }}
{{-- 5. USD Credit --}}
USD Credit
USD {{ number_format($valuation['credit'], 2) }}
Trading credit · Not withdrawable
{{-- 6. Latest Trade --}}
Latest Trade
@if($latestTrade)
{{ $latestTrade['instrument'] }}
{{ $latestTrade['type'] }} · {{ $latestTrade['direction'] }} · {{ $latestTrade['traded_at']?->format('M d, H:i') }}
{{ $latestTrade['currency'] }} {{ number_format($latestTrade['amount'], 2) }}
@else
No trades yet
Your newest trade will appear here.
@endif
Cash Balance{{ $mainCurrency }} {{ number_format($valuation['cash'], 2) }}
Position Cost Basis{{ $mainCurrency }} {{ number_format($valuation['position_cost_basis'], 2) }}
Current Market Exposure{{ $mainCurrency }} {{ number_format($valuation['position_market_value'], 2) }}
Position Mix{{ $valuation['counts']['regular'] }} market · {{ $valuation['counts']['leveraged'] }} leveraged · {{ $valuation['counts']['prediction'] }} prediction · {{ $valuation['counts']['binary'] }} binary
@if(!$valuation['is_complete'])
Some values are estimated because current pricing or currency conversion is unavailable. Details
@endif {{-- Positions table --}}
Market Positions
@if($valuation['regular_rows']->isEmpty())
No open positions yet.
Browse Markets
@else
@foreach($valuation['regular_rows'] as $row) @php $pos = $row['position']; $lp = $row['latest']; $val = $row['value'] ?? 0; $cost = $row['cost'] ?? 0; $pnl = $val - $cost; $pnlPct = $cost > 0 ? ($pnl / $cost) * 100 : 0; [$icn, $icColor] = SymbolPresentation::baseIconPair($pos->symbol); $icIsImg = str_starts_with($icn, 'img:'); @endphp @endforeach
Instrument Category Direction Quantity Avg Entry Current Price Value P&L ($) P&L (%) Actions
@if($icIsImg) {{ strtoupper(substr($pos->symbol->name??'?',0,2)) }} @elseif($icn) @else {{ strtoupper(substr($pos->symbol->name??'?',0,2)) }} @endif
{{ $pos->symbol->name ?? '-' }}
{{ $pos->symbol->base_currency ?? strtoupper(substr($pos->symbol->name??'',0,3)) }}
{{ ucfirst($pos->symbol->category ?? '-') }} BUY / LONG {{ rtrim(rtrim(number_format($pos->quantity, 8), '0'), '.') }} {{ number_format($pos->avg_price, $pos->symbol->decimal_places ?? 2) }} {{ $lp > 0 ? number_format($lp, $pos->symbol->decimal_places ?? 2) : '—' }} {{ $mainCurrency }} {{ number_format($val, 2) }} {{ $lp > 0 ? ($pnl >= 0 ? '+' : '').number_format($pnl, 2) : '—' }} {{ $lp > 0 ? ($pnlPct >= 0 ? '+' : '').number_format($pnlPct, 2).'%' : '—' }}
@endif
@if($valuation['leveraged_rows']->isNotEmpty())
Leveraged Positions
@foreach($valuation['leveraged_rows'] as $row) @php $leveraged = $row['position']; $leveragedDecimals = $leveraged->symbol?->decimal_places ?? 2; $leveragedPnl = $row['pnl']; @endphp @endforeach
InstrumentDirectionLeverageMarginExposureEntryCurrentStop LossTake ProfitP&LPosition ValueStatusAction
{{ $leveraged->symbol?->name ?? 'Unknown asset' }}
{{ $leveraged->symbol?->display_symbol ?? '—' }}
{{ strtoupper($leveraged->direction) }} {{ $leveraged->leverage }}× USD {{ number_format($leveraged->margin, 2) }}@if((float) $leveraged->credit_margin > 0)Credit {{ number_format($leveraged->credit_margin, 2) }}@endif USD {{ number_format($leveraged->exposure, 2) }} {{ number_format($leveraged->entry_price, $leveragedDecimals) }} {{ number_format($row['latest'], $leveragedDecimals) }} {{ $leveraged->stop_loss !== null ? number_format($leveraged->stop_loss, $leveragedDecimals) : '—' }} {{ $leveraged->take_profit !== null ? number_format($leveraged->take_profit, $leveragedDecimals) : '—' }} {{ $leveragedPnl > 0 ? '+' : '' }}USD {{ number_format($leveragedPnl, 2) }} USD {{ number_format($row['positionValue'], 2) }} {{ $row['priceStatus'] }}
@endif @php $portfolioSummary = $valuation['summary']; @endphp
Balance: USD {{ number_format($portfolioSummary['balance'], 2) }} Credit: USD {{ number_format($portfolioSummary['credit'], 2) }} Equity: USD {{ number_format($portfolioSummary['equity'], 2) }} Margin: USD {{ number_format($portfolioSummary['margin'], 2) }} Margin level: {{ $portfolioSummary['margin_level'] === null ? 'N/A' : number_format($portfolioSummary['margin_level'], 2).'%' }} Free margin: USD {{ number_format($portfolioSummary['free_margin'], 2) }} P&L: {{ $portfolioSummary['unrealized_pnl'] >= 0 ? '+' : '-' }}USD {{ number_format(abs($portfolioSummary['unrealized_pnl']), 2) }}
@if($valuation['prediction_rows']->isNotEmpty())
Prediction-Market Positions
@foreach($valuation['prediction_rows'] as $row) @php $predictionPnl = ($row['value'] ?? 0) - ($row['cost'] ?? 0); @endphp @endforeach
MarketOutcomeSharesAvg PriceCurrentCost BasisValueP&LStatus
{{ $row['position']->question }} {{ $row['position']->outcome }} {{ rtrim(rtrim(number_format($row['shares'], 8), '0'), '.') }} {{ number_format($row['average'] * 100, 2) }}¢ {{ number_format($row['latest'] * 100, 2) }}¢ {{ $mainCurrency }} {{ number_format($row['cost'] ?? 0, 2) }} {{ $mainCurrency }} {{ number_format($row['value'] ?? 0, 2) }} {{ $predictionPnl >= 0 ? '+' : '' }}{{ number_format($predictionPnl, 2) }} {{ $row['priceStatus'] }}
@endif @if($valuation['binary_rows']->isNotEmpty())
Open Binary Options
@foreach($valuation['binary_rows'] as $row)@endforeach
InstrumentDirectionStrikeStake at CostExpires
{{ $row['option']->symbol?->name ?? '—' }} {{ strtoupper($row['option']->direction) }} {{ number_format($row['option']->strike_price, 5) }} {{ $mainCurrency }} {{ number_format($row['stake'] ?? 0, 2) }} {{ $row['option']->expires_at?->format('M d, Y H:i') ?? '—' }}
@endif
@endsection @push('scripts') @endpush