Price Gap Detection Automation
This is a complex example from our internal library that detects gaps in price action and illustrates them on the chart for you visually.

describe_indicator('Gap Detector', 'price', { shortName: 'Gap Det' });
const gapFactor = input('Gap factor', 0.5, { min: 0.01, max: 50 });
// An object containing all the necessary data for each gap
const gapObject = () => ({
topPrice: null,
bottomPrice: null,
ascending: null,
topLine: series_of(null),
bottomLine: series_of(null)
});
const maxGaps = 10;
// An array with deep copies of the gap object. One for each gap
const gapArray = [];
for (let gapIndex = 0; gapIndex < maxGaps; gapIndex++) {
gapArray.push(gapObject());
}
let gapNumber = 0;
// When a new gap is needed we check if there is a free one or else we use the oldest one
const getAvailableGapIndex = () => {
// If there are non-used gap slots return the first one and update the gapNumber
if (gapNumber < maxGaps) {
return gapNumber++;
// else return the first one which is the oldest
}
return 0;
};
const removeGap = index => {
// Keep gap lines. We still want them to paint them
const topLine = gapArray[index].topLine;
const bottomLine = gapArray[index].bottomLine;
// Remove the gap item from the array
gapArray.splice(index, 1);
// Add an empty gap item at the end of the array to keep the original size
gapArray.push(gapObject());
// Add the lines of the deleted gap
gapArray[gapArray.length - 1].topLine = topLine;
gapArray[gapArray.length - 1].bottomLine = bottomLine;
// Update gapNumber
gapNumber--;
};
const priceAtr = atr(14);
for (let candleIndex = 1; candleIndex < close.length; candleIndex++) {
// Check if there is a gap
const gapSize = priceAtr[candleIndex] !== null ? gapFactor * priceAtr[candleIndex] : null;
const isUpGap = gapSize !== null && low[candleIndex] - high[candleIndex - 1] > gapSize;
const isDownGap = gapSize !== null && low[candleIndex - 1] - high[candleIndex] > gapSize;
if (isUpGap || isDownGap) {
const availableGapIndex = getAvailableGapIndex();
// Remove last values of the lines to separate them with the new one
gapArray[availableGapIndex].topLine[candleIndex - 1] = null;
gapArray[availableGapIndex].bottomLine[candleIndex - 1] = null;
if (isUpGap) {
gapArray[availableGapIndex].ascending = true;
gapArray[availableGapIndex].topPrice = low[candleIndex];
gapArray[availableGapIndex].bottomPrice = high[candleIndex - 1];
}
else if (isDownGap) {
gapArray[availableGapIndex].ascending = false;
gapArray[availableGapIndex].topPrice = low[candleIndex - 1];
gapArray[availableGapIndex].bottomPrice = high[candleIndex];
}
}
// Update the values of the gaps
for (let gapIndex = 0; gapIndex < gapNumber; gapIndex++) {
// Check if the gap should shrink due to wick entrance
// Do not shrink if a new gap completely covers this one
if (
(high[candleIndex - 1] < gapArray[gapIndex].bottomPrice && low[candleIndex] > gapArray[gapIndex].topPrice) ||
(low[candleIndex - 1] > gapArray[gapIndex].topPrice && high[candleIndex] < gapArray[gapIndex].bottomPrice)
) {
// Toggle this gap direction since the price is now in the other side
gapArray[gapIndex].ascending = !gapArray[gapIndex].ascending;
}
else if (gapArray[gapIndex].ascending && gapArray[gapIndex].topPrice > low[candleIndex]) {
gapArray[gapIndex].topPrice = low[candleIndex];
}
else if (!gapArray[gapIndex].ascending && gapArray[gapIndex].bottomPrice < high[candleIndex]) {
gapArray[gapIndex].bottomPrice = high[candleIndex];
}
// Remove gap if it is fully covered by the prices
if (gapArray[gapIndex].topPrice <= gapArray[gapIndex].bottomPrice) {
// Keep the latest values for the lines before removing current gap
gapArray[gapIndex].topLine[candleIndex] = gapArray[gapIndex].topLine[candleIndex - 1];
gapArray[gapIndex].bottomLine[candleIndex] = gapArray[gapIndex].bottomLine[candleIndex - 1];
removeGap(gapIndex);
// All the following items have shifted due to the deletion. Shift the index too
gapIndex--;
}
else {
// Update the lines of the gaps
gapArray[gapIndex].topLine[candleIndex] = gapArray[gapIndex].topPrice;
gapArray[gapIndex].bottomLine[candleIndex] = gapArray[gapIndex].bottomPrice;
}
}
}
fill(
paint(gapArray[0].topLine, { name: 'A/Top', color: '#1b65bf' }),
paint(gapArray[0].bottomLine, { name: 'A/Bot', color: '#1b65bf' }),
'#1b65bf',
undefined,
'A'
);
fill(
paint(gapArray[1].topLine, { name: 'B/Top', color: '#1b65bf' }),
paint(gapArray[1].bottomLine, { name: 'B/Bot', color: '#1b65bf' }),
'#1b65bf',
undefined,
'B'
);
fill(
paint(gapArray[2].topLine, { name: 'C/Top', color: '#1b65bf' }),
paint(gapArray[2].bottomLine, { name: 'C/Bot', color: '#1b65bf' }),
'#1b65bf',
undefined,
'C'
);
fill(
paint(gapArray[3].topLine, { name: 'D/Top', color: '#1b65bf' }),
paint(gapArray[3].bottomLine, { name: 'D/Bot', color: '#1b65bf' }),
'#1b65bf',
undefined,
'D'
);
fill(
paint(gapArray[4].topLine, { name: 'E/Top', color: '#1b65bf' }),
paint(gapArray[4].bottomLine, { name: 'E/Bot', color: '#1b65bf' }),
'#1b65bf',
undefined,
'E'
);
fill(
paint(gapArray[5].topLine, { name: 'F/Top', color: '#1b65bf' }),
paint(gapArray[5].bottomLine, { name: 'F/Bot', color: '#1b65bf' }),
'#1b65bf',
undefined,
'F'
);
fill(
paint(gapArray[6].topLine, { name: 'G/Top', color: '#1b65bf' }),
paint(gapArray[6].bottomLine, { name: 'G/Bot', color: '#1b65bf' }),
'#1b65bf',
undefined,
'G'
);
fill(
paint(gapArray[7].topLine, { name: 'H/Top', color: '#1b65bf' }),
paint(gapArray[7].bottomLine, { name: 'H/Bot', color: '#1b65bf' }),
'#1b65bf',
undefined,
'H'
);
fill(
paint(gapArray[8].topLine, { name: 'I/Top', color: '#1b65bf' }),
paint(gapArray[8].bottomLine, { name: 'I/Bot', color: '#1b65bf' }),
'#1b65bf',
undefined,
'I'
);
fill(
paint(gapArray[9].topLine, { name: 'J/Top', color: '#1b65bf' }),
paint(gapArray[9].bottomLine, { name: 'J/Bot', color: '#1b65bf' }),
'#1b65bf',
undefined,
'J'
);