Ferramenta - Diário de Bordo

📘 Diário de Bordo — modelo copiável

📘 Diário de Bordo modelo copiável blog · ferramenta local

📌 O que é: Um diário de trading que guarda as tuas operações no teu próprio computador. Regista símbolo, direção, preços, quantidade e observações. Calcula automaticamente o lucro/perda. Permite exportar e importar dados em JSON.

🔧 Como usar: Copia o código HTML abaixo, cola numa página do teu blog ou guarda como ficheiro .html e abre no browser. Todos os dados ficam guardados localmente no teu navegador.

📋 Código HTML completo copia e cola

<!DOCTYPE html> <html lang="pt"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>📘 Diário de Bordo do Caçador</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { background: #0b0e14; color: #f0f4fa; font-family: 'Segoe UI', Roboto, system-ui, sans-serif; padding: 20px; display: flex; justify-content: center; min-height: 100vh; } .container { max-width: 1100px; width: 100%; background: #141a24; border-radius: 28px; padding: 24px 28px; box-shadow: 0 12px 40px rgba(0,0,0,0.7); border: 1px solid #2a3344; } h1 { font-size: 24px; font-weight: 500; display: flex; align-items: center; gap: 12px; color: #ffffff; border-bottom: 1px solid #29313f; padding-bottom: 14px; margin-bottom: 16px; flex-wrap: wrap; } h1 span { background: #1f2a38; padding: 4px 14px; border-radius: 60px; font-size: 13px; font-weight: 400; color: #c8d0dc; } .meta { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-bottom: 16px; } .btn { background: #1f2b3c; border: none; color: #e0e6f0; padding: 6px 16px; border-radius: 30px; font-size: 13px; font-weight: 500; cursor: pointer; border: 1px solid #31405a; transition: 0.2s; } .btn:hover { background: #2b3a50; color: #ffffff; } .btn-primary { background: #2a3a52; border-color: #4d6485; } .btn-primary:hover { background: #3a506e; } .btn-danger { background: #5a2a34; border-color: #7a3a44; } .btn-danger:hover { background: #7a3a44; } .btn-success { background: #1a3a2e; border-color: #3a6a4e; } .btn-success:hover { background: #2a5a3e; } .form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; background: #0f1621; padding: 16px 18px; border-radius: 14px; border: 1px solid #1f2a3a; margin-bottom: 18px; } .form-group { display: flex; flex-direction: column; gap: 4px; } .form-group label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #6a7f9c; } .form-group input, .form-group select, .form-group textarea { background: #1a2332; border: 1px solid #2a3a52; color: #f0f4fa; padding: 6px 10px; border-radius: 8px; font-size: 13px; outline: none; transition: 0.2s; font-family: inherit; } .form-group input:focus, .form-group select:focus, .form-group textarea:focus { border-color: #7a9acd; } .form-group textarea { resize: vertical; min-height: 40px; } .form-group .resultado-calc { font-size: 16px; font-weight: 600; padding: 6px 10px; background: #0b111b; border-radius: 8px; border: 1px solid #1f2a3a; color: #d0dae8; min-height: 36px; display: flex; align-items: center; } .btn-registar { display: flex; align-items: flex-end; justify-content: flex-end; } .btn-registar button { height: 38px; align-self: flex-end; } .table-wrap { overflow-x: auto; border-radius: 14px; border: 1px solid #1f2a3a; background: #0f1621; padding: 4px; margin-top: 12px; } table { width: 100%; border-collapse: collapse; font-size: 14px; min-width: 750px; } th { background: #1a2332; color: #b0baca; font-weight: 500; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; padding: 8px 10px; border-bottom: 2px solid #2a3a52; text-align: left; position: sticky; top: 0; z-index: 2; } th:not(:first-child) { text-align: right; } td { padding: 8px 10px; border-bottom: 1px solid #0f1621; text-align: right; vertical-align: middle; font-size: 13px; color: #f0f4fa; } td:first-child { text-align: left; font-weight: 500; } td .obs { color: #7f92ac; font-size: 12px; max-width: 150px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .lucro { color: #5cdb9e; } .perda { color: #e47a8a; } .neutro { color: #7f92ac; } .acoes { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; } .acoes .btn { font-size: 12px; padding: 4px 14px; } .status { font-size: 12px; color: #7f92ac; padding: 6px 0; } .sem-registos { text-align: center; padding: 30px 0; color: #5a6f8c; } .footer { margin-top: 18px; font-size: 12px; color: #8a94a6; text-align: center; border-top: 1px solid #1b2433; padding-top: 14px; } .badge { background: #1a2f3a; color: #7fc9e6; padding: 2px 10px; border-radius: 30px; font-size: 11px; font-weight: 500; } @media (max-width: 700px) { .container { padding: 18px 14px; } .form-grid { grid-template-columns: 1fr 1fr; } table { font-size: 12px; min-width: 600px; } th, td { padding: 6px 6px; } } </style> </head> <body> <div class="container"> <h1> 📘 Diário de Bordo <span>Caçador</span> <span style="margin-left:auto; font-size:12px; font-weight:400; color:#6a7f9c;">local · apenas neste browser</span> </h1> <div class="meta"> <div> <span class="badge">📝 regista as tuas trades</span> <span class="badge" style="background:#1a2f3a; color:#f1c40f;">💾 guarda localmente</span> </div> <div style="display:flex; gap:6px; flex-wrap:wrap;"> <span id="contagem" style="font-size:13px; color:#6a7f9c;">0 registos</span> </div> </div> <!-- FORMULÁRIO DE REGISTO --> <div class="form-grid" id="formRegisto"> <div class="form-group"> <label for="symbol">Símbolo</label> <input type="text" id="symbol" placeholder="BTCUSDT" value="BTCUSDT"> </div> <div class="form-group"> <label for="direcao">Direção</label> <select id="direcao"> <option value="long">Long (compra)</option> <option value="short">Short (venda)</option> </select> </div> <div class="form-group"> <label for="quantidade">Quantidade (contractos / moedas)</label> <input type="number" id="quantidade" placeholder="1.0" step="0.01" value="1.0"> </div> <div class="form-group"> <label for="precoEntrada">Preço de entrada</label> <input type="number" id="precoEntrada" placeholder="0.00" step="any" value=""> </div> <div class="form-group"> <label for="precoSaida">Preço de saída</label> <input type="number" id="precoSaida" placeholder="0.00" step="any" value=""> </div> <div class="form-group"> <label for="stopLoss">Stop-loss (opcional)</label> <input type="number" id="stopLoss" placeholder="0.00" step="any" value=""> </div> <div class="form-group"> <label for="takeProfit">Take-profit (opcional)</label> <input type="number" id="takeProfit" placeholder="0.00" step="any" value=""> </div> <div class="form-group"> <label for="observacoes">Observações</label> <textarea id="observacoes" rows="1" placeholder="O que sentiste?"></textarea> </div> <div class="form-group" style="grid-column: span 2;"> <label>Resultado estimado</label> <div class="resultado-calc" id="resultadoCalc">—</div> </div> <div class="form-group btn-registar" style="grid-column: span 2; display: flex; gap: 8px; flex-wrap: wrap;"> <button class="btn btn-primary" id="btnRegistar">➕ Registar</button> <button class="btn" id="btnLimparCampos">✕ Limpar campos</button> </div> </div> <!-- BOTÕES DE GESTÃO --> <div class="acoes"> <button class="btn btn-success" id="btnExportar">⬇️ Exportar JSON</button> <button class="btn btn-primary" id="btnImportar">⬆️ Importar JSON</button> <input type="file" id="fileInput" accept=".json" style="display:none;"> <button class="btn btn-danger" id="btnLimparTudo">🗑️ Limpar todos os registos</button> </div> <!-- LISTA DE REGISTOS --> <div class="table-wrap"> <table> <thead> <tr> <th style="min-width:120px;">Data</th> <th>Símbolo</th> <th>Dir.</th> <th>Qtd</th> <th>Entrada</th> <th>Saída</th> <th>Resultado</th> <th style="min-width:120px;">Obs.</th> <th style="width:40px;">🗑️</th> </tr> </thead> <tbody id="corpoTabela"> <tr><td colspan="9" class="sem-registos">Nenhum registo ainda. Regista a tua primeira trade!</td></tr> </tbody> </table> </div> <div class="status" id="statusMsg">💾 Os dados são guardados automaticamente no teu navegador.</div> <div class="footer"> ⚡ Diário de Bordo · PannteraGruel · “Importante não é saber, importante é sentir” </div> </div> <script> // ============================================================ // CONFIG // ============================================================ const STORAGE_KEY = 'diario_bordo_cacador'; let registos = []; // ============================================================ // ELEMENTOS DOM // ============================================================ const symbolInput = document.getElementById('symbol'); const direcaoSelect = document.getElementById('direcao'); const quantidadeInput = document.getElementById('quantidade'); const precoEntradaInput = document.getElementById('precoEntrada'); const precoSaidaInput = document.getElementById('precoSaida'); const stopLossInput = document.getElementById('stopLoss'); const takeProfitInput = document.getElementById('takeProfit'); const observacoesInput = document.getElementById('observacoes'); const resultadoCalc = document.getElementById('resultadoCalc'); const corpoTabela = document.getElementById('corpoTabela'); const contagemEl = document.getElementById('contagem'); const statusMsg = document.getElementById('statusMsg'); // ============================================================ // CARREGAR E GUARDAR // ============================================================ function carregarRegistos() { try { const dados = localStorage.getItem(STORAGE_KEY); if (dados) { registos = JSON.parse(dados); if (!Array.isArray(registos)) registos = []; } else { registos = []; } } catch { registos = []; } registos.sort((a, b) => new Date(b.data) - new Date(a.data)); atualizarContagem(); renderizarTabela(); } function guardarRegistos() { localStorage.setItem(STORAGE_KEY, JSON.stringify(registos)); atualizarContagem(); renderizarTabela(); mostrarStatus('💾 Dados guardados localmente.', '#6a7f9c'); } // ============================================================ // CALCULAR RESULTADO // ============================================================ function calcularResultado() { const direcao = direcaoSelect.value; const entrada = parseFloat(precoEntradaInput.value); const saida = parseFloat(precoSaidaInput.value); const qtd = parseFloat(quantidadeInput.value) || 0; if (isNaN(entrada) || isNaN(saida) || entrada === 0 || qtd === 0) { resultadoCalc.textContent = '—'; resultadoCalc.style.color = '#7f92ac'; return null; } let pct = 0; let usd = 0; if (direcao === 'long') { pct = ((saida - entrada) / entrada) * 100; } else { pct = ((entrada - saida) / entrada) * 100; } usd = (pct / 100) * (entrada * qtd); const cor = pct > 0.01 ? '#5cdb9e' : pct < -0.01 ? '#e47a8a' : '#7f92ac'; resultadoCalc.textContent = `${pct > 0 ? '+' : ''}${pct.toFixed(2)}% ($${usd.toFixed(2)})`; resultadoCalc.style.color = cor; return { pct, usd }; } // ============================================================ // ADICIONAR REGISTO // ============================================================ function adicionarRegisto() { const symbol = symbolInput.value.trim().toUpperCase(); if (!symbol) { mostrarStatus('⚠️ Insere um símbolo.', '#e47a8a'); return; } const direcao = direcaoSelect.value; const quantidade = parseFloat(quantidadeInput.value) || 0; const precoEntrada = parseFloat(precoEntradaInput.value); const precoSaida = parseFloat(precoSaidaInput.value); if (isNaN(precoEntrada) || isNaN(precoSaida) || precoEntrada === 0 || quantidade === 0) { mostrarStatus('⚠️ Preenche entrada, saída e quantidade corretamente.', '#e47a8a'); return; } const resultado = calcularResultado(); const registo = { id: Date.now(), data: new Date().toISOString(), symbol, direcao, quantidade, precoEntrada, precoSaida, stopLoss: parseFloat(stopLossInput.value) || 0, takeProfit: parseFloat(takeProfitInput.value) || 0, observacoes: observacoesInput.value.trim() || '', pct: resultado ? resultado.pct : 0, usd: resultado ? resultado.usd : 0, }; registos.unshift(registo); guardarRegistos(); limparCampos(); mostrarStatus(`✅ Registo de ${symbol} adicionado!`, '#5cdb9e'); } // ============================================================ // REMOVER REGISTO // ============================================================ function removerRegisto(id) { if (!confirm('Remover este registo?')) return; registos = registos.filter(r => r.id !== id); guardarRegistos(); mostrarStatus('🗑️ Registo removido.', '#e47a8a'); } // ============================================================ // LIMPAR CAMPOS // ============================================================ function limparCampos() { symbolInput.value = ''; precoEntradaInput.value = ''; precoSaidaInput.value = ''; stopLossInput.value = ''; takeProfitInput.value = ''; observacoesInput.value = ''; quantidadeInput.value = '1.0'; direcaoSelect.value = 'long'; resultadoCalc.textContent = '—'; resultadoCalc.style.color = '#7f92ac'; } // ============================================================ // RENDERIZAR TABELA // ============================================================ function renderizarTabela() { if (registos.length === 0) { corpoTabela.innerHTML = `Nenhum registo ainda. Regista a tua primeira trade!`; return; } let html = ''; registos.forEach(r => { const data = new Date(r.data); const dataStr = data.toLocaleDateString('pt-PT') + ' ' + data.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' }); const dirLabel = r.direcao === 'long' ? '🟢 L' : '🔴 S'; const resultadoCls = r.pct > 0.01 ? 'lucro' : r.pct < -0.01 ? 'perda' : 'neutro'; const resultadoStr = `${r.pct > 0 ? '+' : ''}${r.pct.toFixed(2)}%`; html += ` ${dataStr} ${r.symbol} ${dirLabel} ${r.quantidade} ${r.precoEntrada.toFixed(4)} ${r.precoSaida.toFixed(4)} ${resultadoStr} ${r.observacoes || '—'} `; }); corpoTabela.innerHTML = html; } // ============================================================ // CONTAGEM // ============================================================ function atualizarContagem() { contagemEl.textContent = `${registos.length} registos`; } // ============================================================ // STATUS // ============================================================ function mostrarStatus(msg, cor = '#6a7f9c') { statusMsg.textContent = msg; statusMsg.style.color = cor; setTimeout(() => { statusMsg.style.color = '#6a7f9c'; statusMsg.textContent = '💾 Os dados são guardados automaticamente no teu navegador.'; }, 4000); } // ============================================================ // EXPORTAR / IMPORTAR // ============================================================ function exportarJSON() { if (registos.length === 0) { mostrarStatus('⚠️ Não há registos para exportar.', '#e47a8a'); return; } const dataStr = JSON.stringify(registos, null, 2); const blob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `diario_bordo_${new Date().toISOString().slice(0,10)}.json`; a.click(); URL.revokeObjectURL(url); mostrarStatus('✅ Ficheiro JSON exportado!', '#5cdb9e'); } function importarJSON(file) { const reader = new FileReader(); reader.onload = function(e) { try { const dados = JSON.parse(e.target.result); if (!Array.isArray(dados)) throw new Error('Formato inválido'); if (dados.length === 0) { mostrarStatus('⚠️ O ficheiro está vazio.', '#e47a8a'); return; } if (!confirm(`Importar ${dados.length} registos? Os registos atuais serão mantidos.`)) return; registos = registos.concat(dados); guardarRegistos(); mostrarStatus(`✅ ${dados.length} registos importados!`, '#5cdb9e'); } catch (err) { mostrarStatus('❌ Erro ao importar: ficheiro inválido.', '#e47a8a'); } }; reader.readAsText(file); } // ============================================================ // LIMPAR TUDO // ============================================================ function limparTudo() { if (registos.length === 0) { mostrarStatus('ℹ️ Já não há registos.', '#6a7f9c'); return; } if (!confirm('⚠️ Tem a certeza que quer eliminar TODOS os registos? Esta ação é irreversível.')) return; if (!confirm('Confirma novamente? Não há volta a dar.')) return; registos = []; guardarRegistos(); mostrarStatus('🗑️ Todos os registos foram eliminados.', '#e47a8a'); } // ============================================================ // EVENTOS // ============================================================ document.addEventListener('DOMContentLoaded', function() { carregarRegistos(); [precoEntradaInput, precoSaidaInput, quantidadeInput, direcaoSelect].forEach(el => { el.addEventListener('input', calcularResultado); el.addEventListener('change', calcularResultado); }); document.getElementById('btnRegistar').addEventListener('click', adicionarRegisto); document.getElementById('btnLimparCampos').addEventListener('click', limparCampos); document.getElementById('btnExportar').addEventListener('click', exportarJSON); document.getElementById('btnImportar').addEventListener('click', function() { document.getElementById('fileInput').click(); }); document.getElementById('fileInput').addEventListener('change', function(e) { if (this.files && this.files[0]) { importarJSON(this.files[0]); } this.value = ''; }); document.getElementById('btnLimparTudo').addEventListener('click', limparTudo); document.querySelectorAll('input, textarea, select').forEach(el => { el.addEventListener('keydown', function(e) { if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA') { e.preventDefault(); document.getElementById('btnRegistar').click(); } }); }); window.addEventListener('storage', function(e) { if (e.key === STORAGE_KEY) { carregarRegistos(); } }); }); </script> </body> </html>

Sem comentários:

Enviar um comentário