Live crypto signals, scored across 10 indicators and 10 strategiesMulti-timeframe confluence, strategy audit, Monte Carlo validation · browser paper mode only
Market connection: checking Binance…

Analyze a pair

Pick a pair, market and timeframe, then press Analyze. Equity, risk, leverage and fees size the trade plan.

Trade signal

Select a pair to see its signal.

Market context

Fear & Greed, funding and long/short positioning load when you analyze a pair.

Pick a pair and timeframe, then press Analyze.

Market scanner

Quality mode: 5m entry must agree with the 15m setup and clearly aligned 1h + 4h trends. Futures supports LONG and SHORT; Spot is LONG-only. Signals expire after 15 minutes.
Fast scan: up to 6 pairs processed concurrently. Signals appear during scanning, not only at the end. Results can still expire or become invalid as market prices change.
Progressive scanner displays qualifying results while remaining pairs are scanned. Quality intraday mode uses completed 5m entry candles and requires 15m setup agreement plus 1h and 4h trend alignment (minimum directional bias 0.25). Any failed or unavailable confirmation produces WAIT; signals expire 15 minutes after the scan. Futures can generate LONG and SHORT; Spot signals are LONG-only. Auto-scan refreshes every 5 minutes by default. Signal strength is a heuristic 0–100 setup score, not a calibrated probability of profit or a win rate. It must be validated against historical and forward paper-trading results. Futures funding/open-interest are passed into the intraday indicator analysis when the Binance endpoints return data; missing derivatives data is not treated as confirmation. News is not scored, and the event-risk gate still applies. Each pair requires candle requests for the selected timeframe(s) and their confirmation timeframe(s). Large all-pairs scans may take time; Binance rate limits are respected and a rate-limit error stops the scan safely.

Live TradingView chart

Candlestick chart and live quote for the selected pair and timeframe.
BINANCE · BTC/USDT
Loading…
Waiting for live price
Connecting to market data…

Selected pair technical analysis

Select any trading pair to load its analysis.
Chart and quote data are provided by TradingView and Binance. Spot and USDT-M futures prices can differ. If the chart does not load, check your internet connection or browser content-blocking settings.

Backtester & trade filters

Replays this engine candle-by-candle on the selected pair/timeframe/market. Stop and target are checked on candle highs/lows (stop first if both are touched). Costs included. Past results do not predict future results.
Choose a pair, market and timeframe in Analyze, then press Run backtest. Equity, risk and cost fields are used for costs. The event-risk gate is ignored in backtests.
Two exit models are compared: Full exit at TP1, and Managed (half off at TP1, stop to break-even, rest to TP2). Paper trades now use the managed model and candle-based checks. Use the first-half / second-half rows to spot overfitting.

Paper trading

A simulated account for testing signals. No real orders are ever sent. NautilusTrader backend is not connected in this standalone HTML build.

Position size for each signal

Choose how to size new paper trades. These settings apply when you open a signal. Investment is margin for futures and purchase amount for spot. Position notional = investment × leverage (futures only). The optional stop-loss risk cap and separate mandatory exposure limits are checked before opening any new position. Existing saved positions are not automatically changed.

Automatic mode sizes your trade from account equity, risk percentage, stop distance and estimated costs.

Account

Pending paper trade queue · 15-minute signal lifetime; waits for entry zone, then revalidates

Missed / cancelled queue entries

Open positions · close at market, move the stop to break-even, or cancel

Break-even activation history

Trade history

P&L convention: Gross P&L = direction × (exit/current price − entry price) × quantity. Net P&L subtracts estimated opening and closing commissions plus configured slippage on both sides. The full estimated round-trip commission is reserved while a position is open; actual closing commission is incurred only when the position closes. Open and closed position tables show gross P&L, estimated fees and net P&L separately. Open a trade with “Paper trade this signal” in the signal panel or the Paper button in the scanner. Break-even stops use a fee-adjusted estimated price, not a guaranteed net-zero fill. Real market gaps, bid-ask spread, changing fees, funding, liquidation and connection delays can still cause a loss. The break-even history records activation and subsequent outcomes for new events. TP1, TP2 and TP3 are displayed with estimated net R:R. The candle-based paper manager checks completed 1-minute candles while this page is open; if the browser is closed or disconnected, simulated exits pause. Intrabar sequencing is approximated conservatively with stop-first handling. Stop gaps use the observed price when worse than the stop. Round-trip commissions use the selected Maker/Taker rates and slippage uses the configured per-side estimate; both are assumptions, not guaranteed execution costs. This paper simulator does not model historical funding payments, exchange maintenance margin, liquidation, partial fills, or tick-level execution; futures results are not an exchange-accurate simulation. Spot is long-only. A few paper trades prove nothing: validate on unseen historical data and forward paper trading before judging a strategy.

Signal journal & setup history

Records new or materially changed BUY/SELL setups from completed scans and links matching paper-trade outcomes when available. It is not a backtest or verified win-rate report.
Journal entries are stored in this browser only. Paper outcomes are linked only when pair, direction, market/timeframe, price and timing match; unmatched setups are labelled “Not paper-traded.” Signal strength is not win probability. Use a reproducible historical backtest for performance evaluation.
`; w.document.open();w.document.write(page);w.document.close(); } $('popen').onclick=e=>{const cb=e.target.closest('button[data-chart-id]');if(cb){const x=paper.find(q=>String(q.id)===cb.dataset.chartId&&q.st==='open');if(x)openPositionChart(x);return}const qb=e.target.closest('button[data-qcancel]');if(qb){const q=pending.find(x=>x.id===qb.dataset.qcancel);if(q)finishPending(q,'Cancelled','Cancelled manually by user');renderPaper();return}const b=e.target.closest('button[data-a]');if(!b)return;const x=paper.find(q=>q.id==b.dataset.id);if(!x)return;const p=px_(x.s); if(b.dataset.a==='cancel')paper=paper.filter(q=>q!==x); else if(b.dataset.a==='be'){if(p==null||x.dir*(p-x.entry)<=0){toast('Break-even is available once the trade is in profit');return}if(setBreakEven(x,'Manual break-even',p,Date.now()))toast('Fee-adjusted break-even stop set at '+f(x.sl));else toast('Existing stop already provides equal or better protection')} else{if(p==null){toast('No current price available');return}x.st='Manual';x.exit=p;x.pnl=pnlOf(x,p);x.ct=Date.now()} savePaper();renderPaper()}; $('sigp').onclick=e=>{ if(lastRes&&lastRes.d.tp&&e.target.closest('button[data-paper]'))openPaper(lastRes.sym,lastRes.d.dir,lastRes.d.tp,lastRes.tf); if(lastRes&&lastRes.d.tp&&e.target.closest('button[data-queue]'))queuePaper(lastRes.sym,lastRes.d.dir,lastRes.d.tp,$('tf').value,$('mkt').value); if(lastRes&&lastRes.liq?.valid&&e.target.closest('button[data-liqqueue]')){ const q=lastRes.liq,inp=getInp(),tp={dir:q.dir,entry:q.entry,zone:q.zone,sl:q.sl,tps:q.tps,rr:q.rr,size:q.size,notional:q.notional,cost:q.entry*(inp.cost/100)}; queuePaper(lastRes.sym,q.dir,tp,$('tf').value,$('mkt').value,'liquidity'); } }; $('pbal').onchange=()=>{pstart=+$('pbal').value||10000;savePaper();renderPaper()}; $('preset').onclick=()=>{if(confirm('Reset the paper account? This deletes all open positions and trade history.')){paper=[];pending=[];missed=[];pstart=+$('pbal').value||10000;savePaper();renderPaper()}}; renderPaper(); renderSignalJournal(); $('go').onclick=run;$('sym').addEventListener('keydown',e=>{if(e.key==='Enter')run()}); let timer=null;$('auto').onchange=()=>{clearInterval(timer);if($('auto').checked)timer=setInterval(()=>mode==='all'?scanAll():run(),30000)}; // ---- Saddal Crypto Insights additions: 5-timeframe panel + Top 5 const MTF=['5m','15m','1h','4h','1d'],tfLabel={'5m':'5m','15m':'15m','1h':'1H','4h':'4H','1d':'1D'}; let mtfCache=null,mtfTok=0,top5=[],top5Err=null,top5Meta=null; const indC=(a,dir)=>{let ok=0,bu=0,be=0;a.ind.forEach((x,i)=>{if(i===6)return;if(x[2]>0)bu++;else if(x[2]<0)be++;if(x[2]*dir>0)ok++});return{ok,bu,be,n:9}}; const trendOf=a=>a.stack>=3?1:a.stack<=1?-1:0; const sigInv=(dir,t,live,A)=>dir*(live-t.sl)<=0?'price has already passed the stop-loss':dir*(live-t.tps[0])>=0?'price has already reached TP1':Math.abs(live-t.entry)>A?'price moved more than 1×ATR from the entry':null; const whyOf=(a,d,ic)=>{if(!d.dir)return(d.why[0]||'Evidence is mixed.')+` ${ic.bu} bullish / ${ic.be} bearish indicators. Watch support ${f(a.sL)} and resistance ${f(a.sH)}.`; const top=a.S.filter(x=>x[1]!=null&&x[1]*d.dir>.2).sort((x,y)=>(y[1]-x[1])*d.dir).slice(0,2).map(x=>`${x[0]} (${x[2].length>60?x[2].slice(0,57)+'…':x[2]})`); return`${top.join(' and ')||'Mixed drivers'} lead the score. ${ic.ok} of ${ic.n} indicators confirm; ${d.dir>0?a.agreeU:a.agreeD} of ${Object.keys(a.gs).length} signal groups agree.`}; async function loadMTF(sym,fut,inp){const tok=++mtfTok,el=$('mtf'),name=sym.replace('USDT','')+'/USDT'; el.innerHTML=`

