Description
BALANCES THE OPENING RANGE OF PIZZA BURGERS
Categories & Tags
Comments (0)
0/2000
Loading comments…
Source code
function calculate(bars, ctx) {
// ═══ USER INPUTS ═══
const sessionStart = ctx.input('Session Start (HHMM)', '0930');
const sessionEnd = ctx.input('Session End (HHMM)', '1600');
const timezone = ctx.input('Timezone Offset (UTC)', '-5', {
options: [
{ label: 'UTC-10', value: '-10' }, { label: 'UTC-8', value: '-8' },
{ label: 'UTC-7', value: '-7' }, { label: 'UTC-6', value: '-6' },
{ label: 'UTC-5', value: '-5' }, { label: 'UTC-4', value: '-4' },
{ label: 'UTC-3', value: '-3' },
{ label: 'UTC+0', value: '0' },
{ label: 'UTC+1', value: '1' }, { label: 'UTC+2', value: '2' },
{ label: 'UTC+3', value: '3' }, { label: 'UTC+4', value: '4' },
{ label: 'UTC+5', value: '5' }, { label: 'UTC+5:30', value: '5.5' },
{ label: 'UTC+8', value: '8' }, { label: 'UTC+9', value: '9' },
{ label: 'UTC+10', value: '10' },
]
});
const displayMode = ctx.input('Display Mode', 'today', {
options: [
{ label: 'Today', value: 'today' },
{ label: 'Two Days', value: 'twoDays' },
{ label: 'All', value: 'all' },
{ label: 'Hidden', value: 'hidden' },
]
});
const showLabels = ctx.input('Show Labels', true);
const fontSize = ctx.input('Font Size', 11, { min: 8, max: 16, step: 1 });
const lineWidth = ctx.input('Line Width', 1, { min: 1, max: 5, step: 1 });
const lineStyle = ctx.input('Line Style', 'dashed', {
options: [
{ label: 'Solid', value: 'solid' },
{ label: 'Dashed', value: 'dashed' },
{ label: 'Dotted', value: 'dotted' },
]
});
const ibColor = ctx.input('IB Color', '#2962ff');
const extendShade = ctx.input('Extend Shaded Region', false);
const extLevels = ctx.input('Extension Levels', 2, { min: 0, max: 10, step: 1 });
const extLevelColor = ctx.input('Extension Level Color', '#ffffff');
if (displayMode === 'hidden') return;
if (bars.length < 2) return;
// ═══ PARSE SESSION TIME ═══
const startHour = parseInt(sessionStart.substring(0, 2));
const startMin = parseInt(sessionStart.substring(2, 4));
const endHour = parseInt(sessionEnd.substring(0, 2));
const endMin = parseInt(sessionEnd.substring(2, 4));
const tzOffset = parseFloat(timezone);
const startMinutes = startHour * 60 + startMin;
const endMinutes = endHour * 60 + endMin;
const sessionLengthMinutes = endMinutes - startMinutes;
// ═══ HELPER: get bar time in UTC-offset minutes ═══
function barTimeMinutes(i) {
const d = new Date(bars[i].timestamp);
const utcMins = d.getUTCHours() * 60 + d.getUTCMinutes();
// Apply user's timezone offset
let localMins = utcMins + tzOffset * 60;
// Normalize to 0-1440
if (localMins < 0) localMins += 1440;
if (localMins >= 1440) localMins -= 1440;
return localMins;
}
// ═══ HELPER: check if bar time falls within session ═══
function isInSession(i) {
const t = barTimeMinutes(i);
if (sessionStart <= sessionEnd) {
return t >= startMinutes && t < endMinutes;
}
// Wraps midnight (e.g., 2100-1700 next day)
return t >= startMinutes || t < endMinutes;
}
// ═══ HELPER: check if bar is the first bar of a session ═══
function isSessionStart(i) {
if (i === 0) return isInSession(0);
if (!isInSession(i)) return false;
return !isInSession(i - 1);
}
// ═══ HELPER: estimate bars for session length ═══
// Approximate bar count from session length minutes
// We'll estimate based on typical bar duration
// ═══ Draw session colors ═══
const bgColors = new Array(bars.length).fill(null);
for (let i = 0; i < bars.length; i++) {
if (isInSession(i)) {
const r = parseInt(ibColor.slice(1, 3), 16);
const g = parseInt(ibColor.slice(3, 5), 16);
const b = parseInt(ibColor.slice(5, 7), 16);
bgColors[i] = 'rgba(' + r + ',' + g + ',' + b + ',0.03)';
}
}
ctx.bgcolor(bgColors);
// ═══ Find all session starts and track IB objects ═══
const ibSessions = []; // { startBar, ibHigh, ibLow, ibMid }
// Detect session starts
for (let i = 0; i < bars.length; i++) {
if (isSessionStart(i)) {
ibSessions.push({
startBar: i,
ibHigh: bars[i].high,
ibLow: bars[i].low,
ibMid: (bars[i].high + bars[i].low) / 2,
});
}
}
// Apply display mode filter
let visibleSessions = ibSessions;
if (displayMode === 'today') {
visibleSessions = ibSessions.slice(-1);
} else if (displayMode === 'twoDays') {
visibleSessions = ibSessions.slice(-2);
}
// ═══ Draw IB levels for each session ═══
const lastBar = bars.length - 1;
for (const session of visibleSessions) {
const s = session.startBar;
const ibHigh = session.ibHigh;
const ibLow = session.ibLow;
const ibMid = session.ibMid;
// Estimate right edge of the initial box (session length)
// Use a reasonable estimate: ~bars per hour based on chart
const barsPerHour = 60; // default estimate
const estBars = Math.max(1, Math.round((sessionLengthMinutes / 60) * barsPerHour));
const rightEdge = Math.min(s + estBars, lastBar);
// Draw IB range box (initial zone)
if (extendShade) {
ctx.box(s, ibHigh, lastBar, ibLow, {
fillColor: 'rgba(' + parseInt(ibColor.slice(1, 3), 16) + ',' +
parseInt(ibColor.slice(3, 5), 16) + ',' +
parseInt(ibColor.slice(5, 7), 16) + ',0.12)',
borderColor: 'transparent',
});
} else {
ctx.box(s, ibHigh, rightEdge, ibLow, {
fillColor: 'rgba(' + parseInt(ibColor.slice(1, 3), 16) + ',' +
parseInt(ibColor.slice(3, 5), 16) + ',' +
parseInt(ibColor.slice(5, 7), 16) + ',0.12)',
borderColor: 'transparent',
});
}
// IB High line
ctx.line(s, ibHigh, lastBar, ibHigh, {
color: ibColor,
lineWidth: lineWidth,
lineStyle: lineStyle,
extend: 'right',
});
// IB Low line
ctx.line(s, ibLow, lastBar, ibLow, {
color: ibColor,
lineWidth: lineWidth,
lineStyle: lineStyle,
extend: 'right',
});
// IB 50% Mid line
ctx.line(s, ibMid, lastBar, ibMid, {
color: ibColor,
lineWidth: lineWidth,
lineStyle: lineStyle === 'solid' ? 'dashed' : 'dotted',
extend: 'right',
});
// Labels
if (showLabels) {
ctx.label(lastBar, ibHigh, ' IB High (' + ibHigh.toFixed(2) + ') ', {
color: ibColor,
textColor: '#ffffff',
size: fontSize,
position: 'above',
style: 'none',
});
ctx.label(lastBar, ibLow, ' IB Low (' + ibLow.toFixed(2) + ') ', {
color: ibColor,
textColor: '#ffffff',
size: fontSize,
position: 'below',
style: 'none',
});
ctx.label(lastBar, ibMid, ' 50% (' + ibMid.toFixed(2) + ') ', {
color: ibColor,
textColor: '#ffffff',
size: fontSize,
position: 'at',
style: 'none',
});
}
// ═══ Extension Levels ═══
if (extLevels > 0) {
const range = ibHigh - ibLow;
for (let i = 1; i <= extLevels; i++) {
const ext = (range / 2) * i;
// Upper extension
const upperPrice = ibHigh + ext;
ctx.line(s, upperPrice, lastBar, upperPrice, {
color: extLevelColor,
lineWidth: 1,
lineStyle: 'dotted',
extend: 'right',
});
if (showLabels) {
ctx.label(lastBar, upperPrice, ' Ext ' + i + ' (' + upperPrice.toFixed(2) + ') ', {
color: extLevelColor,
textColor: '#ffffff',
size: fontSize - 2,
position: 'above',
style: 'none',
});
}
// Lower extension
const lowerPrice = ibLow - ext;
ctx.line(s, lowerPrice, lastBar, lowerPrice, {
color: extLevelColor,
lineWidth: 1,
lineStyle: 'dotted',
extend: 'right',
});
if (showLabels) {
ctx.label(lastBar, lowerPrice, ' Ext -' + i + ' (' + lowerPrice.toFixed(2) + ') ', {
color: extLevelColor,
textColor: '#ffffff',
size: fontSize - 2,
position: 'below',
style: 'none',
});
}
}
}
// ═══ Breakout Detection ═══
// Check if price broke above IB High or below IB Low for the first time
const lastClose = bars[lastBar].close;
if (lastClose > ibHigh && lastBar > s) {
ctx.shape(lastBar, 'arrow_up', {
color: '#26a69a',
position: 'below',
text: 'IB Breakout ↑',
});
} else if (lastClose < ibLow && lastBar > s) {
ctx.shape(lastBar, 'arrow_down', {
color: '#ef5350',
position: 'above',
text: 'IB Breakdown ↓',
});
}
}
}