Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>Trading Record Pro</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://kit.fontawesome.com/a2f8a9e4e3.js" crossorigin="anonymous"></script> | |
| <link href="https://cdn.jsdelivr.net/npm/flowbite@2.3.0/dist/flowbite.min.css" rel="stylesheet" /> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <style> | |
| .bg-gradient-primary { | |
| background: linear-gradient(135deg, #1e3a8a, #3b82f6); | |
| } | |
| .card-hover { | |
| transition: transform 0.3s, box-shadow 0.3s; | |
| } | |
| .card-hover:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); | |
| } | |
| .active-tab { | |
| @apply border-b-2 border-blue-500 text-blue-600 font-semibold; | |
| } | |
| .tab { | |
| @apply cursor-pointer py-3 px-4 text-gray-500 hover:text-blue-600 transition; | |
| } | |
| .news-card { | |
| background: linear-gradient(to right, #f8fafc, #e2e8f0); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen font-sans"> | |
| <!-- Navbar --> | |
| <nav class="bg-white shadow-lg fixed w-full top-0 z-50"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
| <div class="flex justify-between items-center h-16"> | |
| <div class="flex items-center"> | |
| <i class="fas fa-chart-line text-blue-600 text-2xl mr-2"></i> | |
| <span class="font-bold text-xl text-gray-800">Trading Record Pro</span> | |
| </div> | |
| <div class="flex space-x-4"> | |
| <button id="dashboard-tab" class="tab active-tab">Dashboard</button> | |
| <button id="record-tab" class="tab">Catat Saham</button> | |
| <button id="history-tab" class="tab">Riwayat</button> | |
| </div> | |
| </div> | |
| </div> | |
| </nav> | |
| <div class="pt-20 pb-10 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto"> | |
| <!-- Dashboard Tab --> | |
| <div id="dashboard-content" class="tab-content"> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> | |
| <div class="bg-white p-6 rounded-xl shadow-md card-hover text-center"> | |
| <i class="fas fa-coins text-4xl text-green-500 mb-3"></i> | |
| <h3 class="text-gray-500 text-sm font-medium">Total Investasi</h3> | |
| <p id="total-investment" class="text-2xl font-bold text-gray-800">Rp 0</p> | |
| </div> | |
| <div class="bg-white p-6 rounded-xl shadow-md card-hover text-center"> | |
| <i class="fas fa-chart-line text-4xl text-blue-500 mb-3"></i> | |
| <h3 class="text-gray-500 text-sm font-medium">Gain Rata-rata</h3> | |
| <p id="avg-gain" class="text-2xl font-bold text-gray-800">0%</p> | |
| </div> | |
| <div class="bg-white p-6 rounded-xl shadow-md card-hover text-center"> | |
| <i class="fas fa-list-ul text-4xl text-purple-500 mb-3"></i> | |
| <h3 class="text-gray-500 text-sm font-medium">Total Rekor</h3> | |
| <p id="total-records" class="text-2xl font-bold text-gray-800">0</p> | |
| </div> | |
| <div class="bg-white p-6 rounded-xl shadow-md card-hover text-center"> | |
| <i class="fas fa-bullseye text-4xl text-yellow-500 mb-3"></i> | |
| <h3 class="text-gray-500 text-sm font-medium">Win Rate</h3> | |
| <p id="win-rate" class="text-2xl font-bold text-gray-800">0%</p> | |
| </div> | |
| </div> | |
| <!-- Chart & Recent Activity --> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8"> | |
| <div class="bg-white p-6 rounded-xl shadow"> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Perkembangan Portofolio</h3> | |
| <canvas id="portfolioChart" height="250"></canvas> | |
| </div> | |
| <div class="bg-white p-6 rounded-xl shadow"> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Rekomendasi Berita Saat Ini</h3> | |
| <div id="news-container" class="space-y-4 h-72 overflow-y-auto"> | |
| <!-- News items will be injected here --> | |
| <div class="news-card p-4 rounded-lg shadow-sm flex"> | |
| <img src="https://via.placeholder.com/60" alt="News" class="rounded mr-3 object-cover"> | |
| <div> | |
| <h4 class="font-medium text-gray-800">IHSG Diramal Menguat, Ini Rekomendasi Saham Hari Ini</h4> | |
| <p class="text-sm text-gray-600">Bisnis.com - 2 jam lalu</p> | |
| <a href="#" class="text-blue-600 text-xs mt-1 block">Baca selengkapnya</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Record Tab --> | |
| <div id="record-content" class="tab-content hidden"> | |
| <div class="bg-white p-6 rounded-xl shadow mb-8"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6">Catat Transaksi Saham Baru</h2> | |
| <form id="trade-form" class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Nama Saham</label> | |
| <input type="text" id="stock-name" required placeholder="Contoh: BBCA" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"/> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Harga Perolehan (Rp)</label> | |
| <input type="number" id="entry-price" required class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"/> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Konfirmasi Entry</label> | |
| <select id="entry-confirm" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> | |
| <option value="Terkonfirmasi">Terkonfirmasi</option> | |
| <option value="Pending">Pending</option> | |
| <option value="Batal">Batal</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Gain Saat Ini (%)</label> | |
| <input type="number" id="gain" placeholder="Contoh: 12.5" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"/> | |
| </div> | |
| <div class="md:col-span-2"> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Analisis Fundamental</label> | |
| <textarea id="fundamental" rows="3" placeholder="Kualitas manajemen, margin laba, rasio P/E, dsb." class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea> | |
| </div> | |
| <div class="md:col-span-2"> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Sentimen Pasar</label> | |
| <textarea id="sentiment" rows="3" placeholder="Berita makroekonomi, sentimen investor, analisis media sosial, dsb." class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea> | |
| </div> | |
| <div class="md:col-span-2"> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Analisis Teknikal</label> | |
| <textarea id="technical" rows="3" placeholder="Polusi chart, RSI, MACD, volume, level support/resistance, dsb." class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea> | |
| </div> | |
| <div class="md:col-span-2 flex justify-end"> | |
| <button type="submit" class="bg-gradient-primary text-white px-6 py-3 rounded-lg shadow hover:shadow-lg transform hover:scale-105 transition">Simpan Rekaman</button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- History Tab --> | |
| <div id="history-content" class="tab-content hidden"> | |
| <div class="bg-white p-6 rounded-xl shadow mb-6 flex justify-between items-center"> | |
| <h2 class="text-2xl font-bold text-gray-800">Riwayat Trading</h2> | |
| <button id="export-btn" class="bg-green-600 text-white px-4 py-2 rounded text-sm hover:bg-green-700 flex items-center"> | |
| <i class="fas fa-file-export mr-2"></i> Ekspor ke CSV | |
| </button> | |
| </div> | |
| <div class="bg-white rounded-xl shadow overflow-hidden"> | |
| <div class="overflow-x-auto"> | |
| <table class="w-full"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Saham</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Entry</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fundamental</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Sentimen</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Teknikal</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Harga</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Gain</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Aksi</th> | |
| </tr> | |
| </thead> | |
| <tbody id="history-body" class="bg-white divide-y divide-gray-200"> | |
| <!-- Dynamic rows will be inserted here --> | |
| <tr> | |
| <td colspan="8" class="text-center py-8 text-gray-500">Belum ada data</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div id="toast" class="hidden fixed bottom-5 right-5 bg-gray-800 text-white px-6 py-3 rounded-lg shadow-lg flex items-center z-50"> | |
| <i class="fas fa-check-circle mr-2"></i> | |
| <span id="toast-message">Rekaman berhasil disimpan!</span> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/flowbite@2.3.0/dist/flowbite.min.js"></script> | |
| <script> | |
| // Tab Navigation | |
| const tabs = document.querySelectorAll('.tab'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| tabs.forEach(tab => { | |
| tab.addEventListener('click', () => { | |
| tabs.forEach(t => t.classList.remove('active-tab')); | |
| tabContents.forEach(c => c.classList.add('hidden')); | |
| tab.classList.add('active-tab'); | |
| document.getElementById(`${tab.id.replace('-tab', '-content')}`).classList.remove('hidden'); | |
| }); | |
| }); | |
| // Local Storage | |
| const saveToStorage = (key, data) => localStorage.setItem(key, JSON.stringify(data)); | |
| const getFromStorage = (key) => JSON.parse(localStorage.getItem(key)) || []; | |
| let trades = getFromStorage('tradingRecords'); | |
| // Initialize Chart | |
| const ctx = document.getElementById('portfolioChart').getContext('2d'); | |
| let portfolioChart = new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels: [], | |
| datasets: [{ | |
| label: 'Nilai Portofolio (Rp)', | |
| data: [], | |
| borderColor: '#3b82f6', | |
| backgroundColor: 'rgba(59, 130, 246, 0.1)', | |
| fill: true, | |
| tension: 0.4 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { display: false } | |
| } | |
| } | |
| }); | |
| // Update Dashboard Stats | |
| const updateDashboard = () => { | |
| const totalInvestment = trades.reduce((sum, t) => sum + parseFloat(t.entryPrice || 0), 0); | |
| const avgGain = trades.length > 0 ? (trades.reduce((sum, t) => sum + parseFloat(t.gain || 0), 0) / trades.length).toFixed(2) : 0; | |
| const totalRecords = trades.length; | |
| const profitable = trades.filter(t => parseFloat(t.gain) > 0).length; | |
| const winRate = totalRecords > 0 ? ((profitable / totalRecords) * 100).toFixed(1) : 0; | |
| document.getElementById('total-investment').textContent = `Rp ${totalInvestment.toLocaleString()}`; | |
| document.getElementById('avg-gain').textContent = `${avgGain}%`; | |
| document.getElementById('total-records').textContent = totalRecords; | |
| document.getElementById('win-rate').textContent = `${winRate}%`; | |
| // Update Chart | |
| portfolioChart.data.labels = trades.map(t => t.stockName); | |
| portfolioChart.data.datasets[0].data = trades.map(t => parseFloat(t.entryPrice) * (1 + parseFloat(t.gain) / 100)); | |
| portfolioChart.update(); | |
| }; | |
| // Show Toast | |
| const showToast = (message) => { | |
| const toast = document.getElementById('toast'); | |
| document.getElementById('toast-message').textContent = message; | |
| toast.classList.remove('hidden'); | |
| setTimeout(() => toast.classList.add('hidden'), 3000); | |
| }; | |
| // Submit Form | |
| document.getElementById('trade-form').addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const newTrade = { | |
| id: Date.now(), | |
| stockName: document.getElementById('stock-name').value, | |
| entryConfirm: document.getElementById('entry-confirm').value, | |
| fundamental: document.getElementById('fundamental').value, | |
| sentiment: document.getElementById('sentiment').value, | |
| technical: document.getElementById('technical').value, | |
| entryPrice: document.getElementById('entry-price').value, | |
| gain: document.getElementById('gain').value || 0, | |
| date: new Date().toLocaleDateString('id-ID') | |
| }; | |
| trades.push(newTrade); | |
| saveToStorage('tradingRecords', trades); | |
| this.reset(); | |
| showToast('Rekaman berhasil disimpan!'); | |
| // Refresh UI | |
| renderHistory(); | |
| updateDashboard(); | |
| // Switch to history tab | |
| document.getElementById('history-tab').click(); | |
| }); | |
| // Render History Table | |
| const renderHistory = () => { | |
| const tbody = document.getElementById('history-body'); | |
| if (trades.length === 0) { | |
| tbody.innerHTML = `<tr><td colspan="8" class="text-center py-8 text-gray-500">Belum ada data</td></tr>`; | |
| return; | |
| } | |
| tbody.innerHTML = ''; | |
| trades.slice().reverse().forEach(t => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td class="px-6 py-4 whitespace-nowrap font-medium text-gray-900">${t.stockName}</td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${t.entryConfirm}</td> | |
| <td class="px-6 py-4 text-sm text-gray-500 max-w-xs truncate" title="${t.fundamental}">${t.fundamental || '-'}</td> | |
| <td class="px-6 py-4 text-sm text-gray-500 max-w-xs truncate" title="${t.sentiment}">${t.sentiment || '-'}</td> | |
| <td class="px-6 py-4 text-sm text-gray-500 max-w-xs truncate" title="${t.technical}">${t.technical || '-'}</td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Rp ${parseFloat(t.entryPrice).toLocaleString()}</td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${parseFloat(t.gain) >= 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}"> | |
| ${t.gain}% | |
| </span> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> | |
| <button onclick="deleteTrade(${t.id})" class="text-red-600 hover:text-red-900 mr-3"><i class="fas fa-trash"></i></button> | |
| </td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| }; | |
| // Delete Trade | |
| window.deleteTrade = (id) => { | |
| if (confirm('Hapus rekaman ini?')) { | |
| trades = trades.filter(t => t.id !== id); | |
| saveToStorage('tradingRecords', trades); | |
| renderHistory(); | |
| updateDashboard(); | |
| showToast('Rekaman dihapus'); | |
| } | |
| }; | |
| // Export to CSV | |
| document.getElementById('export-btn').addEventListener('click', () => { | |
| const headers = ['Nama Saham', 'Konfirmasi Entry', 'Fundamental', 'Sentimen', 'Teknikal', 'Harga Perolehan', 'Gain (%)', 'Tanggal']; | |
| const csvContent = [ | |
| headers.join(','), | |
| ...trades.map(t => [ | |
| t.stockName, | |
| t.entryConfirm, | |
| `"${t.fundamental.replace(/"/g, '""')}"`, | |
| `"${t.sentiment.replace(/"/g, '""')}"`, | |
| `"${t.technical.replace(/"/g, '""')}"`, | |
| t.entryPrice, | |
| t.gain, | |
| t.date | |
| ].join(',')) | |
| ].join('\n'); | |
| const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement('a'); | |
| link.setAttribute('href', url); | |
| link.setAttribute('download', `trading-record-${new Date().toISOString().slice(0,10)}.csv`); | |
| link.style.visibility = 'hidden'; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| showToast('Data diekspor ke CSV'); | |
| }); | |
| // Simulasi Rekomendasi Berita dari API (mock) | |
| const fetchNewsRecommendations = () => { | |
| const newsContainer = document.getElementById('news-container'); | |
| newsContainer.innerHTML = ''; | |
| const mockNews = [ | |
| { title: 'Saham Teknologi Diprediksi Melonjak Setelah Laporan Kuartal', source: 'CNBC Indonesia', time: '1 jam lalu', link: '#' }, | |
| { title: 'Analisis: Potensi Saham Batubara di Tengah Lonjakan Harga Komoditas Global', source: 'Investing.com', time: '3 jam lalu', link: '#' }, | |
| { title: 'Rekomendasi Minggu Ini: 5 Saham dengan Dividen Tinggi untuk Portofolio Stabil', source: 'Bisnis.com', time: '5 jam lalu', link: '#' }, | |
| ]; | |
| mockNews.forEach(news => { | |
| const newsCard = document.createElement('div'); | |
| newsCard.className = 'news-card p-4 rounded-lg shadow-sm flex'; | |
| newsCard.innerHTML = ` | |
| <img src="https://via.placeholder.com/60/3b82f6/FFFFFF?text=${news.title.charAt(0)}" alt="News" class="rounded mr-3 object-cover"> | |
| <div> | |
| <h4 class="font-medium text-gray-800">${news.title}</h4> | |
| <p class="text-sm text-gray-600">${news.source} - ${news.time}</p> | |
| <a href="${news.link}" class="text-blue-600 text-xs mt-1 block hover:underline">Baca selengkapnya</a> | |
| </div> | |
| `; | |
| newsContainer.appendChild(newsCard); | |
| }); | |
| }; | |
| // Initial Load | |
| renderHistory(); | |
| updateDashboard(); | |
| fetchNewsRecommendations(); | |
| // Recommended Stocks based on current trades (simple logic) | |
| const recommendStocks = () => { | |
| // In real app: use AI or sentiment analysis | |
| const sectorCount = {}; | |
| trades.forEach(t => { | |
| const sector = t.stockName.substring(0,2); // mock: first 2 letters as sector | |
| sectorCount[sector] = (sectorCount[sector] || 0) + 1; | |
| }); | |
| const topSector = Object.keys(sectorCount).reduce((a, b) => sectorCount[a] > sectorCount[b] ? a : b, ""); | |
| localStorage.setItem('recommendedSector', topSector); | |
| }; | |
| recommendStocks(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-qwensite.hf.space/logo.svg" alt="qwensite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-qwensite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >QwenSite</a> - 🧬 <a href="https://enzostvs-qwensite.hf.space?remix=alterzick/trading-record" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |