Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Personal Trading Session Tracker</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/lucide@latest"></script> | |
| <style> | |
| :root { | |
| --bg-color: #0f172a; | |
| --card-bg: #1e293b; | |
| --input-bg: #334155; | |
| --text-color: #f1f5f9; | |
| --border-color: #475569; | |
| --win-color: #10b981; | |
| --loss-color: #ef4444; | |
| --accent-color: #3b82f6; | |
| } | |
| .light-mode { | |
| --bg-color: #f8fafc; | |
| --card-bg: #ffffff; | |
| --input-bg: #f1f5f9; | |
| --text-color: #1e293b; | |
| --border-color: #cbd5e1; | |
| } | |
| body { | |
| background-color: var(--bg-color); | |
| color: var(--text-color); | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| transition: all 0.3s ease; | |
| } | |
| .trade-card { | |
| background-color: var(--card-bg); | |
| border: 1px solid var(--border-color); | |
| transition: all 0.2s; | |
| } | |
| .input-field { | |
| background-color: var(--input-bg); | |
| color: var(--text-color); | |
| border: 1px solid var(--border-color); | |
| } | |
| .input-field:focus { | |
| border-color: var(--accent-color); | |
| outline: none; | |
| ring: 2px solid var(--accent-color); | |
| } | |
| .btn-win { | |
| background-color: var(--win-color); | |
| color: white; | |
| } | |
| .btn-win:hover { | |
| background-color: #059669; | |
| } | |
| .btn-loss { | |
| background-color: var(--loss-color); | |
| color: white; | |
| } | |
| .btn-loss:hover { | |
| background-color: #dc2626; | |
| } | |
| .btn-action { | |
| background-color: var(--accent-color); | |
| color: white; | |
| } | |
| .btn-action:hover { | |
| background-color: #2563eb; | |
| } | |
| .stat-box { | |
| background-color: var(--card-bg); | |
| border: 1px solid var(--border-color); | |
| } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| th, td { | |
| padding: 12px; | |
| text-align: left; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| th { | |
| background-color: var(--input-bg); | |
| font-weight: 600; | |
| } | |
| tr:hover { | |
| background-color: rgba(255,255,255,0.05); | |
| } | |
| .win-row { | |
| color: var(--win-color); | |
| } | |
| .loss-row { | |
| color: var(--loss-color); | |
| } | |
| .currency-input { | |
| position: relative; | |
| } | |
| .currency-input::before { | |
| content: '$'; | |
| position: absolute; | |
| left: 12px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| color: #94a3b8; | |
| } | |
| .currency-input input { | |
| padding-left: 28px; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen p-4 md:p-8"> | |
| <div class="max-w-6xl mx-auto"> | |
| <!-- Header --> | |
| <div class="flex justify-between items-center mb-8"> | |
| <div class="flex items-center gap-3"> | |
| <div class="w-12 h-12 bg-blue-600 rounded-lg flex items-center justify-center"> | |
| <i data-lucide="trending-up" class="text-white w-6 h-6"></i> | |
| </div> | |
| <div> | |
| <h1 class="text-2xl font-bold">Trading Session Tracker</h1> | |
| <p class="text-sm text-gray-400">Personal Trading Calculator</p> | |
| </div> | |
| </div> | |
| <div class="flex gap-3"> | |
| <button onclick="exportData()" class="px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg flex items-center gap-2 text-sm transition-colors"> | |
| <i data-lucide="download" class="w-4 h-4"></i> | |
| Export Data | |
| </button> | |
| <label class="px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg flex items-center gap-2 text-sm transition-colors cursor-pointer"> | |
| <i data-lucide="upload" class="w-4 h-4"></i> | |
| Import Data | |
| <input type="file" id="importFile" accept=".json" class="hidden" onchange="importData(this)"> | |
| </label> | |
| <button onclick="toggleTheme()" class="w-10 h-10 bg-slate-700 hover:bg-slate-600 rounded-lg flex items-center justify-center transition-colors"> | |
| <i data-lucide="sun" class="w-5 h-5" id="themeIcon"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Main Grid --> | |
| <div class="grid grid-cols-1 lg:grid-cols-12 gap-6"> | |
| <!-- Left Column: Controls (C6, C8, C9, C10, C14 equivalent) --> | |
| <div class="lg:col-span-4 space-y-6"> | |
| <!-- Starting Capital (C6) --> | |
| <div class="trade-card rounded-xl p-6"> | |
| <label class="block text-sm font-medium text-gray-400 mb-2">Starting Capital</label> | |
| <div class="currency-input"> | |
| <input type="number" id="startingCapital" class="input-field w-full rounded-lg px-4 py-3 text-xl font-bold" | |
| value="10000" step="0.01" onchange="updateState()"> | |
| </div> | |
| <p class="text-xs text-gray-500 mt-2">Updates automatically when starting new session</p> | |
| </div> | |
| <!-- Counters (C8, C9, C10) --> | |
| <div class="trade-card rounded-xl p-6"> | |
| <h3 class="font-semibold mb-4 flex items-center gap-2"> | |
| <i data-lucide="bar-chart-2" class="w-5 h-5 text-blue-400"></i> | |
| Session Counters | |
| </h3> | |
| <div class="grid grid-cols-2 gap-4 mb-4"> | |
| <div> | |
| <label class="block text-xs text-gray-400 mb-1">Wins</label> | |
| <input type="number" id="winCount" class="input-field w-full rounded-lg px-3 py-2 font-bold text-emerald-400" | |
| value="0" readonly> | |
| </div> | |
| <div> | |
| <label class="block text-xs text-gray-400 mb-1">Losses</label> | |
| <input type="number" id="lossCount" class="input-field w-full rounded-lg px-3 py-2 font-bold text-red-400" | |
| value="0" readonly> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-xs text-gray-400 mb-1">Total Trades</label> | |
| <input type="number" id="totalCount" class="input-field w-full rounded-lg px-3 py-2 font-bold text-blue-400" | |
| value="0" readonly> | |
| </div> | |
| </div> | |
| <!-- Current Trade PNL (C14) --> | |
| <div class="trade-card rounded-xl p-6 border-blue-500/30 border-2"> | |
| <label class="block text-sm font-medium text-blue-400 mb-2">Current Trade PNL</label> | |
| <div class="currency-input"> | |
| <input type="number" id="currentTradePnl" class="input-field w-full rounded-lg px-4 py-3 text-xl font-bold" | |
| placeholder="0.00" step="0.01"> | |
| </div> | |
| <p class="text-xs text-gray-500 mt-2">Enter profit/loss for this trade, then click Win or Loss</p> | |
| </div> | |
| <!-- Session PNL Display --> | |
| <div class="trade-card rounded-xl p-6 bg-gradient-to-br from-slate-800 to-slate-900"> | |
| <label class="block text-sm font-medium text-gray-400 mb-2">Current Session PNL</label> | |
| <div class="text-3xl font-bold" id="sessionPnlDisplay">$0.00</div> | |
| <div class="text-xs text-gray-500 mt-1">Running total for today's session</div> | |
| </div> | |
| <!-- Action Buttons --> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <button onclick="addWin()" class="btn-win py-4 rounded-xl font-bold text-lg flex items-center justify-center gap-2 shadow-lg transform active:scale-95 transition-all"> | |
| <i data-lucide="plus-circle" class="w-5 h-5"></i> | |
| Add Win | |
| </button> | |
| <button onclick="addLoss()" class="btn-loss py-4 rounded-xl font-bold text-lg flex items-center justify-center gap-2 shadow-lg transform active:scale-95 transition-all"> | |
| <i data-lucide="minus-circle" class="w-5 h-5"></i> | |
| Add Loss | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <button onclick="startSession()" class="btn-action py-3 rounded-xl font-semibold flex items-center justify-center gap-2 opacity-80 hover:opacity-100 transition-all"> | |
| <i data-lucide="play" class="w-4 h-4"></i> | |
| Start Session | |
| </button> | |
| <button onclick="endSession()" class="bg-amber-600 hover:bg-amber-700 text-white py-3 rounded-xl font-semibold flex items-center justify-center gap-2 transition-all"> | |
| <i data-lucide="flag" class="w-4 h-4"></i> | |
| End Session | |
| </button> | |
| </div> | |
| <button onclick="resetAll()" class="w-full py-2 bg-red-900/30 hover:bg-red-900/50 text-red-400 rounded-lg text-sm transition-colors border border-red-800/30"> | |
| <i data-lucide="trash-2" class="inline w-4 h-4 mr-2"></i> | |
| Reset All Data | |
| </button> | |
| </div> | |
| <!-- Right Column: Data Tables --> | |
| <div class="lg:col-span-8 space-y-6"> | |
| <!-- Current Session Trades (DATA Sheet) --> | |
| <div class="trade-card rounded-xl p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-semibold flex items-center gap-2"> | |
| <i data-lucide="list" class="w-5 h-5 text-blue-400"></i> | |
| Session Trade Log | |
| </h3> | |
| <span class="text-xs text-gray-500" id="tradeCountBadge">0 trades</span> | |
| </div> | |
| <div class="overflow-x-auto max-h-96 overflow-y-auto"> | |
| <table id="tradeTable"> | |
| <thead class="sticky top-0"> | |
| <tr> | |
| <th>Trade #</th> | |
| <th>Result</th> | |
| <th>Timestamp</th> | |
| <th>Profit/Loss</th> | |
| <th>Action</th> | |
| </tr> | |
| </thead> | |
| <tbody id="tradeTableBody"> | |
| <tr> | |
| <td colspan="5" class="text-center py-8 text-gray-500 italic"> | |
| No trades recorded. Start by entering PNL and clicking Win or Loss. | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Daily PNL History (Column L) --> | |
| <div class="trade-card rounded-xl p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-semibold flex items-center gap-2"> | |
| <i data-lucide="calendar" class="w-5 h-5 text-amber-400"></i> | |
| Daily PNL History | |
| </h3> | |
| <span class="text-xs text-gray-500">Last 14 sessions</span> | |
| </div> | |
| <div class="overflow-x-auto max-h-64 overflow-y-auto"> | |
| <table id="historyTable"> | |
| <thead class="sticky top-0"> | |
| <tr> | |
| <th>Session Date</th> | |
| <th>Daily PNL</th> | |
| <th>Cumulative Capital</th> | |
| </tr> | |
| </thead> | |
| <tbody id="historyTableBody"> | |
| <tr> | |
| <td colspan="3" class="text-center py-8 text-gray-500 italic"> | |
| No session history. Click "End Session" to save daily PNL. | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Instructions --> | |
| <div class="trade-card rounded-xl p-6 bg-blue-900/20 border-blue-800/30"> | |
| <h4 class="font-semibold text-blue-300 mb-3 flex items-center gap-2"> | |
| <i data-lucide="info" class="w-5 h-5"></i> | |
| How to Use (Matching Google Sheets Workflow) | |
| </h4> | |
| <ol class="text-sm text-blue-200 space-y-2 list-decimal list-inside"> | |
| <li><strong>Start Session:</strong> Click to clear trade log and add previous day's PNL to Starting Capital</li> | |
| <li><strong>Enter Trade PNL:</strong> Input the profit or loss amount in the Current Trade PNL field</li> | |
| <li><strong>Record Trade:</strong> Click "Add Win" or "Add Loss" to log the trade with timestamp</li> | |
| <li><strong>End Session:</strong> Click to save today's total PNL to the Daily History</li> | |
| <li><strong>Next Day:</strong> Click Start Session again to roll over the capital and reset counters</li> | |
| </ol> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize Lucide icons | |
| lucide.createIcons(); | |
| // State Management | |
| let appState = { | |
| startingCapital: 10000, | |
| dailyHistory: [], // Array of {date, pnl, id} | |
| currentSession: { | |
| trades: [], // Array of {tradeNumber, result, timestamp, profit, id} | |
| nextTradeNumber: 1 | |
| }, | |
| isDarkMode: true | |
| }; | |
| // Load from LocalStorage on startup | |
| function loadState() { | |
| const saved = localStorage.getItem('tradingSessionData'); | |
| if (saved) { | |
| appState = JSON.parse(saved); | |
| document.getElementById('startingCapital').value = appState.startingCapital || 10000; | |
| updateUI(); | |
| } | |
| } | |
| // Save to LocalStorage | |
| function saveState() { | |
| localStorage.setItem('tradingSessionData', JSON.stringify(appState)); | |
| } | |
| // Update all UI elements | |
| function updateUI() { | |
| // Update counters (C8, C9, C10) | |
| const wins = appState.currentSession.trades.filter(t => t.result === 'Win').length; | |
| const losses = appState.currentSession.trades.filter(t => t.result === 'Loss').length; | |
| const total = appState.currentSession.trades.length; | |
| document.getElementById('winCount').value = wins; | |
| document.getElementById('lossCount').value = losses; | |
| document.getElementById('totalCount').value = total; | |
| // Calculate Session PNL | |
| const sessionPnl = appState.currentSession.trades.reduce((sum, t) => sum + parseFloat(t.profit), 0); | |
| const pnlElement = document.getElementById('sessionPnlDisplay'); | |
| pnlElement.textContent = formatCurrency(sessionPnl); | |
| pnlElement.className = sessionPnl >= 0 ? 'text-3xl font-bold text-emerald-400' : 'text-3xl font-bold text-red-400'; | |
| // Update trade table (DATA sheet) | |
| updateTradeTable(); | |
| // Update history table (Column L) | |
| updateHistoryTable(); | |
| // Update badge | |
| document.getElementById('tradeCountBadge').textContent = `${total} trade${total !== 1 ? 's' : ''}`; | |
| } | |
| function updateTradeTable() { | |
| const tbody = document.getElementById('tradeTableBody'); | |
| if (appState.currentSession.trades.length === 0) { | |
| tbody.innerHTML = ` | |
| <tr> | |
| <td colspan="5" class="text-center py-8 text-gray-500 italic"> | |
| No trades recorded. Start by entering PNL and clicking Win or Loss. | |
| </td> | |
| </tr>`; | |
| return; | |
| } | |
| tbody.innerHTML = appState.currentSession.trades.slice().reverse().map(trade => ` | |
| <tr class="${trade.result === 'Win' ? 'win-row' : 'loss-row'}"> | |
| <td class="font-mono">${trade.tradeNumber}</td> | |
| <td> | |
| <span class="px-2 py-1 rounded text-xs font-bold ${trade.result === 'Win' ? 'bg-emerald-500/20 text-emerald-400' : 'bg-red-500/20 text-red-400'}"> | |
| ${trade.result} | |
| </span> | |
| </td> | |
| <td class="text-sm">${new Date(trade.timestamp).toLocaleString()}</td> | |
| <td class="font-bold">${formatCurrency(trade.profit)}</td> | |
| <td> | |
| <button onclick="deleteTrade('${trade.id}')" class="text-gray-500 hover:text-red-400 transition-colors"> | |
| <i data-lucide="trash-2" class="w-4 h-4"></i> | |
| </button> | |
| </td> | |
| </tr> | |
| `).join(''); | |
| lucide.createIcons(); | |
| } | |
| function updateHistoryTable() { | |
| const tbody = document.getElementById('historyTableBody'); | |
| if (appState.dailyHistory.length === 0) { | |
| tbody.innerHTML = ` | |
| <tr> | |
| <td colspan="3" class="text-center py-8 text-gray-500 italic"> | |
| No session history. Click "End Session" to save daily PNL. | |
| </td> | |
| </tr>`; | |
| return; | |
| } | |
| let cumulative = appState.startingCapital; | |
| tbody.innerHTML = appState.dailyHistory.slice().reverse().map((entry, index, arr) => { | |
| // Calculate cumulative backwards for display | |
| const displayCumulative = arr.slice(index).reduce((sum, e) => sum - e.pnl, appState.startingCapital + appState.dailyHistory.reduce((s, e) => s + e.pnl, 0)); | |
| return ` | |
| <tr> | |
| <td>${new Date(entry.date).toLocaleDateString()}</td> | |
| <td class="font-bold ${entry.pnl >= 0 ? 'text-emerald-400' : 'text-red-400'}"> | |
| ${formatCurrency(entry.pnl)} | |
| </td> | |
| <td class="text-gray-400"> | |
| ${formatCurrency(appState.startingCapital + appState.dailyHistory.slice(0, appState.dailyHistory.indexOf(entry) + 1).reduce((s, e) => s + e.pnl, 0))} | |
| </td> | |
| </tr> | |
| `}).join(''); | |
| } | |
| // Core Functions matching Google Sheets logic | |
| function addWin() { | |
| const pnl = parseFloat(document.getElementById('currentTradePnl').value) || 0; | |
| const trade = { | |
| id: Date.now().toString(), | |
| tradeNumber: appState.currentSession.nextTradeNumber++, | |
| result: 'Win', | |
| timestamp: new Date().toISOString(), | |
| profit: pnl | |
| }; | |
| appState.currentSession.trades.push(trade); | |
| saveState(); | |
| updateUI(); | |
| // Clear input after adding | |
| document.getElementById('currentTradePnl').value = ''; | |
| document.getElementById('currentTradePnl').focus(); | |
| } | |
| function addLoss() { | |
| const pnl = parseFloat(document.getElementById('currentTradePnl').value) || 0; | |
| const trade = { | |
| id: Date.now().toString(), | |
| tradeNumber: appState.currentSession.nextTradeNumber++, | |
| result: 'Loss', | |
| timestamp: new Date().toISOString(), | |
| profit: pnl // Usually negative for losses, but we record what user enters | |
| }; | |
| appState.currentSession.trades.push(trade); | |
| saveState(); | |
| updateUI(); | |
| // Clear input after adding | |
| document.getElementById('currentTradePnl').value = ''; | |
| document.getElementById('currentTradePnl').focus(); | |
| } | |
| function startSession() { | |
| // 1. Get last PNL from daily history (equivalent to reading column L) | |
| let lastPnl = 0; | |
| if (appState.dailyHistory.length > 0) { | |
| lastPnl = appState.dailyHistory[appState.dailyHistory.length - 1].pnl; | |
| } | |
| // 2. Add to starting capital (C6) | |
| appState.startingCapital += lastPnl; | |
| document.getElementById('startingCapital').value = appState.startingCapital; | |
| // 3. Clear trade log (DATA sheet) | |
| appState.currentSession.trades = []; | |
| appState.currentSession.nextTradeNumber = 1; | |
| // 4. Reset counters (C8, C9, C10) | |
| // Already handled by clearing trades array | |
| saveState(); | |
| updateUI(); | |
| alert(`Session started!\nPrevious PNL (${formatCurrency(lastPnl)}) added to Starting Capital.\nNew Capital: ${formatCurrency(appState.startingCapital)}`); | |
| } | |
| function endSession() { | |
| // Calculate current session PNL (equivalent to C14) | |
| const sessionPnl = appState.currentSession.trades.reduce((sum, t) => sum + parseFloat(t.profit), 0); | |
| // Save to daily history (equivalent to saving in L11:L24) | |
| appState.dailyHistory.push({ | |
| date: new Date().toISOString(), | |
| pnl: sessionPnl, | |
| id: Date.now().toString() | |
| }); | |
| // Keep only last 14 entries (L11:L24 = 14 rows) | |
| if (appState.dailyHistory.length > 14) { | |
| appState.dailyHistory = appState.dailyHistory.slice(-14); | |
| } | |
| saveState(); | |
| updateUI(); | |
| alert(`Session ended!\nDaily PNL ${formatCurrency(sessionPnl)} saved to history.`); | |
| } | |
| function deleteTrade(id) { | |
| if (!confirm('Delete this trade record?')) return; | |
| appState.currentSession.trades = appState.currentSession.trades.filter(t => t.id !== id); | |
| // Renumber trades | |
| appState.currentSession.trades.forEach((t, i) => { | |
| t.tradeNumber = i + 1; | |
| }); | |
| appState.currentSession.nextTradeNumber = appState.currentSession.trades.length + 1; | |
| saveState(); | |
| updateUI(); | |
| } | |
| function updateState() { | |
| appState.startingCapital = parseFloat(document.getElementById('startingCapital').value) || 0; | |
| saveState(); | |
| } | |
| function formatCurrency(amount) { | |
| return new Intl.NumberFormat('en-US', { | |
| style: 'currency', | |
| currency: 'USD', | |
| minimumFractionDigits: 2, | |
| maximumFractionDigits: 2 | |
| }).format(amount); | |
| } | |
| function resetAll() { | |
| if (!confirm('WARNING: This will delete ALL data including trade history and daily PNL records. This cannot be undone. Continue?')) return; | |
| appState = { | |
| startingCapital: 10000, | |
| dailyHistory: [], | |
| currentSession: { | |
| trades: [], | |
| nextTradeNumber: 1 | |
| }, | |
| isDarkMode: appState.isDarkMode | |
| }; | |
| localStorage.removeItem('tradingSessionData'); | |
| document.getElementById('startingCapital').value = 10000; | |
| document.getElementById('currentTradePnl').value = ''; | |
| updateUI(); | |
| } | |
| function exportData() { | |
| const dataStr = JSON.stringify(appState, null, 2); | |
| const dataBlob = new Blob([dataStr], { type: 'application/json' }); | |
| const url = URL.createObjectURL(dataBlob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = `trading-session-data-${new Date().toISOString().split('T')[0]}.json`; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| function importData(input) { | |
| const file = input.files[0]; | |
| if (!file) return; | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| try { | |
| const imported = JSON.parse(e.target.result); | |
| if (confirm(`Import ${imported.currentSession?.trades?.length || 0} trades and ${imported.dailyHistory?.length || 0} daily records?`)) { | |
| appState = imported; | |
| document.getElementById('startingCapital').value = appState.startingCapital || 10000; | |
| saveState(); | |
| updateUI(); | |
| alert('Data imported successfully!'); | |
| } | |
| } catch (err) { | |
| alert('Error importing file. Please ensure it is a valid JSON export from this tool.'); | |
| } | |
| }; | |
| reader.readAsText(file); | |
| input.value = ''; | |
| } | |
| function toggleTheme() { | |
| appState.isDarkMode = !appState.isDarkMode; | |
| document.body.classList.toggle('light-mode'); | |
| const icon = document.getElementById('themeIcon'); | |
| if (appState.isDarkMode) { | |
| icon.setAttribute('data-lucide', 'sun'); | |
| } else { | |
| icon.setAttribute('data-lucide', 'moon'); | |
| } | |
| lucide.createIcons(); | |
| saveState(); | |
| } | |
| // Initialize | |
| loadState(); | |
| // Check for light mode preference | |
| if (!appState.isDarkMode) { | |
| document.body.classList.add('light-mode'); | |
| document.getElementById('themeIcon').setAttribute('data-lucide', 'moon'); | |
| lucide.createIcons(); | |
| } | |
| </script> | |
| <script src="https://deepsite.hf.co/deepsite-badge.js"></script> | |
| </body> | |
| </html> |