Description
ADR Candle (Average Daily Range)
A visual ADR indicator inspired by Jimmy Trades that displays a dynamic candle on your chart showing how much of the average daily range has been consumed in the current session.
How It Works
The indicator draws a single candle to the right of price action with two layers:
Solid Fill — Represents the Average Daily Range (calculated over a configurable lookback period). This is the "expected" move for the day, anchored from today's low.
Hollow Outline — Represents today's actual high-to-low range.
When today's range is below 100% ADR, the outline sits inside the solid fill — showing how much room is left in the typical daily move. When today's range exceeds 100% ADR, the outline extends beyond the solid fill — the hollow portion represents how far price has pushed past the average range.
Features
Real-time ADR candle with solid body (expected range) and hollow wick (actual range)
Info table displaying asset, timeframe, ADR value, and current % of ADR
Color-coded volatility condition (Low / Medium / High) with configurable thresholds
Optional ADR level lines projected across the chart
Fully customizable colors, positioning, bar width, and offset
Works on any intraday timeframe
Inputs
ADR Lookback — Number of days used to calculate the average (default: 14)
High / Low Vol Thresholds — Define when the condition label switches between Low, Medium, and High volatility
Bar Offset & Width — Control candle placement and size
Show ADR Lines — Toggle horizontal dashed lines at the ADR levels
Full color customization for bull/bear candles, ADR lines, and volatility states
Categories & Tags
Comments (0)
0/2000
Loading comments…
Source code
function calculate(bars, ctx) {
// ─── INPUTS ───
const lookback = ctx.input('ADR Lookback', 14, { min: 1, max: 500, step: 1 });
const precision = ctx.input('Decimal Precision', 2, { min: 0, max: 6, step: 1 });
const highThreshold = ctx.input('High Vol Threshold %', 100, { min: 50, max: 200, step: 5 });
const lowThreshold = ctx.input('Low Vol Threshold %', 50, { min: 10, max: 100, step: 5 });
const tablePos = ctx.input('Table Position', 'top_right', {
options: [
{ label: 'Top Right', value: 'top_right' },
{ label: 'Top Left', value: 'top_left' },
{ label: 'Top Center', value: 'top_center' },
{ label: 'Bottom Right', value: 'bottom_right' },
{ label: 'Bottom Left', value: 'bottom_left' },
{ label: 'Middle Right', value: 'middle_right' },
]
});
const barOffset = ctx.input('Bar Offset (from right)', 3, { min: 1, max: 20, step: 1 });
const barWidth = ctx.input('Bar Width (bars)', 2, { min: 1, max: 6, step: 1 });
const showLines = ctx.input('Show ADR Lines', false);
const bullColor = ctx.input('Bull Color', '#2962ff');
const bearColor = ctx.input('Bear Color', '#ef4444');
const adrLineColor = ctx.input('ADR Line Color', '#f59e0b');
const highVolColor = ctx.input('High Vol Color', '#ef4444');
const medVolColor = ctx.input('Med Vol Color', '#f59e0b');
const lowVolColor = ctx.input('Low Vol Color', '#22c55e');
if (bars.length < 2) return;
// ─── DETECT TIMEFRAME ───
const t1 = new Date(bars[bars.length - 1].timestamp).getTime();
const t2 = new Date(bars[bars.length - 2].timestamp).getTime();
const barSecs = Math.abs(t1 - t2) / 1000;
let tfLabel = '—';
if (barSecs <= 6) tfLabel = '5s';
else if (barSecs <= 16) tfLabel = '15s';
else if (barSecs <= 35) tfLabel = '30s';
else if (barSecs <= 90) tfLabel = '1m';
else if (barSecs <= 400) tfLabel = '5m';
else if (barSecs <= 1200) tfLabel = '15m';
else if (barSecs <= 5000) tfLabel = '1H';
else if (barSecs <= 20000) tfLabel = '4H';
else if (barSecs <= 100000) tfLabel = '1D';
else tfLabel = '1W';
// ─── DETECT SYMBOL ───
const fullSym = ctx.syminfo.symbol || '';
const asset = fullSym.replace(/[A-Z]\d+$/i, '').replace(/\d+$/, '') || ctx.symbol || '??';
// ─── GET DAILY DATA ───
let daily;
try {
daily = ctx.request('1D');
} catch (e) {
ctx.log('ADR requires an intraday timeframe');
return;
}
const confirmedBars = daily.bars;
if (!confirmedBars || confirmedBars.length < 1) return;
// ─── CALCULATE ADR ───
const usableBars = confirmedBars.slice(-lookback);
let totalRange = 0;
for (let i = 0; i < usableBars.length; i++) {
totalRange += usableBars[i].high - usableBars[i].low;
}
const adr = usableBars.length > 0 ? totalRange / usableBars.length : 0;
// ─── TODAY'S RANGE ───
let todayHigh, todayLow, todayOpen, todayClose;
if (daily.forming) {
todayHigh = daily.forming.high;
todayLow = daily.forming.low;
todayOpen = daily.forming.open;
todayClose = daily.forming.close;
} else {
const lastDay = confirmedBars[confirmedBars.length - 1];
todayHigh = lastDay.high;
todayLow = lastDay.low;
todayOpen = lastDay.open;
todayClose = lastDay.close;
}
const todayRange = todayHigh - todayLow;
const pctOfADR = adr > 0 ? (todayRange / adr) * 100 : 0;
const isBullish = todayClose >= todayOpen;
// ─── VOLATILITY STATE ───
let volState, volColor;
if (pctOfADR >= highThreshold) {
volState = 'HIGH'; volColor = highVolColor;
} else if (pctOfADR <= lowThreshold) {
volState = 'LOW'; volColor = lowVolColor;
} else {
volState = 'MED'; volColor = medVolColor;
}
// ─── ADR CANDLE GEOMETRY ───
// Body (solid) = ADR amount, anchored from today's low
const bodyTop = todayLow + adr;
const bodyBottom = todayLow;
// Wick (outline) = today's ACTUAL range (high to low)
const wickTop = todayHigh;
const wickBottom = todayLow;
const lastBarIdx = bars.length - 1;
const candleCenter = lastBarIdx + barOffset;
const halfWidth = Math.max(1, Math.floor(barWidth / 2));
const candleLeft = candleCenter - halfWidth;
const candleRight = candleCenter + halfWidth;
const bodyColor = isBullish ? bullColor : bearColor;
// STEP 1: Draw BODY first (solid fill) — the ADR expected range
ctx.box(candleLeft, bodyTop, candleRight, bodyBottom, {
borderColor: bodyColor,
fillColor: bodyColor,
borderWidth: 1,
borderStyle: 'solid',
});
// STEP 2: Draw WICK on top (outline only) — today's actual range
// When < 100%: wick is SMALLER than body (body extends above showing ADR target)
// When > 100%: wick extends BEYOND body (shows exceeded ADR)
ctx.box(candleLeft, wickTop, candleRight, wickBottom, {
borderColor: 'rgba(200,200,200,0.6)',
fillColor: 'rgba(0,0,0,0)',
borderWidth: 1,
borderStyle: 'solid',
});
// ─── ADR LEVEL LINES ───
if (showLines) {
ctx.line(lastBarIdx - 30, bodyTop, candleRight + 2, bodyTop, {
color: adrLineColor, lineWidth: 1, lineStyle: 'dashed',
});
ctx.line(lastBarIdx - 30, bodyBottom, candleRight + 2, bodyBottom, {
color: adrLineColor, lineWidth: 1, lineStyle: 'dashed',
});
}
// ─── % LABEL ───
ctx.label(candleCenter, wickBottom, pctOfADR.toFixed(0), {
color: bodyColor, textColor: '#ffffff', size: 10, position: 'below', style: 'label_down',
});
// ─── TABLE ───
const tbl = ctx.table(tablePos, 4, 3, {
bgcolor: 'rgba(13,17,28,0.92)',
borderColor: 'rgba(255,255,255,0.15)',
borderWidth: 1,
textColor: '#aaa',
fontSize: 11,
cellPadding: 5,
paddingTop: 40,
paddingBottom: 0,
});
ctx.cell(tbl, 0, 0, 'Asset', { textColor: '#ccc', fontSize: 10, bgcolor: 'rgba(255,255,255,0.08)', align: 'center' });
ctx.cell(tbl, 0, 1, 'TF', { textColor: '#ccc', fontSize: 10, bgcolor: 'rgba(255,255,255,0.08)', align: 'center' });
ctx.cell(tbl, 0, 2, 'ADR', { textColor: '#ccc', fontSize: 10, bgcolor: 'rgba(255,255,255,0.08)', align: 'center' });
ctx.cell(tbl, 0, 3, '% of ADR', { textColor: '#ccc', fontSize: 10, bgcolor: 'rgba(255,255,255,0.08)', align: 'center' });
ctx.cell(tbl, 1, 0, asset, { textColor: '#fff', fontSize: 11, bgcolor: 'rgba(50,50,120,0.5)', align: 'center' });
ctx.cell(tbl, 1, 1, tfLabel, { textColor: '#fff', fontSize: 11, bgcolor: 'rgba(50,50,120,0.5)', align: 'center' });
ctx.cell(tbl, 1, 2, adr.toFixed(precision), { textColor: '#fff', fontSize: 11, bgcolor: 'rgba(50,50,120,0.5)', align: 'center' });
ctx.cell(tbl, 1, 3, '%' + pctOfADR.toFixed(1), { textColor: '#fff', fontSize: 11, bgcolor: 'rgba(50,50,120,0.5)', align: 'center' });
const condText = 'Condition: ' + (volState === 'HIGH' ? 'High Volatility' : volState === 'LOW' ? 'Low Volatility' : 'Medium Volatility');
ctx.cell(tbl, 2, 0, '', { bgcolor: volColor, align: 'center' });
ctx.cell(tbl, 2, 1, condText, { textColor: '#000', fontSize: 10, bgcolor: volColor, align: 'center' });
ctx.cell(tbl, 2, 2, '', { bgcolor: volColor, align: 'center' });
ctx.cell(tbl, 2, 3, '', { bgcolor: volColor, align: 'center' });
}