📘 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