Five-timeframe signals · ${name}

Loading 5m, 15m, 1H, 4H and 1D candles from Binance…

`; try{const need=[...new Set([...MTF,...MTF.map(t=>HTF[t])])],raw=await Promise.all(need.map(t=>kl(sym,t,fut).catch(e=>({err:e.message})))),m={};need.forEach((t,i)=>m[t]=raw[i]); if(MTF.every(t=>m[t].err))throw new Error(m['5m'].err); let fund=null,oi=0;if(fut){try{const p=await api(`/fapi/v1/premiumIndex?symbol=${sym}`,true);fund=+p.lastFundingRate;const o=await api(`/futures/data/openInterestHist?symbol=${sym}&period=1h&limit=6`,true);oi=o.length>1?o[o.length-1].sumOpenInterest-o[0].sumOpenInterest:0}catch(e){}} const rows=MTF.map(t=>{const c=m[t];if(c.err)return{t,err:'data request failed'};const L=c[c.length-1]; if(Date.now()-L.t>3*TFMS(t))return{t,err:'stale data: the latest candle is too old'};if(c.length<210||c.some(x=>!isFinite(x.c)))return{t,err:`only ${c.length} candles; 210+ needed`}; const h=m[HTF[t]],ok=h&&!h.err&&h.length>=201,hb=ok?htfBias(h.slice(0,-1)):0,a=analyze(c.slice(0,-1),t,fut,fund,oi),d=decide(a,hb,inp); const ic=indC(a,d.dir);return{t,a,d,hb,ok,live:L.c,ic,inv:d.tp?sigInv(d.dir,d.tp,L.c,a.A):null,v:{dir:d.dir,hb,hbOk:ok,total:a.total,ag:d.dir>0?a.agreeU:a.agreeD,ng:Object.keys(a.gs).length,ic:ic.ok,mom:a.S[6][1],adx:a.ADX,rv:a.rv,z:a.z,qv:(pairs.find(p=>p.s===sym)||{}).qv}}}); const vs=rows.filter(x=>x.v).map(x=>x.v);rows.forEach(x=>{if(x.v)x.str=x.v.dir?strength(x.v,vs):Math.min(100,Math.round(Math.abs(x.a.total)*100))}); if(tok===mtfTok){mtfCache={sym,rows};renderMTF(name,fut?'USDT-M futures':'spot',rows);if(lastRes)renderSignal()}} catch(e){if(tok===mtfTok)el.innerHTML=`

Five-timeframe signals · ${name}

Could not load timeframe data: ${e.message}

`}} function renderMTF(name,mk,rows){const ok=rows.filter(r=>!r.err),act=r=>r.inv?0:r.d.dir,B=ok.filter(r=>act(r)>0).length,S=ok.filter(r=>act(r)<0).length,live=ok.length?ok[0].live:null, k=(l,v)=>`
${l}${v}
`, card=r=>{if(r.err)return`
${tfLabel[r.t]}Unavailable

${r.err}

`; const a=r.a,d=r.d,t=d.tp,dir=act(r),b=dir>0?'buy':dir<0?'sell':'wait',col=dir>0?'var(--up)':dir<0?'var(--dn)':'var(--warn)',str=r.str,tr=trendOf(a); return`
${tfLabel[r.t]}${dir>0?'▲ BUY':dir<0?'▼ SELL':'◷ HOLD'}
${k('Strength',`${str}%`)}
${dir?k('Entry (last close)',f(t.entry))+k('Stop-loss',''+f(t.sl)+'')+t.tps.map((x,i)=>k('TP'+(i+1),''+f(x)+'')).join('')+k('Risk : reward','1 : '+t.rr.map(x=>f(x,1)).join(' · ')):k('Entry / SL / TP','–')} ${k('Trend',`${tr>0?'▲ Bullish':tr<0?'▼ Bearish':'● Neutral'} · ${HTF[r.t]} ${r.ok?(r.hb>.3?'▲':r.hb<-.3?'▼':'●'):'n/a'}`)} ${k('Confirmation',dir?`${r.ic.ok} of 9 indicators`:`${r.ic.bu} bull · ${r.ic.be} bear`)}

${r.inv?'Expired: '+r.inv+'. Wait for the next completed candle.':whyOf(a,d,r.ic)}

Candle closed ${new Date(a.time+TFMS(r.t)).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}${t?` · live vs entry ${sg((r.live/t.entry-1)*100)}%`:''}
`}; const verdict=B&&!S?(B===ok.length?'All timeframes agree.':'No timeframe disagrees.'):S&&!B?(S===ok.length?'All timeframes agree.':'No timeframe disagrees.'):B&&S?'Timeframes conflict; there is no clean edge.':'No timeframe has a valid signal.'; $('mtf').innerHTML=`

Five-timeframe signals · ${name} ${mk}

Live price ${f(live)} · updated ${new Date().toLocaleTimeString()} · Binance feed OK · completed candles only

${B} BUY · ${S} SELL · ${ok.length-B-S} HOLD across ${ok.length} timeframes. ${verdict}

${rows.map(card).join('')}
Entry is the close of the last completed candle, not the live price. Stops and targets come from ATR and swing levels. Strength is a score, not a win probability. Informational only; paper trading only.
`} function buildTop5(fut,tfs,n){const bp={},thr=+$('fstr').value;scanRes.forEach(r=>(bp[r.s]=bp[r.s]||[]).push(r));let inv=0,weak=0;const best={}; scanRes.forEach(r=>{if(!r.dir||!r.tp)return;if(r.inv||r.tp.slBeyondLiq){inv++;return}if(!(r.str>thr)){weak++;return}const sib=bp[r.s],q=r.str; if(!best[r.s]||q>best[r.s].q)best[r.s]={...r,q,tfa:sib.length>1?`${sib.filter(x=>x.dir===r.dir).length} of ${sib.length}`:''}}); top5=Object.values(best).sort((x,y)=>y.q-x.q||y.str-x.str).slice(0,5); top5Meta={at:new Date(),fut,tfs:tfs.join(', '),n,inv,weak,rl:stt.rl,stale:stt.stale||0}} function renderTop5(){const el=$('top5'),H=`

Top 5 signals Minimum strength: above ${$('fstr').value}%

`,box=x=>`
${H}${x}
`; if(scanning){el.innerHTML=box('

Scanning… the ranked list appears here as soon as the scan finishes.

');return} if(top5Err){el.innerHTML=box(`

The scan failed: ${top5Err}.

`);return} if(!top5Meta){el.innerHTML=box('

Press Scan market to rank the five strongest, best-confirmed setups.

');return} const m=top5Meta,card=(r,i)=>{const t=r.tp,b=r.dir>0?'buy':'sell',col=r.dir>0?'var(--up)':'var(--dn)',hv=r.hbOk?(r.hb*r.dir>.3?'agrees':r.hb*r.dir<-.3?'opposes':'is mixed'):'is unavailable'; return`
${i+1}
${r.s.replace('USDT','')}/USDTbest on ${tfLabel[r.tf]||r.tf}${r.tfa?` · ${r.tfa} timeframes agree`:''}
${r.dir>0?'▲ BUY':'▼ SELL'}
Price at scan${f(r.price)}
${new Date(r.t).toLocaleTimeString()}
Entry${f(t.entry)}
Stop-loss${f(t.sl)}
${t.tps.map((x,j)=>`
TP${j+1}${f(x)}
`).join('')}
Strength${r.str}%
Risk : reward (TP1 · TP2 · TP3)1 : ${t.rr.map(x=>f(x,1)).join(' · ')}

