CONECTADO
BTCUSDT
💰 Preço Atual
--.--
--.--%
📊 Volatilidade (ATR%)
--.--%?
Ideal: 0.5% – 3.0%
📈 MACD Histograma AGUARDANDO
🔻 Venda⚪ Neutro🟢 Compra
Risco/Op:R$ 100.00
Stop:1.5× ATR
Alvo:2.5× ATR
Trailing:Ativa em +1× ATR
⛶ Tela Cheia (TV/Tablet)
🎯 Timing de Entrada — MACD + Volatilidade + Tendência
🔍 AGUARDANDO CONFIRMAÇÃO...
📐 RSI (14)
--.--
Neutro
📈 EMA21
$--.--
Aguardando...
✅ Regras de Entrada:
• MACD cruza zero com direção definida
• Volatilidade entre 0.5% e 3.0%
• Preço alinhado com EMA21
• RSI fora de zonas extremas (<25 ou >75)
🚪 Saída Inteligente — Proteção de Lucro
🛑 Stop Loss
--.--
1.5× ATR
🔄 Trailing
--.--
Após +1× ATR
✅ Take Profit
--.--
2.5× ATR
📊 Lote Sugerido (Risco Fixo)
--.---- un
P&L ATUAL
--.--%
📋 Log em Tempo Real
🔌 Inicializando sistema...
🎯 Timing Pro ULTRA v3.0 • Dados: Binance API • Atualização: 30s
🎯 Timing Pro ULTRA — MACD Elite
CONECTADO
BTCUSDT
💰 Preço Atual
--.--
--.--%
📊 Volatilidade (ATR%)
--.--%?
Ideal: 0.5% – 3.0%
📈 MACD Histograma AGUARDANDO
🔻 Venda⚪ Neutro🟢 Compra
Risco/Op:R$ 100.00
Stop:1.5× ATR
Alvo:2.5× ATR
Trailing:Ativa em +1× ATR
⛶ Tela Cheia (TV/Tablet)
🎯 Timing de Entrada — MACD + Volatilidade + Tendência
🔍 AGUARDANDO CONFIRMAÇÃO...
📐 RSI (14)
--.--
Neutro
📈 EMA21
$--.--
Aguardando...
✅ Regras de Entrada:
• MACD cruza zero com direção definida
• Volatilidade entre 0.5% e 3.0%
• Preço alinhado com EMA21
• RSI fora de zonas extremas (<25 ou >75)
🚪 Saída Inteligente — Proteção de Lucro
🛑 Stop Loss
--.--
1.5× ATR
🔄 Trailing
--.--
Após +1× ATR
✅ Take Profit
--.--
2.5× ATR
📊 Lote Sugerido (Risco Fixo)
--.---- un
P&L ATUAL
--.--%
📋 Log em Tempo Real
🔌 Inicializando sistema...
🎯 Timing Pro ULTRA v3.0 • Dados: Binance API • Atualização: 30s
🎯 Timing Pro ULTRA — MACD Elite
CONECTADO
BTCUSDT
💰 Preço Atual
--.--
--.--%
📊 Volatilidade (ATR%)
--.--%?
Ideal: 0.5% – 3.0%
📈 MACD Histograma AGUARDANDO
🔻 Venda⚪ Neutro🟢 Compra
Risco/Op:R$ 100.00
Stop:1.5× ATR
Alvo:2.5× ATR
Trailing:Ativa em +1× ATR
⛶ Tela Cheia (TV/Tablet)
🎯 Timing de Entrada — MACD + Volatilidade + Tendência
🔍 AGUARDANDO CONFIRMAÇÃO...
📐 RSI (14)
--.--
Neutro
📈 EMA21
$--.--
Aguardando...
✅ Regras de Entrada:
• MACD cruza zero com direção definida
• Volatilidade entre 0.5% e 3.0%
• Preço alinhado com EMA21
• RSI fora de zonas extremas (<25 ou >75)
🚪 Saída Inteligente — Proteção de Lucro
🛑 Stop Loss
--.--
1.5× ATR
🔄 Trailing
--.--
Após +1× ATR
✅ Take Profit
--.--
2.5× ATR
📊 Lote Sugerido (Risco Fixo)
--.---- un
P&L ATUAL
--.--%
📋 Log em Tempo Real
🔌 Inicializando sistema...
🎯 Timing Pro ULTRA v3.0 • Dados: Binance API • Atualização: 30s
return tr_sum / period def calc_macd(closes, fast=12, slow=26, signal=9): if len(closes) < slow + signal: return {"last": None, "hist": []} ef = sum(closes[:fast])/fast es = sum(closes[:slow])/slow k_f, k_s = 2/(fast+1), 2/(slow+1) ef_vals, es_vals = [], [] for i, p in enumerate(closes): ef = (p - ef)*k_f + ef if i >= fast else sum(closes[:i+1])/(i+1) es = (p - es)*k_s + es if i >= slow else sum(closes[:i+1])/(i+1) ef_vals.append(ef) es_vals.append(es) macd_line = [f - s for f, s in zip(ef_vals, es_vals)] # Signal line sig_vals = [None] * slow k_sig = 2/(signal+1) s_val = sum(macd_line[slow:slow+signal])/signal sig_vals.append(s_val) for i in range(slow+signal, len(macd_line)): s_val = (macd_line[i] - s_val)*k_sig + s_val sig_vals.append(s_val) hist = [m - s if s is not None else None for m, s in zip(macd_line, sig_vals)] last_idx = max([i for i, h in enumerate(hist) if h is not None], default=-1) return { "last": {"m": macd_line[last_idx], "s": sig_vals[last_idx], "h": hist[last_idx]} if last_idx != -1 else None, "hist": hist } # 🌐 DADOS BINANCE def fetch_klines(symbol, interval, limit): url = f"https://api.binance.com/api/v3/klines?symbol={symbol}&interval={interval}&limit={limit}" try: r = requests.get(url, timeout=10) r.raise_for_status() return r.json() except Exception as e: logging.error(f"Falha ao buscar klines: {e}") return [] # 📊 LÓGICA DE ESTRATÉGIA def check_volatility(atr, price): if not atr or not price: return False, "N/A" p = atr / price if p < CONFIG["min_vol"]: return False, "Baixa" if p > CONFIG["max_vol"]: return False, "Excessiva" return True, "Ideal" def detect_crossover(hist_history): valid = [h for h in hist_history if h is not None][-3:] if len(valid) < 2: return None return "bull" if valid[-2] < 0 and valid[-1] >= 0 else "bear" if valid[-2] > 0 and valid[-1] <= 0 else None def check_exit(pos, price, ind): if not pos: return None e, d, stop, tp, tr = pos["entry"], pos["dir"], pos["stop"], pos["tp"], pos["trail_trigger"] if d == "BUY": if price <= stop: return "🛑 STOP LOSS" if price >= tp: return "✅ TAKE PROFIT" if pos.get("trail_active") and price <= (price - CONFIG["sl_mult"]*ind["atr"]): return "🔄 TRAILING" if not pos.get("trail_active") and price >= tr: pos["trail_active"] = True return None if ind["hist"] < 0 and pos.get("macd_hist", 0) > 0 and ind["hist"] < -abs(ind["m"])*0.3: return "⚠️ MACD Reverteu" else: if price >= stop: return "🛑 STOP LOSS" if price <= tp: return "✅ TAKE PROFIT" if not pos.get("trail_active") and price <= tr: pos["trail_active"] = True return None if ind["hist"] > 0 and pos.get("macd_hist", 0) < 0 and ind["hist"] > abs(ind["m"])*0.3: return "⚠️ MACD Reverteu" return None def calc_exits(entry, atr, d): sd, td, trd = atr*CONFIG["sl_mult"], atr*CONFIG["tp_mult"], atr*CONFIG["trail_trigger"] if d == "BUY": return {"stop": entry-sd, "tp": entry+td, "trail_trigger": entry+trd, "sl_dist": sd, "tp_dist": td} return {"stop": entry+sd, "tp": entry-td, "trail_trigger": entry-trd, "sl_dist": sd, "tp_dist": td} # 📱 TELEGRAM & ESTADO def send_alert(msg, cooldown=300): if time.time() - STATE["last_alert_ts"] < cooldown: return try: url = f"https://api.telegram.org/bot{CONFIG['telegram_token']}/sendMessage" requests.post(url, json={"chat_id": CONFIG["telegram_chat_id"], "text": msg, "parse_mode": "HTML"}, timeout=5) STATE["last_alert_ts"] = time.time() logging.info("📤 Alerta enviado ao Telegram") except Exception as e: logging.error(f"Falha Telegram: {e}") def load_state(): global STATE try: with open(STATE_FILE, "r") as f: STATE = {**DEFAULT_STATE, **json.load(f)} except (FileNotFoundError, json.JSONDecodeError): STATE = DEFAULT_STATE.copy() logging.info("📂 Estado carregado") def save_state(): try: with open(STATE_FILE, "w") as f: json.dump(STATE, f, indent=2) except Exception as e: logging.error(f"Erro ao salvar estado: {e}") # 🔄 ENGINE PRINCIPAL def run_cycle(): logging.info(f"🔍 Analisando {CONFIG['symbol']}...") klines = fetch_klines(CONFIG["symbol"], CONFIG["interval"], CONFIG["limit"]) if len(klines) < 30: logging.warning("Dados insuficientes. Aguardando...") return closes = [float(k[4]) for k in klines] price = closes[-1] rsi = calc_rsi(closes, 14) atr = calc_atr(klines, 14) ema21 = calc_ema(closes, 21) or price macd = calc_macd(closes) # Atualiza histórico MACD if macd["last"] and macd["last"]["h"] is not None: STATE["hist_history"].append(macd["last"]["h"]) if len(STATE["hist_history"]) > 20: STATE["hist_history"] = STATE["hist_history"][-20:] cross = detect_crossover(STATE["hist_history"]) vol_ok, vol_txt = check_volatility(atr, price) trend = "alta" if price > ema21*1.01 else "baixa" if price < ema21*0.99 else "lateral" # Lógica de Entrada act, msg = None, "Aguardando..." buy_ok = cross=="bull" and vol_ok and trend=="alta" and CONFIG["rsi_buy_min"]<=rsi<=CONFIG["rsi_buy_max"] sell_ok = cross=="bear" and vol_ok and trend=="baixa" and CONFIG["rsi_sell_min"]<=rsi<=CONFIG["rsi_sell_max"] if buy_ok: act, msg = "BUY", "🟢 COMPRA CONFIRMADA" elif sell_ok: act, msg = "SELL", "🔴 VENDA CONFIRMADA" elif not vol_ok: msg = f"⏳ Volatilidade {vol_txt}" elif not cross: msg = "⏳ Aguardando MACD..." else: msg = "⏳ Confirmando..." logging.info(f"📊 RSI:{rsi:.1f} | ATR%:{(atr/price*100):.2f} | EMA21:{ema21:.2f} | MACD:{msg}") # Executa Entrada if act and act != STATE["last_signal"] and not STATE["position"]: exits = calc_exits(price, atr, act) STATE["position"] = {"entry": price, "dir": act, **exits, "active": True, "opened": datetime.now(timezone.utc).isoformat(), "macd_hist": macd["last"]["h"]} STATE["last_signal"] = act save_state() alert = f"🎯 {act} em {CONFIG['symbol']}\n💰 $ {price:.2f}\n📊 RSI:{rsi:.1f} • ATR%:{(atr/price*100):.2f}%\n🛑 SL:${exits['stop']:.2f} • ✅ TP:${exits['tp']:.2f}" send_alert(alert) logging.info(f"🎯 Nova posição {act} aberta") # Verifica Saída if STATE["position"]: exit_reason = check_exit(STATE["position"], price, {"atr": atr, "hist": macd["last"]["h"], "m": macd["last"]["m"]}) if exit_reason: pnl = (price - STATE["position"]["entry"])*(1 if STATE["position"]["dir"]=="BUY" else -1)/STATE["position"]["entry"]*100 alert = f"{exit_reason} em {CONFIG['symbol']}\n💰 Entrada: $ {STATE['position']['entry']:.2f}\n🔚 Saída: $ {price:.2f}\n📊 P&L: {pnl:+.2f}%" send_alert(alert) logging.info(f"🔚 Posição fechada: {exit_reason} | P&L: {pnl:+.2f}%") STATE["position"] = None save_state() logging.info(f"⏳ Próxima verificação em {CONFIG['check_interval']}s\n") # 🚀 INICIALIZAÇÃO if __name__ == "__main__": load_state() logging.info("🎯 Timing Pro ULTRA - Python Edition iniciado") logging.info("Pressione Ctrl+C para parar") try: while True: run_cycle() time.sleep(CONFIG["check_interval"]) except KeyboardInterrupt: logging.info("⏸️ Bot interrompido pelo usuário") save_state() let audioCtx; function beep(f=800,d=0.15){if(!audioCtx)audioCtx=new(window.AudioContext||window.webkitAudioContext)();const o=audioCtx.createOscillator(),g=audioCtx.createGain();o.connect(g);g.connect(audioCtx.destination);o.frequency.value=f;o.type='sine';g.gain.setValueAtTime(0.2,audioCtx.currentTime);g.gain.exponentialRampToValueAtTime(0.01,audioCtx.currentTime+d);o.start();o.stop(audioCtx.currentTime+d);} document.addEventListener('touchstart',()=>{if(!audioCtx)beep();},{once:true}); async function fetchSafe(url,retries=2){for(let i=0;isetTimeout(r,1000*(i+1)));}}return null;} // Seleção de Ativos document.querySelectorAll('.asset-btn').forEach(btn=>{ btn.addEventListener('click',()=>{ document.querySelectorAll('.asset-btn').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); $('sym').value = btn.dataset.sym; run(); }); }); $('sym').value = 'BTCUSDT'; // INDICADORES const getSMA=(d,p,f=4)=>d.lengtha+parseFloat(c[f]),0)/p; const getRSI=(d,p=14)=>{if(d.length<=p)return 50;let g=0,l=0;for(let i=d.length-p;i=0?g+=df:l-=df;}let ag=g/p,al=l/p;for(let i=d.length-p+1;i{if(d.length<=p)return null;const trs=[];for(let i=1;ix+y,0)/p;for(let i=p;i{if(d.length({h:parseFloat(k[2]),l:parseFloat(k[3])})).slice(-lb*3);let s=null,r=null;for(let i=lb;ix.l))&&Lx.l));const isHi=H>Math.max(...ps.slice(i-lb,i).map(x=>x.h))&&H>Math.max(...ps.slice(i+1,i+1+lb).map(x=>x.h));if(isLo&&(!s||L>s))s=L;if(isHi&&(!r||H{if(data.lengths+parseFloat(c[4]),0)/fast,eS=data.slice(0,slow).reduce((s,c)=>s+parseFloat(c[4]),0)/slow;for(let i=fast;i{if(!tr||tr.length===0)return{bp:'50.0',sp:'50.0',d:'0.0'};let b=0,s=0,tr.forEach(t=>{const pr=parseFloat(t.price),qy=parseFloat(t.qty);if(pr>0&&qy>0){const v=pr*qy;t.isBuyerMaker?s+=v:b+=v;}});const T=b+s||1;return{bp:(b/T*100).toFixed(1),sp:(s/T*100).toFixed(1),d:((b-s)/T*100).toFixed(1)};}; const analyzeVol=(d,p)=>{if(d.lengthparseFloat(k[5]));const av=vs.slice(-p).reduce((a,b)=>a+b,0)/p;return{cr:vs[vs.length-1],av,r:(vs[vs.length-1]/av).toFixed(2),ab:vs[vs.length-1]>av*1.3};}; // CÁLCULOS PRINCIPAIS const calcTiming=({price,ma9,ma21,rsi,atr,atrP,trend,sw,fl,vl})=>{let sc=50,f=[];if(trend==='up'&&price>ma21){sc+=15;f.push('✅ Tendência alta');}else if(trend==='down'&&price=40&&rsi<=65)||(trend==='down'&&rsi>=35&&rsi<=60)){sc+=20;f.push('✅ RSI neutro');}else if(rsi<=30&&trend==='up'){sc+=15;f.push('✅ Sobrevenda');}else f.push(`⚪ RSI: ${rsi.toFixed(1)}`);if(vl.ab){sc+=15;f.push('✅ Volume alto');}else f.push('⚪ Vol médio');const dl=parseFloat(fl.d);if((trend==='up'&&dl>10)||(trend==='down'&&dl<-10)){sc+=15;f.push(`✅ Delta: ${dl}%`);}else if(Math.abs(dl)<=5)f.push('⚪ Fluxo neutro');else{f.push(`⚠️ Delta contra`);sc-=5;}if(atrP>=0.3&&atrP<=3){sc+=10;f.push('✅ Volatilidade OK');}return{sc:Math.max(0,Math.min(100,sc)),f};}; const calcLevels=({price,atr,sw,trend})=>{const as=atr*1.5;if(trend==='up'){const en=sw.s&&sw.sprice?Math.min(sw.r,price*1.002):price*1.001;const ss=sw.r?sw.r*1.005:null;const st=ss?Math.max(ss,price+as):price+Math.max(as,0.01);return{en,st,rs:st-en,tg:[{l:en-(st-en)*1.5},{l:en-(st-en)*2.5},{l:en-(st-en)*4}],tp:'Short'};}; // ATUALIZAÇÃO UI function updateUI(d){ if(d.pbData){$('pbCard').style.display='block';$('pbTitle').textContent=`📊 Pullback ${d.pbData.subTF} vs ${d.pbData.mainTF}`;$('pbStrength').textContent=d.pbData.strength;$('pbStrength').className=`pb-strength ${d.pbData.strengthClass}`;$('pbDirection').textContent=d.pbData.isOpposite?`🔴 ${d.pbData.subTF}: ${d.pbData.trendSub==='up'?'ALTA':'BAIXA'} | ${d.pbData.mainTF}: ${d.pbData.trendMain==='up'?'ALTA':'BAIXA'}`:`✅ Ambos: ${d.pbData.trendMain==='up'?'ALTA':'BAIXA'}`;$('pbDirection').className=`pb-dir ${d.pbData.isOpposite?'opp':'same'}`;$('macdFill').style.width=`${Math.min(100,Math.abs(d.pbData.macd.histogram)/2)}%`;$('macdFill').className=`macd-fill ${d.pbData.macd.macd>=0?'macd-pos':'macd-neg'}`;$('macdVal').textContent=fmt(d.pbData.macd.macd,2);$('signalVal').textContent=fmt(d.pbData.macd.signal,2);$('macdVal').style.color=d.pbData.macd.macd>=0?'var(--suc)':'var(--dan)';$('pbAnalysis').innerHTML=d.pbData.analysis;}else{$('pbCard').style.display='none';} $('tScore').textContent=Math.round(d.tm.sc);$('tBadge').textContent=d.tm.sc>=80?'✅ Ex':d.tm.sc>=65?'🟢 Bom':d.tm.sc>=50?'🟡 Reg':'🔴 Ev';$('tBadge').className=`badge ${d.tm.sc>=80?'bull':d.tm.sc>=65?'neut':'bear'}`;$('tFill').style.width=`${d.tm.sc}%`;$('tFill').textContent=Math.round(d.tm.sc);$('tFill').className=`fill ${d.tm.sc>=80?'ex':d.tm.sc>=65?'gd':d.tm.sc>=50?'wt':'av'}`;$('tLabel').textContent=d.tm.sc>=80?'🎯 Entrada ideal!':d.tm.sc>=65?'✅ Boa chance':d.tm.sc>=50?'⏳ Aguarde':'🚫 Evite';$('tFacs').innerHTML=d.tm.f.slice(0,4).map(x=>`
${x}
`).join('');$('tRec').innerHTML=d.tm.sc>=80?`✅ Entrar ${d.lv.tp}. Confluência alta.`:d.tm.sc>=65?`🟢 Pode entrar. Confirme vol.`:d.tm.sc>=50?`🟡 Aguarde.`:`🔴 Não entre.`; $('ePrice').textContent=fp(d.lv.en);$('sPrice').textContent=fp(d.lv.st);$('rUnit').textContent=`$${fmt(d.lv.rs,4)}`;$('lot').textContent=`${fmt(d.pos,5)} un.`;d.lv.tg.forEach((x,i)=>$(`t${i+1}`).textContent=fp(x.l));const rr=d.lv.tg.reduce((a,x)=>a+1.5*0.3+2.5*0.3+4*0.3,0);$('rrV').textContent=`${rr.toFixed(1)}x`; $('cBadge').textContent=d.cond.l;$('cBadge').className=`badge ${d.cond.c}`;$('fBuy').textContent=`${d.fl.bp}%`;$('fSell').textContent=`${d.fl.sp}%`;$('fDel').innerHTML=`${parseFloat(d.fl.d)>=0?'+':''}${d.fl.d}%`;$('fVol').textContent=`${d.vl.r}x ${d.vl.ab?'🟢':'⚪'}`;$('fAtr').textContent=d.atr?`$${fmt(d.atr)}`:'---'; $('conf').innerHTML=[{o:d.trend==='up'&&parseFloat(d.fl.d)>10,t:'Delta favorável'},{o:d.vl.ab,t:'Volume confirmando'},{o:Math.abs(d.rsi-50)<20,t:'RSI neutro'}].map(x=>`
${x.o?'✅':'⚪'} ${x.t}
`).join(''); const ok=d.tm.sc>=65&&d.lv.rs>0;$('sig').textContent=ok?(d.lv.tp==='Long'?'🟢 COMPRA':'🔴 VENDA'):'⏳ AGUARDAR';$('sig').className=`badge ${ok?(d.lv.tp==='Long'?'bull':'bear'):'neut'}`;$('txt').textContent=ok?`Entrar em ${d.lv.tp}`:'Aguardar setup'; $('plan').innerHTML=`
  • Entrada: ${fp(d.lv.en)} (${d.lv.tp})
  • Stop: ${fp(d.lv.st)} (${fmt(d.lv.rs/d.lv.en*100,2)}%)
  • Alvos: T1:${fp(d.lv.tg[0].l)} | T2:${fp(d.lv.tg[1].l)} | T3:${fp(d.lv.tg[2].l)}
  • `; } async function run(){ const sym=$('asset-btn.active').dataset.sym;const tf=$('tf').value;const risk=parseFloat($('risk').value)||1; $('btn').disabled=true;$('btn').textContent='⏳ Buscando...';$('stBadge').className='badge neut';$('stBadge').textContent='🟡 Conectando';log(`🔄 Analisando ${sym} (${tf})...`); try{ const k=await fetchSafe(`${CFG.API}/klines?symbol=${sym}&interval=${tf}&limit=100`); const tr=await fetchSafe(`${CFG.API}/trades?symbol=${sym}&limit=200`); if(!k?.length)throw new Error('Dados insuficientes'); const price=parseFloat(k[k.length-1][4]),ma9=getSMA(k,9),ma21=getSMA(k,21),rsi=getRSI(k,14); const trend=ma9>ma21?'up':'down',atr=calcATR(k,14),atrP=atr?(atr/price)*100:1; const sw=findSwings(k,10),vl=analyzeVol(k,20),fl=analyzeFlow(tr||[]); const tmData=calcTiming({price,ma9,ma21,rsi,atr,atrP,trend,sw,fl,vl}); const lv=calcLevels({price,atr,sw,trend}),cond=atrP>2?{l:'🌪️ Volátil',c:'bear'}:{l:'⚖️ Neutro',c:'neut'}; const bal=1000,rAmt=bal*(risk/100),pos=lv.rs>0?rAmt/lv.rs:0; // Pullback simplificado let kSub,macdSub,trendSub=null;const subTF=tf==='1h'?'15m':'5m'; kSub=await fetchSafe(`${CFG.API}/klines?symbol=${sym}&interval=${subTF}&limit=100`); if(kSub&&kSub.length>26){macdSub=getMACD(kSub);trendSub=getSMA(kSub,9)>getSMA(kSub,21)?'up':'down';} const pbData=macdSub&&trendSub?{subTF,mainTF:tf,trendMain:trend,trendSub,macd:macdSub,isOpposite:trend!==trendSub,strength:trend!==trendSub?(Math.abs(macdSub.macd)>50?'Forte 🔴':'Fraco ✅'):'Alinhado ✅',strengthClass:trend!==trendSub?'strong':'weak',analysis:trend!==trendSub?'⏳ Pullback detectado':'✅ Timeframes alinhados'}:null; updateUI({sym,price,ma9,ma21,rsi,trend,atr,atrP,sw,fl,vl,cond,tm:tmData,lv,pos,risk,pbData}); $('stBadge').className='badge bull';$('stBadge').textContent='🟢 Online'; log(`✅ ${sym} | Score: ${tmData.sc}/100 | ${lv.tp}`); $('upd').textContent=tm(Date.now());$('cache').textContent=`K:${k.length} T:${tr?.length||0}`; if(tmData.sc>=80)beep(900,0.2); }catch(e){log(`❌ Erro: ${e.message}`);$('stBadge').className='badge bear';$('stBadge').textContent='🔴 Erro';} finally{$('btn').disabled=false;$('btn').textContent='🔍 CALCULAR TIMING';} } let autoT=null;function togAuto(){if(autoT){clearInterval(autoT);autoT=null;$('autoBtn').textContent='🔄 Auto: OFF';log('⏸️ Auto desligado');}else{$('autoBtn').textContent='🔄 Auto: ON';log('🔄 Auto a cada 60s');run();autoT=setInterval(()=>{if(document.visibilityState==='visible')run();},60000);}} log('📱 Carregado. Toque para ativar áudio e calcular.');