// History tab — closed trades, compact 2-line rows, period selector const HISTORY_TRADES = (() => { // Generate plausible trade history with relative dates const now = new Date(2026, 3, 27, 14, 30); // Apr 27, 2026 const mins = (n) => new Date(now.getTime() - n * 60000); const raw = [ // Today { sym: 'EURUSD', side: 'buy', vol: 0.50, openP: 1.07512, closeP: 1.07684, openT: mins(85), closeT: mins(40) }, { sym: 'XAUUSD', side: 'sell', vol: 0.10, openP: 2371.20, closeP: 2364.80, openT: mins(180), closeT: mins(120) }, { sym: 'GBPUSD', side: 'buy', vol: 0.30, openP: 1.26420, closeP: 1.26312, openT: mins(240), closeT: mins(200) }, { sym: 'BTCUSD', side: 'buy', vol: 0.05, openP: 66890, closeP: 67280, openT: mins(320), closeT: mins(60) }, { sym: 'NAS100', side: 'sell', vol: 0.50, openP: 18288, closeP: 18215, openT: mins(410), closeT: mins(150) }, // Yesterday { sym: 'USDJPY', side: 'sell', vol: 0.40, openP: 154.520, closeP: 154.180, openT: mins(60*22), closeT: mins(60*20) }, { sym: 'XAUUSD', side: 'buy', vol: 0.05, openP: 2362.10, closeP: 2358.40, openT: mins(60*24), closeT: mins(60*23) }, { sym: 'US500', side: 'buy', vol: 1.00, openP: 5172.0, closeP: 5184.5, openT: mins(60*26), closeT: mins(60*22) }, { sym: 'ETHUSD', side: 'sell', vol: 0.10, openP: 3540.20, closeP: 3508.10, openT: mins(60*30), closeT: mins(60*25) }, // This week { sym: 'AUDUSD', side: 'buy', vol: 0.50, openP: 0.65020, closeP: 0.65185, openT: mins(60*54), closeT: mins(60*50) }, { sym: 'USOIL', side: 'sell', vol: 0.20, openP: 79.40, closeP: 78.95, openT: mins(60*68), closeT: mins(60*64) }, { sym: 'GER40', side: 'buy', vol: 0.50, openP: 18594, closeP: 18642, openT: mins(60*72), closeT: mins(60*70) }, { sym: 'BTCUSD', side: 'sell', vol: 0.02, openP: 67950, closeP: 67500, openT: mins(60*84), closeT: mins(60*80) }, { sym: 'EURJPY', side: 'buy', vol: 0.30, openP: 166.120, closeP: 165.840, openT: mins(60*96), closeT: mins(60*92) }, { sym: 'XAGUSD', side: 'buy', vol: 0.20, openP: 30.842, closeP: 31.156, openT: mins(60*108),closeT: mins(60*100) }, // Last week / earlier { sym: 'GBPUSD', side: 'sell', vol: 0.50, openP: 1.26780, closeP: 1.26420, openT: mins(60*180),closeT: mins(60*175) }, { sym: 'NAS100', side: 'buy', vol: 0.20, openP: 18102, closeP: 18248, openT: mins(60*200),closeT: mins(60*195) }, { sym: 'XAUUSD', side: 'buy', vol: 0.10, openP: 2348.50, closeP: 2362.40, openT: mins(60*220),closeT: mins(60*215) }, { sym: 'SOLUSD', side: 'buy', vol: 1.00, openP: 162.40, closeP: 169.80, openT: mins(60*260),closeT: mins(60*255) }, { sym: 'USDCAD', side: 'sell', vol: 0.30, openP: 1.37580, closeP: 1.37120, openT: mins(60*300),closeT: mins(60*295) }, { sym: 'JP225', side: 'buy', vol: 0.50, openP: 38760, closeP: 38920, openT: mins(60*340),closeT: mins(60*335) }, { sym: 'NGAS', side: 'sell', vol: 0.50, openP: 2.912, closeP: 2.847, openT: mins(60*420),closeT: mins(60*410) }, ]; // Compute P&L using the same multipliers from data.jsx const mult = { 'XAUUSD': 100, 'XAGUSD': 5000, 'XPTUSD': 50, 'XPDUSD': 100, 'BTCUSD': 1, 'ETHUSD': 1, 'SOLUSD': 1, 'XRPUSD': 1, 'LTCUSD': 1, 'US500': 50, 'US30': 5, 'NAS100': 20, 'GER40': 25, 'UK100': 10, 'JP225': 5, 'USOIL': 1000, 'UKOIL': 1000, 'NGAS': 10000, }; return raw.map((tr, i) => { const m = mult[tr.sym] ?? 100000; const diff = tr.side === 'buy' ? (tr.closeP - tr.openP) : (tr.openP - tr.closeP); return { id: 'h' + i, ...tr, pl: diff * tr.vol * m, }; }); })(); // The period value is the code the filter compares against (`period === 'Today'` // below, and the cutoff map) — it stays English. The chip's label is looked up // from this map at RENDER time; a t() call out here would run before initI18n(). const HIST_PERIODS = ['Today', 'Week', 'Month', 'All']; const HIST_PERIOD_KEYS = { Today: 'history.period.today', Week: 'history.period.week', Month: 'history.period.month', All: 'history.period.all', }; function formatHistTime(d, now) { const diffMs = now - d; const diffH = diffMs / 3600000; const diffD = diffH / 24; if (diffH < 24 && d.getDate() === now.getDate()) { return fmtClock(d); // IO9: 24-hour, and 00:30 no longer prints as 24:30 } if (diffD < 2) return t('history.yesterday'); if (diffD < 7) return weekdayShort(d); return fmtMonthDay(d); } const HIST_CAT_COLOR = { FX: '#3B82F6', Metals: '#E0A21A', Energy: '#E2563B', Indices: '#8B5CF6', Crypto: '#F7931A' }; // MM/DD/YYYY HH:MM — one canonical timestamp in every locale (ID6). The whole // run is isolated so RTL text around it cannot reorder the date against the // time, and fmtClock supplies the hours (IO9). function fmtFullTS(d) { return bidiIsolate( d.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }) + ' ' + fmtClock(d)); } // One leg's execution list: "#dealId vol price" per line. function ExecColumn({ execs, inst, theme }) { if (!execs || !execs.length) { return
; } return (
{execs.map((e, i) => (
#{e.id || '—'} {e.vol.toFixed(2)} {fmtPrice(e.price, inst.digits)}
))}
); } function HistoryRow({ trade, theme }) { const [open, setOpen] = React.useState(false); const inst = INSTRUMENTS.find(i => i.sym === trade.sym) || { digits: 2, cat: 'FX' }; const isProfit = trade.pl >= 0; const isBuy = trade.side === 'buy'; const c = HIST_CAT_COLOR[inst.cat] || theme.accent; const now = new Date(); const sym = trade.sym.length === 6 && trade.sym.match(/^[A-Z]{6}$/) ? trade.sym.slice(0, 3) + '/' + trade.sym.slice(3) : trade.sym; const expandable = trade.positionId || (trade.openExecs && trade.openExecs.length) || (trade.closeExecs && trade.closeExecs.length); return (
expandable && setOpen(o => !o)} style={{ padding: '10px 16px', cursor: expandable ? 'pointer' : 'default' }}> {/* line 1: dot · symbol · side tag · time · P&L */}
{sym} {isBuy ? t('common.side.buy') : t('common.side.sell')} {trade.vol.toFixed(2)} {fmtMoney(trade.pl)}
{/* line 2: open → close prices · time */}
{fmtPrice(trade.openP, inst.digits)} {fmtPrice(trade.closeP, inst.digits)} {formatHistTime(trade.closeT, now)} {expandable && ( )}
{/* expanded: position id, leg timestamps, per-leg executions */} {open && (
#{trade.positionId || trade.ticket}
{t('history.detail.open')} {fmtFullTS(trade.openT)} {t('history.detail.close')} {fmtFullTS(trade.closeT)}
{t('history.executions')}
{/* One key per leg heading rather than side + "(open)" assembled: the English mixes cases inside the phrase (BUY (open)), which no textTransform can reproduce and no split key can reorder. */}
{isBuy ? t('history.leg.openBuy') : t('history.leg.openSell')}
{isBuy ? t('history.leg.closeSell') : t('history.leg.closeBuy')}
)}
); } function HistoryTab({ theme, period, setPeriod, trades }) { const now = new Date(); const cutoff = { Today: 0, // same day Week: 7 * 24 * 3600 * 1000, Month: 30 * 24 * 3600 * 1000, All: Infinity, }[period]; // Live: session-local closed trades from the Concierge stream. Server-side // history (Postgres replica) lands with Stage 2 / F2. const source = trades || []; const filtered = source.filter(tr => { if (period === 'Today') return tr.closeT.getDate() === now.getDate() && tr.closeT.getMonth() === now.getMonth(); return (now - tr.closeT) <= cutoff; }); // Group by day const groups = {}; for (const tr of filtered) { const key = tr.closeT.toDateString(); if (!groups[key]) groups[key] = []; groups[key].push(tr); } const groupKeys = Object.keys(groups).sort((a, b) => new Date(b) - new Date(a)); // Stats const totalPL = filtered.reduce((s, tr) => s + tr.pl, 0); const wins = filtered.filter(tr => tr.pl >= 0).length; const winRate = filtered.length > 0 ? (wins / filtered.length) * 100 : 0; const isProfit = totalPL >= 0; const groupLabel = (dateStr) => { const d = new Date(dateStr); if (d.toDateString() === now.toDateString()) return t('history.today'); const yest = new Date(now); yest.setDate(yest.getDate() - 1); if (d.toDateString() === yest.toDateString()) return t('history.yesterday'); return fmtWeekdayMonthDay(d); }; return (
{t('history.title')}
{/* Period selector — segmented */}
{HIST_PERIODS.map(p => (
setPeriod(p)} style={{ flex: 1, textAlign: 'center', padding: '7px 0', borderRadius: 8, fontSize: 13, fontWeight: 600, cursor: 'pointer', letterSpacing: -0.1, background: period === p ? theme.text : 'transparent', color: period === p ? theme.bg : theme.textSec, transition: 'all 140ms', }} >{t(HIST_PERIOD_KEYS[p])}
))}
{/* Summary strip */} {filtered.length > 0 && (
{t('history.stat.netPl')}
{fmtMoney(totalPL)}
{t('history.stat.trades')}
{filtered.length} {t('history.stat.winLoss', { wins, losses: filtered.length - wins })}
{t('history.stat.winRate')}
{winRate.toFixed(0)}%
)} {/* Grouped list */} {filtered.length > 0 ? ( groupKeys.map(key => (
{groupLabel(key)} {tn('history.tradeCount', groups[key].length)}
{groups[key].map((tr, i) => (
))}
)) ) : ( )}
); } Object.assign(window, { HistoryTab, HISTORY_TRADES });