${r.type||'Setup'} on ${r.tf}: 15m setup ${Number.isFinite(r.setup15)?(r.setup15*r.dir>0?'agrees':'conflicts'):'not available'}; 1h trend ${hv}; 4h trend ${Number.isFinite(r.hb4)?(r.hb4*r.dir>=.25?'agrees':'does not meet confirmation'):'not available'}; ${r.ag} of ${r.ng} signal groups agree, ${r.ic} of 9 indicators confirm, volume ${f(r.rv,1)}× average, 24h turnover ${f(r.qv/1e6,0)}M USDT.

Rank score ${r.q}/100
`}; const ex=`${m.inv} expired (price already moved) · ${m.weak} at or below the strength threshold · ${m.stale} stale`; let body=top5.length?`
${top5.map(card).join('')}
`:''; if(top5.length<5)body+=`

${top5.length?`Only ${top5.length} of 5 signals meet your strength threshold.`:'No trading signals meet your selected strength threshold. Try lowering the minimum strength.'} Nothing is added to fill the list. Excluded: ${ex}.

`; if(m.rl)body+='

Binance rate-limited the scan. The ranking covers only the pairs analyzed before it stopped.

'; el.innerHTML=box(`
${m.n} pairs · ${m.tfs} · ${m.fut?'USDT-M futures':'spot'} · scanned ${m.at.toLocaleTimeString()} · Binance feed · completed candles only
${body}
Rank score uses the same scanner strength score shown in the results table; Top 5 no longer calculates a separate ranking formula. One entry per pair, on its best timeframe. HOLD never ranks. Entry is the last completed candle close; the live price is shown separately. Informational only; paper trading only.
`)} $('top5').onclick=e=>{if(e.target.id==='t5r')return scanMarket();const o=e.target.closest('[data-o]'),p=e.target.closest('[data-pt]'),q=e.target.closest('[data-qt]');if(o){const r=top5[+o.dataset.o];selPair(r.s,r.tf)}else if(p){const r=top5[+p.dataset.pt];openPaper(r.s,r.dir,r.tp,r.tf)}else if(q){const r=top5[+q.dataset.qt];queuePaper(r.s,r.dir,r.tp,r.tf,$('mkt').value)}}; $('mtf').onclick=e=>{if(e.target.id==='mtfr')run()}; $('mtf').innerHTML='

Five-timeframe signals

Press Analyze to compare 5m, 15m, 1H, 4H and 1D signals for the selected pair.

';renderTop5(); // ---- strength model, saved preferences, connection handling const u01=x=>Math.max(0,Math.min(1,x)),tierCol=v=>v>=80?'var(--up)':v>=60?'var(--ac)':v>=40?'var(--warn)':'var(--mute)'; function strength(r,sib){const d=r.dir,o=sib.filter(x=>x!==r),A1=r.hbOk?(cl(r.hb*d)+1)/2:.5,al=o.length?.5*A1+.5*mean(o.map(x=>(cl(x.total*d*2)+1)/2)):A1; return Math.round(100*(.28*al+.28*(.5*u01(r.ag/r.ng)+.5*u01(r.ic/9))+.2*(.6*u01(r.mom*d)+.4*u01((r.adx-12)/16))+.14*(.6*u01((Math.log10(Math.max(r.qv||1,1))-6.5)/2.5)+.4*u01(r.rv/2))+.1*u01(1-Math.max(0,r.z*d)/3)))} function strAll(){/* Use the same per-timeframe score inputs as Analysis. Do not let the scanner's selected timeframe set change a pair's score. */scanRes.forEach(r=>{if(r.dir)r.str=strength(r,[r])})} function savePrefs(){try{localStorage.setItem('sci_prefs',JSON.stringify({sym:$('sym').value,tf:$('tf').value,mkt:$('mkt').value,fstr:$('fstr').value}))}catch(e){}} function restorePrefs(){try{const p=JSON.parse(localStorage.getItem('sci_prefs')||'{}');if(p.mkt==='fut'||p.mkt==='spot')$('mkt').value=p.mkt;if(TFS.includes(p.tf))$('tf').value=p.tf;if([...$('fstr').options].some(o=>o.value===p.fstr))$('fstr').value=p.fstr;if(/^[A-Z0-9]{3,20}$/.test(p.sym||''))$('sym').innerHTML=``}catch(e){}} ['sym','tf','mkt','fstr'].forEach(id=>$(id).addEventListener('change',savePrefs)); $('fstr').addEventListener('change',()=>{if(top5Meta){const m=top5Meta,at=m.at;buildTop5(m.fut,m.tfs.split(', '),m.n);top5Meta.at=at}renderTop5()}); document.addEventListener('visibilitychange',()=>{if(!document.hidden)loadPairs().catch(()=>{})}); addEventListener('online',()=>{toast('Back online: reconnecting');loadPairs().catch(()=>{})});addEventListener('offline',()=>feedPill('bad','Offline')); renderTop5(); // ===== v2: filters, backtester, candle-based paper exits ===== const CFG={on:true,maxStopATR:5,minNetRR:0.8,costMult:3,hbMin:-0.3}; function decide(a,hb,inp){const d=decideRaw(a,hb,inp);if(!CFG.on||!d.dir||!d.tp)return d;const t=d.tp,w=[]; if(t.risk>CFG.maxStopATR*a.A)w.push('Stop is '+(t.risk/a.A).toFixed(1)+' ATR wide (max '+CFG.maxStopATR+').'); if(t.cost>0&&t.risk({t:+x[0],o:+x[1],h:+x[2],l:+x[3],c:+x[4],v:+x[5]}));all=b.concat(all);end=b[0].t-1;if(d.length0&&stage===0&&sl!==feeBE&&pf!=null&&(dir>0?pf-e:e-pf)>=BE_FRAC*Math.abs(t1-e))sl=feeBE;pf=pf==null?(dir>0?hi:lo):(dir>0?Math.max(pf,hi):Math.min(pf,lo)); if(dir>0?lo<=sl:hi>=sl){const fill=dir>0?(opsl?op:sl);R+=rem*dir*(fill-e)/risk;return{R:R-costR,j}} if(!managed){if(dir>0?hi>=t1:lo<=t1){R+=dir*(t1-e)/risk;return{R:R-costR,j}}} else if(stage===0){if(dir>0?hi>=t1:lo<=t1){R+=.5*dir*(t1-e)/risk;rem=.5;sl=feeBE;stage=1}} else if(dir>0?hi>=t2:lo<=t2){R+=rem*dir*(t2-e)/risk;return{R:R-costR,j}}} const jj=Math.min(j,end);R+=rem*dir*(c[jj].c-e)/risk;return{R:R-costR,j:jj}} function stats(tr,k){const n=tr.length;if(!n)return{n:0,w:0,avg:0,pf:0,tot:0,dd:0};const r=tr.map(x=>x[k]);let gw=0,gl=0,cum=0,pk=0,dd=0; r.forEach(v=>{v>0?gw+=v:gl-=v;cum+=v;pk=Math.max(pk,cum);dd=Math.max(dd,pk-cum)}); return{n,w:r.filter(v=>v>0).length/n*100,avg:cum/n,pf:gl?gw/gl:(gw?Infinity:0),tot:cum,dd}} let lastBTTrades=[]; async function runBacktest(){readCfg();BE_FRAC=(+$('beAt').value||0)/100;const b=$('btGo'),o=$('btOut');$('mcOut').hidden=true;lastBTTrades=[];b.disabled=true; const sym=$('sym').value.trim().toUpperCase(),tf=$('tf').value,fut=$('mkt').value==='fut',htf=HTF[tf],inp={...getInp(),noGate:true},N=+$('btN').value,maxB=+$('btMax').value||100,W=400; try{o.innerHTML='Downloading and validating history…'; const need=Math.min(5000,Math.ceil(N*TFMS(tf)/TFMS(htf))+260); const [c0,h0]=await Promise.all([klHist(sym,tf,fut,N+1),klHist(sym,htf,fut,need)]); const c=c0.slice(0,-1),h=h0.slice(0,-1);if(c.lengthReplaying '+(i-W)+' / '+(c.length-W)+' candles · TP1 '+tp1Trades.length+' · managed '+managedTrades.length;await new Promise(r=>setTimeout(r))} if(i<=busy1&&i<=busy2)continue; const tClose=c[i].t+tfm;while(hp=200&&htf!==tf){hb=hbCache[hp]??(hbCache[hp]=htfBias(h.slice(0,hp)))} const a=analyze(c.slice(i-W+1,i+1),tf,fut,null,0),d=decide(a,hb,inp);if(!d.dir||!d.tp)continue; const tp=d.tp,risk=Math.abs(tp.entry-tp.sl),costR=tp.entry*inp.cost/100/risk,meta={i,dir:d.dir,sc:Math.abs(a.total),hb:hb*d.dir}; if(i>busy1){const z=simTrade(c,i,tp,costR,false,maxB);busy1=z.j;tp1Trades.push({...meta,R:z.R})} if(i>busy2){const z=simTrade(c,i,tp,costR,true,maxB);busy2=z.j;managedTrades.push({...meta,R:z.R})} } renderBT({tp1:tp1Trades,managed:managedTrades},sym,tf,fut,c.length-W,Math.floor(c.length/2))} catch(e){o.innerHTML='Backtest failed: '+esc(e.message)+''} b.disabled=false} function renderBT(models,sym,tf,fut,bars,splitAt){const o=$('btOut'),tp1=models.tp1||[],managed=models.managed||[];lastBTTrades=managed.slice();if(!tp1.length&&!managed.length){o.innerHTML='No trades passed the filters over '+bars+' candles. Try loosening the filters.';return} const groups=[['All trades',x=>true],['Long',x=>x.dir>0],['Short',x=>x.dir<0],['Score 0.15–0.30',x=>x.sc<.3],['Score 0.30–0.45',x=>x.sc>=.3&&x.sc<.45],['Score 0.45+',x=>x.sc>=.45],['HTF agrees',x=>x.hb>.3],['HTF neutral/weak',x=>x.hb<=.3],['First half (in-sample)',x=>x.ix.i>=splitAt]], pf=v=>v===Infinity?'∞':v.toFixed(2),cc=v=>v>0?'up':v<0?'dn':'mu',rows=groups.map(g=>{const A=stats(tp1.filter(g[1]),'R'),B=stats(managed.filter(g[1]),'R');if(!A.n&&!B.n)return'';return`${g[0]}${A.n}${A.w.toFixed(0)}%${A.avg.toFixed(2)}R${B.n}${B.w.toFixed(0)}%${B.avg.toFixed(2)}R${pf(B.pf)}${B.tot.toFixed(1)}R${B.dd.toFixed(1)}R`}).join(''), B=stats(managed,'R'),verdict=B.n<30?'Too few managed-model trades for a reliable conclusion (need 30+, ideally 100+).':B.avg>0.1&&B.pf>1.3?'Positive expectancy in this sample. Confirm it holds in the second-half holdout and on other pairs before trusting it.':B.avg>0?'Marginal: expectancy is close to zero after modeled costs.':'Negative expectancy in this sample: this setup configuration lost money after modeled costs.'; o.innerHTML=`
${sym.replace('USDT','')}/USDT · ${fut?'USDT-M futures':'spot'} · ${tf} · ${bars} candles replayed · estimated fees + slippage included · auto break-even ${BE_FRAC?Math.round(BE_FRAC*100)+'% of TP1':'off'}

