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+=`
${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