Email o contraseña incorrectos
Cuentas de demostración
🛡️ Panel de Administración ACCESO RESTRINGIDO
Gestión de usuarios, planes y balances · Sbalott FX
Total Usuarios
—
Activos Ahora
—
Balance Total
—
Operaciones Total
—
Usuario Plan Balance Operaciones Última conexión Cambiar Plan Acciones
--:--:-- UTC
DEMO · $100,000
📰 Noticias de Mercado
⚙️ Ajustes
🔔 Alertas de Precio
NUEVA ALERTA
📅 Calendario Económico
📊 Informes de Cuenta
Nuevo Orden
EUR/USD
Euro / Dólar
Vender BID
—
—
Comprar ASK
—
 
FX
Crypto
Todo
Posiciones
Órdenes
Historial
Mensajes 0
ParDir.LotesAperturaActualP&LExposiciónMargen✕
🤖 IA Trader
📊 Profundidad
🤖
NexAI Analyzer
● Claude-powered
🤖 NexAI
¡Hola! Soy tu asistente IA. Pregúntame sobre análisis, señales, o gestión de riesgo en cualquier par.
ASKVol
Spread: — pip
BIDVol
EUR/USD
O: —H: —L: —C: —
✏️Lápiz
╱LíneaL
→Rayo
⟋Segmento
〰Polilínea
▭Rectángulo
△Triángulo
○Elipse
⬡Polígono
▲Signal Up
▼Signal Down
—Nivel de precioAlt+H
│Marcador de tiempoAlt+V
⇔Canal
〰Canal StdDev
TₜTexto
☐Cuadro de texto
⟋Ángulo de Gann
⊞Rejilla de Gann
⟁Fibonacci Fan Lines
◠Fibonacci Fan Arcs
≡Fibonacci Retracements
⏱Fibonacci Time Zones
↗Fibonacci Expansion
SMA (20)
EMA (50)
Bollinger Bands
RSI (14)
MACD
Volumen
Herramienta: Cursor
NEGOCIACIÓN — EUR/USD
Vender BID
—
—
Comprar ASK
—
 