${verdict}

${rows}
GroupTP1 tradesTP1 win%TP1 avg RManaged tradesManaged win%Managed avg RManaged PFManaged total RManaged max DD
Resamples managed-model R-multiples; not a forecast.
TP1 and managed models now maintain independent trade-overlap timelines. Futures funding, maintenance margin, liquidation and partial fills are not yet included in this historical replay.
`} function runMonteCarlo(){const host=$('mcOut'),sample=lastBTTrades.map(x=>Number(x.R)).filter(Number.isFinite);if(sample.length<5){host.hidden=false;host.innerHTML='Monte Carlo unavailable

At least 5 completed backtest trades are required.

';return}const paths=10000,n=sample.length,ends=[],dds=[];let ruin=0;for(let p=0;p{const b=a.slice().sort((x,y)=>x-y);return b[Math.floor((b.length-1)*p)]};const med=q(ends,.5),lo=q(ends,.05),hi=q(ends,.95),dd50=q(dds,.5),dd95=q(dds,.95);host.hidden=false;host.innerHTML=`

Monte Carlo · ${paths.toLocaleString()} bootstrap paths

Median ending result${f(med,2)}R
5th–95th percentile${f(lo,2)}R to ${f(hi,2)}R
Paths ending negative${(ruin/paths*100).toFixed(1)}%
Median max drawdown${f(dd50,2)}R
95th percentile drawdown${f(dd95,2)}R
Trades resampled per path${n}
This is a bootstrap sensitivity test using the observed managed trade outcomes, sampled with replacement. It assumes the historical trade-R distribution is representative and ignores changing market regimes, serial dependence, parameter uncertainty and future execution differences. It is not a probability that the strategy will be profitable.
`;host.scrollIntoView({behavior:'smooth',block:'nearest'})} $('btOut').addEventListener('click',e=>{if(e.target&&e.target.id==='mcRun')runMonteCarlo()}); $('btGo').onclick=runBacktest;['cfgOn','cfgStop','cfgRR','cfgCost','cfgHB'].forEach(id=>$(id).addEventListener('change',readCfg)); // Candle-based paper exits: stop first if both touched; managed exit = half at TP1, stop to break-even, rest to TP2. let paperBusy=false; async function updatePaper(){if(paperBusy){return}paperBusy=true; try{for(const x of paper.filter(p=>p.st==='open')){ try{const fut=x.market==='fut',d=await api(`${fut?'/fapi/v1':'/api/v3'}/klines?symbol=${x.s}&interval=1m&limit=1000&startTime=${Math.max(x.lc||0,x.t)}`,fut),now=Date.now(); for(const k of d){const t=k[0];if(t<=x.t||t<=(x.lc||0)||t+6e4>now)continue;const hi=+k[2],lo=+k[3];x.lc=t;const stopBeforeCandle=x.sl; // Never move stop using a high/low that occurs later in this candle if(x.dir>0?lo<=stopBeforeCandle:hi>=stopBeforeCandle){x.st='SL';const op=+k[1];x.exit=x.dir>0?(opstopBeforeCandle?op:stopBeforeCandle);x.pnl=pnlOf(x,x.exit);x.ct=Date.now();recordStopAudit(x,'completed 1m candle',stopBeforeCandle,op,t,hi,lo);toast(`Paper ${x.be?'break-even stop':'stop hit'}: ${x.s} ${sg(x.pnl)} USDT`);break} if(x.dir>0?hi>=x.tp:lo<=x.tp){ if(!halfTaken(x)&&x.tps&&x.tps[1]){const half={...x,id:x.id+'h',size:x.size/2,st:'TP1',exit:x.tp,ct:Date.now()};half.pnl=pnlOf(half,x.tp);paper.push(half); x.size/=2;x.notional=x.entry*x.size;setBreakEven(x,'TP1 reached, remaining half protected',x.tp,Date.now());x.tp=x.tps[1];x.half=true;toast(`Paper TP1: half closed ${sg(half.pnl)} USDT, stop moved to break-even · ${x.s}`)} else{x.st='TP1';x.exit=x.tp;x.pnl=pnlOf(x,x.exit);x.ct=Date.now();toast(`Paper target hit: ${x.s} ${sg(x.pnl)} USDT`);break}} if(x.st==='open')autoBE(x,hi,lo,false);} }catch(e){}} }finally{paperBusy=false} updatePaperSnap();} // ===== rebuild additions & fixes ===== function cmpK(a,b){if(a==null&&b==null)return 0;if(a==null)return -1;if(b==null)return 1;return typeof a==='string'||typeof b==='string'?String(a).localeCompare(String(b)):a-b} function halfTaken(x){return !!x.half||(!!x.tps&&x.tps[0]!==x.tp)} let runBusy=false,runAgain=false; async function run(){if(runBusy){runAgain=true;return}runBusy=true;try{do{runAgain=false;await runCore()}while(runAgain)}finally{runBusy=false}} function takeTP(x){if(!halfTaken(x)&&x.tps&&x.tps[1]){const h={...x,id:x.id+'h',size:x.size/2,st:'TP1',exit:x.tp,ct:Date.now()};h.pnl=pnlOf(h,x.tp);paper.push(h); x.size/=2;x.notional=x.entry*x.size;setBreakEven(x,'TP1 reached, remaining half protected',x.tp,Date.now());x.tp=x.tps[1];x.half=true;toast(`Paper TP1: half closed ${sg(h.pnl)} USDT, remaining stop moved to fee-adjusted break-even · ${x.s}`)} else{x.st='TP1';x.exit=x.tp;x.pnl=pnlOf(x,x.exit);x.ct=Date.now();toast(`Paper target hit: ${x.s} ${sg(x.pnl)} USDT`)}} function updatePaperSnap(){paper.filter(x=>x.st==='open').forEach(x=>{const p=px_(x.s);if(p==null)return; if(x.dir>0?p<=x.sl:p>=x.sl){x.st='SL';x.exit=x.dir>0?Math.min(p,x.sl):Math.max(p,x.sl);x.pnl=pnlOf(x,x.exit);x.ct=Date.now();recordStopAudit(x,'observed quote',x.sl,p,Date.now(),p,p);toast(`Paper ${x.be?'break-even stop':'stop hit'}: ${x.s} ${sg(x.pnl)} USDT`)} else if(x.dir>0?p>=x.tp:p<=x.tp)takeTP(x)});savePaper();renderPaper()} function openPaper(s,dir,tp,tfv){if(!tp||!dir)return;const market=$('mkt').value; if(market==='spot'&&dir<0){toast('Spot is long-only in this simulator. Use Futures for short paper trades.');return} const inp=getInp(),acct=pAcct(),lev=market==='fut'?Math.max(1,inp.lev):1,lp=window.__scalpFuturesFill?.s===s?window.__scalpFuturesFill.p:px_(s),entry=Number.isFinite(lp)?lp:+tp.entry,sl=+tp.sl,t1=Array.isArray(tp.tps)?+tp.tps[0]:NaN; if(!(entry>0)||!Number.isFinite(sl)||!Number.isFinite(t1)){toast('Trade rejected: invalid entry, stop or target');return} if(dir*(entry-sl)<=0||dir*(t1-entry)<=0){toast('Trade rejected: live price '+f(entry)+' is already beyond the stop or first target');return} if(!Array.isArray(tp.zone)||tp.zone.length!==2||entryMath.max(...tp.zone)){toast('Trade rejected: live price '+f(entry)+' is outside the calculated entry zone '+(tp.zone?f(Math.min(...tp.zone))+'–'+f(Math.max(...tp.zone)):'(unavailable)')+'. Queue the setup and wait for price to return.');return} const risk=Math.abs(entry-sl),cost=entry*Math.max(0,inp.cost)/100,netR=netRR(entry,sl,t1,cost),riskBudget=Math.max(0,acct.eq)*Math.max(0,inp.rk)/100; if(netR==null||netR0)||!(riskBudget>0)){toast('Trade rejected: invalid stop distance or risk settings');return} const sizingMode=$('positionSizingMode')?.value||'risk'; const fixedInvestment=Number($('signalInvestment')?.value),fixedNotional=Number($('signalNotional')?.value); const enforceRisk=$('signalRiskCap')?.checked!==false; const sized=safePaperSize(sizingMode,entry,sl,cost,lev,acct,riskBudget,fixedInvestment,fixedNotional); if(sized.error){toast('Trade rejected: '+sized.error);return} const {size,notional,margin}=sized; if(!(size>0)||!Number.isFinite(notional)){toast('Trade rejected: calculated position size is invalid');return} if(margin>acct.avail){toast('Not enough virtual funds: needs '+f(margin,2)+' USDT margin, available '+f(acct.avail,2));return} paper.push({id:Date.now()*1000+Math.floor(Math.random()*1000),s,dir,market,tf:tfv||$('tf').value,entry,sl,initialSL:sl,tp:t1,tps:tp.tps.slice(),size,cost,notional,lev,t:Date.now(),st:'open',initialRR:(tp.rr||tp.tps.map(t=>netRR(entry,sl,t,cost))).slice(0,3)}); savePaper();renderPaper();toast('Paper trade opened: '+s.replace('USDT','')+'/USDT at '+f(entry))} // Keep sizing inputs understandable and persist choices locally. function updateSizingHelp(){const mode=$('positionSizingMode').value,lev=$('mkt').value==='fut'?Math.max(1,Number($('lev').value)||1):1; $('signalInvestment').disabled=mode!=='investment';$('signalNotional').disabled=mode!=='notional'; $('sizingExplanation').textContent=mode==='risk'?'Automatic: position size is limited by stop-loss risk, maximum notional exposure, maximum margin allocation, and available funds.':mode==='investment'?'Fixed investment: '+$('signalInvestment').value+' USDT allocated as '+($('mkt').value==='fut'?'futures margin at '+lev+'× leverage':'spot purchase value')+'. Estimated stop-loss risk is checked against your account risk limit.':'Fixed notional: '+$('signalNotional').value+' USDT position value. Required '+($('mkt').value==='fut'?'margin is notional / '+lev:'spot investment equals notional')+'. Estimated stop-loss risk is checked against your account risk limit.'; try{localStorage.setItem('sci_sizing',JSON.stringify({mode,investment:$('signalInvestment').value,notional:$('signalNotional').value,cap:$('signalRiskCap').checked,maxExposure:$('maxExposure').value,maxMarginPct:$('maxMarginPct').value}))}catch(e){} } try{const z=JSON.parse(localStorage.getItem('sci_sizing')||'null');if(z){if(['risk','investment','notional'].includes(z.mode))$('positionSizingMode').value=z.mode;if(z.investment!=null)$('signalInvestment').value=z.investment;if(z.notional!=null)$('signalNotional').value=z.notional;if(typeof z.cap==='boolean')$('signalRiskCap').checked=z.cap;if(z.maxExposure!=null)$('maxExposure').value=z.maxExposure;if(z.maxMarginPct!=null)$('maxMarginPct').value=z.maxMarginPct}}catch(e){} ['positionSizingMode','signalInvestment','signalNotional','signalRiskCap','maxExposure','maxMarginPct','lev','mkt'].forEach(id=>$(id).addEventListener('change',updateSizingHelp));updateSizingHelp(); // switching market invalidates scan results (they belong to the other market) $('mkt').addEventListener('change',()=>{scanRes=[];top5=[];top5Meta=null;prevSig={};scanN=0;renderScan();renderTop5();renderCards()}); // ---- views function showView(v){if(window.sciNavigate)window.sciNavigate(v);else{document.querySelectorAll('.view').forEach(e=>e.hidden=e.id!=='v-'+v);document.querySelectorAll('.nav button').forEach(b=>b.classList.toggle('on',b.dataset.go===v));} if(v==='chart'){try{tvLastSymbol='';renderTVChart();updateTVPrice()}catch(e){console.error('Chart load error',e);const n=$('tvStatus');if(n)n.textContent='Chart unavailable: '+e.message}}try{localStorage.setItem('sci_view',v)}catch(e){}try{scrollTo(0,0)}catch(e){}} document.querySelector('.nav').onclick=null; let v0='analyze';try{v0=localStorage.getItem('sci_view')||v0}catch(e){}showView(document.getElementById('v-'+v0)?v0:'analyze');try{scrollTo(0,0)}catch(e){} // ===== auto break-even: move stop to entry once price covers X% of the way to TP1 ===== let BE_FRAC=0; function beProgress(x){const t1=x.tps&&x.tps[0];if(x.pf==null||!(t1>0))return 0;return(x.pf-x.entry)/(t1-x.entry)} function autoBE(x,hi,lo,now){const th=(+$('beAt').value||0)/100;if(!(th>0)||x.be||halfTaken(x))return; const upd=()=>{const e=x.dir>0?hi:lo;x.pf=x.pf==null?e:(x.dir>0?Math.max(x.pf,e):Math.min(x.pf,e))}; if(now)upd(); if(beProgress(x)>=th){x.auto=true;if(setBreakEven(x,'Automatic trigger at '+Math.round(th*100)+'% of TP1',x.dir>0?hi:lo,Date.now()))toast('Auto fee-adjusted break-even: '+x.s+' stop set to '+f(x.sl))} if(!now)upd()} // Quote checks may close a position, but must not retroactively activate break-even. // Auto BE is evaluated only after the completed candle's original stop and target checks. try{const b=localStorage.getItem('sci_be');if(b!==null)$('beAt').value=b}catch(e){} $('beAt').addEventListener('change',()=>{try{localStorage.setItem('sci_be',$('beAt').value)}catch(e){}}); // Universal pair navigation and live queue prices. const universalQuotes=new Map();let universalSocket=null,universalSocketKey=''; function universalPairLink(symbol,tf){ const pair=String(symbol||'').toUpperCase().replace('/',''); if(!/^[A-Z0-9]{2,25}USDT$/.test(pair))return; const market=$('mkt').value; selectChartPair(pair,TFS.includes(tf)?tf:($('tf').value||'5m')); showView('chart'); $('universalChartDetails').textContent='Loading technical analysis for '+pair+' ('+market+')…'; setTimeout(universalRefreshAnalysis,1000); } function universalRefreshAnalysis(){ const host=$('universalChartDetails');if(!host)return; const sig=$('sigp'),out=$('out'); host.innerHTML=''; for(const el of [sig,out]){if(!el)continue;const clone=el.cloneNode(true);clone.removeAttribute('id');clone.querySelectorAll('[id]').forEach(x=>x.removeAttribute('id'));clone.querySelectorAll('button,input,select').forEach(x=>x.remove());host.appendChild(clone)} } // Click pair names in any table, scanner card, journal or dashboard. Ignore existing buttons. document.addEventListener('click',e=>{ if(e.target.closest('button,a,input,select,label,[data-go]'))return; const cell=e.target.closest('td,th,.t5,.tfc,.k,.card');if(!cell)return; const text=cell.textContent||''; let match=text.match(/\b([A-Z0-9]{2,25})\s*\/\s*USDT\b/i)||text.match(/\b([A-Z0-9]{2,25}USDT)\b/i); if(!match && cell.tagName==='TD' && cell.cellIndex===0){const t=text.trim().replace(/\s+/g,'');match=t.match(/^([A-Z0-9]{2,25})\/USDT$/i)} if(!match)return; const symbol=match[1].toUpperCase().endsWith('USDT')?match[1].toUpperCase():match[1].toUpperCase()+'USDT'; const row=cell.closest('tr');const tf=(row?.textContent||'').match(/\b(1m|3m|5m|15m|30m|1h|2h|4h|6h|12h|1d|3d|1w)\b/)?.[1]; universalPairLink(symbol,tf); }); function universalPaintPrices(){document.querySelectorAll('[data-live-pair]').forEach(el=>{const price=universalQuotes.get(el.dataset.livePair);if(Number.isFinite(price)&&price>0){el.textContent=f(price);el.title='Live Binance quote, updates while connected';}});paintEntryProximity()} function universalSubscribeQueue(){ const symbols=[...new Set(pending.map(x=>x.s.toLowerCase()))].sort();const key=symbols.join(',');if(key===universalSocketKey)return; universalSocketKey=key;if(universalSocket){universalSocket.close();universalSocket=null}if(!symbols.length)return; const endpoint=$('mkt').value==='fut'?'fstream.binance.com':'stream.binance.com:9443'; try{universalSocket=new WebSocket('wss://'+endpoint+'/stream?streams='+symbols.map(s=>s+'@miniTicker').join('/')); universalSocket.onmessage=ev=>{try{const data=JSON.parse(ev.data).data;if(!data)return;const price=Number(data.c);if(Number.isFinite(price)&&price>0){universalQuotes.set(data.s,price);universalPaintPrices()}}catch(e){}}; universalSocket.onclose=()=>{universalSocket=null;universalSocketKey=''}; }catch(e){universalSocketKey=''} } const universalOriginalRenderPaper=renderPaper; renderPaper=function(){universalOriginalRenderPaper();universalSubscribeQueue();universalPaintPrices()}; $('mkt').addEventListener('change',()=>{universalSocketKey='';universalSubscribeQueue()}); setInterval(()=>{if(!document.hidden)universalSubscribeQueue()},4000); const universalObserver=new MutationObserver(()=>{if(!$('v-chart').hidden)universalRefreshAnalysis()}); universalObserver.observe($('sigp'),{childList:true,subtree:false}); universalObserver.observe($('out'),{childList:true,subtree:false}); // Quality-gate strictness selector (added): Strict = 1h and 4h both aligned; Balanced = 1h aligned, 4h only must not oppose. (function(){const m=document.getElementById('intradayMode');if(!m||document.getElementById('qmode'))return;const l=document.createElement('label');l.innerHTML='Quality gate';m.parentElement.after(l)})(); // Dedicated Quick Scalper module. Paper-only. Existing scanner remains unchanged. (function(){ const nav=document.querySelector('.nav'); const btn=document.createElement('button');btn.dataset.go='scalper';btn.innerHTML='⚡ Quick Scalper'; nav.insertBefore(btn,nav.querySelector('[data-go="paper"]')); const section=document.createElement('section');section.id='v-scalper';section.className='view';section.hidden=true; section.innerHTML=`

