About Tradeflow
Tradeflow is your go-to blog for insightful market analysis and practical trading strategies tailored for every investor.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TradeFlow MVP - Simulador de Trading Profissional</title>
<!-- Tailwind CSS para Estilização Moderna -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Fonte Inter para Legibilidade Excepcional -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Ícones Lucide para Interface Minimalista -->
<script src="https://unpkg.com/lucide@latest"></script>
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #0b0f19;
color: #f1f5f9;
}
/* Scrollbar Personalizada para Visual de Terminal */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #0f172a;
}
::-webkit-scrollbar-thumb {
background: #334155;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #475569;
}
/* Animações de Transição de Cor para Tickers */
.price-up {
animation: flashGreen 0.5s ease-out;
}
.price-down {
animation: flashRed 0.5s ease-out;
}
@keyframes flashGreen {
0% { background-color: rgba(34, 197, 94, 0.2); }
100% { background-color: transparent; }
}
@keyframes flashRed {
0% { background-color: rgba(239, 68, 68, 0.2); }
100% { background-color: transparent; }
}
</style>
</head>
<body class="min-h-screen flex flex-col overflow-x-hidden">
<header class="border-b border-slate-800 bg-slate-900/80 backdrop-blur-md sticky top-0 z-50 px-4 py-3">
<div class="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-4">
<!-- Brand -->
<div class="flex items-center gap-3">
<div class="p-2 bg-gradient-to-tr from-emerald-500 to-teal-500 rounded-lg shadow-lg shadow-emerald-500/20">
<i data-lucide="trending-up" class="w-6 h-6 text-slate-950 stroke-[2.5]"></i>
</div>
<div>
<h1 class="text-xl font-extrabold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-white via-slate-100 to-slate-400">TradeFlow <span class="text-xs bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 px-2 py-0.5 rounded-full font-medium">MVP</span></h1>
<p class="text-[10px] text-slate-400 font-medium">Ambiente Profissional de Operações</p>
</div>
</div>
<!-- Centro: Tipo de Conexão (Simulado vs Real) -->
<div class="flex bg-slate-950 p-1 rounded-xl border border-slate-800">
<button id="btnModeSim" onclick="setConnectionMode('simulated')" class="px-3 py-1.5 text-xs font-semibold rounded-lg transition-all flex items-center gap-2 bg-slate-800 text-emerald-400 shadow-sm">
<i data-lucide="cpu" class="w-3.5 h-3.5"></i> Simulador Local
</button>
<button id="btnModeLive" onclick="setConnectionMode('live')" class="px-3 py-1.5 text-xs font-semibold rounded-lg transition-all flex items-center gap-2 text-slate-400 hover:text-white">
<i data-lucide="radio" class="w-3.5 h-3.5"></i> Binance Live API
</button>
</div>
<!-- Direita: Informações de Saldo da Carteira -->
<div class="flex items-center gap-4">
<div class="text-right">
<div class="text-[10px] text-slate-400 uppercase tracking-wider font-semibold">Saldo Disponível</div>
<div class="text-lg font-bold text-slate-100" id="walletBalance">S$ 10.000,00</div>
</div>
<div class="h-8 w-px bg-slate-800"></div>
<div class="text-right">
<div class="text-[10px] text-slate-400 uppercase tracking-wider font-semibold">P&L Aberto</div>
<div class="text-lg font-bold text-slate-400 transition-colors" id="totalFloatingPL">S$ 0,00</div>
</div>
</div>
</div>
</header>
<main class="flex-1 max-w-7xl w-full mx-auto p-4 grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Coluna Esquerda: Ticker, Gráfico e Sinais (Larga) -->
<div class="lg:col-span-2 flex flex-col gap-6">
<!-- Card de Cotação de Ativos (Ticker) -->
<section class="bg-slate-900 border border-slate-800/80 rounded-2xl p-4 flex flex-wrap items-center justify-between gap-4 shadow-xl transition-all duration-300" id="tickerPanel">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-slate-850 flex items-center justify-center border border-slate-850" id="assetLogo">
<!-- Será inserido via JS -->
<span class="font-bold text-sm text-emerald-400">BTC</span>
</div>
<div>
<div class="flex items-center gap-2">
<span class="font-bold text-base text-slate-100" id="tickerSymbol">BTC/USDT</span>
<span class="text-[10px] px-1.5 py-0.5 rounded bg-slate-800 font-medium text-slate-400" id="tickerCategory">Crypto</span>
</div>
<span class="text-xs text-slate-400" id="tickerName">Bitcoin / Tether US</span>
</div>
</div>
<div class="flex items-center gap-6">
<div class="text-right">
<div class="text-2xl font-black font-mono tracking-tight text-emerald-400 transition-all duration-100" id="tickerPrice">$ 65,420.50</div>
<div class="text-xs font-semibold text-emerald-400 flex items-center justify-end gap-1" id="tickerPercentChange">
<i data-lucide="trending-up" class="w-3.5 h-3.5"></i> +1.24%
</div>
</div>
<div class="hidden sm:block h-10 w-px bg-slate-800"></div>
<div class="hidden sm:block text-right">
<div class="text-[10px] text-slate-500 font-semibold uppercase">Máxima 24h</div>
<div class="text-xs font-mono font-bold text-slate-300" id="tickerHigh">$ 65,980.00</div>
</div>
<div class="hidden sm:block text-right">
<div class="text-[10px] text-slate-500 font-semibold uppercase">Mínima 24h</div>
<div class="text-xs font-mono font-bold text-slate-300" id="tickerLow">$ 64,210.00</div>
</div>
</div>
</section>
<!-- Card de Gráfico Interativo -->
<section class="bg-slate-900 border border-slate-800/80 rounded-2xl flex flex-col shadow-xl overflow-hidden min-h-[450px]">
<!-- Controles do Gráfico -->
<div class="border-b border-slate-800 p-4 bg-slate-900/50 flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<span class="text-xs font-bold text-slate-300 uppercase tracking-wider flex items-center gap-1.5">
<i data-lucide="candlestick-chart" class="w-4 h-4 text-emerald-400"></i> Gráfico de Velas (5s)
</span>
</div>
<!-- Ferramentas -->
<div class="flex items-center gap-2">
<button onclick="changeZoom(1.1)" class="p-1.5 rounded-lg bg-slate-800 text-slate-300 hover:text-white hover:bg-slate-700 transition" title="Aumentar Zoom">
<i data-lucide="zoom-in" class="w-4 h-4"></i>
</button>
<button onclick="changeZoom(0.9)" class="p-1.5 rounded-lg bg-slate-800 text-slate-300 hover:text-white hover:bg-slate-700 transition" title="Diminuir Zoom">
<i data-lucide="zoom-out" class="w-4 h-4"></i>
</button>
<div class="h-6 w-px bg-slate-800"></div>
<button onclick="toggleIndicator('sma')" id="btnSma" class="px-2 py-1.5 text-[10px] font-bold rounded-lg bg-emerald-500/20 text-emerald-400 border border-emerald-500/25 hover:bg-emerald-500/30 transition">
Média Móvel (SMA)
</button>
<button onclick="toggleIndicator('rsi')" id="btnRsi" class="px-2 py-1.5 text-[10px] font-bold rounded-lg bg-slate-800 text-slate-400 hover:bg-slate-750 transition">
RSI Panel
</button>
</div>
</div>
<!-- Container do Canvas -->
<div class="flex-1 relative bg-[#090d16] min-h-[300px]" id="canvasContainer">
<canvas id="tradingChart" class="absolute inset-0 w-full h-full cursor-crosshair"></canvas>
</div>
</section>
<section class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-slate-900 border border-slate-800/80 rounded-2xl p-4 flex flex-col justify-between shadow-lg">
<div class="flex items-center justify-between mb-2">
<span class="text-xs font-bold text-slate-400 uppercase tracking-wider">Índice RSI (14)</span>
<i data-lucide="activity" class="w-4 h-4 text-blue-400"></i>
</div>
<div class="flex items-baseline gap-2 mt-1">
<span class="text-3xl font-black font-mono" id="rsiVal">54.2</span>
<span class="text-xs px-2 py-0.5 rounded-full bg-slate-800 text-slate-300 font-medium" id="rsiState">Neutro</span>
</div>
<div class="w-full bg-slate-800 h-1.5 rounded-full mt-3 overflow-hidden">
<div class="bg-blue-500 h-full transition-all duration-300" id="rsiBar" style="width: 54.2%"></div>
</div>
</div>
<div class="bg-slate-900 border border-slate-800/80 rounded-2xl p-4 flex flex-col justify-between shadow-lg">
<div class="flex items-center justify-between mb-2">
<span class="text-xs font-bold text-slate-400 uppercase tracking-wider">Média Móvel (9)</span>
<i data-lucide="line-chart" class="w-4 h-4 text-emerald-400"></i>
</div>
<div class="flex items-baseline gap-2 mt-1">
<span class="text-lg font-bold font-mono text-slate-300" id="smaVal">$ 65,410.20</span>
</div>
<span class="text-xs font-semibold text-emerald-400 flex items-center gap-1 mt-2" id="smaTrend">
<i data-lucide="arrow-up-right" class="w-3.5 h-3.5"></i> Tendência de Alta
</span>
</div>
<div class="bg-slate-900 border border-slate-850 bg-gradient-to-br from-slate-900 to-slate-950 rounded-2xl p-4 flex flex-col justify-between shadow-lg border-l-4 border-l-emerald-500" id="signalCard">
<div class="flex items-center justify-between mb-2">
<span class="text-xs font-bold text-slate-400 uppercase tracking-wider">Recomendação</span>
<span class="animate-pulse flex h-2 w-2 relative">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
</span>
</div>
<div class="mt-1">
<span class="text-2xl font-black tracking-tight text-emerald-400 uppercase" id="recSignal">Compra Forte</span>
<p class="text-[10px] text-slate-400 mt-1">Análise baseada em cruzamento tático e força relativa.</p>
</div>
</div>
</section>
</div>
<div class="flex flex-col gap-6">
<!-- Boleta de Operação Rápida -->
<section class="bg-slate-900 border border-slate-800/80 rounded-2xl p-5 shadow-xl flex flex-col gap-4">
<div class="flex items-center justify-between border-b border-slate-800 pb-3">
<h3 class="text-sm font-bold uppercase tracking-wider flex items-center gap-2">
<i data-lucide="wallet" class="w-4 h-4 text-emerald-400"></i> Boleta de Operações
</h3>
<span class="text-xs text-slate-400 font-semibold font-mono" id="assetLabel">BTC/USDT</span>
</div>
<!-- Abas Compra / Venda -->
<div class="grid grid-cols-2 gap-2 bg-slate-950 p-1 rounded-xl border border-slate-850">
<button onclick="setTradeDirection('BUY')" id="btnBuy" class="py-2.5 text-xs font-bold uppercase rounded-lg transition-all bg-emerald-500 text-slate-950 shadow-md">
Compra (Long)
</button>
<button onclick="setTradeDirection('SELL')" id="btnSell" class="py-2.5 text-xs font-bold uppercase rounded-lg transition-all text-slate-400 hover:text-rose-500">
Venda (Short)
</button>
</div>
<!-- Input Lote (Size) -->
<div>
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-wider block mb-1.5 flex justify-between">
<span>Quantidade (Tamanho da Ordem)</span>
<span class="text-slate-500 font-mono">Min: 0.01</span>
</label>
<div class="relative rounded-xl bg-slate-950 border border-slate-800 p-2 focus-within:border-emerald-500 transition">
<input type="number" id="tradeSize" value="0.5" step="0.05" min="0.01" class="bg-transparent text-xl font-bold font-mono text-slate-100 outline-none w-full" oninput="calculateMargin()">
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-xs font-semibold text-slate-500 font-mono">LOTE</span>
</div>
</div>
<!-- Alavancagem Simulada -->
<div>
<div class="flex justify-between text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1.5">
<span>Alavancagem Cruzada</span>
<span class="text-emerald-400 font-mono" id="leverageLabel">20x</span>
</div>
<input type="range" id="leverageRange" min="1" max="100" value="20" class="w-full accent-emerald-500 h-1 bg-slate-800 rounded-lg cursor-pointer" oninput="updateLeverage(this.value)">
<div class="flex justify-between text-[9px] text-slate-500 mt-1 font-semibold">
<span>1x</span>
<span>25x</span>
<span>50x</span>
<span>100x</span>
</div>
</div>
<!-- Stop Loss e Take Profit -->
<div class="grid grid-cols-2 gap-3">
<div>
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-wider block mb-1.5">Take Profit (Preço)</label>
<div class="relative rounded-xl bg-slate-950 border border-slate-800 p-2.5 focus-within:border-emerald-500 transition">
<input type="number" id="tpInput" placeholder="Nenhum" class="bg-transparent text-sm font-bold font-mono text-slate-200 outline-none w-full">
<i data-lucide="target" class="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-600"></i>
</div>
</div>
<div>
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-wider block mb-1.5">Stop Loss (Preço)</label>
<div class="relative rounded-xl bg-slate-950 border border-slate-800 p-2.5 focus-within:border-rose-500 transition">
<input type="number" id="slInput" placeholder="Nenhum" class="bg-transparent text-sm font-bold font-mono text-slate-200 outline-none w-full">
<i data-lucide="shield-alert" class="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-600"></i>
</div>
</div>
</div>
<!-- Custos e Margem de Risco -->
<div class="bg-slate-950/60 rounded-xl p-3 border border-slate-850 text-xs flex flex-col gap-2 font-mono">
<div class="flex justify-between">
<span class="text-slate-500">Custo de Margem (Exigido):</span>
<span class="text-slate-300 font-semibold" id="marginRequired">S$ 1.635,51</span>
</div>
<div class="flex justify-between">
<span class="text-slate-500">Exposição Total:</span>
<span class="text-slate-300 font-semibold" id="totalExposure">S$ 32.710,25</span>
</div>
</div>
<!-- Botão de Envio de Ordem -->
<button onclick="executeOrder()" class="w-full py-4 rounded-xl text-sm font-black uppercase tracking-wider bg-gradient-to-r from-emerald-500 to-teal-500 text-slate-950 shadow-lg shadow-emerald-500/20 hover:shadow-emerald-500/30 active:scale-[0.98] transition-all flex items-center justify-center gap-2" id="btnSubmitOrder">
<i data-lucide="zap" class="w-4 h-4 fill-current"></i> Abrir Posição de Compra
</button>
</section>
<section class="bg-slate-900 border border-slate-800/80 rounded-2xl p-5 shadow-xl flex-1 flex flex-col min-h-[300px]">
<div class="flex bg-slate-950 p-1 rounded-xl border border-slate-850 mb-4">
<button onclick="setLedgerTab('positions')" id="tabPos" class="flex-1 py-1.5 text-xs font-bold rounded-lg transition bg-slate-800 text-white shadow-sm">
Posições Ativas (<span id="countPos">0</span>)
</button>
<button onclick="setLedgerTab('history')" id="tabHist" class="flex-1 py-1.5 text-xs font-bold rounded-lg transition text-slate-400 hover:text-white">
Histórico
</button>
</div>
<!-- Container Rolável das Posições ou Histórico -->
<div class="flex-1 overflow-y-auto max-h-[250px]" id="ledgerContent">
<!-- Dinâmico via JS -->
<div class="text-center py-10 text-slate-500 text-xs flex flex-col items-center gap-2">
<i data-lucide="inbox" class="w-8 h-8 opacity-40"></i>
Nenhuma posição aberta no momento.
</div>
</div>
</section>
</div>
</main>
<div id="toastContainer" class="fixed bottom-4 right-4 z-50 flex flex-col gap-2 pointer-events-none"></div>
<footer class="border-t border-slate-850 bg-slate-950 px-4 py-3 text-center text-[10px] text-slate-500">
<p>© 2026 TradeFlow MVP. Todos os direitos reservados. Este é um ambiente de simulação financeira e desenvolvimento de testes táticos.</p>
</footer>
<script>
// --- CONSTANTES & ESTADOS DO APP ---
const appState = {
balance: 10000.00,
floatingPL: 0.00,
positions: [],
history: [],
candles: [],
connectionMode: 'simulated', // 'simulated' ou 'live'
activeSymbol: 'BTC/USDT',
currentPrice: 65420.50,
priceHigh24h: 65980.00,
priceLow24h: 64210.00,
priceChange24h: 1.24,
activeDirection: 'BUY', // 'BUY' ou 'SELL'
leverage: 20,
zoom: 1.0,
indicators: {
sma: true,
rsi: true
},
ws: null
};
// DOM elements cache
const dom = {
walletBalance: document.getElementById('walletBalance'),
totalFloatingPL: document.getElementById('totalFloatingPL'),
tickerPrice: document.getElementById('tickerPrice'),
tickerPercentChange: document.getElementById('tickerPercentChange'),
tickerHigh: document.getElementById('tickerHigh'),
tickerLow: document.getElementById('tickerLow'),
btnModeSim: document.getElementById('btnModeSim'),
btnModeLive: document.getElementById('btnModeLive'),
btnBuy: document.getElementById('btnBuy'),
btnSell: document.getElementById('btnSell'),
tradeSize: document.getElementById('tradeSize'),
leverageRange: document.getElementById('leverageRange'),
leverageLabel: document.getElementById('leverageLabel'),
tpInput: document.getElementById('tpInput'),
slInput: document.getElementById('slInput'),
marginRequired: document.getElementById('marginRequired'),
totalExposure: document.getElementById('totalExposure'),
btnSubmitOrder: document.getElementById('btnSubmitOrder'),
ledgerContent: document.getElementById('ledgerContent'),
tabPos: document.getElementById('tabPos'),
tabHist: document.getElementById('tabHist'),
countPos: document.getElementById('countPos'),
rsiVal: document.getElementById('rsiVal'),
rsiState: document.getElementById('rsiState'),
rsiBar: document.getElementById('rsiBar'),
smaVal: document.getElementById('smaVal'),
smaTrend: document.getElementById('smaTrend'),
recSignal: document.getElementById('recSignal'),
signalCard: document.getElementById('signalCard'),
tickerPanel: document.getElementById('tickerPanel'),
toastContainer: document.getElementById('toastContainer')
};
let activeTab = 'positions'; // 'positions' ou 'history'
// Configurações do Gráfico
const canvas = document.getElementById('tradingChart');
const ctx = canvas.getContext('2d');
// --- INICIALIZAÇÃO DO APP ---
window.onload = function() {
lucide.createIcons();
initMockCandles();
resizeCanvas();
updateDashboard();
calculateMargin();
// Inicia o loop de simulação local de ticks
startSimulationLoop();
};
function resizeCanvas() {
const container = document.getElementById('canvasContainer');
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
drawChart();
}
window.addEventListener('resize', resizeCanvas);
// Gerar Dados Iniciais de Candlestick (Simulando Histórico)
function initMockCandles() {
let basePrice = appState.currentPrice - 300;
const now = Date.now();
for (let i = 80; i >= 0; i--) {
const time = now - (i * 5000); // interval de 5 segundos por vela
const open = basePrice + (Math.random() - 0.5) * 80;
const close = open + (Math.random() - 0.5) * 85;
const high = Math.max(open, close) + Math.random() * 40;
const low = Math.min(open, close) - Math.random() * 40;
appState.candles.push({ time, open, high, low, close });
basePrice = close;
}
appState.currentPrice = basePrice;
}
// --- MOTOR DE SIMULAÇÃO DE TICKERS ---
let simInterval = null;
function startSimulationLoop() {
if (simInterval) clearInterval(simInterval);
simInterval = setInterval(() => {
if (appState.connectionMode === 'simulated') {
// Flutuação aleatória e tática de mercado
const lastCandle = appState.candles[appState.candles.length - 1];
const volatility = 15;
const change = (Math.random() - 0.49) * volatility; // ligeira tendência de alta natural
const oldPrice = appState.currentPrice;
appState.currentPrice += change;
// Atualiza a vela atual
lastCandle.close = appState.currentPrice;
if (appState.currentPrice > lastCandle.high) lastCandle.high = appState.currentPrice;
if (appState.currentPrice < lastCandle.low) lastCandle.low = appState.currentPrice;
// Cria nova vela a cada 5 segundos
const now = Date.now();
if (now - lastCandle.time >= 5000) {
appState.candles.push({
time: now,
open: lastCandle.close,
high: lastCandle.close,
low: lastCandle.close,
close: lastCandle.close
});
if (appState.candles.length > 120) appState.candles.shift(); // limite de memória das velas
}
handleTickUpdate(oldPrice < appState.currentPrice);
}
}, 500);
}
// --- INTEGRAÇÃO REAL-TIME WEB_SOCKETS (BINANCE LIVE) ---
function connectBinanceWS() {
if (appState.ws) {
appState.ws.close();
}
showToast("Conectando ao feed em tempo real da Binance...", "info");
// Stream público do Bitcoin (Velas de 1 Minuto)
appState.ws = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt@kline_1m");
appState.ws.onmessage = (event) => {
if (appState.connectionMode !== 'live') return;
const data = JSON.parse(event.data);
const k = data.k; // candle info
const oldPrice = appState.currentPrice;
appState.currentPrice = parseFloat(k.c);
appState.priceHigh24h = parseFloat(k.h); // Aproximado para a vela atual
appState.priceLow24h = parseFloat(k.l);
// Mapeia os dados recebidos para as velas do gráfico local
const lastCandle = appState.candles[appState.candles.length - 1];
const incomingTime = parseInt(k.t);
if (lastCandle && incomingTime >= lastCandle.time) {
// Atualiza vela atual
lastCandle.close = appState.currentPrice;
lastCandle.high = parseFloat(k.h);
lastCandle.low = parseFloat(k.l);
lastCandle.open = parseFloat(k.o);
} else {
// Nova vela recebida
appState.candles.push({
time: incomingTime,
open: parseFloat(k.o),
high: parseFloat(k.h),
low: parseFloat(k.l),
close: appState.currentPrice
});
if (appState.candles.length > 120) appState.candles.shift();
}
handleTickUpdate(oldPrice < appState.currentPrice);
};
appState.ws.onerror = (err) => {
showToast("Erro ao conectar no feed live. Retornando ao Simulador.", "error");
setConnectionMode('simulated');
};
appState.ws.onclose = () => {
if (appState.connectionMode === 'live') {
showToast("Conexão com a Binance encerrada.", "warning");
}
};
}
// --- LÓGICA CORE DE EXECUÇÃO DE TICKS ---
function handleTickUpdate(isPositive) {
// Atualizar cor e efeitos no painel de ticker
dom.tickerPrice.textContent = `$ ${appState.currentPrice.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
if (isPositive) {
dom.tickerPrice.className = "text-2xl font-black font-mono tracking-tight text-emerald-400";
dom.tickerPanel.classList.add('price-up');
setTimeout(() => dom.tickerPanel.classList.remove('price-up'), 500);
} else {
dom.tickerPrice.className = "text-2xl font-black font-mono tracking-tight text-rose-500";
dom.tickerPanel.classList.add('price-down');
setTimeout(() => dom.tickerPanel.classList.remove('price-down'), 500);
}
// Monitorar P&L Aberto e gatilhos de stop loss / take profit
updatePositionsPL();
updateDashboard();
drawChart();
}
// --- CONTROLES DE INTERFACE (UI INTERACTORS) ---
function setConnectionMode(mode) {
appState.connectionMode = mode;
if (mode === 'live') {
dom.btnModeLive.className = "px-3 py-1.5 text-xs font-semibold rounded-lg transition-all flex items-center gap-2 bg-slate-800 text-amber-400 shadow-sm";
dom.btnModeSim.className = "px-3 py-1.5 text-xs font-semibold rounded-lg transition-all flex items-center gap-2 text-slate-400 hover:text-white";
connectBinanceWS();
} else {
dom.btnModeSim.className = "px-3 py-1.5 text-xs font-semibold rounded-lg transition-all flex items-center gap-2 bg-slate-800 text-emerald-400 shadow-sm";
dom.btnModeLive.className = "px-3 py-1.5 text-xs font-semibold rounded-lg transition-all flex items-center gap-2 text-slate-400 hover:text-white";
if (appState.ws) {
appState.ws.close();
}
showToast("Simulador Local de Preço reativado.", "success");
startSimulationLoop();
}
}
function setTradeDirection(dir) {
appState.activeDirection = dir;
if (dir === 'BUY') {
dom.btnBuy.className = "py-2.5 text-xs font-bold uppercase rounded-lg transition-all bg-emerald-500 text-slate-950 shadow-md";
dom.btnSell.className = "py-2.5 text-xs font-bold uppercase rounded-lg transition-all text-slate-400 hover:text-rose-500";
dom.btnSubmitOrder.className = "w-full py-4 rounded-xl text-sm font-black uppercase tracking-wider bg-gradient-to-r from-emerald-500 to-teal-500 text-slate-950 shadow-lg shadow-emerald-500/20 hover:shadow-emerald-500/30 active:scale-[0.98] transition-all flex items-center justify-center gap-2";
dom.btnSubmitOrder.innerHTML = `<i data-lucide="zap" class="w-4 h-4 fill-current"></i> Abrir Posição de Compra`;
} else {
dom.btnSell.className = "py-2.5 text-xs font-bold uppercase rounded-lg transition-all bg-rose-500 text-white shadow-md";
dom.btnBuy.className = "py-2.5 text-xs font-bold uppercase rounded-lg transition-all text-slate-400 hover:text-emerald-400";
dom.btnSubmitOrder.className = "w-full py-4 rounded-xl text-sm font-black uppercase tracking-wider bg-gradient-to-r from-rose-500 to-red-600 text-white shadow-lg shadow-rose-500/20 hover:shadow-rose-500/30 active:scale-[0.98] transition-all flex items-center justify-center gap-2";
dom.btnSubmitOrder.innerHTML = `<i data-lucide="zap" class="w-4 h-4 fill-current"></i> Abrir Posição de Venda`;
}
lucide.createIcons();
calculateMargin();
}
function updateLeverage(val) {
appState.leverage = parseInt(val);
dom.leverageLabel.textContent = `${val}x`;
calculateMargin();
}
function calculateMargin() {
const size = parseFloat(dom.tradeSize.value) || 0;
const exposure = size * appState.currentPrice;
const margin = exposure / appState.leverage;
dom.totalExposure.textContent = `S$ ${exposure.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
dom.marginRequired.textContent = `S$ ${margin.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}
function changeZoom(factor) {
appState.zoom *= factor;
if (appState.zoom < 0.4) appState.zoom = 0.4;
if (appState.zoom > 3.0) appState.zoom = 3.0;
drawChart();
}
function toggleIndicator(ind) {
appState.indicators[ind] = !appState.indicators[ind];
const btn = ind === 'sma' ? dom.btnSma : dom.btnRsi;
if (appState.indicators[ind]) {
btn.className = "px-2 py-1.5 text-[10px] font-bold rounded-lg bg-emerald-500/20 text-emerald-400 border border-emerald-500/25 transition";
} else {
btn.className = "px-2 py-1.5 text-[10px] font-bold rounded-lg bg-slate-800 text-slate-400 hover:bg-slate-750 transition";
}
drawChart();
}
// --- STREAMING_CHUNK: Motor de Execução de Ordens (Trading System) ---
function executeOrder() {
const size = parseFloat(dom.tradeSize.value);
if (isNaN(size) || size <= 0) {
showToast("Insira um lote de tamanho válido.", "warning");
return;
}
const exposure = size * appState.currentPrice;
const requiredMargin = exposure / appState.leverage;
if (requiredMargin > appState.balance) {
showToast("Saldo insuficiente para cobrir a margem exigida.", "error");
return;
}
const tpPrice = parseFloat(dom.tpInput.value) || null;
const slPrice = parseFloat(dom.slInput.value) || null;
// Validações básicas de SL/TP táticos
if (appState.activeDirection === 'BUY') {
if (tpPrice && tpPrice <= appState.currentPrice) {
showToast("Take Profit de Compra deve ser superior ao preço atual.", "warning");
return;
}
if (slPrice && slPrice >= appState.currentPrice) {
showToast("Stop Loss de Compra deve ser inferior ao preço atual.", "warning");
return;
}
} else {
if (tpPrice && tpPrice >= appState.currentPrice) {
showToast("Take Profit de Venda deve ser inferior ao preço atual.", "warning");
return;
}
if (slPrice && slPrice <= appState.currentPrice) {
showToast("Stop Loss de Venda deve ser superior ao preço atual.", "warning");
return;
}
}
// Deduz margem exigida do saldo operacional
appState.balance -= requiredMargin;
// Cria o objeto da posição tática
const position = {
id: Date.now() + Math.random().toString(36).substr(2, 5),
symbol: appState.activeSymbol,
type: appState.activeDirection,
entryPrice: appState.currentPrice,
size: size,
leverage: appState.leverage,
margin: requiredMargin,
takeProfit: tpPrice,
stopLoss: slPrice,
floatingPL: 0.00,
timestamp: Date.now()
};
appState.positions.push(position);
showToast(`Posição de ${position.type === 'BUY' ? 'COMPRA' : 'VENDA'} aberta com sucesso!`, "success");
// Reset de inputs da boleta
dom.tpInput.value = '';
dom.slInput.value = '';
updateLedger();
updateDashboard();
}
// Atualização e Monitor de Lucros/Perdas Flutuantes das Posições
function updatePositionsPL() {
let totalPL = 0;
const positionsToClose = [];
appState.positions.forEach(pos => {
const diff = appState.currentPrice - pos.entryPrice;
const percentChange = diff / pos.entryPrice;
if (pos.type === 'BUY') {
pos.floatingPL = percentChange * pos.size * pos.entryPrice * pos.leverage;
} else {
pos.floatingPL = -percentChange * pos.size * pos.entryPrice * pos.leverage;
}
totalPL += pos.floatingPL;
// Checagem de Acionamento Automático de Stop Loss / Take Profit
if (pos.type === 'BUY') {
if (pos.takeProfit && appState.currentPrice >= pos.takeProfit) {
positionsToClose.push({ pos, reason: 'Take Profit' });
} else if (pos.stopLoss && appState.currentPrice <= pos.stopLoss) {
positionsToClose.push({ pos, reason: 'Stop Loss' });
}
} else {
if (pos.takeProfit && appState.currentPrice <= pos.takeProfit) {
positionsToClose.push({ pos, reason: 'Take Profit' });
} else if (pos.stopLoss && appState.currentPrice >= pos.stopLoss) {
positionsToClose.push({ pos, reason: 'Stop Loss' });
}
}
});
appState.floatingPL = totalPL;
// Processar fechamentos automáticos
positionsToClose.forEach(({ pos, reason }) => {
closePosition(pos.id, reason);
});
if (appState.positions.length > 0) {
updateLedger();
}
}
function closePosition(posId, reason = 'Manual') {
const index = appState.positions.findIndex(p => p.id === posId);
if (index === -1) return;
const pos = appState.positions[index];
const finalPL = pos.floatingPL;
// Devolve margem + adiciona/subtrai P&L ao saldo líquido
appState.balance += pos.margin + finalPL;
// Adiciona ao histórico de operações
appState.history.unshift({
...pos,
exitPrice: appState.currentPrice,
finalPL: finalPL,
closeReason: reason,
closedAt: Date.now()
});
// Remove das posições ativas
appState.positions.splice(index, 1);
showToast(`Posição encerrada (${reason}). P&L: S$ ${finalPL.toFixed(2)}`, finalPL >= 0 ? "success" : "error");
updateLedger();
updateDashboard();
}
// --- ATUALIZAÇÕES DE INTERFACE (DASHBOARD & LEDGER) ---
function updateDashboard() {
dom.walletBalance.textContent = `S$ ${appState.balance.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
const plText = `S$ ${appState.floatingPL.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
dom.totalFloatingPL.textContent = plText;
if (appState.floatingPL > 0) {
dom.totalFloatingPL.className = "text-lg font-bold text-emerald-400";
} else if (appState.floatingPL < 0) {
dom.totalFloatingPL.className = "text-lg font-bold text-rose-500";
} else {
dom.totalFloatingPL.className = "text-lg font-bold text-slate-400";
}
dom.countPos.textContent = appState.positions.length;
calculateMargin();
// Computar Sinais Analíticos de Mercado
updateTechnicalSignals();
}
function setLedgerTab(tab) {
activeTab = tab;
if (tab === 'positions') {
dom.tabPos.className = "flex-1 py-1.5 text-xs font-bold rounded-lg transition bg-slate-800 text-white shadow-sm";
dom.tabHist.className = "flex-1 py-1.5 text-xs font-bold rounded-lg transition text-slate-400 hover:text-white";
} else {
dom.tabHist.className = "flex-1 py-1.5 text-xs font-bold rounded-lg transition bg-slate-800 text-white shadow-sm";
dom.tabPos.className = "flex-1 py-1.5 text-xs font-bold rounded-lg transition text-slate-400 hover:text-white";
}
updateLedger();
}
function updateLedger() {
dom.ledgerContent.innerHTML = '';
if (activeTab === 'positions') {
if (appState.positions.length === 0) {
dom.ledgerContent.innerHTML = `
<div class="text-center py-10 text-slate-500 text-xs flex flex-col items-center gap-2">
<i data-lucide="inbox" class="w-8 h-8 opacity-40"></i>
Nenhuma posição aberta no momento.
</div>
`;
lucide.createIcons();
return;
}
appState.positions.forEach(pos => {
const card = document.createElement('div');
card.className = "bg-slate-950 p-3 rounded-xl border border-slate-850 mb-2 flex flex-col gap-2";
const isProfit = pos.floatingPL >= 0;
const plClass = isProfit ? "text-emerald-400" : "text-rose-500";
card.innerHTML = `
<div class="flex justify-between items-center text-xs">
<span class="font-bold flex items-center gap-1.5">
<span class="w-2 h-2 rounded-full ${pos.type === 'BUY' ? 'bg-emerald-500' : 'bg-rose-500'}"></span>
${pos.type === 'BUY' ? 'COMPRA' : 'VENDA'}
<span class="text-slate-500 text-[10px] font-mono">${pos.leverage}x</span>
</span>
<button onclick="closePosition('${pos.id}')" class="px-2 py-1 text-[10px] font-extrabold rounded bg-slate-800 text-slate-300 hover:bg-rose-500 hover:text-white transition uppercase">Fechar</button>
</div>
<div class="grid grid-cols-3 gap-1 text-[10px] text-slate-400 font-mono">
<div>
<div class="text-slate-500">Lote</div>
<div class="font-semibold text-slate-200">${pos.size} BTC</div>
</div>
<div>
<div class="text-slate-500">Entrada</div>
<div class="font-semibold text-slate-200">$ ${pos.entryPrice.toFixed(2)}</div>
</div>
<div class="text-right">
<div class="text-slate-500">P&L Aberto</div>
<div class="font-bold ${plClass}">$ ${pos.floatingPL.toFixed(2)}</div>
</div>
</div>
${(pos.takeProfit || pos.stopLoss) ? `
<div class="border-t border-slate-900 pt-1.5 flex gap-3 text-[9px] font-semibold font-mono text-slate-500">
${pos.takeProfit ? `<span>TP: <span class="text-emerald-500">$ ${pos.takeProfit}</span></span>` : ''}
${pos.stopLoss ? `<span>SL: <span class="text-rose-500">$ ${pos.stopLoss}</span></span>` : ''}
</div>
` : ''}
`;
dom.ledgerContent.appendChild(card);
});
} else {
if (appState.history.length === 0) {
dom.ledgerContent.innerHTML = `
<div class="text-center py-10 text-slate-500 text-xs flex flex-col items-center gap-2">
<i data-lucide="archive" class="w-8 h-8 opacity-40"></i>
Histórico de operações vazio.
</div>
`;
lucide.createIcons();
return;
}
appState.history.forEach(hist => {
const card = document.createElement('div');
card.className = "bg-slate-950/40 p-3 rounded-xl border border-slate-900 mb-2 flex flex-col gap-1.5";
const isProfit = hist.finalPL >= 0;
const plClass = isProfit ? "text-emerald-400" : "text-rose-500";
card.innerHTML = `
<div class="flex justify-between text-[10px]">
<span class="font-bold flex items-center gap-1">
<span class="text-slate-500">[${hist.closeReason}]</span>
${hist.type === 'BUY' ? 'COMPRA' : 'VENDA'}
</span>
<span class="text-slate-500 font-mono">${new Date(hist.closedAt).toLocaleTimeString()}</span>
</div>
<div class="flex justify-between items-baseline text-xs font-mono">
<span class="text-slate-400">${hist.size} BTC @ $ ${hist.entryPrice.toFixed(2)}</span>
<span class="font-bold ${plClass}">$ ${hist.finalPL.toFixed(2)}</span>
</div>
`;
dom.ledgerContent.appendChild(card);
});
}
lucide.createIcons();
}
// --- STREAMING_CHUNK: Cálculos e Sinais de Análise Técnica ---
function updateTechnicalSignals() {
if (appState.candles.length < 15) return;
// 1. Simulação Simples de RSI
// Baseia-se na variação recente do preço para criar um oscilador real de momentum
const changes = [];
for (let i = appState.candles.length - 14; i < appState.candles.length; i++) {
if (appState.candles[i] && appState.candles[i-1]) {
changes.push(appState.candles[i].close - appState.candles[i-1].close);
}
}
let gains = 0, losses = 0;
changes.forEach(change => {
if (change > 0) gains += change;
else losses += Math.abs(change);
});
let rsi = 50;
if (losses !== 0) {
const rs = gains / losses;
rsi = 100 - (100 / (1 + rs));
}
dom.rsiVal.textContent = rsi.toFixed(1);
dom.rsiBar.style.width = `${rsi}%`;
let rsiState = "Neutro";
if (rsi > 70) {
rsiState = "Sobrecomprado";
dom.rsiState.className = "text-xs px-2 py-0.5 rounded-full bg-rose-500/10 text-rose-400 font-medium";
} else if (rsi < 30) {
rsiState = "Sobrevendido";
dom.rsiState.className = "text-xs px-2 py-0.5 rounded-full bg-emerald-500/10 text-emerald-400 font-medium";
} else {
dom.rsiState.className = "text-xs px-2 py-0.5 rounded-full bg-slate-800 text-slate-300 font-medium";
}
dom.rsiState.textContent = rsiState;
// 2. Média Móvel Simples (SMA-9)
let smaSum = 0;
const period = 9;
for (let i = appState.candles.length - period; i < appState.candles.length; i++) {
smaSum += appState.candles[i].close;
}
const sma = smaSum / period;
dom.smaVal.textContent = `$ ${sma.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
// 3. Recomendação Tática Inteligente
let recommendation = "NEUTRO";
let recColorClass = "text-slate-300 uppercase";
let borderClass = "border-l-slate-600";
if (appState.currentPrice > sma && rsi < 65) {
recommendation = "COMPRA FORTE";
recColorClass = "text-emerald-400 uppercase font-black";
borderClass = "border-l-4 border-l-emerald-500";
} else if (appState.currentPrice < sma && rsi > 35) {
recommendation = "VENDA";
recColorClass = "text-rose-500 uppercase font-black";
borderClass = "border-l-4 border-l-rose-500";
} else if (rsi < 30) {
recommendation = "SUPERCOMPRA";
recColorClass = "text-emerald-400 uppercase font-black animate-pulse";
borderClass = "border-l-4 border-l-emerald-500";
} else if (rsi > 70) {
recommendation = "SUPERVENDA";
recColorClass = "text-rose-500 uppercase font-black animate-pulse";
borderClass = "border-l-4 border-l-rose-500";
}
dom.recSignal.textContent = recommendation;
dom.recSignal.className = `text-2xl font-black tracking-tight ${recColorClass}`;
dom.signalCard.className = `bg-slate-900 border border-slate-850 bg-gradient-to-br from-slate-900 to-slate-950 rounded-2xl p-4 flex flex-col justify-between shadow-lg ${borderClass}`;
}
// --- STREAMING_CHUNK: Renderização Customizada do Gráfico de Velas ---
function drawChart() {
if (!ctx || appState.candles.length === 0) return;
// Limpa o canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
const paddingLeft = 10;
const paddingRight = 80;
const paddingTop = 20;
const paddingBottom = 40;
const chartWidth = canvas.width - paddingLeft - paddingRight;
const chartHeight = canvas.height - paddingTop - paddingBottom;
// Quantidade de velas visíveis baseada no zoom
const maxCandlesVisible = Math.floor(45 * appState.zoom);
const visibleCandles = appState.candles.slice(-maxCandlesVisible);
if (visibleCandles.length === 0) return;
// Encontrar máxima e mínima do preço do corte visível para o Auto-scale
let maxPrice = -Infinity;
let minPrice = Infinity;
visibleCandles.forEach(c => {
if (c.high > maxPrice) maxPrice = c.high;
if (c.low < minPrice) minPrice = c.low;
});
// Dar um respiro de 5% acima e abaixo para encaixe estético
const priceRange = maxPrice - minPrice;
maxPrice += priceRange * 0.05;
minPrice -= priceRange * 0.05;
const candleWidth = chartWidth / visibleCandles.length;
// Desenhar Grade de Fundo (Grids)
ctx.strokeStyle = '#1e293b';
ctx.lineWidth = 0.5;
ctx.fillStyle = '#64748b';
ctx.font = '9px font-mono';
// Grade Horizontal (Preços)
const gridLines = 5;
for (let i = 0; i <= gridLines; i++) {
const y = paddingTop + (chartHeight / gridLines) * i;
const priceLabel = maxPrice - ((maxPrice - minPrice) / gridLines) * i;
ctx.beginPath();
ctx.moveTo(paddingLeft, y);
ctx.lineTo(paddingLeft + chartWidth, y);
ctx.stroke();
// Desenha etiqueta do preço à direita
ctx.fillText(`$ ${priceLabel.toFixed(1)}`, paddingLeft + chartWidth + 5, y + 3);
}
// Desenhar Velas (Candlesticks)
visibleCandles.forEach((c, index) => {
const x = paddingLeft + (index * candleWidth) + (candleWidth * 0.1);
const w = candleWidth * 0.8;
// Mapear preço para Y no canvas
const yOpen = paddingTop + chartHeight * (1 - (c.open - minPrice) / (maxPrice - minPrice));
const yClose = paddingTop + chartHeight * (1 - (c.close - minPrice) / (maxPrice - minPrice));
const yHigh = paddingTop + chartHeight * (1 - (c.high - minPrice) / (maxPrice - minPrice));
const yLow = paddingTop + chartHeight * (1 - (c.low - minPrice) / (maxPrice - minPrice));
const isBullish = c.close >= c.open;
ctx.strokeStyle = isBullish ? '#10b981' : '#f43f5e';
ctx.fillStyle = isBullish ? '#10b981' : '#f43f5e';
ctx.lineWidth = 1.5;
// Linha de pavio (sombra)
ctx.beginPath();
ctx.moveTo(x + w / 2, yHigh);
ctx.lineTo(x + w / 2, yLow);
ctx.stroke();
// Corpo da vela
const bodyHeight = Math.max(Math.abs(yClose - yOpen), 1);
ctx.fillRect(x, Math.min(yOpen, yClose), w, bodyHeight);
});
// Média Móvel Tática (SMA) sobreposta
if (appState.indicators.sma && visibleCandles.length > 9) {
ctx.strokeStyle = '#38bdf8';
ctx.lineWidth = 1.8;
ctx.beginPath();
for (let i = 0; i < visibleCandles.length; i++) {
const idxInGlobal = appState.candles.indexOf(visibleCandles[i]);
if (idxInGlobal >= 9) {
let sum = 0;
for (let j = 0; j < 9; j++) {
sum += appState.candles[idxInGlobal - j].close;
}
const avg = sum / 9;
const x = paddingLeft + (i * candleWidth) + (candleWidth / 2);
const y = paddingTop + chartHeight * (1 - (avg - minPrice) / (maxPrice - minPrice));
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
}
ctx.stroke();
}
// Desenhar Linhas de Posições Ativas (Visual Trading!)
appState.positions.forEach(pos => {
if (pos.entryPrice >= minPrice && pos.entryPrice <= maxPrice) {
const y = paddingTop + chartHeight * (1 - (pos.entryPrice - minPrice) / (maxPrice - minPrice));
ctx.strokeStyle = pos.type === 'BUY' ? '#10b981' : '#f43f5e';
ctx.setLineDash([5, 3]);
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(paddingLeft, y);
ctx.lineTo(paddingLeft + chartWidth, y);
ctx.stroke();
ctx.setLineDash([]); // limpa o tracejado
// Etiqueta de Posição
ctx.fillStyle = pos.type === 'BUY' ? '#10b981' : '#f43f5e';
ctx.fillRect(paddingLeft + 5, y - 12, 60, 10);
ctx.fillStyle = '#0b0f19';
ctx.font = 'bold 8px font-mono';
ctx.fillText(`${pos.type} ${pos.size}`, paddingLeft + 8, y - 4);
}
});
// Preço Atual Linha Guia Dinâmica (Glow Tracker)
if (appState.currentPrice >= minPrice && appState.currentPrice <= maxPrice) {
const y = paddingTop + chartHeight * (1 - (appState.currentPrice - minPrice) / (maxPrice - minPrice));
ctx.strokeStyle = 'rgba(255, 255, 255, 0.25)';
ctx.lineWidth = 0.8;
ctx.setLineDash([2, 2]);
ctx.beginPath();
ctx.moveTo(paddingLeft, y);
ctx.lineTo(paddingLeft + chartWidth, y);
ctx.stroke();
ctx.setLineDash([]);
// Caixa de Preço de Mercado à direita
ctx.fillStyle = '#0f172a';
ctx.fillRect(paddingLeft + chartWidth + 2, y - 7, 75, 14);
ctx.strokeStyle = '#64748b';
ctx.lineWidth = 0.5;
ctx.strokeRect(paddingLeft + chartWidth + 2, y - 7, 75, 14);
ctx.fillStyle = '#f8fafc';
ctx.font = '9px font-mono';
ctx.fillText(`$ ${appState.currentPrice.toFixed(2)}`, paddingLeft + chartWidth + 6, y + 3);
}
}
// --- SISTEMA INTERNO DE NOTIFICAÇÕES (TOAST MANAGER) ---
function showToast(message, type = 'info') {
const id = Date.now().toString();
const toast = document.createElement('div');
toast.id = id;
toast.className = `transform translate-y-2 opacity-0 transition-all duration-300 pointer-events-auto flex items-center gap-3 p-4 rounded-xl shadow-2xl border text-sm max-w-sm ${
type === 'success' ? 'bg-emerald-950/95 text-emerald-400 border-emerald-500/30' :
type === 'error' ? 'bg-rose-950/95 text-rose-400 border-rose-500/30' :
type === 'warning' ? 'bg-amber-950/95 text-amber-400 border-amber-500/30' :
'bg-slate-900/95 text-slate-300 border-slate-800'
}`;
let icon = 'info';
if (type === 'success') icon = 'check-circle';
if (type === 'error') icon = 'alert-triangle';
if (type === 'warning') icon = 'alert-circle';
toast.innerHTML = `
<i data-lucide="${icon}" class="w-5 h-5 flex-shrink-0"></i>
<div class="flex-1 font-medium">${message}</div>
<button onclick="removeToast('${id}')" class="text-slate-500 hover:text-white transition"><i data-lucide="x" class="w-4 h-4"></i></button>
`;
dom.toastContainer.appendChild(toast);
lucide.createIcons();
// Animação de Entrada
setTimeout(() => {
toast.classList.remove('translate-y-2', 'opacity-0');
}, 10);
// Auto descarte após 4 segundos
setTimeout(() => {
removeToast(id);
}, 4000);
}
function removeToast(id) {
const toast = document.getElementById(id);
if (toast) {
toast.classList.add('translate-y-2', 'opacity-0');
setTimeout(() => {
toast.remove();
}, 300);
}
}
</script>
</body>
</html>
Feedback

”Tradeflow’s tutorials helped me finally understand complex trading strategies clearly.”

”The market analysis articles keep me updated and confident in my investment choices.”
