Description
Ham on the chart
Comments (0)
0/2000
Loading comments…
Source code
// HAM3 - Clean Session Levels Indicator with Key Levels
// Shows only the most recent Asian, London, NY, and Previous Day session highs/lows
// Plus VWAP and Daily Pivot Points
function calculate(bars, ctx) {
// User inputs
const showAsian = ctx.input('Show Asian Session', true);
const showLondon = ctx.input('Show London Session', true);
const showNY = ctx.input('Show NY Session', true);
const showPrevDay = ctx.input('Show Previous Day', true);
const showVWAP = ctx.input('Show VWAP', true);
const showPivots = ctx.input('Show Daily Pivots', true);
const asianStart = ctx.input('Asian Start (ET)', 20, { min: 0, max: 23, step: 1 });
const asianEnd = ctx.input('Asian End (ET)', 0, { min: 0, max: 23, step: 1 });
const londonStart = ctx.input('London Start (ET)', 2, { min: 0, max: 23, step: 1 });
const londonEnd = ctx.input('London End (ET)', 5, { min: 0, max: 23, step: 1 });
const nyStart = ctx.input('NY Start (ET)', 9, { min: 0, max: 23, step: 1 });
const nyEnd = ctx.input('NY End (ET)', 12, { min: 0, max: 23, step: 1 });
const asianColor = ctx.input('Asian Color', '#9c27b0');
const londonColor = ctx.input('London Color', '#2196f3');
const nyColor = ctx.input('NY Color', '#4caf50');
const prevDayColor = ctx.input('Prev Day Color', '#ff9800');
const vwapColor = ctx.input('VWAP Color', '#ff5722');
// Pivot colors - bright dark blue for R series, bright green for S series, pink for pivot
const rColor = ctx.input('R Series Color', '#2962ff'); // Bright dark blue
const sColor = ctx.input('S Series Color', '#00e676'); // Bright green
const pivotColor = ctx.input('Pivot Line Color', '#e91e63'); // Pink
const lineWidth = ctx.input('Line Width', 3, { min: 1, max: 10, step: 1 });
const pivotLineWidth = ctx.input('Pivot Line Width', 4, { min: 1, max: 8, step: 1 }); // Increased
const lookbackDays = ctx.input('Lookback Days', 5, { min: 1, max: 30, step: 1 });
const labelSize = ctx.input('Label Size', 12, { min: 8, max: 20, step: 1 });
// Session check helper — handles midnight wrap
function inSessionRange(barIndex, startHour, endHour) {
const h = ctx.time.hourIn(barIndex, 'America/New_York');
if (startHour < endHour) return h >= startHour && h < endHour;
return h >= startHour || h < endHour;
}
// Lookback cutoff
let dayCount = 0, cutoffBar = 0;
for (let i = bars.length - 1; i >= 1; i--) {
if (ctx.time.isNewSession(i)) {
dayCount++;
if (dayCount > lookbackDays) {
cutoffBar = i;
break;
}
}
}
// Track only the most recent session of each type
let latestAsian = null, latestLondon = null, latestNY = null;
let prevDayHigh = null, prevDayLow = null, prevDayClose = null;
let prevDayHighBar = null, prevDayLowBar = null;
let currentDayHigh = null, currentDayLow = null, currentDayClose = null;
let currentDayHighBar = null, currentDayLowBar = null;
// Track if we're currently in each session
let asianStarted = false, londonStarted = false, nyStarted = false;
for (let i = cutoffBar; i < bars.length; i++) {
const inAsian = inSessionRange(i, asianStart, asianEnd);
const inLondon = inSessionRange(i, londonStart, londonEnd);
const inNY = inSessionRange(i, nyStart, nyEnd);
const isNewDay = ctx.time.isNewSession(i);
// Day tracking - only track the most recent previous day
if (isNewDay) {
// Store the previous day's levels before resetting
if (currentDayHigh !== null && showPrevDay) {
prevDayHigh = currentDayHigh;
prevDayLow = currentDayLow;
prevDayClose = currentDayClose;
prevDayHighBar = currentDayHighBar;
prevDayLowBar = currentDayLowBar;
}
// Reset current day tracking
currentDayHigh = bars[i].high;
currentDayLow = bars[i].low;
currentDayClose = bars[i].close;
currentDayHighBar = i;
currentDayLowBar = i;
} else if (currentDayHigh !== null) {
if (bars[i].high > currentDayHigh) {
currentDayHigh = bars[i].high;
currentDayHighBar = i;
}
if (bars[i].low < currentDayLow) {
currentDayLow = bars[i].low;
currentDayLowBar = i;
}
currentDayClose = bars[i].close; // Update close as we go
}
// Asian session tracking - only track the most recent COMPLETED session
if (inAsian) {
if (!asianStarted) {
// Start of new Asian session
latestAsian = { high: bars[i].high, low: bars[i].low, highBar: i, lowBar: i };
asianStarted = true;
} else {
// Continue tracking current Asian session
if (bars[i].high > latestAsian.high) {
latestAsian.high = bars[i].high;
latestAsian.highBar = i;
}
if (bars[i].low < latestAsian.low) {
latestAsian.low = bars[i].low;
latestAsian.lowBar = i;
}
}
} else if (asianStarted) {
// Asian session just ended - keep it as the latest
asianStarted = false;
}
// London session tracking - only track the most recent COMPLETED session
if (inLondon) {
if (!londonStarted) {
// Start of new London session
latestLondon = { high: bars[i].high, low: bars[i].low, highBar: i, lowBar: i };
londonStarted = true;
} else {
// Continue tracking current London session
if (bars[i].high > latestLondon.high) {
latestLondon.high = bars[i].high;
latestLondon.highBar = i;
}
if (bars[i].low < latestLondon.low) {
latestLondon.low = bars[i].low;
latestLondon.lowBar = i;
}
}
} else if (londonStarted) {
// London session just ended - keep it as the latest
londonStarted = false;
}
// NY session tracking - only track the most recent COMPLETED session
if (inNY) {
if (!nyStarted) {
// Start of new NY session
latestNY = { high: bars[i].high, low: bars[i].low, highBar: i, lowBar: i };
nyStarted = true;
} else {
// Continue tracking current NY session
if (bars[i].high > latestNY.high) {
latestNY.high = bars[i].high;
latestNY.highBar = i;
}
if (bars[i].low < latestNY.low) {
latestNY.low = bars[i].low;
latestNY.lowBar = i;
}
}
} else if (nyStarted) {
// NY session just ended - keep it as the latest
nyStarted = false;
}
}
// Build sessions array with only the most recent sessions
const sessions = [];
if (showAsian && latestAsian) {
sessions.push({
type: 'Asia',
high: latestAsian.high,
low: latestAsian.low,
endBar: bars.length - 1,
highBar: latestAsian.highBar,
lowBar: latestAsian.lowBar
});
}
if (showLondon && latestLondon) {
sessions.push({
type: 'London',
high: latestLondon.high,
low: latestLondon.low,
endBar: bars.length - 1,
highBar: latestLondon.highBar,
lowBar: latestLondon.lowBar
});
}
if (showNY && latestNY) {
sessions.push({
type: 'NY',
high: latestNY.high,
low: latestNY.low,
endBar: bars.length - 1,
highBar: latestNY.highBar,
lowBar: latestNY.lowBar
});
}
// Add previous day levels if available
if (prevDayHigh !== null && showPrevDay) {
sessions.push({
type: 'PD',
high: prevDayHigh,
low: prevDayLow,
endBar: bars.length - 1,
highBar: prevDayHighBar,
lowBar: prevDayLowBar
});
}
// Draw session levels with solid lines and labels
const endDraw = bars.length - 1;
for (const sess of sessions) {
const color =
sess.type === 'Asia' ? asianColor :
sess.type === 'London' ? londonColor :
sess.type === 'NY' ? nyColor :
prevDayColor;
const labelText = sess.type === 'PD' ? 'Prev Day' : sess.type;
// Draw high line - solid and thick
ctx.line(sess.highBar, sess.high, endDraw, sess.high, {
color: color,
lineWidth: lineWidth,
lineStyle: 'solid'
});
// Add label for high line at the right edge
ctx.label(endDraw, sess.high, labelText + ' High', {
color: color,
textColor: '#ffffff',
size: labelSize,
style: 'bubble'
});
// Draw low line - solid and thick
ctx.line(sess.lowBar, sess.low, endDraw, sess.low, {
color: color,
lineWidth: lineWidth,
lineStyle: 'solid'
});
// Add label for low line at the right edge
ctx.label(endDraw, sess.low, labelText + ' Low', {
color: color,
textColor: '#ffffff',
size: labelSize,
style: 'bubble'
});
}
// Add Previous Day Close if available
if (prevDayClose !== null && showPrevDay) {
ctx.hline(prevDayClose, {
color: prevDayColor,
lineWidth: lineWidth - 1,
lineStyle: 'dashed'
});
ctx.label(endDraw, prevDayClose, 'Prev Close', {
color: prevDayColor,
textColor: '#ffffff',
size: labelSize,
style: 'bubble'
});
}
// Calculate and draw VWAP
if (showVWAP && bars.length > 0) {
// Calculate VWAP for the current day
let cumulativeTPV = 0; // Typical Price * Volume
let cumulativeVolume = 0;
const vwapValues = new Array(bars.length).fill(null);
for (let i = 0; i < bars.length; i++) {
const typicalPrice = (bars[i].high + bars[i].low + bars[i].close) / 3;
const volume = bars[i].volume || 1; // Use 1 if no volume data
cumulativeTPV += typicalPrice * volume;
cumulativeVolume += volume;
if (cumulativeVolume > 0) {
vwapValues[i] = cumulativeTPV / cumulativeVolume;
}
}
// Draw VWAP line
ctx.plot(vwapValues, 'VWAP', {
color: vwapColor,
lineWidth: lineWidth,
lineStyle: 'solid'
});
// Add VWAP label at the right edge
if (vwapValues[endDraw] !== null) {
ctx.label(endDraw, vwapValues[endDraw], 'VWAP', {
color: vwapColor,
textColor: '#ffffff',
size: labelSize,
style: 'bubble'
});
}
}
// Calculate and draw Daily Pivot Points
if (showPivots && prevDayHigh !== null && prevDayLow !== null && prevDayClose !== null) {
// Standard Pivot Point Calculation
const pivot = (prevDayHigh + prevDayLow + prevDayClose) / 3;
const r1 = (2 * pivot) - prevDayLow;
const s1 = (2 * pivot) - prevDayHigh;
const r2 = pivot + (prevDayHigh - prevDayLow);
const s2 = pivot - (prevDayHigh - prevDayLow);
const r3 = prevDayHigh + 2 * (pivot - prevDayLow);
const s3 = prevDayLow - 2 * (prevDayHigh - pivot);
const pivotLevels = [
{ price: r3, label: 'R3', color: rColor },
{ price: r2, label: 'R2', color: rColor },
{ price: r1, label: 'R1', color: rColor },
{ price: pivot, label: 'Pivot', color: pivotColor },
{ price: s1, label: 'S1', color: sColor },
{ price: s2, label: 'S2', color: sColor },
{ price: s3, label: 'S3', color: sColor }
];
// Draw pivot levels with solid lines and larger width
for (const level of pivotLevels) {
ctx.hline(level.price, {
color: level.color,
lineWidth: pivotLineWidth, // Larger width
lineStyle: 'solid'
});
// Add label at the right edge
ctx.label(endDraw, level.price, level.label, {
color: level.color,
textColor: '#ffffff',
size: labelSize,
style: 'bubble'
});
}
}
}