⚡ Quick Scalper · Futures Paper Mode

15m trend bias, 5m setup, 1m closed-candle entry. Uses Binance USDT-M Futures. No real orders.

Signal filters: EMA 9/21 alignment, VWAP, RSI 7, ATR 14, relative volume, 5m and 15m confirmation, and net cost-aware R:R. Prices and indicators use completed candles. Pending orders are simulated only while this browser is open. Risk applies when opening a scalp paper position.

Ready to scan.
Auto queue is enabled when auto scan is selected.

Scalping paper trades

Dedicated view of scalp positions. All positions remain in the main Paper trading account and history.

Open scalp positions

Closed scalp trades

Scalping performance

Scalping opportunities and diagnostics

Scalping entry watchlist

Queue a qualified setup to wait for its entry zone. Each queued trade expires after 15 minutes and must pass a fresh confirmation before opening.

Scalping automation activity log

`; // Fast-trade controls augment the existing scalping engine without replacing it. const fastPanel=document.createElement('div');fastPanel.className='panel';fastPanel.innerHTML=`

⚡ Fast trade controls

1-minute entries, 5-minute setup, 15-minute trend. Paper trading only.

Fast scans are subject to exchange rate limits. Signals require all risk and cost filters. No real orders are placed.

`; section.insertBefore(fastPanel,section.children[1]); document.querySelector('main.main').appendChild(section); const el=id=>document.getElementById(id); let results=[],watch=[],busy=false,watchBusy=false,lastScan=0,autoProcessing=false,scUniversePairs=[],scVolumePairs=[],scCancelScan=false; try{watch=JSON.parse(localStorage.getItem('sci_scalp_watch')||'[]');if(!Array.isArray(watch))watch=[]}catch(e){watch=[]} let scalpEvents=[];try{scalpEvents=JSON.parse(localStorage.getItem('sci_scalp_events')||'[]');if(!Array.isArray(scalpEvents))scalpEvents=[]}catch(e){scalpEvents=[]} const persist=()=>{try{localStorage.setItem('sci_scalp_watch',JSON.stringify(watch));localStorage.setItem('sci_scalp_events',JSON.stringify(scalpEvents.slice(-300)))}catch(e){}}; function scalpLog(s,action,detail){scalpEvents.push({time:Date.now(),s,action,detail});scalpEvents=scalpEvents.slice(-300);persist();drawScalpLog()} function drawScalpLog(){const host=el('scEventLog');if(!host)return;host.innerHTML='TimePairActionDetails'+scalpEvents.slice(-80).reverse().map(x=>`${new Date(x.time).toLocaleString()}${esc(x.s)}${esc(x.action)}${esc(x.detail)}`).join('')} const valid=s=>/^[A-Z0-9]{2,24}USDT$/.test(s); const fmtPrice=x=>Number.isFinite(x)?String(Number(x.toPrecision(9))):'—'; const closes=c=>c.map(x=>x.c); function vwap(c){const day=Math.floor(c.at(-1).t/86400000);let pv=0,v=0;for(const b of c){if(Math.floor(b.t/86400000)!==day)continue;pv+=(b.h+b.l+b.c)/3*b.v;v+=b.v}return v?pv/v:NaN} function atr14(c){const z=c.slice(-30),tr=z.map((x,i)=>i?Math.max(x.h-x.l,Math.abs(x.h-z[i-1].c),Math.abs(x.l-z[i-1].c)):x.h-x.l);return mean(tr.slice(-14))} function calc(c1,c5,c15,s){ if([c1,c5,c15].some(c=>c.length<60))return {s,status:'WAIT',reason:'Insufficient closed candles'}; const C1=closes(c1),C5=closes(c5),C15=closes(c15),p=C1.at(-1),e9=ema(C1,9).at(-1),e21=ema(C1,21).at(-1),e5=ema(C5,21).at(-1),e5slow=ema(C5,50).at(-1),e15=ema(C15,21).at(-1),e15slow=ema(C15,50).at(-1),vw=vwap(c1),r=rsi(C1,7).at(-1),atr=atr14(c1),rv=c1.at(-1).v/(mean(c1.slice(-21,-1).map(x=>x.v))||1),cost=(feeRate('fut',el('feeEntry').value)+feeRate('fut',el('feeExit').value)+2*Math.max(0,Number(el('slip').value)||0)); const bias=e15>e15slow?1:e15e5slow?1:e5e21&&p>vw?1:e90?r>=52&&r<=78:r<=48&&r>=22))why.push('RSI 7 outside directional momentum range'); if(rv0))why.push('ATR unavailable'); if(cost>Number(el('scMaxCost').value))why.push('Round-trip costs exceed limit'); if(gateReasons().length)why.push('Event-risk gate active'); const mode=el('scStrategy').value; const recent=c1.slice(-22,-2),swingLow=Math.min(...recent.map(b=>b.l)),swingHigh=Math.max(...recent.map(b=>b.h)); const prev=c1.at(-2),last=c1.at(-1); const sweepLong=last.lswingLow,sweepShort=last.h>swingHigh&&last.cprev.h,shiftShort=last.c0?(sweepLong||shiftLong):dir<0?(sweepShort||shiftShort):false; const balanced=mode!=='classic'; if(balanced&&dir&&!structure)why.push('Balanced: no confirmed 1m sweep/reclaim or structure shift'); const structuralSL=dir>0?Math.min(swingLow,last.l)-.20*atr:Math.max(swingHigh,last.h)+.20*atr; const sl=dir?(balanced?structuralSL:p-dir*1.25*atr):NaN,stop=Math.abs(p-sl),tps=dir?(balanced?[1.6,2.4,3.2].map(k=>p+dir*k*stop):[1.8,2.6,3.6].map(k=>p+dir*k*atr)):[],net=dir?netRR(p,sl,tps[0],p*cost/100):null; if(dir&&balanced&&stop>3*atr)why.push('Balanced: structural stop wider than 3 ATR'); if(dir&&(net===null||netkl(s,tf,true,120)));return calc(...sets.map(c=>c.slice(0,-1)),s)} function draw(){const ready=results.filter(x=>x.status==='READY').length;el('scStats').innerHTML=`
Pairs evaluated${results.length}
Qualified setups${ready}
Rejected / waiting${results.length-ready}
`; el('scResults').innerHTML='PairStrategyDecisionPriceRSI 7VolumeNet TP1 R:REntry / Stop / TP1 / TP2 / TP3DiagnosticsAction'+results.map((x,i)=>`${esc(x.s)}${esc(x.strategy||"classic")}${x.status==='READY'?(x.dir>0?'LONG':'SHORT'):'WAIT'}${fmtPrice(x.p)}${Number.isFinite(x.r)?x.r.toFixed(1):'—'}${Number.isFinite(x.rv)?x.rv.toFixed(2)+'x':'—'}${x.net==null?'—':x.net.toFixed(2)}${x.status==='READY'?[x.p,x.sl,...x.tps].map(fmtPrice).join(' / '):'—'}${esc(x.reason)}${x.status==='READY'?` `:''}`).join(''); drawQueue();} function drawQueue(){el('scQueue').innerHTML='PairSideEntry zoneExpiryAction'+(watch.length?watch.map((w,i)=>`${esc(w.s)}${w.dir>0?'LONG':'SHORT'}${w.zone.map(fmtPrice).join(' to ')}${new Date(w.exp).toLocaleTimeString()}`).join(''):'No pending scalp setups.')} async function loadScalperPairs(){ el('scStatus').textContent='Loading full Binance USDT-M futures pair list...'; try{ const [info,tickers]=await Promise.all([api('/fapi/v1/exchangeInfo',true),api('/fapi/v1/ticker/24hr',true)]); const eligible=new Set((info.symbols||[]).filter(x=>x.status==='TRADING'&&x.quoteAsset==='USDT'&&x.contractType==='PERPETUAL'&&valid(x.symbol)).map(x=>x.symbol)); scUniversePairs=[...eligible].sort(); scVolumePairs=(tickers||[]).filter(x=>eligible.has(x.symbol)).sort((a,b)=>(Number(b.quoteVolume)||0)-(Number(a.quoteVolume)||0)).map(x=>x.symbol); updateScalperPick();el('scStatus').textContent=`Loaded ${scUniversePairs.length} active USDT perpetual futures pairs. Select Top 25/50/100, All or custom.`; }catch(e){el('scStatus').textContent='Could not load Binance futures pairs: '+e.message+'. Try Refresh full pair list.';} } function updateScalperPick(){const q=el('scSearch').value.trim().toUpperCase();const matches=scUniversePairs.filter(s=>s.includes(q)).slice(0,250);el('scPick').innerHTML=matches.map(s=>``).join('')||'';} el('scSearch').addEventListener('input',updateScalperPick); el('scLoad').addEventListener('click',loadScalperPairs); el('scAdd').addEventListener('click',()=>{const v=el('scPick').value;if(!v)return;const old=el('scPairs').value.toUpperCase().split(/[\s,;]+/).filter(valid);el('scPairs').value=[...new Set([...old,v])].join(',');el('scUniverse').value='custom';el('scPairs').dispatchEvent(new Event('change'));}); function selectedScalpPairs(){const mode=el('scUniverse').value;if(mode==='custom')return [...new Set(el('scPairs').value.toUpperCase().split(/[\s,;]+/).filter(Boolean))].filter(s=>valid(s)&&(!scUniversePairs.length||scUniversePairs.includes(s)));if(!scVolumePairs.length)return [];return mode==='all'?scVolumePairs.slice():scVolumePairs.slice(0,Number(mode.replace('top',''))||50);} function activeScalps(){return paper.filter(t=>t.st==='open'&&(t.scalp||String(t.strategy||'').startsWith('scalp-')))} function alreadyTracked(x){return watch.some(w=>w.s===x.s)||activeScalps().some(t=>t.s===x.s)} function queueScalp(x,automatic=false){if(!x||x.status!=='READY'||alreadyTracked(x))return false;watch.push({...x,exp:Date.now()+900000,queuedAt:Date.now(),automatic});scalpLog(x.s,'QUEUED','Waiting for entry zone, 15-minute expiry');drawQueue();return true} function autoStatus(msg){el('scAutomation').textContent=msg} async function autoHandle(x){ if(!x||x.status!=='READY'||el('scAutoAction').value==='off'||alreadyTracked(x))return; const limit=Math.max(1,Number(el('scMaxOpen').value)||3); if(activeScalps().length>=limit){autoStatus('Position limit reached. New setups remain available for manual queueing.');return} if(el('scAutoAction').value==='queue'){if(queueScalp(x,true))autoStatus('Automatically queued '+x.s+' '+(x.dir>0?'LONG':'SHORT')+' at '+new Date().toLocaleTimeString());return} if(el('scAutoAction').value==='open'){ try{const d=await api('/fapi/v1/ticker/price?symbol='+encodeURIComponent(x.s),true),p=Number(d.price); if(Number.isFinite(p)&&p>=Math.min(...x.zone)&&p<=Math.max(...x.zone)){ const fresh=await evaluate(x.s); if(fresh.status==='READY'&&fresh.dir===x.dir&&p>=Math.min(...fresh.zone)&&p<=Math.max(...fresh.zone)){ const ok=await paperOpen(fresh);if(ok)autoStatus('Auto paper trade opened: '+x.s);else if(queueScalp(x,true))autoStatus('Paper entry not filled. Queued '+x.s); }else if(queueScalp(x,true))autoStatus('Fresh confirmation changed. Queued '+x.s); }else if(queueScalp(x,true))autoStatus('Waiting for entry zone. Queued '+x.s); }catch(e){if(queueScalp(x,true))autoStatus('Quote unavailable. Queued '+x.s)} } } async function scan(){ if(busy)return; const ss=selectedScalpPairs(); if(!ss.length){el('scStatus').textContent='No pairs loaded. Refresh full pair list, then scan.';return} busy=true;scCancelScan=false;el('scRun').disabled=true;results=[];draw(); const started=Date.now();let index=0,completed=0; try{ const worker=async()=>{ while(indexz.status==='READY').length} qualified.`} } }; await Promise.all(Array.from({length:Math.min(3,ss.length)},()=>worker())); draw();el('scStatus').textContent=`${scCancelScan?'Stopped':'Completed'} ${completed}/${ss.length} in ${Math.round((Date.now()-started)/1000)}s. ${results.filter(x=>x.status==='READY').length} qualified.`; }catch(e){el('scStatus').textContent='Scan failed: '+String(e.message||e);scalpLog('SYSTEM','SCAN ERROR',String(e.message||e))} finally{lastScan=Date.now();busy=false;el('scRun').disabled=false} } async function paperOpen(x,expectedPrice=null){ if(!x||x.status!=='READY')return false; const market=el('mkt').value,risk=el('rk').value,tf=el('tf').value; try{ if(activeScalps().length>=Math.max(1,Number(el('scMaxOpen').value)||3)){scalpLog(x.s,'REJECTED','Maximum active positions');return false} const quote=await api('/fapi/v1/ticker/price?symbol='+encodeURIComponent(x.s),true),fp=Number(quote.price); if(!(fp>0&&Number.isFinite(fp))){scalpLog(x.s,'REJECTED','Invalid live quote');return false} if(expectedPrice!=null&&Math.abs(fp-expectedPrice)/expectedPrice>0.002){scalpLog(x.s,'WAITING','Price moved more than 0.20% during validation');return false} if(fpMath.max(...x.zone)){scalpLog(x.s,'WAITING','Live price '+fmtPrice(fp)+' outside entry zone');return false} if(x.dir*(fp-x.sl)<=0||x.dir*(x.tps[0]-fp)<=0){scalpLog(x.s,'REJECTED','Live price beyond stop or TP1');return false} const cost=fp*(Number(x.cost)||0)/100; const rr=netRR(fp,x.sl,x.tps[0],cost); if(!(rr>=Number(el('scRR').value))){scalpLog(x.s,'REJECTED','Live net R:R '+(rr==null?'unavailable':rr.toFixed(2))+' below scalp minimum');return false} el('mkt').value='fut';el('rk').value=String(Math.min(2,Math.max(.01,Number(el('scRisk').value)||.25)));el('tf').value='1m'; updateFeeEstimate(); const before=new Set(paper.map(t=>String(t.id))); window.__scalpFuturesFill={s:x.s,p:fp}; openPaper(x.s,x.dir,{entry:fp,sl:x.sl,tps:x.tps,zone:x.zone,rr:x.tps.map(t=>netRR(fp,x.sl,t,fp*Number(x.cost)/100))},'1m'); let opened=false; for(const t of paper)if(!before.has(String(t.id))){t.strategy='scalp-'+(x.strategy||'classic');t.scalp=true;t.signalPrice=x.p;t.scalpOpenedAt=Date.now();opened=true} if(opened){savePaper();drawScalpPaper();scalpLog(x.s,'FILLED','Paper position opened at '+fmtPrice(fp))} else scalpLog(x.s,'REJECTED','Account sizing, exposure, available margin or global trade filter rejected entry'); return opened; }catch(e){scalpLog(x.s,'ERROR',String(e.message||e));return false} finally{window.__scalpFuturesFill=null;el('mkt').value=market;el('rk').value=risk;el('tf').value=tf;updateFeeEstimate()} } function drawScalpPaper(){ const trades=paper.filter(x=>x.scalp||String(x.strategy||'').startsWith('scalp-')); const opened=trades.filter(x=>x.st==='open'),closed=trades.filter(x=>x.st!=='open'); const fmt=x=>Number.isFinite(+x)?Number(x).toFixed(4):'—'; const row=x=>`${esc(x.s)}${esc(x.strategy||'scalp')}${x.dir>0?'LONG':'SHORT'}${fmtPrice(x.entry)}${fmtPrice(x.sl)}${fmtPrice(x.tps?.[0])}${esc(x.st)}${x.st==='open'?fmt(pnlOf(x,Number(universalQuotes.get(x.s)||x.entry))):fmt(x.pnl)}${new Date(x.t).toLocaleString()}`; const head='PairStrategySideEntryStopTP1StatusNet P&L USDTOpenedChart'; el('scOpenPositions').innerHTML=head+(opened.map(row).join('')||'No open scalp paper positions.'); el('scClosedPositions').innerHTML=head+(closed.slice().reverse().slice(0,100).map(row).join('')||'No closed scalp paper trades.'); const pnl=closed.reduce((a,x)=>a+(Number(x.pnl)||0),0),wins=closed.filter(x=>Number(x.pnl)>0).length; el('scPaperStats').innerHTML=`
Open scalps${opened.length}
Closed scalps${closed.length}
Win rate${closed.length?(wins/closed.length*100).toFixed(1)+'%':'—'}
Realized net P&L${fmt(pnl)}
`; } el('scPaperPage').onclick=()=>showView('paper'); for(const id of ['scOpenPositions','scClosedPositions'])el(id).addEventListener('click',e=>{const b=e.target.closest('[data-sc-chart]');if(b){selectChartPair(b.dataset.scChart,'1m');showView('chart')}}); setInterval(()=>{if(!document.hidden)drawScalpPaper()},5000); // Keep fast controls synchronized with the existing scanner and paper queue. const fastPreset=el('scFastPreset'),fastInterval=el('scFastInterval'),fastAction=el('scFastAction'); const applyFastPreset=()=>{ const presets={balanced:{rr:'1.3',vol:'1.30',cost:'0.25'},active:{rr:'1.0',vol:'1.10',cost:'0.25'},conservative:{rr:'1.6',vol:'1.50',cost:'0.20'}}; const cfg=presets[fastPreset.value]||presets.balanced; el('scRR').value=cfg.rr;el('scVol').value=cfg.vol;el('scMaxCost').value=cfg.cost; el('scFastInfo').textContent='Preset: '+fastPreset.selectedOptions[0].textContent+'. Minimum net TP1 R:R '+cfg.rr+', relative volume '+cfg.vol+'x, maximum estimated cost '+cfg.cost+'%. These are screening thresholds, not a profitability guarantee.'; }; fastPreset.addEventListener('change',applyFastPreset); fastAction.addEventListener('change',()=>{el('scAutoAction').value=fastAction.value;el('scAutoAction').dispatchEvent(new Event('change'))}); fastInterval.addEventListener('change',()=>{el('scAuto').value=fastInterval.value;el('scAuto').dispatchEvent(new Event('change'))}); el('scFastStart').onclick=()=>{applyFastPreset();el('scAutoAction').value=fastAction.value;el('scAuto').value=fastInterval.value==='0'?'30':fastInterval.value;fastInterval.value=el('scAuto').value;el('scAuto').dispatchEvent(new Event('change'));scan()}; el('scFastPause').onclick=()=>{el('scAuto').value='0';fastInterval.value='0';scCancelScan=true;el('scStatus').textContent='Fast scanning paused.';el('scAuto').dispatchEvent(new Event('change'))}; el('scRun').onclick=scan;el('scStop').onclick=()=>{scCancelScan=true;el('scStatus').textContent='Stopping scan after current pair...'}; el('scResults').onclick=e=>{const b=e.target.closest('button');if(!b)return;const qi=b.dataset.scQueue,oi=b.dataset.scOpen;if(qi!=null){const x=results[+qi];if(!x||x.status!=='READY')return;if(!queueScalp(x)){toast('Already queued or active');return}toast('Scalp queued: '+x.s)}else if(oi!=null)paperOpen(results[+oi])}; el('scQueue').onclick=e=>{const b=e.target.closest('[data-sc-cancel]');if(!b)return;watch.splice(+b.dataset.scCancel,1);persist();drawQueue()}; async function monitor(){ if(watchBusy||!watch.length||document.hidden)return; watchBusy=true; try{ for(const w of [...watch]){ if(Date.now()>=w.exp){watch=watch.filter(q=>q!==w);scalpLog(w.s,'EXPIRED','Entry not reached within queue lifetime');continue} if(activeScalps().some(t=>t.s===w.s)){watch=watch.filter(q=>q!==w);scalpLog(w.s,'REMOVED','Already in an open scalp position');continue} if(activeScalps().length>=Math.max(1,Number(el('scMaxOpen').value)||3))continue; try{ const quote=await api('/fapi/v1/ticker/price?symbol='+encodeURIComponent(w.s),true),p=Number(quote.price); if(!(Number.isFinite(p)&&p>=Math.min(...w.zone)&&p<=Math.max(...w.zone)))continue; const x=await evaluate(w.s); if(x.status!=='READY'||x.dir!==w.dir){watch=watch.filter(q=>q!==w);scalpLog(w.s,'INVALIDATED',x.reason||'Signal direction changed');continue} if(pMath.max(...x.zone)){scalpLog(w.s,'WAITING','New entry zone differs from queued entry');continue} const filled=await paperOpen(x,p); if(filled){watch=watch.filter(q=>q!==w);autoStatus('Queued scalp filled: '+w.s)} else autoStatus('Entry attempted, see automation activity log: '+w.s); }catch(e){scalpLog(w.s,'RETRY','Market request failed: '+String(e.message||e))} } persist();drawQueue() }finally{watchBusy=false} } setInterval(()=>{if(!document.hidden){monitor();const n=Number(el('scAuto').value);if(n&&!busy&&Date.now()-lastScan>=n*1000)scan()}},10000); try{const cfg=JSON.parse(localStorage.getItem('sci_scalp_cfg')||'null');if(cfg)for(const [id,val] of Object.entries(cfg))if(el(id))el(id).value=val}catch(e){} ['scPairs','scUniverse','scRR','scVol','scMaxCost','scRisk','scAuto','scStrategy','scAutoAction','scMaxOpen'].forEach(id=>el(id).addEventListener('change',()=>{const cfg={};for(const k of ['scPairs','scUniverse','scRR','scVol','scMaxCost','scRisk','scAuto','scStrategy','scAutoAction','scMaxOpen'])cfg[k]=el(k).value;try{localStorage.setItem('sci_scalp_cfg',JSON.stringify(cfg))}catch(e){}})); draw();drawScalpPaper();drawScalpLog();loadScalperPairs();autoStatus('Automatic scanning runs while this browser tab is visible. Choose a scan interval to enable it.'); })();