Posiciones Abiertas
Objetos Dibujados
BID
—
ASK
—
Spread
—
Cambio 24h
—
Máx
—
Mín
—
Velas
—
Balance
$100,000.00
Equity
$100,000.00
Margen
$0.00
Libre
$100,000.00
P&L Flotante
$0.00
Posiciones
0
Sbalott FX · DEMO · DEMO2bPCWC
`; popup.document.open(); popup.document.write(html); popup.document.close(); // Feed live updates to this popup const interval = setInterval(()=>{ if(!popup||popup.closed){ clearInterval(interval); delete popupWindows[pair]; updWinCount(); return; } try{ const pp=AP[pair]; popup.postMessage({type:'tick',pair,bid:pp.bid,ask:pp.ask,dig:pp.dig,hist:S.hist[pair].slice(-3)},'*'); }catch(e){} }, 900); updWinCount(); return popup; } function updWinCount(){ getOpenWindowCount(); // cleans closed ones const count = Object.keys(popupWindows).length; const badge = document.getElementById('winCountBadge'); const txt = document.getElementById('winCountTxt'); if(!badge||!txt) return; const effectivePlan = S.isAdmin ? 'master' : (S.plan||'free'); const limit = PLAN_WINDOW_LIMITS[effectivePlan] ?? 0; if(count > 0 && limit > 0){ badge.style.display = 'inline-flex'; txt.textContent = count + (limit === Infinity ? '' : '/'+limit); } else { badge.style.display = 'none'; } } // ══════════════════════════════════════════════════════════════ // BOT AUTOMÁTICO — Plan Master Only // ══════════════════════════════════════════════════════════════ let _botOrderCount = 0; let _botPL = 0; function isMasterPlan(){ return S.isAdmin || S.plan === 'master'; } function updBotVisibility(){ const locked = document.getElementById('botLocked'); const panel = document.getElementById('botPanel'); const section= document.getElementById('botSection'); if(!locked||!panel||!section)return; if(isMasterPlan()){ locked.style.display='none'; panel.style.display='block'; section.style.display='block'; } else { panel.style.display='none'; locked.style.display='block'; section.style.display='block'; // If bot was running and user lost master plan, stop it if(S.botActive) stopBot(); } } function botLog(msg, type='info'){ const log = document.getElementById('botLog'); if(!log)return; const now = new Date(); const t = now.getHours().toString().padStart(2,'0')+':'+now.getMinutes().toString().padStart(2,'0')+':'+now.getSeconds().toString().padStart(2,'0'); const entry = document.createElement('div'); entry.className = 'bl-entry'; entry.innerHTML = `${t}${msg}`; log.appendChild(entry); // Keep last 80 entries while(log.children.length > 80) log.removeChild(log.firstChild); log.scrollTop = log.scrollHeight; } function placeOrder(dir, pair, vol){ // Direct order placement used by bot (bypasses modal) const p = AP[pair]; if(!p)return; const price = dir==='BUY' ? p.ask : p.bid; const mgn = (vol * 1e5 * price / 100); const pos = { id: Date.now(), pair, dir, vol: parseFloat(vol), openPrice: price, currentPrice: price, sl:'—', tp:'—', mgn, pl: 0, time: new Date().toLocaleTimeString() }; S.positions.push(pos); addMsg(`🤖 BOT ${dir} ${vol}L ${pair} @ ${price.toFixed(p.dig)}`,'ok'); showNotif(`🤖 ${dir==='BUY'?'▲':'▼'} ${pair} ${dir} ${vol}L @ ${price.toFixed(p.dig)}`,'ok'); rendPos(); updAcct(); updFSPositions(); saveUserData(); // Update bot stats _botOrderCount++; const countEl = document.getElementById('botOrderCount'); if(countEl) countEl.textContent = _botOrderCount; } function runBotCycle(){ if(!S.botActive || !isMasterPlan()) return; const pair = S.pair; const hist = S.hist[pair]; if(!hist || hist.length < 5){ botLog(`⚠ Sin suficiente historial para ${pair}`, 'warn'); return; } // Take last 5 candle values and compute slope const last5 = hist.slice(-5).map(d=>d.y); const first = last5[0]; const last = last5[last5.length-1]; const slope = last - first; const p = AP[pair]; const slopeStr = (slope >= 0 ? '+' : '') + slope.toFixed(p.dig); if(slope > 0){ botLog(`📈 ${pair} — pendiente +${slope.toFixed(p.dig)} → BUY 0.01L`, 'buy'); placeOrder('BUY', pair, 0.01); } else if(slope < 0){ botLog(`📉 ${pair} — pendiente ${slope.toFixed(p.dig)} → SELL 0.01L`, 'sell'); placeOrder('SELL', pair, 0.01); } else { botLog(`➡ ${pair} — pendiente neutra (${slopeStr}), sin acción`, 'info'); } // Update P&L display const botPositions = S.positions.slice(-_botOrderCount); _botPL = botPositions.reduce((sum,pos)=>sum+pos.pl, 0); const plEl = document.getElementById('botPL'); if(plEl){ plEl.textContent = (_botPL>=0?'+':'')+'$'+_botPL.toFixed(2); plEl.style.color = _botPL>=0?'var(--g)':'var(--r)'; } } function startBot(){ if(!isMasterPlan()){ showPricingScreen('Bot Automático'); return; } S.botActive = true; // Update toggle UI const toggle = document.getElementById('botToggle'); const lbl = document.getElementById('botToggleLbl'); const dot = document.getElementById('botDot'); const badge = document.getElementById('botBadge'); if(toggle) toggle.classList.add('on'); if(lbl) lbl.textContent='ON'; if(dot) dot.classList.add('on'); if(badge) badge.style.display='inline-flex'; botLog('🚀 Bot iniciado — analizando cada 15 segundos', 'info'); // Run immediately then every 15s runBotCycle(); S.botInterval = setInterval(runBotCycle, 15000); } function stopBot(){ S.botActive = false; if(S.botInterval){ clearInterval(S.botInterval); S.botInterval=null; } const toggle = document.getElementById('botToggle'); const lbl = document.getElementById('botToggleLbl'); const dot = document.getElementById('botDot'); const badge = document.getElementById('botBadge'); if(toggle) toggle.classList.remove('on'); if(lbl) lbl.textContent='OFF'; if(dot) dot.classList.remove('on'); if(badge) badge.style.display='none'; botLog('⏹ Bot detenido', 'warn'); } function toggleBot(){ if(!isMasterPlan()){ showPricingScreen('Bot Automático'); return; } if(S.botActive) stopBot(); else startBot(); } // Listen for orders coming back from popups window.addEventListener('message', e=>{ if(!e.data||e.data.source!=='sbalottfx_popup') return; const {pair, dir, vol, price} = e.data; const p = AP[pair]; if(!p) return; const mgn = (vol*100000*price/100); const pos = {id:Date.now(),pair,dir,vol:parseFloat(vol),openPrice:price,currentPrice:price,sl:'—',tp:'—',mgn,pl:0,time:new Date().toLocaleTimeString()}; S.positions.push(pos); addMsg(`✅ ${dir} ${vol}L ${pair} @ ${price.toFixed(p.dig)} (ventana popup)`,'ok'); showNotif(`${dir==='BUY'?'▲':'▼'} ${pair} ${dir} ${vol}L @ ${price.toFixed(p.dig)}`,'ok'); rendPos(); updAcct(); }); function chgFSTF(tf){ S.fsTF=tf;document.querySelectorAll('.fstf').forEach(b=>b.classList.toggle('active',b.textContent===tf)); rebuildH(S.fsPair,300);buildFSChart(); } function buildFSChart(){ const pair=S.fsPair,p=AP[pair]; const wrap=document.getElementById('fsWrap'); const cv=document.getElementById('fsChartCanvas'); cv.width=wrap.clientWidth;cv.height=wrap.clientHeight; // Also size draw canvas const dc=document.getElementById('fsDrawCanvas'); dc.width=wrap.clientWidth;dc.height=wrap.clientHeight; S.drawCanvas=dc;S.drawCtx=dc.getContext('2d'); const ctx=cv.getContext('2d'),h=S.hist[pair]; const grad=ctx.createLinearGradient(0,0,0,cv.height); grad.addColorStop(0,p.col+(S.fsChartType==='area'?'55':'20')); grad.addColorStop(1,p.col+'00'); const datasets=[{data:h.map(d=>({x:d.x,y:d.y})),borderColor:p.col,backgroundColor:S.fsChartType==='area'?grad:'transparent',borderWidth:2,pointRadius:0,fill:S.fsChartType==='area',tension:S.fsChartType==='line'||S.fsChartType==='area'?.15:0}]; // Add MA indicator if(S.indicators.ma){const ma=calcSMA(h.map(d=>d.y),20);datasets.push({data:h.slice(19).map((d,i)=>({x:d.x,y:ma[i]})),borderColor:'#ff9100',borderWidth:1.5,pointRadius:0,fill:false,tension:.2,label:'SMA20'});} if(S.indicators.ema){const ema=calcEMA(h.map(d=>d.y),50);datasets.push({data:h.slice(49).map((d,i)=>({x:d.x,y:ema[i]})),borderColor:'#ce93d8',borderWidth:1.5,pointRadius:0,fill:false,tension:.2,label:'EMA50'});} if(S.indicators.bb){const bb=calcBB(h.map(d=>d.y),20);datasets.push({data:h.slice(19).map((d,i)=>({x:d.x,y:bb.upper[i]})),borderColor:'rgba(0,176,255,.4)',borderWidth:1,pointRadius:0,fill:false,tension:.2,borderDash:[4,4]},{data:h.slice(19).map((d,i)=>({x:d.x,y:bb.lower[i]})),borderColor:'rgba(0,176,255,.4)',borderWidth:1,pointRadius:0,fill:false,tension:.2,borderDash:[4,4]});} if(S.fsChart)S.fsChart.destroy(); S.fsChart=new Chart(ctx,{type:'line',data:{datasets},options:{responsive:false,animation:false,scales:{x:{type:'linear',grid:{color:'rgba(30,45,72,.6)'},ticks:{color:'#8a9bc0',font:{family:'JetBrains Mono',size:11},maxTicksLimit:12,callback:v=>{const d=new Date(v);return d.getHours().toString().padStart(2,'0')+':'+d.getMinutes().toString().padStart(2,'0');}}},y:{position:'right',grid:{color:'rgba(30,45,72,.6)'},ticks:{color:'#8a9bc0',font:{family:'JetBrains Mono',size:11},maxTicksLimit:8}}},plugins:{legend:{display:false},tooltip:{backgroundColor:'#0d1525',borderColor:'#2a3f60',borderWidth:1,bodyColor:'#e8f0fe',bodyFont:{family:'JetBrains Mono',size:13},callbacks:{label:c=>`${pair}: ${c.parsed.y.toFixed(p.dig)}`}}}}}); // Setup draw canvas events setupDrawEvents(); // Update status updFSStatus(); updFSOrderPanel(); // Redraw existing drawings redrawAll(); } function updFSLive(){ if(!document.getElementById('fso').classList.contains('show'))return; const pair=S.fsPair,p=AP[pair]; if(S.fsChart){S.fsChart.data.datasets[0].data=S.hist[pair].map(d=>({x:d.x,y:d.y}));S.fsChart.update('none');} updFSStatus();updFSOrderPanel();updFSPositions(); } function updFSStatus(){ const pair=S.fsPair,p=AP[pair]; const h=S.hist[pair].map(x=>x.y); const hi=Math.max(...h),lo=Math.min(...h); const chg=((h[h.length-1]-h[0])/h[0]*100).toFixed(2); document.getElementById('fsSBid').textContent=p.bid.toFixed(p.dig); document.getElementById('fsSAsk').textContent=p.ask.toFixed(p.dig); document.getElementById('fsSpr').textContent=((p.ask-p.bid)/p.pip).toFixed(1)+' pip'; const ce=document.getElementById('fsChg');ce.textContent=(parseFloat(chg)>=0?'+':'')+chg+'%';ce.className='fssv'+(parseFloat(chg)>=0?' up':' dn'); document.getElementById('fsSH').textContent=hi.toFixed(p.dig); document.getElementById('fsSL').textContent=lo.toFixed(p.dig); document.getElementById('fsSCount').textContent=S.hist[pair].length; // OHLC const last5=S.hist[pair].slice(-5); document.getElementById('fsO').textContent=last5[0]?.y.toFixed(p.dig)||'—'; document.getElementById('fsH').textContent=Math.max(...last5.map(x=>x.y)).toFixed(p.dig); document.getElementById('fsL').textContent=Math.min(...last5.map(x=>x.y)).toFixed(p.dig); document.getElementById('fsC').textContent=p.bid.toFixed(p.dig); // Price line const plEl=document.getElementById('fsPL'),plVal=document.getElementById('fsPLVal'); if(S.fsChart){ const yScale=S.fsChart.scales.y; if(yScale){ const yPx=yScale.getPixelForValue(p.bid); plEl.style.top=yPx+'px';plEl.style.display='flex'; plVal.textContent=p.bid.toFixed(p.dig); } } } function updFSOrderPanel(){ const pair=S.fsPair,p=AP[pair]; document.getElementById('fsRpBid').textContent=p.bid.toFixed(p.dig); document.getElementById('fsRpAsk').textContent=p.ask.toFixed(p.dig); document.getElementById('fsRpSpr').textContent='Spread: '+((p.ask-p.bid)/p.pip).toFixed(1)+' pip'; } function updFSPositions(){ const el=document.getElementById('fsPosList'); const pos=S.positions.filter(p=>p.pair===S.fsPair); if(!pos.length){el.innerHTML='
Sin posiciones en este par
';return;} el.innerHTML=pos.map(p2=>{ const p=AP[p2.pair]; return`
${p2.dir==='BUY'?'▲':'▼'} ${p2.vol}L
@ ${p2.openPrice.toFixed(p.dig)}
${p2.pl>=0?'+':''}$${p2.pl.toFixed(2)}
`; }).join(''); } function closeFS(){ document.getElementById('fso').classList.remove('show'); if(S.fsChart){S.fsChart.destroy();S.fsChart=null;} } function reloadFS(){rebuildH(S.fsPair,300);buildFSChart();} // ── Chart type switcher ────────────────────────────────────── function setChartType(t){ S.fsChartType=t; ['line','candle','bar','area'].forEach(x=>document.getElementById('ct-'+x).classList.toggle('on',x===t)); buildFSChart(); } // ── PAIR POPUP ─────────────────────────────────────────────── function buildPairPopup(){ const list=document.getElementById('ppList'); list.innerHTML=Object.keys(AP).map(pair=>{ const p=AP[pair]; return`
${pair}${p.bid.toFixed(p.dig)}
`; }).join(''); } function togglePairPopup(){ const pp=document.getElementById('pairPopup');pp.classList.toggle('show'); if(pp.classList.contains('show'))document.getElementById('ppSearch').focus(); } function filterPairs(){ const q=document.getElementById('ppSearch').value.toLowerCase(); document.querySelectorAll('.pp-item').forEach(el=>{el.style.display=el.textContent.toLowerCase().includes(q)?'':'none';}); } function changeFSPair(pair){ S.fsPair=pair;document.getElementById('fsPairName').textContent=pair;document.getElementById('fsRpPair').textContent=pair; document.getElementById('pairPopup').classList.remove('show'); S.drawings=[]; rebuildH(pair,300);buildFSChart();updFSPositions(); } document.addEventListener('click',e=>{if(!e.target.closest('#pairPopup')&&!e.target.closest('#fsPairBtn'))document.getElementById('pairPopup').classList.remove('show');}); // ── TOOLBAR MENUS ──────────────────────────────────────────── function toggleDrawMenu(){const m=document.getElementById('drawMenu');m.classList.toggle('show');document.getElementById('indMenu').classList.remove('show');} function toggleIndMenu(){const m=document.getElementById('indMenu');m.classList.toggle('show');document.getElementById('drawMenu').classList.remove('show');} document.addEventListener('click',e=>{if(!e.target.closest('#drawMenu')&&!e.target.closest('#drawToggle'))document.getElementById('drawMenu').classList.remove('show');if(!e.target.closest('#indMenu')&&!e.target.closest('#indToggle'))document.getElementById('indMenu').classList.remove('show');}); // ── DRAWING TOOLS ──────────────────────────────────────────── const toolLabels={'cursor':'Cursor','pencil':'Lápiz','line':'Línea','ray':'Rayo','segment':'Segmento','polyline':'Polilínea','rect':'Rectángulo','triangle':'Triángulo','ellipse':'Elipse','polygon':'Polígono','signal_up':'Signal Up','signal_dn':'Signal Down','hline':'Nivel de Precio','vline':'Marcador Tiempo','channel':'Canal','stddev':'Canal StdDev','text':'Texto','textbox':'Cuadro Texto','gann_angle':'Ángulo Gann','gann_grid':'Rejilla Gann','fib_fan':'Fib Fan Lines','fib_arc':'Fib Fan Arcs','fib_ret':'Fib Retracements','fib_time':'Fib Time Zones','fib_exp':'Fib Expansion'}; function selTool(tool){ S.activeTool=tool;S.isDrawing=false;S.drawStart=null; document.getElementById('drawMenu').classList.remove('show'); document.getElementById('currentToolBtn').textContent=toolLabels[tool]||tool; document.getElementById('toolStatus').textContent='Herramienta: '+(toolLabels[tool]||tool); document.getElementById('drawToggle').classList.toggle('tol',tool!=='cursor'); const dc=document.getElementById('fsDrawCanvas'); dc.style.cursor=tool==='cursor'?'default':tool==='pencil'?'crosshair':'crosshair'; // Show active in menu document.querySelectorAll('.dmi').forEach(el=>{el.classList.toggle('sel',el.onclick&&el.onclick.toString().includes(`'${tool}'`));}); } function clearDrawings(){S.drawings=[];redrawAll();updObjList();} function setupDrawEvents(){ const dc=document.getElementById('fsDrawCanvas'); const newDC=dc.cloneNode(true);dc.parentNode.replaceChild(newDC,dc); S.drawCanvas=newDC;S.drawCtx=newDC.getContext('2d'); newDC.addEventListener('mousedown',onDrawStart); newDC.addEventListener('mousemove',onDrawMove); newDC.addEventListener('mouseup',onDrawEnd); newDC.addEventListener('dblclick',onDrawDblClick); newDC.addEventListener('contextmenu',e=>{e.preventDefault();selTool('cursor');}); } function getCanvasPos(e){ const r=S.drawCanvas.getBoundingClientRect(); return{x:e.clientX-r.left,y:e.clientY-r.top}; } function onDrawStart(e){ if(S.activeTool==='cursor')return; const pos=getCanvasPos(e); S.isDrawing=true;S.drawStart=pos; if(S.activeTool==='text'||S.activeTool==='textbox'){ const t=prompt('Ingresa el texto:',''); if(t){S.drawings.push({type:S.activeTool,x:pos.x,y:pos.y,text:t,color:'#ffd600'});redrawAll();updObjList();} S.isDrawing=false;selTool('cursor'); } } function onDrawMove(e){ const pos=getCanvasPos(e); // Update crosshair info if(S.crosshairOn&&S.fsChart){ const xScale=S.fsChart.scales.x,yScale=S.fsChart.scales.y; if(xScale&&yScale){ const d=new Date(xScale.getValueForPixel(pos.x)); const pv=yScale.getValueForPixel(pos.y); const p=AP[S.fsPair]; document.getElementById('fsCH').style.display='block'; document.getElementById('fsCH').textContent=`${d.getHours().toString().padStart(2,'0')}:${d.getMinutes().toString().padStart(2,'0')} | ${pv.toFixed(p.dig)}`; document.getElementById('fsCoords').textContent=`${d.toLocaleDateString('es')} ${d.getHours().toString().padStart(2,'0')}:${d.getMinutes().toString().padStart(2,'0')} | ${pv.toFixed(p.dig)}`; } } if(!S.isDrawing||!S.drawStart||S.activeTool==='cursor')return; redrawAll(); // Preview current drawing const ctx=S.drawCtx; drawShape(ctx,S.activeTool,S.drawStart,pos,'rgba(255,214,0,.8)',true); } function onDrawEnd(e){ if(!S.isDrawing||!S.drawStart||S.activeTool==='cursor')return; const pos=getCanvasPos(e); const drawing={type:S.activeTool,x1:S.drawStart.x,y1:S.drawStart.y,x2:pos.x,y2:pos.y,color:S.activeTool.startsWith('fib')?'#00b0ff':S.activeTool==='hline'?'#ffd600':S.activeTool==='signal_up'?'#00e676':S.activeTool==='signal_dn'?'#ff1744':'rgba(255,214,0,.9)',id:Date.now()}; S.drawings.push(drawing); S.isDrawing=false;S.drawStart=null; redrawAll();updObjList(); } function onDrawDblClick(e){selTool('cursor');} function drawShape(ctx,type,start,end,color,preview){ ctx.strokeStyle=color;ctx.fillStyle=color;ctx.lineWidth=preview?1.5:2;ctx.setLineDash([]); const {x:x1,y:y1}=start,{x:x2,y:y2}=end; ctx.beginPath(); if(type==='line'||type==='segment'){ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke();} else if(type==='ray'){const dx=x2-x1,dy=y2-y1,len=3000;ctx.moveTo(x1,y1);ctx.lineTo(x1+dx*len,y1+dy*len);ctx.stroke();} else if(type==='pencil'){ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke();} else if(type==='hline'){ctx.setLineDash([6,4]);ctx.moveTo(0,y1);ctx.lineTo(S.drawCanvas.width,y1);ctx.stroke();ctx.setLineDash([]);ctx.font='10px JetBrains Mono';ctx.fillText(y1.toFixed(0),4,y1-3);} else if(type==='vline'){ctx.setLineDash([6,4]);ctx.moveTo(x1,0);ctx.lineTo(x1,S.drawCanvas.height);ctx.stroke();ctx.setLineDash([]);} else if(type==='rect'){ctx.strokeRect(x1,y1,x2-x1,y2-y1);ctx.fillStyle=color.replace('.9','0.06');ctx.fillRect(x1,y1,x2-x1,y2-y1);} else if(type==='triangle'){ctx.moveTo(x1,y2);ctx.lineTo((x1+x2)/2,y1);ctx.lineTo(x2,y2);ctx.closePath();ctx.stroke();} else if(type==='ellipse'){ctx.ellipse((x1+x2)/2,(y1+y2)/2,Math.abs(x2-x1)/2,Math.abs(y2-y1)/2,0,0,2*Math.PI);ctx.stroke();} else if(type==='channel'){ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke();const off=30;ctx.moveTo(x1,y1+off);ctx.lineTo(x2,y2+off);ctx.stroke();} else if(type==='signal_up'){ctx.fillStyle=color;ctx.moveTo(x2,y2-15);ctx.lineTo(x2-8,y2);ctx.lineTo(x2+8,y2);ctx.closePath();ctx.fill();} else if(type==='signal_dn'){ctx.fillStyle=color;ctx.moveTo(x2,y2+15);ctx.lineTo(x2-8,y2);ctx.lineTo(x2+8,y2);ctx.closePath();ctx.fill();} else if(type==='fib_ret'){// Fibonacci retracements const levels=[0,0.236,0.382,0.5,0.618,0.786,1]; const dy=y2-y1; levels.forEach((lv,i)=>{ const y=y1+dy*lv; const colors=['#ff1744','#ff9100','#ffd600','#00e676','#00b0ff','#7c4dff','#ff1744']; ctx.strokeStyle=colors[i];ctx.setLineDash([4,3]); ctx.beginPath();ctx.moveTo(x1,y);ctx.lineTo(S.drawCanvas.width,y);ctx.stroke(); ctx.setLineDash([]);ctx.font='10px JetBrains Mono';ctx.fillStyle=colors[i]; ctx.fillText((lv*100).toFixed(1)+'%',x1+4,y-3); }); ctx.strokeStyle=color;ctx.setLineDash([]); } else if(type==='gann_angle'){// Gann angles const angles=[1,2,3,4,8]; angles.forEach(a=>{ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y1-(y1-y2)*a);ctx.stroke();}); } else if(type==='fib_time'){// Fib time zones const levels=[0,1,2,3,5,8,13,21];const dx=x2-x1; levels.forEach(lv=>{const x=x1+dx*lv;ctx.setLineDash([4,3]);ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,S.drawCanvas.height);ctx.stroke();ctx.setLineDash([]);}); } else{ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke();} } function redrawAll(){ const ctx=S.drawCtx;if(!ctx)return; ctx.clearRect(0,0,S.drawCanvas.width,S.drawCanvas.height); S.drawings.forEach(d=>{ if(d.type==='text'||d.type==='textbox'){ ctx.font='13px JetBrains Mono';ctx.fillStyle=d.color||'#ffd600'; if(d.type==='textbox'){ctx.strokeStyle=d.color;ctx.strokeRect(d.x-4,d.y-16,ctx.measureText(d.text).width+8,20);} ctx.fillText(d.text,d.x,d.y); } else { drawShape(ctx,d.type,{x:d.x1,y:d.y1},{x:d.x2,y:d.y2},d.color||'rgba(255,214,0,.9)',false); } }); } function updObjList(){ const el=document.getElementById('fsObjItems'); if(!S.drawings.length){el.innerHTML='
Sin objetos
';return;} el.innerHTML=S.drawings.map(d=>`
📐 ${toolLabels[d.type]||d.type}✕
`).join(''); } function delObj(id){S.drawings=S.drawings.filter(d=>d.id!==id);redrawAll();updObjList();} // ── INDICATORS ─────────────────────────────────────────────── function toggleInd(name){ S.indicators[name]=!S.indicators[name]; document.getElementById('ind-'+name).classList.toggle('on',S.indicators[name]); buildFSChart(); } function calcSMA(data,n){const r=[];for(let i=n-1;ia+b,0)/n);return r;} function calcEMA(data,n){const k=2/(n+1);let ema=data[0];const r=[];for(let i=1;i=n-1)r.push(parseFloat(ema.toFixed(6)));}return r;} function calcBB(data,n,std=2){const sma=calcSMA(data,n);const r={upper:[],lower:[]};for(let i=n-1;ia+Math.pow(b-mean,2),0)/n;const sd=Math.sqrt(variance);r.upper.push(parseFloat((mean+std*sd).toFixed(6)));r.lower.push(parseFloat((mean-std*sd).toFixed(6)));}return r;} // ── TOOLBAR EXTRAS ─────────────────────────────────────────── function toggleCrosshair(){S.crosshairOn=!S.crosshairOn;document.getElementById('crossBtn').classList.toggle('on',S.crosshairOn);if(!S.crosshairOn)document.getElementById('fsCH').style.display='none';} function toggleMagnet(){S.magnetOn=!S.magnetOn;document.getElementById('magnetBtn').classList.toggle('on',S.magnetOn);} function toggleLock(){S.lockScale=!S.lockScale;document.getElementById('lockBtn').classList.toggle('on',S.lockScale);} function zoomIn(){if(S.fsChart){const x=S.fsChart.scales.x;const r=(x.max-x.min)*.15;S.fsChart.scales.x.min+=r;S.fsChart.scales.x.max-=r;S.fsChart.update('none');}} function zoomOut(){if(S.fsChart){const x=S.fsChart.scales.x;const r=(x.max-x.min)*.15;S.fsChart.scales.x.min-=r;S.fsChart.scales.x.max+=r;S.fsChart.update('none');}} function resetZoom(){if(S.fsChart){delete S.fsChart.scales.x.min;delete S.fsChart.scales.x.max;S.fsChart.update('none');}} // ── FS ORDER ───────────────────────────────────────────────── function tFSPF(){document.getElementById('fsPFG').style.display=document.getElementById('fsOT').value==='market'?'none':'';} function tFSSL(){document.getElementById('fsSLG').style.display=document.getElementById('fsSLC').checked?'':'none';} function tFSTP(){document.getElementById('fsTPG').style.display=document.getElementById('fsTPC').checked?'':'none';} function adjFS(d){const e=document.getElementById('fsVol');e.value=Math.max(.01,parseFloat(e.value)+d).toFixed(2);} function fsOrder(dir){ // Temporarily set S.pair to FS pair, open modal, restore const prev=S.pair;S.pair=S.fsPair; const vol=parseFloat(document.getElementById('fsVol').value||'0.10'); document.getElementById('vol').value=vol.toFixed(2); const slc=document.getElementById('fsSLC').checked; const tpc=document.getElementById('fsTPC').checked; document.getElementById('slc').checked=slc;document.getElementById('tpc').checked=tpc; if(slc)document.getElementById('sli').value=document.getElementById('fsSLI').value; if(tpc)document.getElementById('tpi').value=document.getElementById('fsTPI').value; openModal(dir); S.pair=prev;// restore – modal uses pendOrd which was set with fsPair } // ══════════════════════════════════════════════════════════════ // ORDER FLOW (main + FS) // ══════════════════════════════════════════════════════════════ function tPF(){document.getElementById('pfg').style.display=document.getElementById('ot').value==='market'?'none':'';} function tSL(){document.getElementById('slg').style.display=document.getElementById('slc').checked?'':'none';} function tTP(){document.getElementById('tpg').style.display=document.getElementById('tpc').checked?'':'none';} function adj(d){const e=document.getElementById('vol');e.value=Math.max(.01,parseFloat(e.value)+d).toFixed(2);} function openModal(dir){ const pair=S.pair,p=AP[pair]; const vol=parseFloat(document.getElementById('vol').value); const price=dir==='BUY'?p.ask:p.bid; const mgn=(vol*1e5*price/100).toFixed(2); const sl=document.getElementById('slc').checked?document.getElementById('sli').value:'—'; const tp=document.getElementById('tpc').checked?document.getElementById('tpi').value:'—'; S.pendOrd={pair,dir,vol,price,sl,tp,mgn:parseFloat(mgn)}; document.getElementById('mttl').textContent=dir==='BUY'?'▲ Confirmar COMPRA':'▼ Confirmar VENTA'; document.getElementById('mttl').style.color=dir==='BUY'?'var(--g)':'var(--r)'; document.getElementById('mpair').textContent=pair; document.getElementById('mdir').textContent=dir==='BUY'?'▲ COMPRA':'▼ VENTA'; document.getElementById('mdir').style.color=dir==='BUY'?'var(--g)':'var(--r)'; document.getElementById('mvol').textContent=vol.toFixed(2)+' lotes'; document.getElementById('mprice').textContent=price.toFixed(p.dig); document.getElementById('msl').textContent=sl;document.getElementById('mtp').textContent=tp; document.getElementById('mmgn').textContent='$'+parseFloat(mgn).toLocaleString('es',{minimumFractionDigits:2}); const btn=document.getElementById('mcbtn');btn.textContent=dir==='BUY'?'▲ COMPRAR':'▼ VENDER';btn.className='mb '+(dir==='BUY'?'mb-b':'mb-s'); document.getElementById('omodal').classList.add('show'); } function closeModal(){document.getElementById('omodal').classList.remove('show');S.pendOrd=null;} function execOrder(){ const o=S.pendOrd;if(!o)return;closeModal();const p=AP[o.pair]; const pos={id:Date.now(),pair:o.pair,dir:o.dir,vol:o.vol,openPrice:o.price,currentPrice:o.price,sl:o.sl,tp:o.tp,mgn:o.mgn,pl:0,time:new Date().toLocaleTimeString()}; S.positions.push(pos); addMsg(`✅ ${pos.dir} ${pos.vol} lotes ${pos.pair} @ ${pos.openPrice.toFixed(p.dig)}`,'ok'); showNotif(`${pos.dir==='BUY'?'▲':'▼'} ${pos.pair} ${pos.dir} ${pos.vol}L @ ${pos.openPrice.toFixed(p.dig)}`,'ok'); rendPos();updAcct();updFSPositions(); } // ── POSITIONS ──────────────────────────────────────────────── function updPosPL(){ let tpl=0,tm=0; [...S.positions].forEach(pos=>{ const p=AP[pos.pair];const cur=pos.dir==='BUY'?p.bid:p.ask; pos.currentPrice=cur;const pips=pos.dir==='BUY'?(cur-pos.openPrice)/p.pip:(pos.openPrice-cur)/p.pip; pos.pl=parseFloat((pips*pos.vol*10).toFixed(2));tpl+=pos.pl;tm+=pos.mgn; if(pos.tp!=='—'&&pos.tp&&((pos.dir==='BUY'&&cur>=parseFloat(pos.tp))||(pos.dir==='SELL'&&cur<=parseFloat(pos.tp))))closePos(pos.id,'TP'); if(pos.sl!=='—'&&pos.sl&&((pos.dir==='BUY'&&cur<=parseFloat(pos.sl))||(pos.dir==='SELL'&&cur>=parseFloat(pos.sl))))closePos(pos.id,'SL'); }); S.floatPL=tpl;S.usedMgn=tm;rendPos(); } function rendPos(){ const tb=document.getElementById('posTb'); if(!S.positions.length){tb.innerHTML='Sin posiciones abiertas';return;} tb.innerHTML=S.positions.map(pos=>{const p=AP[pos.pair];return`${pos.pair}${pos.dir==='BUY'?'▲':'▼'} ${pos.dir}${pos.vol.toFixed(2)}${pos.openPrice.toFixed(p.dig)}${pos.currentPrice.toFixed(p.dig)}${pos.pl>=0?'+':''}$${pos.pl.toFixed(2)}$${(pos.vol*1e5*pos.currentPrice).toLocaleString('es',{maximumFractionDigits:0})}$${pos.mgn.toFixed(2)}`;}).join(''); } function closePos(id,reason){ const i=S.positions.findIndex(p=>p.id===id);if(i<0)return; const pos=S.positions[i],p=AP[pos.pair]; S.balance+=pos.mgn+pos.pl;S.history.unshift({...pos,closePrice:pos.currentPrice,closeTime:new Date().toLocaleTimeString(),reason:reason||'Manual'}); S.positions.splice(i,1); const m=`${reason||'Cierre'}: ${pos.pair} P&L ${pos.pl>=0?'+':''}$${pos.pl.toFixed(2)}`; addMsg(reason?`🔔 ${m}`:m,pos.pl>=0?'ok':'err');showNotif(m,pos.pl>=0?'ok':'err'); rendPos();rendHist();updAcct();updFSPositions(); saveUserData(); } function closeAll(){if(!S.positions.length){showNotif('No hay posiciones','err');return;}[...S.positions].forEach(pos=>closePos(pos.id,'Cierre masivo'));showNotif('✅ Todas las posiciones cerradas','ok');} function rendHist(){ const tb=document.getElementById('histTb'); if(!S.history.length){tb.innerHTML='Sin historial';return;} tb.innerHTML=S.history.slice(0,50).map(h=>{const p=AP[h.pair];return`${h.pair}${h.dir}${h.vol.toFixed(2)}${h.openPrice.toFixed(p.dig)}${h.closePrice.toFixed(p.dig)}${h.pl>=0?'+':''}$${h.pl.toFixed(2)}${h.closeTime} · ${h.reason}`;}).join(''); } // ── ACCOUNT ────────────────────────────────────────────────── function updAcct(){ const pl=S.floatPL||0,m=S.usedMgn||0,eq=S.balance+pl; document.getElementById('abal').textContent='$'+S.balance.toLocaleString('es',{minimumFractionDigits:2}); const ae=document.getElementById('aeq');ae.textContent='$'+eq.toLocaleString('es',{minimumFractionDigits:2});ae.className='abv '+(eq>=S.balance?'pos':'neg'); document.getElementById('amgn').textContent='$'+m.toLocaleString('es',{minimumFractionDigits:2}); document.getElementById('afre').textContent='$'+(eq-m).toLocaleString('es',{minimumFractionDigits:2}); const pe=document.getElementById('apl');pe.textContent=(pl>=0?'+':'')+'$'+pl.toFixed(2);pe.className='abv '+(pl>=0?'pos':'neg'); document.getElementById('apos').textContent=S.positions.length; } // ── MESSAGES ───────────────────────────────────────────────── function addMsg(t,type){ S.messages.unshift({t,type,time:new Date().toLocaleTimeString()}); document.getElementById('mb2').textContent=S.messages.length; document.getElementById('mle').innerHTML=S.messages.slice(0,30).map(m=>`
${m.time}${m.t}
`).join(''); } // ── DEPTH ──────────────────────────────────────────────────── function updDepth(){ const p=AP[S.pair],dig=p.dig,asks=[],bids=[]; for(let i=0;i<8;i++){asks.push({price:parseFloat((p.ask+i*p.pip*.6).toFixed(dig)),vol:(Math.random()*8+.5).toFixed(2)});bids.push({price:parseFloat((p.bid-i*p.pip*.6).toFixed(dig)),vol:(Math.random()*8+.5).toFixed(2)});} const mx=Math.max(...asks.map(a=>parseFloat(a.vol)),...bids.map(b=>parseFloat(b.vol))); document.getElementById('dask').innerHTML=[...asks].reverse().map(a=>`
${a.price.toFixed(dig)}
${a.vol}M
`).join(''); document.getElementById('dbid').innerHTML=bids.map(b=>`
${b.price.toFixed(dig)}
${b.vol}M
`).join(''); document.getElementById('dspr').textContent='Spread: '+((p.ask-p.bid)/p.pip).toFixed(1)+' pip'; } // ── TAB SWITCHING ──────────────────────────────────────────── function bTab(t){['pos','ord','hist','msg'].forEach(x=>{document.getElementById('bt-'+x).classList.toggle('active',x===t);document.getElementById('tab-'+x).style.display=x===t?'':'none';});if(t==='msg')document.getElementById('mb2').textContent='0';} function rTab(t){['ai','dep'].forEach(x=>document.getElementById('rt-'+x).classList.toggle('active',x===t));document.getElementById('aip').style.display=t==='ai'?'flex':'none';document.getElementById('dep').classList.toggle('show',t==='dep');} // ── NAV ────────────────────────────────────────────────────── function navGo(panel){ S.curPanel=panel; ['platform','analysis','news','settings','alerts','calendar','reports'].forEach(p=>{const el=document.getElementById('n-'+p);if(el)el.classList.toggle('active',p===panel);}); const ops=['analysis','news','settings','alerts','calendar','reports']; if(panel==='platform'){ document.getElementById('mainPlat').style.display='flex'; ops.forEach(p=>{const el=document.getElementById('op-'+p);if(el){el.classList.remove('show');if(p==='analysis')el.style.display='none';}}); return; } document.getElementById('mainPlat').style.display='none'; ops.forEach(p=>{const el=document.getElementById('op-'+p);if(el){el.classList.remove('show');if(p==='analysis')el.style.display='none';}}); const t=document.getElementById('op-'+panel); if(t){if(panel==='analysis')t.style.display='';t.classList.add('show');} if(panel==='analysis'){ const plan=S.isAdmin?'master':(S.plan||'free'); S.anPair=S.anPair||S.pair||'EUR/USD'; const lbl=document.getElementById('anPairLabel');if(lbl){lbl.textContent=S.anPair;lbl.style.color=AP[S.anPair]?.col||'var(--bl)';} const planEl=document.getElementById('anStatusPlan');if(planEl)planEl.textContent=plan.toUpperCase(); const planEl2=document.getElementById('anPlanLabel');if(planEl2)planEl2.textContent='Plan: '+plan.toUpperCase(); renderAnWatchList();updateAnIndLocks(); setTimeout(()=>{buildAnChart();updateAnPosList();renderAnSignals();renderAnSummary();updateAnIndicatorBar();},60); } else if(panel==='news')rendNews();else if(panel==='settings')rendSettings();else if(panel==='alerts')rendAlerts();else if(panel==='calendar')rendCal();else if(panel==='reports')rendReports(); } // ── AI ──────────────────────────────────────────────────────── async function sendAI(){ const inp=document.getElementById('aiinp');const msg=inp.value.trim();if(!msg)return;inp.value=''; const box=document.getElementById('aimsgs'); box.innerHTML+=`
Tú
${msg}
`; const th=document.createElement('div');th.className='aim ai';th.innerHTML='
🤖 NexAI
Analizando';box.appendChild(th);box.scrollTop=box.scrollHeight; const p=AP[S.pair],h=S.hist[S.pair].slice(-5).map(x=>x.y);const trend=h[h.length-1]>h[0]?'alcista':'bajista'; const sys=`Eres NexAI, experto en trading Forex y Cripto en Sbalott FX. Contexto: Par: ${S.pair} BID:${p.bid.toFixed(p.dig)} Tendencia: ${trend} Posiciones: ${S.positions.length} Balance: $${S.balance.toFixed(2)}. Responde en español, conciso (máx 100 palabras). Señales: ▲ COMPRA / ▼ VENTA.`; try{const r=await fetch('https://api.anthropic.com/v1/messages',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({model:'claude-sonnet-4-20250514',max_tokens:1000,system:sys,messages:[{role:'user',content:msg}]})});const d=await r.json();th.innerHTML=`
🤖 NexAI
${d.content?.[0]?.text||'Error de conexión.'}`;}catch(e){th.innerHTML='
🤖 NexAI
Error de red.';} box.scrollTop=box.scrollHeight; } // ── NOTIFICATIONS ───────────────────────────────────────────── function showNotif(msg,type){const n=document.createElement('div');n.className=`notif ${type}`;n.innerHTML=msg;document.body.appendChild(n);setTimeout(()=>n.remove(),4000);} // ── ANALYSIS ───────────────────────────────────────────────── function rendAnalysis(){ document.getElementById('tc').innerHTML=Object.keys(AP).slice(0,8).map(pair=>{const p=AP[pair];const s=Math.random();const sig=s>.6?'COMPRA':s<.4?'VENTA':'NEUTRAL';const col=sig==='COMPRA'?'var(--g)':sig==='VENTA'?'var(--r)':'var(--go)';const pct=((Math.random()-.5)*1.5).toFixed(2);return`
${pair}
${p.bid.toFixed(p.dig)}
${sig}
${parseFloat(pct)>=0?'+':''}${pct}%
`;}).join(''); const inds=[{n:'RSI (14)',v:(30+Math.random()*40).toFixed(1),s:Math.random()>.5?'▲ Alcista':'Neutral'},{n:'MACD',v:((Math.random()-.5)*.002).toFixed(5),s:Math.random()>.5?'▲ Alcista':'▼ Bajista'},{n:'Bollinger Bands',v:'Precio dentro',s:'Neutral'},{n:'SMA (20)',v:(AP['EUR/USD'].bid*(1+(Math.random()-.5)*.005)).toFixed(5),s:Math.random()>.5?'▲ Sobre SMA':'▼ Bajo SMA'},{n:'EMA (50)',v:(AP['EUR/USD'].bid*(1+(Math.random()-.5)*.01)).toFixed(5),s:Math.random()>.5?'▲ Alcista':'▼ Bajista'},{n:'Estocástico',v:(20+Math.random()*60).toFixed(1),s:'Neutral'},{n:'ATR (14)',v:(Math.random()*.001+.0005).toFixed(5),s:'Volatilidad media'},{n:'CCI',v:((Math.random()-.5)*200).toFixed(1),s:Math.random()>.5?'▲ Sobrecomprado':'▼ Sobrevendido'}]; document.getElementById('ti').innerHTML=inds.map(ind=>{const c=ind.s.includes('▲')?'var(--g)':ind.s.includes('▼')?'var(--r)':'var(--go)';return`
${ind.n}
${ind.v}
${ind.s}
`;}).join(''); const pats=[{n:'Doji',p:'EUR/USD',s:'Indecisión',c:'var(--go)'},{n:'Martillo',p:'GBP/USD',s:'▲ Alcista',c:'var(--g)'},{n:'Estrella Fugaz',p:'USD/JPY',s:'▼ Bajista',c:'var(--r)'},{n:'Engulfing',p:'BTC/USD',s:'▲ Fuerte Compra',c:'var(--g)'},{n:'Tres Cuervos',p:'ETH/USD',s:'▼ Fuerte Venta',c:'var(--r)'},{n:'Harami',p:'SOL/USD',s:'▲ Reversión',c:'var(--g)'}]; document.getElementById('tp').innerHTML=pats.map(pt=>`
${pt.n}
${pt.p}
${pt.s}
`).join(''); } function rendNews(){ const news=[{time:'08:30',imp:'ALTO',flag:'🇺🇸',title:'NFP supera expectativas con 256K empleos',body:'El mercado laboral de EE.UU. sorprende positivamente, fortaleciendo al dólar.',tag:'USD+'},{time:'09:15',imp:'ALTO',flag:'🇪🇺',title:'BCE mantiene tasas en 3.75%',body:'Lagarde señala moderación gradual de la inflación en la Eurozona.',tag:'EUR-'},{time:'10:00',imp:'MEDIO',flag:'🇬🇧',title:'PMI Manufacturero UK: 49.8 vs 50.1 est.',body:'Sector manufacturero en leve contracción, presionando a la libra esterlina.',tag:'GBP-'},{time:'11:45',imp:'ALTO',flag:'🔷',title:'Bitcoin rompe resistencia $63,000',body:'BTC supera zona clave con volumen elevado. Altcoins siguen la tendencia.',tag:'BTC+'},{time:'13:00',imp:'MEDIO',flag:'🇯🇵',title:'BoJ confirma política monetaria laxa',body:'El banco central japonés no modifica el control de la curva de rendimientos.',tag:'JPY-'},{time:'14:30',imp:'ALTO',flag:'🔷',title:'ETF Ethereum reporta $450M de entrada',body:'Flujos positivos en ETFs de ETH generan presión compradora significativa.',tag:'ETH+'}]; document.getElementById('ne').innerHTML=news.map(n=>{const ic=n.imp==='ALTO'?'var(--r)':n.imp==='MEDIO'?'var(--go)':'var(--t3)';const tc=n.tag.endsWith('+')?'var(--g)':'var(--r)';return`
${n.flag}
${n.time}
${n.imp}
${n.title} ${n.tag}
${n.body}
`;}).join(''); } function rendSettings(){ const items=[{l:'Idioma',type:'sel',opts:['Español','English','Português']},{l:'Velocidad precios',type:'sel',opts:['Rápido (800ms)','Normal (1s)','Lento (2s)']},{l:'Apalancamiento',type:'sel',opts:['1:100','1:50','1:30','1:200']},{l:'Moneda de cuenta',type:'sel',opts:['USD','EUR','GBP']},{l:'Confirmación de orden',type:'tog',val:true},{l:'Sonidos de alerta',type:'tog',val:false},{l:'Notificaciones push',type:'tog',val:true},{l:'Modo oscuro',type:'tog',val:true}]; document.getElementById('se').innerHTML=items.map(s=>{if(s.type==='tog')return`
${s.l}
`;return`
${s.l}
`;}).join(''); } function addAlert(){const pair=document.getElementById('alp').value;const cond=document.getElementById('alc').value;const price=parseFloat(document.getElementById('alpr').value);if(!price)return;S.alerts.push({id:Date.now(),pair,cond,price,active:true});document.getElementById('alpr').value='';rendAlerts();showNotif(`🔔 Alerta: ${pair} ${cond==='gte'?'≥':'≤'} ${price}`,'ok');} function rmAlert(id){S.alerts=S.alerts.filter(a=>a.id!==id);rendAlerts();} function rendAlerts(){const el=document.getElementById('ale');if(!el)return;if(!S.alerts.length){el.innerHTML=`
Sin alertas configuradas
`;return;}el.innerHTML=S.alerts.map(a=>`
${a.pair}${a.cond==='gte'?'≥':'≤'}${a.price}
● ${a.active?'Activa':'Activada'}
`).join('');} function rendCal(){const evs=[{time:'08:00',flag:'🇪🇺',ev:'IPC Eurozona (a/a)',actual:'2.3%',prev:'2.6%',fore:'2.4%',imp:'ALTO'},{time:'09:30',flag:'🇬🇧',ev:'Ventas minoristas UK',actual:'-0.3%',prev:'0.4%',fore:'0.1%',imp:'MEDIO'},{time:'11:00',flag:'🇺🇸',ev:'Solicitudes desempleo',actual:'218K',prev:'225K',fore:'222K',imp:'MEDIO'},{time:'13:00',flag:'🇺🇸',ev:'Discurso Fed — Powell',actual:'—',prev:'—',fore:'—',imp:'ALTO'},{time:'14:30',flag:'🔷',ev:'Halving BTC aniversario',actual:'—',prev:'—',fore:'—',imp:'ALTO'},{time:'15:00',flag:'🇺🇸',ev:'Confianza Michigan',actual:'—',prev:'67.8',fore:'68.5',imp:'MEDIO'},{time:'18:00',flag:'🇨🇦',ev:'Tasa de interés BoC',actual:'—',prev:'4.25%',fore:'4.00%',imp:'ALTO'}];const ic=i=>i==='ALTO'?'var(--r)':i==='MEDIO'?'var(--go)':'var(--t3)';document.getElementById('cale').innerHTML=`${['Hora UTC','País','Evento','Actual','Anterior','Pronóstico','Impacto'].map(h=>``).join('')}${evs.map(e=>``).join('')}
${h}
${e.time}${e.flag}${e.ev}${e.actual}${e.prev}${e.fore}${e.imp}
`;} function rendReports(){const total=S.history.length,wins=S.history.filter(h=>h.pl>0).length;const tpl=S.history.reduce((a,h)=>a+h.pl,0);const wr=total?((wins/total)*100).toFixed(1):'—';document.getElementById('rpe').innerHTML=`
${[{l:'Operaciones',v:total,c:'var(--bl)'},{l:'Ganadoras',v:wins,c:'var(--g)'},{l:'Win Rate',v:wr+'%',c:parseFloat(wr)>50?'var(--g)':'var(--r)'},{l:'P&L Total',v:(tpl>=0?'+':'')+'$'+tpl.toFixed(2),c:tpl>=0?'var(--g)':'var(--r)'}].map(s=>`
${s.l}
${s.v}
`).join('')}
Historial
${!S.history.length?'
Sin operaciones
':''}${S.history.length?`${S.history.slice(0,20).map(h=>{const p=AP[h.pair];return``}).join('')}
ParDir.LotesAperturaCierreP&LMotivo
${h.pair}${h.dir}${h.vol.toFixed(2)}${h.openPrice.toFixed(p.dig)}${h.closePrice.toFixed(p.dig)}${h.pl>=0?'+':''}$${h.pl.toFixed(2)}${h.reason}
`:''}
`;} // ── RESIZE ─────────────────────────────────────────────────── window.addEventListener('resize',()=>{ CPAIRS.forEach(pair=>{const id=pid(pair),cv=document.getElementById('cc_'+id),bd=document.getElementById('cb_'+id);if(cv&&bd){cv.width=bd.clientWidth;cv.height=bd.clientHeight;initChart(pair);}}); if(document.getElementById('fso').classList.contains('show'))buildFSChart(); }); // ── KEYBOARD SHORTCUTS ─────────────────────────────────────── document.addEventListener('keydown',e=>{ if(e.key==='Escape'&&document.getElementById('pricingOverlay').classList.contains('show')){closePricing();return;} if(!document.getElementById('fso').classList.contains('show'))return; if(e.key==='Escape'){if(S.activeTool!=='cursor')selTool('cursor');else closeFS();} if(e.altKey&&e.key.toLowerCase()==='h'){e.preventDefault();selTool('hline');} if(e.altKey&&e.key.toLowerCase()==='v'){e.preventDefault();selTool('vline');} if(e.key.toLowerCase()==='l')selTool('line'); }); // ══════════════════════════════════════════════════════════════ // AUTH SYSTEM — LOGIN / LOGOUT / USERS // ══════════════════════════════════════════════════════════════ // Plan colors and avatar colors const PLAN_COLORS = {free:'#69f0ae',trader:'#00b0ff',pro:'#7c4dff',master:'#ffd600',admin:'#ff1744'}; const PLAN_LABELS = {free:'Free',trader:'Trader',pro:'Pro',master:'Master',admin:'Admin'}; // ══════════════════════════════════════════════════════════════ // PERSISTENCIA POR USUARIO — localStorage key: sbalott_data_${userId} // ══════════════════════════════════════════════════════════════ function userDataKey(userId){ return 'sbalott_data_' + userId; } function saveUserData(){ if(!S.currentUser) return; const payload = { balance: S.balance, positions: S.positions, history: S.history, alerts: S.alerts }; try{ localStorage.setItem(userDataKey(S.currentUser.id), JSON.stringify(payload)); }catch(e){ console.warn('saveUserData error:', e); } } function loadUserData(userId){ try{ const raw = localStorage.getItem(userDataKey(userId)); if(!raw) return null; return JSON.parse(raw); }catch(e){ return null; } } // Autosave every 30 seconds while logged in let _saveInterval = null; function startAutosave(){ stopAutosave(); _saveInterval = setInterval(()=>{ if(S.currentUser) saveUserData(); }, 30000); } function stopAutosave(){ if(_saveInterval){ clearInterval(_saveInterval); _saveInterval=null; } } // Default demo users — seeded into localStorage once const DEFAULT_USERS = [ {id:'u1',email:'free@sbalott.es',password:'free123',name:'Carlos Free',plan:'free',isAdmin:false}, {id:'u2',email:'trader@sbalott.es',password:'trader123',name:'Ana Trader',plan:'trader',isAdmin:false}, {id:'u3',email:'pro@sbalott.es',password:'pro123',name:'Luis Pro',plan:'pro',isAdmin:false}, {id:'u4',email:'master@sbalott.es',password:'master123',name:'Elena Master',plan:'master',isAdmin:false}, {id:'u5',email:'admin@sbalott.es',password:'admin123',name:'Super Admin',plan:'master',isAdmin:true}, ]; function seedUsers(){ try{ const existing = localStorage.getItem('sbalott_users'); if(!existing){ localStorage.setItem('sbalott_users', JSON.stringify(DEFAULT_USERS)); } }catch(e){ console.warn('localStorage not available'); } } function getUsers(){ try{ return JSON.parse(localStorage.getItem('sbalott_users')||'[]'); } catch(e){ return DEFAULT_USERS; } } function saveUsers(users){ try{ localStorage.setItem('sbalott_users', JSON.stringify(users)); }catch(e){} } function buildDemoGrid(){ const grid = document.getElementById('daccGrid'); if(!grid) return; const users = getUsers(); grid.innerHTML = users.map(u => { const planClass = u.isAdmin ? 'plan-admin' : 'plan-' + u.plan; const planLabel = u.isAdmin ? 'ADMIN' : PLAN_LABELS[u.plan] || u.plan; return `
${planLabel} — ${u.name.split(' ')[0]}
`; }).join(''); } function quickLogin(email, password){ document.getElementById('lEmail').value = email; document.getElementById('lPass').value = password; doLogin(); } function doLogin(){ const email = document.getElementById('lEmail').value.trim().toLowerCase(); const pass = document.getElementById('lPass').value; const errEl = document.getElementById('lerr'); const emailEl = document.getElementById('lEmail'); const passEl = document.getElementById('lPass'); errEl.classList.remove('show'); emailEl.classList.remove('err'); passEl.classList.remove('err'); if(!email || !pass){ errEl.textContent = 'Por favor ingresa email y contraseña'; errEl.classList.add('show'); if(!email) emailEl.classList.add('err'); if(!pass) passEl.classList.add('err'); return; } const users = getUsers(); const user = users.find(u => u.email.toLowerCase() === email && u.password === pass); if(!user){ errEl.textContent = 'Email o contraseña incorrectos. Verifica tus datos.'; errEl.classList.add('show'); emailEl.classList.add('err'); passEl.classList.add('err'); passEl.value = ''; return; } // Save session try{ sessionStorage.setItem('sbalott_current', JSON.stringify(user)); }catch(e){} // Set state S.currentUser = user; S.plan = user.isAdmin ? 'master' : user.plan; S.isAdmin = user.isAdmin; updBotVisibility(); // Load persisted data for this user (balance, positions, history, alerts) const _saved = loadUserData(user.id); if(_saved){ if(typeof _saved.balance === 'number') S.balance = _saved.balance; if(Array.isArray(_saved.positions)) S.positions = _saved.positions; if(Array.isArray(_saved.history)) S.history = _saved.history; if(Array.isArray(_saved.alerts)) S.alerts = _saved.alerts; } startAutosave(); writeHeartbeat(); // record login time setInterval(writeHeartbeat, 120000); // heartbeat every 2 min // Hide login, show platform document.getElementById('loginScreen').style.display = 'none'; applyUserUI(user); bootPlatform(); } function applyUserUI(user){ const planCol = user.isAdmin ? PLAN_COLORS.admin : (PLAN_COLORS[user.plan] || '#00b0ff'); const planLabel = user.isAdmin ? 'ADMIN' : (PLAN_LABELS[user.plan] || user.plan); const initials = user.name.split(' ').map(n=>n[0]).join('').slice(0,2).toUpperCase(); // Account badge const badgeEl = document.getElementById('acctBadge'); if(badgeEl) badgeEl.textContent = `${user.plan.toUpperCase()} · $100,000`; // Admin badge const adminBadge = document.getElementById('adminBadgeEl'); if(adminBadge) adminBadge.style.display = user.isAdmin ? 'flex' : 'none'; // Plan badge const planBadge = document.getElementById('planBadgeEl'); if(planBadge){ const cls = user.isAdmin ? 'admin' : user.plan; planBadge.className = 'plan-badge ' + cls; planBadge.textContent = planLabel; planBadge.style.display = 'inline-flex'; } // User menu const menuEl = document.getElementById('userMenuEl'); if(menuEl) menuEl.style.display = 'flex'; // Avatar const avatar = document.getElementById('uAvatar'); if(avatar){ avatar.textContent = initials; avatar.style.background = planCol; } // Name const nameEl = document.getElementById('uName'); if(nameEl) nameEl.textContent = user.name.split(' ')[0]; // Dropdown details const ddName = document.getElementById('uDDName'); if(ddName) ddName.textContent = user.name; const ddEmail = document.getElementById('uDDEmail'); if(ddEmail) ddEmail.textContent = user.email; // Admin menu item (dropdown) const adminMenuItem = document.getElementById('adminMenuItem'); if(adminMenuItem) adminMenuItem.style.display = user.isAdmin ? 'flex' : 'none'; // Admin nav item (navbar) const adminNavItem = document.getElementById('n-admin'); if(adminNavItem) adminNavItem.style.display = user.isAdmin ? 'flex' : 'none'; // FS user info const fsRpTitle = document.getElementById('fsRpPair'); if(fsRpTitle){} // already set by pair } function toggleUserDD(){ document.getElementById('userDD').classList.toggle('show'); } document.addEventListener('click', e => { if(!e.target.closest('.user-menu')) document.getElementById('userDD')?.classList.remove('show'); }); function doLogout(){ saveUserData(); // persist final state before clearing session stopAutosave(); try{ sessionStorage.removeItem('sbalott_current'); }catch(e){} // NOTE: localStorage is intentionally NOT cleared — each user keeps their data S.currentUser = null; S.plan = 'free'; S.isAdmin = false; stopBot(); updBotVisibility(); // Reset topbar document.getElementById('userMenuEl').style.display = 'none'; document.getElementById('planBadgeEl').style.display = 'none'; document.getElementById('adminBadgeEl').style.display = 'none'; const _adminNav = document.getElementById('n-admin'); if(_adminNav) _adminNav.style.display = 'none'; document.getElementById('userDD').classList.remove('show'); // Clear intervals clearInterval(tickInterval); clearInterval(depthInterval); tickInterval = null; depthInterval = null; // Reset state S.positions=[]; S.history=[]; S.messages=[]; S.floatPL=0; S.usedMgn=0; S.balance=100000; // Show login document.getElementById('lEmail').value = ''; document.getElementById('lPass').value = ''; document.getElementById('lerr').classList.remove('show'); document.getElementById('loginScreen').style.display = 'flex'; if(S.fsChart){ S.fsChart.destroy(); S.fsChart=null; } document.getElementById('fso').classList.remove('show'); buildDemoGrid(); showNotif('✅ Sesión cerrada correctamente','ok'); } // ── Count active users via sessionStorage key sbalott_current ───────── function countActiveUsers(){ // We can only detect the current session from this tab. // We extend this by writing a heartbeat key per userId. const ACTIVE_WINDOW = 5 * 60 * 1000; // 5 min window const now = Date.now(); let active = 0; try{ for(let i=0;i{ const d = loadUserData(u.id); if(d){ if(typeof d.balance === 'number') totalBal += d.balance; if(Array.isArray(d.history)) totalOps += d.history.length; } else { totalBal += 100000; // default } }); const activeCount = countActiveUsers(); // Update stats bar document.getElementById('asTotalUsers').textContent = users.length; document.getElementById('asActiveNow').textContent = activeCount; document.getElementById('asTotalBal').textContent = '$' + totalBal.toLocaleString('es',{maximumFractionDigits:0}); document.getElementById('asTotalOps').textContent = totalOps; document.getElementById('adminRefreshTs').textContent = 'Actualizado: ' + now.toLocaleTimeString('es-PE'); // Build table rows document.getElementById('adminUserList').innerHTML = users.map(u => { const planCol = u.isAdmin ? PLAN_COLORS.admin : (PLAN_COLORS[u.plan]||'#8a9bc0'); const planLabel = u.isAdmin ? 'ADMIN' : (PLAN_LABELS[u.plan]||u.plan); const initials = u.name.split(' ').map(n=>n[0]).join('').slice(0,2).toUpperCase(); const userData = loadUserData(u.id); const bal = userData && typeof userData.balance === 'number' ? userData.balance : 100000; const ops = userData && Array.isArray(userData.history) ? userData.history.length : 0; const lastSeen = getUserLastSeen(u.id); const isMe = S.currentUser && u.id === S.currentUser.id; const balColor = bal > 100000 ? 'var(--g)' : bal < 100000 ? 'var(--r)' : 'var(--t1)'; return `
${initials}
${u.name}${isMe?' (tú)':''}
${u.email}
${u.isAdmin?'👑 ':''}${planLabel} $${bal.toLocaleString('es',{minimumFractionDigits:2,maximumFractionDigits:2})} ${ops} ${lastSeen} ${u.isAdmin ? '—' : `` } ${u.isAdmin ? 'Protegido' : `` } `; }).join(''); } function openAdminPanel(){ document.getElementById('userDD').classList.remove('show'); if(!S.isAdmin){ showNotif('⛔ Acceso denegado','err'); return; } renderAdminPanel(); document.getElementById('adminPanel').classList.add('show'); } function changeUserPlan(userId, newPlan){ const users = getUsers(); const u = users.find(u=>u.id===userId); if(!u || u.isAdmin) return; u.plan = newPlan; saveUsers(users); // If this is the currently logged-in user, update live state if(S.currentUser && S.currentUser.id === userId){ S.currentUser.plan = newPlan; S.plan = newPlan; try{ sessionStorage.setItem('sbalott_current', JSON.stringify(S.currentUser)); }catch(e){} } showNotif(`✅ Plan de ${u.name.split(' ')[0]} → ${PLAN_LABELS[newPlan]}`,'ok'); renderAdminPanel(); } function resetUserBalance(userId){ const users = getUsers(); const u = users.find(u=>u.id===userId); if(!u || u.isAdmin) return; const key = userDataKey(userId); try{ const raw = localStorage.getItem(key); const data = raw ? JSON.parse(raw) : {}; data.balance = 100000; data.positions = []; // Keep history intact — just reset balance localStorage.setItem(key, JSON.stringify(data)); }catch(e){} // If resetting current user live if(S.currentUser && S.currentUser.id === userId){ S.balance = 100000; S.positions = []; rendPos(); updAcct(); showNotif('✅ Tu balance fue reseteado a $100,000','ok'); } else { showNotif(`✅ Balance de ${u.name.split(' ')[0]} reseteado a $100,000`,'ok'); } renderAdminPanel(); } // ── BOOT — called after successful login ─────────────────── let tickInterval = null, depthInterval = null; function bootPlatform(){ buildGrid();rendPos();rendHist();updAcct();updDepth(); addMsg('🚀 Sbalott FX iniciado — Bienvenido ' + (S.currentUser?.name?.split(' ')[0]||'') + '','ok'); addMsg('💡 Clic en gráfico o ⛶ → terminal completa con herramientas de dibujo y negociación','ok'); setTimeout(()=>{const b=document.getElementById('aimsgs');if(b)b.innerHTML+=`
🤖 NexAI
BTC/USD consolida sobre $62,000. RSI 58, momentum positivo.
▲ COMPRA POTENCIAL — BTC/USD · SL $61,200 · TP $64,500
`;if(b)b.scrollTop=b.scrollHeight;},2500); if(!tickInterval) tickInterval = setInterval(tick, 800); if(!depthInterval) depthInterval = setInterval(updDepth, 1500); updBotVisibility(); } // ── SESSION RESTORE ──────────────────────────────────────── function tryRestoreSession(){ seedUsers(); buildDemoGrid(); try{ const stored = sessionStorage.getItem('sbalott_current'); if(stored){ const user = JSON.parse(stored); S.currentUser = user; S.plan = user.isAdmin?'master':user.plan; S.isAdmin = user.isAdmin; updBotVisibility(); const _savedR = loadUserData(user.id); if(_savedR){ if(typeof _savedR.balance === 'number') S.balance = _savedR.balance; if(Array.isArray(_savedR.positions)) S.positions = _savedR.positions; if(Array.isArray(_savedR.history)) S.history = _savedR.history; if(Array.isArray(_savedR.alerts)) S.alerts = _savedR.alerts; } startAutosave(); document.getElementById('loginScreen').style.display = 'none'; applyUserUI(user); bootPlatform(); return; } }catch(e){} // No session — show login document.getElementById('loginScreen').style.display = 'flex'; } // ── INIT ────────────────────────────────────────────────── tryRestoreSession(); // ══════════════════════════════════════════════════════════════ // PRICING SCREEN // ══════════════════════════════════════════════════════════════ const PLANS = [ {id:'free',name:'Gratuito',tagline:'Empieza sin costo alguno',price:0,currency:'S/',billing:'Gratis para siempre',color:'#69f0ae',features:[{on:true,text:'4 pares FX básicos'},{on:true,text:'Gráficos en tiempo real'},{on:true,text:'1 posición simultánea'},{on:true,text:'IA básica (5 consultas/día)'},{on:false,text:'Criptomonedas'},{on:false,text:'Herramientas de dibujo'},{on:false,text:'Indicadores técnicos'},{on:false,text:'Alertas de precio'},{on:false,text:'Soporte prioritario'}]}, {id:'trader',name:'Trader Activo',tagline:'Para traders en desarrollo',price:49,currency:'S/',billing:'S/490/año — ahorra 2 meses',color:'#00b0ff',features:[{on:true,text:'Todos los pares FX'},{on:true,text:'10 criptomonedas'},{on:true,text:'5 posiciones simultáneas'},{on:true,text:'IA avanzada (50 consultas/día)'},{on:true,text:'Herramientas de dibujo completas'},{on:true,text:'5 indicadores técnicos'},{on:true,text:'10 alertas de precio'},{on:false,text:'Fibonacci y Gann'},{on:false,text:'Soporte prioritario'}]}, {id:'pro',name:'PRC Pro',tagline:'El preferido de los profesionales',price:129,currency:'S/',billing:'S/1,290/año — ahorra 2 meses',color:'#7c4dff',popular:true,features:[{on:true,text:'Todos los pares FX'},{on:true,text:'Todas las criptomonedas'},{on:true,text:'Posiciones ilimitadas'},{on:true,text:'IA sin límite de consultas'},{on:true,text:'Herramientas de dibujo Pro'},{on:true,text:'Todos los indicadores'},{on:true,text:'Alertas ilimitadas'},{on:true,text:'Fibonacci y Gann completos'},{on:false,text:'Soporte VIP 24/7'}]}, {id:'master',name:'Master Trader',tagline:'Máxima potencia sin restricciones',price:299,currency:'S/',billing:'S/2,990/año — ahorra 2 meses',color:'#ffd600',features:[{on:true,text:'Todo lo del plan Pro'},{on:true,text:'API de trading directa'},{on:true,text:'Señales IA en tiempo real'},{on:true,text:'Backtesting automático'},{on:true,text:'Análisis de sentimiento'},{on:true,text:'Reportes avanzados PDF'},{on:true,text:'Cuenta gestionada (MAM)'},{on:true,text:'Acceso anticipado a funciones'},{on:true,text:'Soporte VIP 24/7 + llamadas'}]} ]; const PLAN_ORDER=['free','trader','pro','master']; function showPricingScreen(blockedFeature){ const overlay=document.getElementById('pricingOverlay'); const banner=document.getElementById('pricingBlockedBanner'); const bannerTxt=document.getElementById('pricingBlockedText'); const subMsg=document.getElementById('pricingBlockedMsg'); if(blockedFeature){banner.style.display='flex';bannerTxt.textContent='🔒 '+blockedFeature+' requiere un plan superior';subMsg.textContent='Actualiza tu plan para desbloquear esta función';} else{banner.style.display='none';subMsg.textContent='Accede a todas las herramientas profesionales de trading';} renderPricingCards(); overlay.classList.add('show'); } function closePricing(){document.getElementById('pricingOverlay').classList.remove('show');} document.addEventListener('click',e=>{const o=document.getElementById('pricingOverlay');if(e.target===o)closePricing();}); function renderPricingCards(){ const userPlan=S.currentUser?(S.isAdmin?'master':S.currentUser.plan):'free'; const container=document.getElementById('pricingCards'); container.innerHTML=PLANS.map(plan=>{ const isCurrent=plan.id===userPlan; const isPopular=plan.popular; const planIdx=PLAN_ORDER.indexOf(plan.id); const userIdx=PLAN_ORDER.indexOf(userPlan); const isUpgrade=planIdx>userIdx; let cardClass='pc'+(isCurrent?' current':'')+(isPopular?' popular':''); const ribbon=isPopular?'
⭐ MÁS POPULAR
':''; const currentBadge=isCurrent?`
TU PLAN ACTUAL
`:''; const priceBlock=plan.price===0?`
GRATIS
`:`
${plan.currency}${plan.price}/mes
`; let btnClass='pc-btn ',btnText='',btnClick=''; if(isCurrent){btnClass+='pc-btn-current';btnText='✓ Plan Activo';btnClick='';} else if(plan.id==='master'){btnClass+='pc-btn-master';btnText=isUpgrade?'SUSCRIBIRME →':'CAMBIAR PLAN';btnClick=`subscribePlan('${plan.id}')`;} else if(isPopular){btnClass+='pc-btn-popular';btnText=isUpgrade?'SUSCRIBIRME →':'CAMBIAR PLAN';btnClick=`subscribePlan('${plan.id}')`;} else{btnClass+='pc-btn-sub';btnText=isUpgrade?'SUSCRIBIRME →':'CAMBIAR PLAN';btnClick=`subscribePlan('${plan.id}')`;} const btn=``; const featureList=plan.features.map(f=>`
${f.on?'':''}${f.text}
`).join(''); return`
${ribbon}${currentBadge}
${plan.name}
${plan.tagline}
${priceBlock}
${plan.billing}
${featureList}
${btn}
`; }).join(''); } function subscribePlan(planId){ const plan=PLANS.find(p=>p.id===planId);if(!plan)return; if(S.currentUser){ try{const users=JSON.parse(localStorage.getItem('sbalott_users')||'[]');const u=users.find(u=>u.id===S.currentUser.id);if(u&&!u.isAdmin){u.plan=planId;localStorage.setItem('sbalott_users',JSON.stringify(users));S.currentUser.plan=planId;S.plan=planId;sessionStorage.setItem('sbalott_current',JSON.stringify(S.currentUser));}}catch(e){} const planBadge=document.getElementById('planBadgeEl'); if(planBadge){planBadge.className='plan-badge '+planId;planBadge.textContent=PLAN_LABELS[planId]||planId;} renderPricingCards(); } showNotif('🎉 ¡Bienvenido al plan '+plan.name+'! Funciones desbloqueadas.','ok'); setTimeout(()=>closePricing(),1800); } function requirePlan(minPlan,featureName){ const userIdx=PLAN_ORDER.indexOf(S.isAdmin?'master':(S.plan||'free')); const minIdx=PLAN_ORDER.indexOf(minPlan); if(userIdx>=minIdx)return true; showPricingScreen(featureName||'Esta función'); return false; } // ══════════════════════════════════════════════════════════════ // ANÁLISIS PANEL — Full Chart Analysis System // ══════════════════════════════════════════════════════════════ // Analysis state — added to S as new properties S.anPair = 'EUR/USD'; S.anTF = '1H'; S.anChart = null; S.anRSIChart = null; S.anMACDChart = null; S.anChartType = 'line'; S.anActiveTool = 'cursor'; S.anDrawings = []; S.anIsDrawing = false; S.anDrawStart = null; S.anDrawCanvas = null; S.anDrawCtx = null; S.anWatchCat = 'fx'; S.anIndicators = {ma:false, ema:false, bb:false, rsi:false, macd:false, vwap:false, prc:false}; // Indicator plan requirements const IND_PLANS = {ma:'trader', ema:'trader', bb:'pro', rsi:'pro', macd:'pro', vwap:'master', prc:'master'}; const DRAW_PLANS = {fib_ret:'pro', fib_fan:'pro', gann_angle:'master'}; // ── Plan helpers ───────────────────────────────────────────── function getEffPlan(){ return S.isAdmin ? 'master' : (S.plan||'free'); } function canUsePlan(needed){ return PLAN_ORDER.indexOf(getEffPlan()) >= PLAN_ORDER.indexOf(needed); } // ── Watchlist ───────────────────────────────────────────────── function anWatchTab(cat){ S.anWatchCat = cat; document.getElementById('anWFX').classList.toggle('active', cat==='fx'); document.getElementById('anWCR').classList.toggle('active', cat==='crypto'); renderAnWatchList(); } function renderAnWatchList(){ const el = document.getElementById('anWatchList'); if(!el)return; const pairs = S.anWatchCat==='fx' ? Object.keys(FX) : Object.keys(CRYPTO); el.innerHTML = pairs.map(pair=>{ const p = AP[pair]; const isActive = pair === S.anPair; const chg = ((Math.random()-.495)*(pair.startsWith('BTC')?900:pair.startsWith('ETH')?55:.003)); const pct = ((chg/p.bid)*100).toFixed(2); const cl = pct>=0?'up':'dn'; return `
${pair}
${p.desc.split('/')[0]}
${p.bid.toFixed(p.dig)}
${parseFloat(pct)>=0?'+':''}${pct}%
`; }).join(''); } function anSelPair(pair){ S.anPair = pair; document.getElementById('anPairLabel').textContent = pair; document.getElementById('anPairLabel').style.color = AP[pair].col; document.getElementById('anPairDD').style.display = 'none'; S.anDrawings = []; buildAnChart(); updateAnLivePrices(); renderAnWatchList(); updateAnPosList(); renderAnSignals(); renderAnSummary(); } // ── Pair DD ─────────────────────────────────────────────────── function toggleAnPairDD(){ const dd = document.getElementById('anPairDD'); const isOpen = dd.style.display !== 'none'; dd.style.display = isOpen ? 'none' : 'block'; if(!isOpen){ buildAnPairList(); setTimeout(()=>document.getElementById('anPairSearch').focus(),50); } } function buildAnPairList(){ const el = document.getElementById('anPairList'); if(!el) return; const cats = {fx:'FX — Forex', crypto:'Cripto'}; const grouped = {fx:[], crypto:[]}; Object.keys(AP).forEach(k=>{ const c=AP[k].cat||'fx'; (grouped[c]=grouped[c]||[]).push(k); }); let html = ''; Object.keys(cats).forEach(cat=>{ if(!grouped[cat]||!grouped[cat].length) return; html += `
${cats[cat]}
`; grouped[cat].forEach(pair=>{ const p2=AP[pair]; const active = pair===S.anPair ? 'active' : ''; html += `
${pair} ${p2.bid.toFixed(p2.dig)}
`; }); }); el.innerHTML = html; } function filterAnPairs(){ const q = document.getElementById('anPairSearch').value.toLowerCase(); document.querySelectorAll('#anPairList .ps-item').forEach(el=>{ el.style.display = el.dataset.pair && el.dataset.pair.toLowerCase().includes(q) ? '' : 'none'; }); } document.addEventListener('click', e=>{ if(!e.target.closest('#anPairWrap')) { const dd=document.getElementById('anPairDD'); if(dd) dd.style.display='none'; } if(!e.target.closest('#anIndToggle') && !e.target.closest('#anIndMenu')) { const m=document.getElementById('anIndMenu'); if(m) m.style.display='none'; } if(!e.target.closest('#anDrawToggle') && !e.target.closest('#anDrawMenu')) { const m=document.getElementById('anDrawMenu'); if(m) m.style.display='none'; } }); // ── Menus ───────────────────────────────────────────────────── function toggleAnIndMenu(){ const m=document.getElementById('anIndMenu'); m.style.display=m.style.display==='none'?'block':'none'; updateAnIndLocks(); } function toggleAnDrawMenu(){ const m=document.getElementById('anDrawMenu'); m.style.display=m.style.display==='none'?'block':'none'; } function updateAnIndLocks(){ Object.keys(IND_PLANS).forEach(ind=>{ const lk = document.getElementById('anind-'+ind+'-lk'); if(lk) lk.style.display = canUsePlan(IND_PLANS[ind]) ? 'none' : 'inline'; }); const fibLk = document.getElementById('an-fib-lk'); const fibFanLk = document.getElementById('an-fibfan-lk'); const gannLk = document.getElementById('an-gann-lk'); if(fibLk) fibLk.style.display = canUsePlan('pro') ? 'none' : 'inline'; if(fibFanLk) fibFanLk.style.display = canUsePlan('pro') ? 'none' : 'inline'; if(gannLk) gannLk.style.display = canUsePlan('master') ? 'none' : 'inline'; } // ── TF & Chart type ─────────────────────────────────────────── function setAnTF(btn, tf){ S.anTF = tf; document.querySelectorAll('#anTFRow .fstf').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); buildAnChart(); } function setAnChartType(t){ S.anChartType = t; ['line','area','bar'].forEach(x=>{ const el=document.getElementById('an-ct-'+x); if(el) el.classList.toggle('on', x===t); }); buildAnChart(); } // ── Indicators ──────────────────────────────────────────────── function toggleAnInd(ind){ const needed = IND_PLANS[ind]; if(!canUsePlan(needed)){ showPricingScreen(ind.toUpperCase()+' Indicator'); return; } S.anIndicators[ind] = !S.anIndicators[ind]; const el = document.getElementById('anind-'+ind); if(el) el.classList.toggle('on', S.anIndicators[ind]); // Show/hide sub-charts if(ind==='rsi'){ const w=document.getElementById('anRSIWrap'); if(w) w.style.display=S.anIndicators.rsi?'block':'none'; } if(ind==='macd'){ const w=document.getElementById('anMACDWrap'); if(w) w.style.display=S.anIndicators.macd?'block':'none'; } buildAnChart(); updateAnIndicatorBar(); document.getElementById('anIndMenu').style.display = 'none'; } // ── Drawing tools ───────────────────────────────────────────── function selAnTool(tool){ const needed = DRAW_PLANS[tool]; if(needed && !canUsePlan(needed)){ showPricingScreen(tool+' (Dibujo)'); return; } S.anActiveTool = tool; const dc = document.getElementById('anDrawCanvas'); if(dc) dc.style.cursor = tool==='cursor' ? 'default' : 'crosshair'; const badge = document.getElementById('anToolBadge'); if(badge){ badge.textContent = '✏️ '+tool; badge.style.display = tool==='cursor'?'none':'block'; } document.getElementById('anStatusTool').textContent = tool; document.getElementById('anToolStatus').textContent = tool; document.getElementById('anDrawMenu').style.display = 'none'; } function clearAnDrawings(){ S.anDrawings=[]; redrawAnAll(); document.getElementById('anStatusObjs').textContent='0'; document.getElementById('anDrawMenu').style.display='none'; } // ── Build Main Chart ────────────────────────────────────────── function buildAnChart(){ const wrap = document.getElementById('anChartWrap'); if(!wrap)return; const cv = document.getElementById('anChartCanvas'); if(!cv)return; const pair = S.anPair, p = AP[pair]; cv.width = wrap.clientWidth; cv.height = wrap.clientHeight; const dc = document.getElementById('anDrawCanvas'); if(dc){ dc.width=wrap.clientWidth; dc.height=wrap.clientHeight; S.anDrawCanvas=dc; S.anDrawCtx=dc.getContext('2d'); } const ctx = cv.getContext('2d'); const h = S.hist[pair] || []; const grad = ctx.createLinearGradient(0,0,0,cv.height); grad.addColorStop(0, p.col+(S.anChartType==='area'?'55':'20')); grad.addColorStop(1, p.col+'00'); const datasets = [{ data: h.map(d=>({x:d.x,y:d.y})), borderColor: p.col, backgroundColor: S.anChartType==='area' ? grad : 'transparent', borderWidth: 2, pointRadius: 0, fill: S.anChartType==='area', tension: .15 }]; // Indicators if(S.anIndicators.ma && h.length>=20){ const ma=calcSMA(h.map(d=>d.y),20); datasets.push({data:h.slice(19).map((d,i)=>({x:d.x,y:ma[i]})),borderColor:'#ff9100',borderWidth:1.5,pointRadius:0,fill:false,tension:.2}); } if(S.anIndicators.ema && h.length>=50){ const ema=calcEMA(h.map(d=>d.y),50); datasets.push({data:h.slice(49).map((d,i)=>({x:d.x,y:ema[i]})),borderColor:'#ce93d8',borderWidth:1.5,pointRadius:0,fill:false,tension:.2}); } if(S.anIndicators.bb && h.length>=20){ const bb=calcBB(h.map(d=>d.y),20); datasets.push({data:h.slice(19).map((d,i)=>({x:d.x,y:bb.upper[i]})),borderColor:'rgba(0,176,255,.5)',borderWidth:1,pointRadius:0,fill:false,borderDash:[4,4]}); datasets.push({data:h.slice(19).map((d,i)=>({x:d.x,y:bb.lower[i]})),borderColor:'rgba(0,176,255,.5)',borderWidth:1,pointRadius:0,fill:false,borderDash:[4,4]}); } if(S.anIndicators.vwap){ const vals = h.map(d=>d.y); const avg = vals.reduce((a,b)=>a+b,0)/vals.length; datasets.push({data:h.map(d=>({x:d.x,y:avg})),borderColor:'#69f0ae',borderWidth:1,pointRadius:0,fill:false,borderDash:[6,3]}); } if(S.anIndicators.prc){ // PRC v2: regression channel const n = h.length; if(n>10){ const xs = h.map((_,i)=>i); const ys = h.map(d=>d.y); const xm=xs.reduce((a,b)=>a+b,0)/n; const ym=ys.reduce((a,b)=>a+b,0)/n; const num=xs.reduce((a,x,i)=>a+(x-xm)*(ys[i]-ym),0); const den=xs.reduce((a,x)=>a+(x-xm)**2,0); const slope=num/den; const inter=ym-slope*xm; const reg=h.map((_,i)=>({x:h[i].x,y:inter+slope*i})); const resids=h.map((d,i)=>d.y-(inter+slope*i)); const std=Math.sqrt(resids.reduce((a,r)=>a+r*r,0)/n); datasets.push({data:reg,borderColor:'#ffd600',borderWidth:2,pointRadius:0,fill:false}); datasets.push({data:reg.map(d=>({x:d.x,y:d.y+2*std})),borderColor:'rgba(255,214,0,.4)',borderWidth:1,pointRadius:0,fill:false,borderDash:[3,3]}); datasets.push({data:reg.map(d=>({x:d.x,y:d.y-2*std})),borderColor:'rgba(255,214,0,.4)',borderWidth:1,pointRadius:0,fill:false,borderDash:[3,3]}); } } if(S.anChart) S.anChart.destroy(); S.anChart = new Chart(ctx, { type:'line', data:{datasets}, options:{responsive:false,animation:false, scales:{ x:{type:'linear',grid:{color:'rgba(30,45,72,.6)'},ticks:{color:'#8a9bc0',font:{family:'JetBrains Mono',size:10},maxTicksLimit:10,callback:v=>{const d=new Date(v);return d.getHours().toString().padStart(2,'0')+':'+d.getMinutes().toString().padStart(2,'0');}}}, y:{position:'right',grid:{color:'rgba(30,45,72,.6)'},ticks:{color:'#8a9bc0',font:{family:'JetBrains Mono',size:10},maxTicksLimit:8}} }, plugins:{legend:{display:false},tooltip:{backgroundColor:'#0d1525',borderColor:'#2a3f60',borderWidth:1,bodyColor:'#e8f0fe',bodyFont:{family:'JetBrains Mono',size:12},callbacks:{label:c=>`${pair}: ${c.parsed.y.toFixed(p.dig)}`}}} } }); // Build RSI sub-chart if(S.anIndicators.rsi) buildAnRSI(h); // Build MACD sub-chart if(S.anIndicators.macd) buildAnMACD(h); setupAnDrawEvents(); redrawAnAll(); updateAnLivePrices(); document.getElementById('anStatusCount').textContent = h.length; } function buildAnRSI(h){ const w=document.getElementById('anRSIWrap'); if(!w)return; const cv=document.getElementById('anRSICanvas'); if(!cv)return; cv.width=w.clientWidth; cv.height=w.clientHeight; if(S.anRSIChart) S.anRSIChart.destroy(); const vals=h.map(d=>d.y); const rsiData=calcRSI(vals,14); const last=rsiData[rsiData.length-1]; if(document.getElementById('anRSIVal')) document.getElementById('anRSIVal').textContent=last?last.toFixed(2):'—'; S.anRSIChart=new Chart(cv.getContext('2d'),{type:'line',data:{datasets:[{data:h.slice(14).map((d,i)=>({x:d.x,y:rsiData[i]})),borderColor:'#7c4dff',borderWidth:1.5,pointRadius:0,fill:false}]},options:{responsive:false,animation:false,scales:{x:{type:'linear',grid:{display:false},ticks:{display:false}},y:{position:'right',min:0,max:100,grid:{color:'rgba(30,45,72,.3)'},ticks:{color:'#4a5a78',font:{size:8},maxTicksLimit:4}}},plugins:{legend:{display:false}}}}); } function buildAnMACD(h){ const w=document.getElementById('anMACDWrap'); if(!w)return; const cv=document.getElementById('anMACDCanvas'); if(!cv)return; cv.width=w.clientWidth; cv.height=w.clientHeight; if(S.anMACDChart) S.anMACDChart.destroy(); const vals=h.map(d=>d.y); const ema12=calcEMA(vals,12); const ema26=calcEMA(vals,26); const off=26-12; const macdLine=ema26.map((v,i)=>v-(ema12[i+off]||v)); const signal=calcEMA(macdLine,9); const hist2=macdLine.slice(9).map((v,i)=>v-signal[i]); const last=hist2[hist2.length-1]; if(document.getElementById('anMACDVal')) document.getElementById('anMACDVal').textContent=last?last.toFixed(5):'—'; S.anMACDChart=new Chart(cv.getContext('2d'),{type:'bar',data:{datasets:[{data:h.slice(26+9-1).map((d,i)=>({x:d.x,y:hist2[i]})),backgroundColor:hist2.map(v=>v>=0?'rgba(0,230,118,.5)':'rgba(255,23,68,.5)'),borderWidth:0}]},options:{responsive:false,animation:false,scales:{x:{type:'linear',grid:{display:false},ticks:{display:false}},y:{position:'right',grid:{color:'rgba(30,45,72,.3)'},ticks:{color:'#4a5a78',font:{size:8},maxTicksLimit:3}}},plugins:{legend:{display:false}}}}); } function calcRSI(vals,period){ const rsi=[]; for(let i=period;i0?gains.push(d):losses.push(Math.abs(d));} const ag=gains.reduce((a,b)=>a+b,0)/period; const al=losses.reduce((a,b)=>a+b,0)/period; rsi.push(al===0?100:100-(100/(1+ag/al))); } return rsi; } // ── Live price updates in analysis panel ───────────────────── function updateAnLivePrices(){ const pair=S.anPair; const p=AP[pair]; if(!p)return; const spr=((p.ask-p.bid)/p.pip).toFixed(1); const h=S.hist[pair]||[]; const vals=h.map(x=>x.y); const hi=vals.length?Math.max(...vals):p.bid; const lo=vals.length?Math.min(...vals):p.bid; const chg=vals.length?((vals[vals.length-1]-vals[0])/vals[0]*100).toFixed(2):0; document.getElementById('anLivePair').textContent=pair; document.getElementById('anLivePair').style.color=p.col; document.getElementById('anLiveBid').textContent=p.bid.toFixed(p.dig); document.getElementById('anLiveBid').style.color=p.col; document.getElementById('anB').textContent=p.bid.toFixed(p.dig); document.getElementById('anA').textContent=p.ask.toFixed(p.dig); document.getElementById('anSpr').textContent=spr+' pip'; document.getElementById('anHi').textContent=hi.toFixed(p.dig); document.getElementById('anLo').textContent=lo.toFixed(p.dig); const chgEl=document.getElementById('anChg'); chgEl.textContent=(parseFloat(chg)>=0?'+':'')+chg+'%'; chgEl.style.color=parseFloat(chg)>=0?'var(--g)':'var(--r)'; document.getElementById('anRBid').textContent=p.bid.toFixed(p.dig); document.getElementById('anRAsk').textContent=p.ask.toFixed(p.dig); document.getElementById('anRSpr').textContent='Spread: '+spr+' pip'; // Price line if(S.anChart){ const yScale=S.anChart.scales.y; if(yScale){const yPx=yScale.getPixelForValue(p.bid); const pl=document.getElementById('anPriceLine'); if(pl){pl.style.display='flex';pl.style.top=yPx+'px';} const plv=document.getElementById('anPriceLineVal'); if(plv)plv.textContent=p.bid.toFixed(p.dig);} } if(S.anChart){ S.anChart.data.datasets[0].data=h.map(d=>({x:d.x,y:d.y})); S.anChart.update('none'); } } // ── Analysis Order ──────────────────────────────────────────── function anOrder(dir){ const pair=S.anPair; const p=AP[pair]; if(!p)return; const vol=parseFloat(document.getElementById('anVol').value)||0.1; const price=dir==='BUY'?p.ask:p.bid; const mgn=(vol*100000*price/100); const pos={id:Date.now(),pair,dir,vol,openPrice:price,currentPrice:price,sl:'—',tp:'—',mgn,pl:0,time:new Date().toLocaleTimeString()}; S.positions.push(pos); addMsg(`✅ ${dir} ${vol}L ${pair} @ ${price.toFixed(p.dig)} (Análisis)`,'ok'); showNotif(`${dir==='BUY'?'▲':'▼'} ${pair} ${dir} ${vol}L @ ${price.toFixed(p.dig)}`,'ok'); rendPos(); updAcct(); updateAnPosList(); } function adjAnVol(d){ const el=document.getElementById('anVol'); if(!el)return; el.value=Math.max(.01,parseFloat(el.value||.1)+d).toFixed(2); } function updateAnPosList(){ const el=document.getElementById('anPosList'); if(!el)return; const pos=S.positions.filter(p=>p.pair===S.anPair); if(!pos.length){el.innerHTML='
Sin posiciones en este par
';return;} el.innerHTML=pos.map(pos2=>{ const p=AP[pos2.pair]; return`
${pos2.dir==='BUY'?'▲':'▼'} ${pos2.vol}L
${pos2.pl>=0?'+':''}$${pos2.pl.toFixed(2)}
@ ${pos2.openPrice.toFixed(p.dig)}
`; }).join(''); } // ── Right panel tabs ────────────────────────────────────────── function anRTab(t){ ['trade','signals','summary'].forEach(x=>{ document.getElementById('anRT-'+x).classList.toggle('active',x===t); document.getElementById('anRTab-'+x).style.display=x===t?'block':'none'; }); if(t==='signals') renderAnSignals(); if(t==='summary') renderAnSummary(); } function renderAnSignals(){ const el=document.getElementById('anSignalsList'); if(!el)return; document.getElementById('anSigPair').textContent=S.anPair; const pair=S.anPair; const p=AP[pair]; const h=S.hist[pair]||[]; const last=h[h.length-1]?.y||p.bid; const vals=h.map(d=>d.y); const sma=vals.length>=20?calcSMA(vals,20):[]; const lastSMA=sma[sma.length-1]; const ema=vals.length>=50?calcEMA(vals,50):[]; const lastEMA=ema[ema.length-1]; const rsi=vals.length>=14?calcRSI(vals,14):[]; const lastRSI=rsi[rsi.length-1]; const trend=lastSMA?(last>lastSMA?'bull':'bear'):'neutral'; const signals=[ {name:'Tendencia SMA20',val:lastSMA?lastSMA.toFixed(p.dig):'—',sig:trend==='bull'?'▲ ALCISTA':'▼ BAJISTA',col:trend==='bull'?'var(--g)':'var(--r)',req:'trader'}, {name:'EMA50 Señal',val:lastEMA?lastEMA.toFixed(p.dig):'—',sig:lastEMA?(last>lastEMA?'▲ Por encima':'▼ Por debajo'):'—',col:lastEMA?(last>lastEMA?'var(--g)':'var(--r)'):'var(--t3)',req:'trader'}, {name:'RSI (14)',val:lastRSI?lastRSI.toFixed(1):'—',sig:lastRSI?(lastRSI>70?'⚠️ Sobrecomprado':lastRSI<30?'⚠️ Sobrevendido':'● Neutral'):'—',col:lastRSI?(lastRSI>70?'var(--r)':lastRSI<30?'var(--g)':'var(--t2)'):'var(--t3)',req:'pro'}, {name:'Momentum',val:'—',sig:(Math.random()>.5?'▲ Positivo':'▼ Negativo'),col:Math.random()>.5?'var(--g)':'var(--r)',req:'free'}, {name:'Volatilidad',val:'—',sig:(Math.random()>.5?'Alta':'Normal'),col:'var(--go)',req:'pro'}, {name:'PRC v2',val:'—',sig:(Math.random()>.5?'▲ En canal alcista':'▼ En canal bajista'),col:Math.random()>.5?'var(--g)':'var(--r)',req:'master'}, ]; el.innerHTML=signals.map(s=>{ const locked=!canUsePlan(s.req); return`
${s.name} ${locked?`🔒`:''}
${s.val}
${locked?'Bloqueado':s.sig}
`; }).join(''); } function renderAnSummary(){ const el=document.getElementById('anSummaryContent'); if(!el)return; const pair=S.anPair; const p=AP[pair]; const h=S.hist[pair]||[]; const vals=h.map(d=>d.y); const hi=vals.length?Math.max(...vals):p.bid; const lo=vals.length?Math.min(...vals):p.bid; const chg=vals.length?((vals[vals.length-1]-vals[0])/vals[0]*100).toFixed(2):0; const sentiment = parseFloat(chg)>=0 ? 'COMPRA' : 'VENTA'; const sentimentCol = parseFloat(chg)>=0 ? 'var(--g)' : 'var(--r)'; // Overall gauge const bullPct = Math.round(Math.random()*40+30); el.innerHTML=`
Señal Global
${sentiment}
${pair}
▲ Compra${bullPct}%
▼ Venta${100-bullPct}%
${[ {l:'Máximo 24h',v:hi.toFixed(p.dig),c:'var(--g)'}, {l:'Mínimo 24h',v:lo.toFixed(p.dig),c:'var(--r)'}, {l:'Cambio 24h',v:(parseFloat(chg)>=0?'+':'')+chg+'%',c:parseFloat(chg)>=0?'var(--g)':'var(--r)'}, {l:'Spread',v:((p.ask-p.bid)/p.pip).toFixed(1)+' pip',c:'var(--t1)'}, {l:'Pip Value',v:p.pip.toString(),c:'var(--t2)'}, {l:'Decimales',v:p.dig.toString(),c:'var(--t2)'}, ].map(row=>`
${row.l}${row.v}
`).join('')} `; } // ── Indicator bar ───────────────────────────────────────────── function updateAnIndicatorBar(){ const el = document.getElementById('anIndValues'); if(!el)return; const pair=S.anPair; const p=AP[pair]; const h=S.hist[pair]||[]; const vals=h.map(d=>d.y); const active=[]; if(S.anIndicators.ma&&vals.length>=20){const ma=calcSMA(vals,20);active.push({n:'SMA20',v:ma[ma.length-1]?.toFixed(p.dig)||'—',c:'#ff9100'});} if(S.anIndicators.ema&&vals.length>=50){const ema=calcEMA(vals,50);active.push({n:'EMA50',v:ema[ema.length-1]?.toFixed(p.dig)||'—',c:'#ce93d8'});} if(S.anIndicators.rsi&&vals.length>=14){const r=calcRSI(vals,14);active.push({n:'RSI',v:r[r.length-1]?.toFixed(1)||'—',c:'#7c4dff'});} if(S.anIndicators.prc) active.push({n:'PRC v2',v:'ON',c:'#ffd600'}); el.innerHTML=active.map(a=>`
${a.n}${a.v}
`).join(''); document.getElementById('anPlanLabel').textContent='Plan: '+(getEffPlan().toUpperCase()); document.getElementById('anStatusPlan').textContent=getEffPlan().toUpperCase(); } // ── Drawing on analysis chart ───────────────────────────────── function setupAnDrawEvents(){ const dc=document.getElementById('anDrawCanvas'); if(!dc)return; const newDC=dc.cloneNode(true); dc.parentNode.replaceChild(newDC,dc); S.anDrawCanvas=newDC; S.anDrawCtx=newDC.getContext('2d'); newDC.addEventListener('mousedown',onAnDrawStart); newDC.addEventListener('mousemove',onAnDrawMove); newDC.addEventListener('mouseup',onAnDrawEnd); newDC.addEventListener('contextmenu',e=>{e.preventDefault();selAnTool('cursor');}); } function getAnCanvasPos(e){const r=S.anDrawCanvas.getBoundingClientRect();return{x:e.clientX-r.left,y:e.clientY-r.top};} function onAnDrawStart(e){ if(S.anActiveTool==='cursor')return; const pos=getAnCanvasPos(e); S.anIsDrawing=true; S.anDrawStart=pos; if(S.anActiveTool==='text'){ const t=prompt('Texto:',''); if(t){S.anDrawings.push({type:'text',x:pos.x,y:pos.y,text:t,color:'#ffd600'});redrawAnAll();} S.anIsDrawing=false; } } function onAnDrawMove(e){ if(!S.anIsDrawing||!S.anDrawStart)return; const pos=getAnCanvasPos(e); redrawAnAll(); const ctx=S.anDrawCtx; if(!ctx)return; ctx.strokeStyle='#ffd600'; ctx.lineWidth=1.5; ctx.setLineDash([4,4]); ctx.beginPath(); if(S.anActiveTool==='hline'){ctx.moveTo(0,pos.y);ctx.lineTo(S.anDrawCanvas.width,pos.y);} else if(S.anActiveTool==='vline'){ctx.moveTo(pos.x,0);ctx.lineTo(pos.x,S.anDrawCanvas.height);} else if(S.anActiveTool==='rect'){ctx.strokeRect(S.anDrawStart.x,S.anDrawStart.y,pos.x-S.anDrawStart.x,pos.y-S.anDrawStart.y);} else{ctx.moveTo(S.anDrawStart.x,S.anDrawStart.y);ctx.lineTo(pos.x,pos.y);} ctx.stroke(); ctx.setLineDash([]); } function onAnDrawEnd(e){ if(!S.anIsDrawing||!S.anDrawStart)return; const pos=getAnCanvasPos(e); S.anDrawings.push({type:S.anActiveTool,x1:S.anDrawStart.x,y1:S.anDrawStart.y,x2:pos.x,y2:pos.y,color:'#ffd600'}); S.anIsDrawing=false; S.anDrawStart=null; redrawAnAll(); document.getElementById('anStatusObjs').textContent=S.anDrawings.length; } function redrawAnAll(){ const ctx=S.anDrawCtx; if(!ctx)return; const w=S.anDrawCanvas.width, h=S.anDrawCanvas.height; ctx.clearRect(0,0,w,h); S.anDrawings.forEach(d=>{ ctx.strokeStyle=d.color||'#ffd600'; ctx.lineWidth=1.5; ctx.setLineDash([]); ctx.beginPath(); if(d.type==='hline'){ctx.moveTo(0,d.y1);ctx.lineTo(w,d.y1);} else if(d.type==='vline'){ctx.moveTo(d.x1,0);ctx.lineTo(d.x1,h);} else if(d.type==='rect'){ctx.strokeRect(d.x1,d.y1,d.x2-d.x1,d.y2-d.y1);} else if(d.type==='text'){ctx.font='12px JetBrains Mono';ctx.fillStyle=d.color;ctx.fillText(d.text,d.x,d.y);} else if(d.type==='fib_ret'){ const top=Math.min(d.y1,d.y2), bot=Math.max(d.y1,d.y2), range=bot-top; [0,0.236,0.382,0.5,0.618,0.786,1].forEach(lvl=>{ const y=top+range*lvl; ctx.strokeStyle=`rgba(255,214,0,${lvl===0||lvl===1?.8:.5})`; ctx.beginPath(); ctx.moveTo(d.x1,y); ctx.lineTo(d.x2||w,y); ctx.stroke(); ctx.fillStyle='rgba(255,214,0,.7)'; ctx.font='9px JetBrains Mono'; ctx.fillText((lvl*100).toFixed(1)+'%',d.x1+4,y-2); }); return; } else{ctx.moveTo(d.x1,d.y1);ctx.lineTo(d.x2||d.x1,d.y2||d.y1);} ctx.stroke(); }); } // ── Open in Full Terminal ───────────────────────────────────── function openAnInFullTerminal(){ openFS(S.anPair); navGo('platform'); } // ── Live update hook for analysis panel ────────────────────── // (Analysis panel live updates are handled via existing tick function) // updateAnLivePrices() is called from tick() when analysis panel is active
Elige tu Plan
🔒 Pago seguro · Cancela cuando quieras · Soporte 24/7