Description
Quantum Breakout AI — Community Description
Quantum Breakout AI is an institutional-grade breakout detection system that combines volatility compression analysis, multi-factor scoring, and automated trade management into a single indicator. Ported from AnkeAlgo's Pine v6 release to the SXVNT SDK.
What it does
The script hunts for high-probability breakouts by waiting for the market to coil — measured as ATR-normalized range compression — and then grading every breakout candidate across four independent scoring axes before drawing a signal. Each accepted breakout auto-projects its full trade plan (entry, stop, three targets) and the running stats table tracks how the system is actually performing on your chart.
Key features
ATR compression engine — detects when recent bar ranges have compressed below a configurable multiple of ATR, the precondition for explosive expansion
Power score — measures breakout candle strength relative to surrounding volatility
Whale score — volume-based confirmation looking for unusual participation on the trigger bar
Lifecycle score — tracks how long the compression has been building (longer coils = stronger releases)
Quality score — composite filter that gates whether a breakout is plotted at all
Directional bias analysis — reads recent structure to weight long vs short setups
Automatic risk model — SL placed at structural invalidation, TP1 at 1R, TP2 at 1.5R, TP3 at 2R
Energy bar — visual gauge showing real-time compression buildup so you can see a setup forming before it triggers
Breakout arrows — clean directional markers (▲ long / ▼ short) at the trigger bar
Live stats table — tracks wins, losses, win rate, and average R-multiple across the chart
~30 configurable inputs — every threshold, multiplier, length, and color is tunable
How to use
Add the indicator to a chart and let it scan a few sessions of history
Watch the energy bar — when it fills toward the top, compression is high and a breakout is imminent
When the arrow prints, the SL / TP1 / TP2 / TP3 levels are drawn automatically
Use the stats table to gauge whether the current settings are profitable for the instrument and timeframe you're trading
Tune the quality score threshold higher for fewer/cleaner signals, lower for more setups
Math
Compression is measured as range(N) / ATR(N) — when this ratio drops below your threshold, the market is statistically quiet relative to its own recent volatility. The four scores combine into a composite confidence value that must clear the quality gate before a signal fires. R-multiples are computed live so the stats table reflects realized performance, not hypothetical fills.
Best on liquid futures (ES, NQ, CL, GC) on 1m–15m timeframes. Pairs well with VWAP bands and session anchors for confluence.
Categories & Tags
Comments (0)
0/2000
Loading comments…
Source code
// Quantum Breakout AI — SXVNT port of AnkeAlgo's Pine v6 indicator
//
// Detects ATR-compressed consolidation ranges, scores them by power /
// whale activity / lifecycle / directional bias, then maps confirmed
// breakouts to SL + 1R/1.5R/2R targets. Tracks per-trade stats for
// the on-chart stats table.
function calculate(bars, ctx) {
// ─── INPUTS ─────────────────────────────────────────────────────
// Core
const showBreakoutArrows = ctx.input('Breakout Arrows', true);
const showDirection = ctx.input('Direction Marker', true);
const showEnergyBar = ctx.input('Energy Bar', true);
const showEnergyPercent = ctx.input('Show Energy %', true);
const showSLTP = ctx.input('SL/TP', true);
const slAtrMult = ctx.input('SL ATR Mult', 0.5, { min: 0.1, max: 2.0, step: 0.1 });
// Detection
const atrPeriod = ctx.input('ATR Period', 14, { min: 5, max: 50, step: 1 });
const rangeATRMult = ctx.input('Range ATR Mult', 2.5, { min: 1.0, max: 5.0, step: 0.1 });
const minBarsInRange = ctx.input('Min Bars In Range', 15, { min: 5, max: 100, step: 1 });
// Targets
const showTP2 = ctx.input('Show TP2 (1.5R)', true);
const showTP3 = ctx.input('Show TP3 (2R)', true);
const slColor = ctx.input('SL Color', '#FF6D00');
const tpColor = ctx.input('TP Color', '#00BFA5');
// Energy
const energyHighThresh = ctx.input('Energy High Threshold', 60.0, { min: 30, max: 80, step: 1 });
const energyCriticalThresh = ctx.input('Energy Critical Threshold', 80.0, { min: 50, max: 100, step: 1 });
// Stats
const showStatsTable = ctx.input('Show Stats Table', true);
const statsRangePreset = ctx.input('Stats Range', 'Last 90 Days', {
options: ['All', 'Last 30 Days', 'Last 90 Days', 'Last 180 Days', 'Year 2025']
});
// Advanced
const maxDisplayBars = ctx.input('Max Lookback Bars', 2000, { min: 300, max: 20000, step: 100 });
const maxZonesToDraw = ctx.input('Max Drawn Zones', 400, { min: 50, max: 500, step: 10 });
const activeExtendBars = ctx.input('Active Extend Bars', 5, { min: 0, max: 100, step: 1 });
const volAnomalyMult = ctx.input('Volume Anomaly Multiplier', 2.0, { min: 1.5, max: 4.0, step: 0.1 });
const dirConfidenceMin = ctx.input('Min Confidence %', 55.0, { min: 50, max: 90, step: 1 });
// Colors
const bullColor = ctx.input('Bullish Color', '#00E676');
const bearColor = ctx.input('Bearish Color', '#FF5252');
const neutralColor = ctx.input('Neutral Color', '#9E9E9E');
const boxMainColor = ctx.input('Box Main Color', '#00E5FF');
const energyLowColor = ctx.input('Energy Low Color', '#4CAF50');
const energyMedColor = ctx.input('Energy Medium Color', '#2979FF');
const energyHighColor = ctx.input('Energy High Color', '#AA00FF');
const energyCritColor = ctx.input('Energy Critical Color','#F44336');
// ─── CONSTANTS ──────────────────────────────────────────────────
const tp1Ratio = 1.0, tp2Ratio = 1.5, tp3Ratio = 2.0;
const PHASE_FORMING = 'Forming', PHASE_GROWTH = 'Growth',
PHASE_MATURE = 'Mature', PHASE_EXHAUSTION = 'Exhaustion';
const DIR_BULLISH = 'Bullish', DIR_BEARISH = 'Bearish', DIR_NEUTRAL = 'Neutral';
// ─── PRICE / VOLUME / TA ────────────────────────────────────────
const high = ctx.price.high;
const low = ctx.price.low;
const close = ctx.price.close;
const volume = ctx.price.volume;
const n = bars.length;
// ATR signature in this SDK: (high, low, close, period) — NOT (period).
const atrArr = ctx.ta.atr(high, low, close, atrPeriod);
const avgVolArr = ctx.ta.sma(volume, 20);
// ─── HELPERS ────────────────────────────────────────────────────
function highestN(arr, period, idx) {
let h = -Infinity;
const start = Math.max(0, idx - period + 1);
for (let j = start; j <= idx; j++) if (arr[j] > h) h = arr[j];
return h;
}
function lowestN(arr, period, idx) {
let l = Infinity;
const start = Math.max(0, idx - period + 1);
for (let j = start; j <= idx; j++) if (arr[j] < l) l = arr[j];
return l;
}
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
function computeCompressionScore(dur, h, vol) {
if (h <= 0 || vol <= 0 || dur <= 0) return 0;
const squeezeRatio = vol / h;
const squeezeScore = Math.min(squeezeRatio * 45, 40);
const durationScore = Math.min(dur * 2, 35);
const ageBonus = dur >= 30 ? 15 : dur >= 20 ? 10 : dur >= 15 ? 5 : 0;
const tightBonus = h < vol * 0.5 ? 10 : h < vol * 0.75 ? 5 : 0;
return clamp(squeezeScore + durationScore + ageBonus + tightBonus, 5, 100);
}
function getVolumeWeightedMean(upper, lower, t1, t2) {
let sumPV = 0, sumV = 0;
const len = Math.min(t2 - t1, 500);
for (let j = t2 - len; j <= t2; j++) {
if (j < 0 || j >= n) continue;
const mp = (high[j] + low[j] + close[j]) / 3;
if (mp <= upper && mp >= lower) { sumPV += mp * volume[j]; sumV += volume[j]; }
}
return sumV > 0 ? sumPV / sumV : (upper + lower) / 2;
}
function getRejectionCount(lvl, tol, t1, t2, checkHigh) {
let cnt = 0;
const len = Math.min(t2 - t1, 500);
for (let j = t2 - len; j <= t2; j++) {
if (j < 0 || j >= n) continue;
const isTouch = checkHigh
? (high[j] >= lvl - tol && high[j] <= lvl + tol)
: (low[j] >= lvl - tol && low[j] <= lvl + tol);
const isWick = checkHigh ? (close[j] < high[j] - tol) : (close[j] > low[j] + tol);
if (isTouch && isWick) cnt++;
}
return cnt;
}
function analyzeBreakoutBias(vwap, upper, lower, topRej, btmRej) {
const h = upper - lower;
const mid = (upper + lower) / 2;
const volBias = (vwap - mid) / (h / 2);
const wickBias = (topRej > 0 || btmRej > 0) ? (btmRej - topRej) / Math.max(topRej + btmRej, 1) : 0;
const totalBias = volBias * 0.55 + wickBias * 0.45;
const conf = Math.abs(totalBias) * 100;
const dir = totalBias > 0.1 ? DIR_BULLISH : totalBias < -0.1 ? DIR_BEARISH : DIR_NEUTRAL;
return { dir, conf: Math.min(conf + 50, 100) };
}
function scanWhaleActivity(t1, t2, upper, lower, avgVol) {
let spikes = 0, spikeVol = 0;
const len = Math.min(t2 - t1, 500);
let counted = 0;
for (let j = t2 - len; j <= t2; j++) {
if (j < 0 || j >= n) continue;
counted++;
if (high[j] <= upper && low[j] >= lower && volume[j] > avgVol * volAnomalyMult) {
spikes++;
spikeVol += volume[j];
}
}
const spikeRatio = counted > 0 ? (spikes / counted) * 100 : 0;
const intensity = (avgVol > 0 && spikes > 0) ? spikeVol / (avgVol * spikes) : 0;
return Math.min(spikeRatio * 0.6 + Math.min(intensity * 8, 50), 100);
}
function determineLifecycle(dur) {
return dur < 10 ? PHASE_FORMING : dur < 25 ? PHASE_GROWTH : dur < 50 ? PHASE_MATURE : PHASE_EXHAUSTION;
}
function evaluateZoneQuality(pwr, whale, cycle, conf) {
const cycleVal = cycle === PHASE_FORMING ? 20 : cycle === PHASE_GROWTH ? 50
: cycle === PHASE_MATURE ? 80 : cycle === PHASE_EXHAUSTION ? 60 : 50;
return Math.min(pwr * 0.35 + whale * 0.2 + cycleVal * 0.25 + conf * 0.2, 100);
}
function getEnergyColor(e) {
return e >= energyCriticalThresh ? energyCritColor
: e >= energyHighThresh ? energyHighColor
: e >= 40 ? energyMedColor : energyLowColor;
}
function withAlpha(hex, a) {
const h = hex.replace('#', '');
const r = parseInt(h.slice(0, 2), 16);
const g = parseInt(h.slice(2, 4), 16);
const b = parseInt(h.slice(4, 6), 16);
return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
}
// ─── SINGLE PASS: detect ranges → form zones → fire breakouts ──
const zones = [];
const trades = [];
let rangeHigh = null, rangeLow = null, rangeStartBar = null, barsInRange = 0;
let currentZone = null;
let activeTrade = null;
for (let i = 0; i < n; i++) {
const a = atrArr[i] || 0;
const avgV = avgVolArr[i] || 0;
if (!a || !isFinite(a)) continue;
const lookHi = highestN(high, 5, i);
const lookLo = lowestN(low, 5, i);
const compressed = (lookHi - lookLo) < a * rangeATRMult;
if (compressed) {
if (rangeHigh === null) {
rangeHigh = lookHi;
rangeLow = lookLo;
rangeStartBar = Math.max(0, i - 4);
barsInRange = 5;
} else if (high[i] <= rangeHigh + a * 0.1 && low[i] >= rangeLow - a * 0.1) {
rangeHigh = Math.max(rangeHigh, high[i]);
rangeLow = Math.min(rangeLow, low[i]);
barsInRange++;
} else {
rangeHigh = null; rangeLow = null; barsInRange = 0;
}
} else {
if (rangeHigh !== null && barsInRange >= minBarsInRange && currentZone) {
currentZone.isLive = false;
currentZone.endIdx = i - 1;
}
rangeHigh = null; rangeLow = null; barsInRange = 0;
}
if (barsInRange === minBarsInRange && rangeHigh !== null) {
if (currentZone) currentZone.isLive = false;
currentZone = {
startIdx: rangeStartBar, endIdx: i,
upperLevel: rangeHigh, lowerLevel: rangeLow,
isLive: true, isBreached: false, breakSide: '',
powerScore: 0, vwapLevel: 0,
topRejections: 0, btmRejections: 0,
whaleActivity: 0, lifeCycle: PHASE_FORMING,
biasDir: DIR_NEUTRAL, biasStrength: 0, qualityScore: 0,
entryPx: null, stopPx: null,
tp1: null, tp2: null, tp3: null,
labelY: null,
};
zones.push(currentZone);
}
if (currentZone && currentZone.isLive) {
const dur = i - currentZone.startIdx;
const rangeH = currentZone.upperLevel - currentZone.lowerLevel;
const tol = rangeH * 0.1;
if (high[i] <= currentZone.upperLevel + tol && low[i] >= currentZone.lowerLevel - tol) {
currentZone.endIdx = i;
currentZone.powerScore = computeCompressionScore(dur, rangeH, a);
currentZone.vwapLevel = getVolumeWeightedMean(currentZone.upperLevel, currentZone.lowerLevel, currentZone.startIdx, i);
currentZone.topRejections= getRejectionCount(currentZone.upperLevel, tol, currentZone.startIdx, i, true);
currentZone.btmRejections= getRejectionCount(currentZone.lowerLevel, tol, currentZone.startIdx, i, false);
currentZone.whaleActivity= scanWhaleActivity(currentZone.startIdx, i, currentZone.upperLevel, currentZone.lowerLevel, avgV);
currentZone.lifeCycle = determineLifecycle(dur);
const bias = analyzeBreakoutBias(currentZone.vwapLevel, currentZone.upperLevel, currentZone.lowerLevel, currentZone.topRejections, currentZone.btmRejections);
currentZone.biasDir = bias.dir;
currentZone.biasStrength = bias.conf;
currentZone.qualityScore = evaluateZoneQuality(currentZone.powerScore, currentZone.whaleActivity, currentZone.lifeCycle, currentZone.biasStrength);
}
}
if (currentZone && currentZone.isLive) {
const up = close[i] > currentZone.upperLevel;
const dn = close[i] < currentZone.lowerLevel;
if (up || dn) {
currentZone.isLive = false;
currentZone.isBreached = true;
currentZone.breakSide = up ? DIR_BULLISH : DIR_BEARISH;
currentZone.endIdx = i;
const buf = a * slAtrMult;
if (up) {
currentZone.entryPx = currentZone.upperLevel;
currentZone.stopPx = currentZone.lowerLevel - buf;
const risk = currentZone.entryPx - currentZone.stopPx;
currentZone.tp1 = currentZone.entryPx + risk * tp1Ratio;
currentZone.tp2 = currentZone.entryPx + risk * tp2Ratio;
currentZone.tp3 = currentZone.entryPx + risk * tp3Ratio;
} else {
currentZone.entryPx = currentZone.lowerLevel;
currentZone.stopPx = currentZone.upperLevel + buf;
const risk = currentZone.stopPx - currentZone.entryPx;
currentZone.tp1 = currentZone.entryPx - risk * tp1Ratio;
currentZone.tp2 = currentZone.entryPx - risk * tp2Ratio;
currentZone.tp3 = currentZone.entryPx - risk * tp3Ratio;
}
if (showDirection && currentZone.biasDir === currentZone.breakSide && currentZone.biasStrength >= dirConfidenceMin) {
const rangeH = currentZone.upperLevel - currentZone.lowerLevel;
currentZone.labelY = up ? currentZone.lowerLevel - rangeH * 0.5 - a * 0.1
: currentZone.stopPx + a * 0.2;
}
activeTrade = {
entryBar: i, entryTime: bars[i].timestamp,
isLong: up, entry: currentZone.entryPx,
sl: currentZone.stopPx,
tp1: currentZone.tp1, tp2: currentZone.tp2, tp3: currentZone.tp3,
status: 0, exitBar: null, exitTime: null,
};
trades.push(activeTrade);
}
}
if (activeTrade && activeTrade.status === 0 && i > activeTrade.entryBar) {
const slHit = activeTrade.isLong ? low[i] <= activeTrade.sl : high[i] >= activeTrade.sl;
const finalTP = showTP3 ? activeTrade.tp3 : showTP2 ? activeTrade.tp2 : activeTrade.tp1;
const finalHit = activeTrade.isLong ? high[i] >= finalTP : low[i] <= finalTP;
if (slHit) {
activeTrade.status = -1;
activeTrade.exitBar = i; activeTrade.exitTime = bars[i].timestamp;
activeTrade = null;
} else if (finalHit) {
activeTrade.status = 1;
activeTrade.exitBar = i; activeTrade.exitTime = bars[i].timestamp;
activeTrade = null;
}
}
}
// ─── RENDERING ──────────────────────────────────────────────────
const lastBar = n - 1;
const zonesToDraw = zones.slice(-maxZonesToDraw);
let lastBreachedSltp = null;
for (const z of zonesToDraw) {
if (lastBar - z.startIdx > maxDisplayBars) continue;
const rightBar = z.isLive ? lastBar + activeExtendBars : z.endIdx;
const h = z.upperLevel - z.lowerLevel;
const shadowOff = h * 0.05;
ctx.box(z.startIdx, z.upperLevel - shadowOff, rightBar, z.lowerLevel - shadowOff, {
borderColor: 'rgba(0,0,0,0)',
borderWidth: 0,
fillColor: 'rgba(0,0,0,0.4)',
});
ctx.box(z.startIdx, z.upperLevel, rightBar, z.lowerLevel, {
borderColor: boxMainColor,
borderWidth: 1,
fillColor: withAlpha(boxMainColor, 0.15),
});
if (showEnergyBar) {
const rangeH = z.upperLevel - z.lowerLevel;
const barH = rangeH * 0.25;
const barBot = z.lowerLevel - barH * 2;
const barTop = barBot + barH;
const widthBars = rightBar - z.startIdx;
const filledWidth = Math.round(widthBars * (z.powerScore / 100));
ctx.box(z.startIdx, barTop, rightBar, barBot, {
borderColor: 'rgba(158,158,158,0.5)',
borderWidth: 1,
fillColor: 'rgba(0,0,0,0.8)',
});
if (filledWidth > 0) {
const eColor = getEnergyColor(z.powerScore);
ctx.box(z.startIdx, barTop, z.startIdx + filledWidth, barBot, {
borderColor: 'rgba(0,0,0,0)',
borderWidth: 0,
fillColor: withAlpha(eColor, 0.8),
});
}
if (showEnergyPercent) {
ctx.label(Math.round((z.startIdx + rightBar) / 2), (barTop + barBot) / 2,
z.powerScore.toFixed(0) + '%',
{ color: 'rgba(0,0,0,0)', textColor: '#FFFFFF' });
}
}
if (z.labelY != null) {
const mid = Math.round((z.startIdx + z.endIdx) / 2);
const txt = z.breakSide === DIR_BULLISH ? 'B' : 'S';
const c = z.breakSide === DIR_BULLISH ? bullColor : bearColor;
ctx.label(mid, z.labelY, txt, { color: withAlpha(c, 0.2), textColor: c });
}
if (z.isBreached) lastBreachedSltp = z;
}
if (showSLTP && lastBreachedSltp) {
const z = lastBreachedSltp;
const left = z.startIdx;
const right = activeTrade ? lastBar : (lastBreachedSltp === zones[zones.length - 1] ? lastBar : z.endIdx + 30);
ctx.line(left, z.entryPx, right, z.entryPx, {
color: 'rgba(255,255,255,0.5)', lineWidth: 1, lineStyle: 'dashed',
});
ctx.line(left, z.stopPx, right, z.stopPx, {
color: slColor, lineWidth: 2, lineStyle: 'solid',
});
ctx.label(left, z.stopPx, 'SL ' + z.stopPx.toFixed(2), { color: 'rgba(0,0,0,0)', textColor: slColor });
ctx.line(left, z.tp1, right, z.tp1, {
color: withAlpha(tpColor, 0.7), lineWidth: 1, lineStyle: 'dashed',
});
ctx.label(left, z.tp1, 'TP1 ' + z.tp1.toFixed(2), { color: 'rgba(0,0,0,0)', textColor: tpColor });
if (showTP2) {
ctx.line(left, z.tp2, right, z.tp2, {
color: withAlpha(tpColor, 0.8), lineWidth: 1, lineStyle: 'dashed',
});
ctx.label(left, z.tp2, 'TP2 ' + z.tp2.toFixed(2), { color: 'rgba(0,0,0,0)', textColor: tpColor });
}
if (showTP3) {
ctx.line(left, z.tp3, right, z.tp3, {
color: tpColor, lineWidth: 2, lineStyle: 'solid',
});
ctx.label(left, z.tp3, 'TP3 ' + z.tp3.toFixed(2), { color: 'rgba(0,0,0,0)', textColor: tpColor });
}
}
if (showBreakoutArrows && lastBreachedSltp && lastBreachedSltp.endIdx >= n - 5) {
const z = lastBreachedSltp;
if (z.breakSide === DIR_BULLISH) {
const rangeH = z.upperLevel - z.lowerLevel;
const arrowY = z.lowerLevel - rangeH * 0.5 - rangeH * 0.1;
ctx.shape(z.endIdx, 'arrow_up', { position: 'absolute', price: arrowY, color: bullColor });
} else {
const a = atrArr[z.endIdx] || 0;
const arrowY = z.stopPx + a * 0.2;
ctx.shape(z.endIdx, 'arrow_down', { position: 'absolute', price: arrowY, color: bearColor });
}
}
// ─── STATS TABLE ────────────────────────────────────────────────
if (showStatsTable) {
const lastTs = bars[lastBar].timestamp;
const DAY = 86400000;
let startT = 0, endT = lastTs;
if (statsRangePreset === 'Last 30 Days') startT = lastTs - 30 * DAY;
else if (statsRangePreset === 'Last 90 Days') startT = lastTs - 90 * DAY;
else if (statsRangePreset === 'Last 180 Days')startT = lastTs - 180 * DAY;
else if (statsRangePreset === 'Year 2025') {
startT = new Date(2025, 0, 1).getTime();
endT = new Date(2025, 11, 31, 23, 59).getTime();
}
let total = 0, longs = 0, shorts = 0, wins = 0, losses = 0, equity = 1.0;
for (const t of trades) {
if (t.entryTime < startT || t.entryTime > endT) continue;
total++;
if (t.isLong) longs++; else shorts++;
if (t.status === 1) {
wins++;
const tpFinal = showTP3 ? t.tp3 : showTP2 ? t.tp2 : t.tp1;
const pct = t.isLong ? (tpFinal - t.entry) / t.entry : (t.entry - tpFinal) / t.entry;
equity *= (1 + pct);
} else if (t.status === -1) {
losses++;
const pct = t.isLong ? (t.sl - t.entry) / t.entry : (t.entry - t.sl) / t.entry;
equity *= (1 + pct);
}
}
const closed = wins + losses;
const totalReturn = closed > 0 ? (equity - 1) * 100 : null;
const winRate = closed > 0 ? wins / closed * 100 : null;
ctx.table('top_right', {
bgColor: 'rgba(11,16,32,0.9)',
borderColor: '#00E5FF',
});
const titleBg = '#131A2A';
const rowBgA = '#0B1020', rowBgB = '#0E1630';
ctx.cell(0, 0, 'Quantum Breakout', { textColor: '#FFFFFF', bgColor: titleBg, colspan: 2 });
ctx.cell(0, 1, 'Total Orders', { bgColor: rowBgA, textColor: '#FFFFFF' });
ctx.cell(1, 1, String(total), { bgColor: rowBgA, textColor: '#00E5FF' });
ctx.cell(0, 2, 'Long Orders', { bgColor: rowBgB, textColor: '#FFFFFF' });
ctx.cell(1, 2, String(longs), { bgColor: rowBgB, textColor: bullColor });
ctx.cell(0, 3, 'Short Orders', { bgColor: rowBgA, textColor: '#FFFFFF' });
ctx.cell(1, 3, String(shorts), { bgColor: rowBgA, textColor: bearColor });
ctx.cell(0, 4, 'TP Wins', { bgColor: rowBgB, textColor: '#FFFFFF' });
ctx.cell(1, 4, String(wins), { bgColor: rowBgB, textColor: '#00E676' });
ctx.cell(0, 5, 'Stop Losses', { bgColor: rowBgA, textColor: '#FFFFFF' });
ctx.cell(1, 5, String(losses), { bgColor: rowBgA, textColor: '#FF5252' });
ctx.cell(0, 6, 'Total Return', { bgColor: rowBgB, textColor: '#FFFFFF' });
ctx.cell(1, 6,
totalReturn == null ? 'n/a' : totalReturn.toFixed(2) + '%',
{ bgColor: rowBgB, textColor: totalReturn != null && totalReturn >= 0 ? '#00E676' : '#FF5252' });
ctx.cell(0, 7, 'Win Rate', { bgColor: rowBgA, textColor: '#FFFFFF' });
ctx.cell(1, 7,
winRate == null ? 'n/a' : winRate.toFixed(2) + '%',
{ bgColor: rowBgA, textColor: '#00E676' });
}
if (currentZone && currentZone.isLive && currentZone.qualityScore >= 70 &&
currentZone.powerScore >= energyCriticalThresh) {
ctx.log('[Quantum] Imminent breakout — energy ' + currentZone.powerScore.toFixed(0) + '%');
}
}