Description
Order block detector similar to LuxAlgo on tradingview, free for the community. use it to help you reference the sdk to build other better tools for our platform. Built with the help of our in house AI script assistant SXVNT Ai. Shows the prompt in the right hand panel next to the code. Has time filters, amount of days to look back, auto hides mitigates OB's, but i checked them in the settings tab to display here for image.
Categories & Tags
Comments (0)
0/2000
Loading comments…
Source code
// LuxAlgo-Style Order Block Detector for SXVNT
// Detects institutional order blocks using 3-candle patterns with proper time filtering
function calculate(bars, ctx) {
// ========== USER CONFIGURATION ==========
const lookbackDays = ctx.input('Lookback Days', 5, { min: 1, max: 30 });
const minBlockSize = ctx.input('Min Block Size (ticks)', 4, { min: 1, max: 50 });
const swingLookback = ctx.input('Swing Lookback', 10, { min: 3, max: 50 });
// Time filtering - using SXVNT SDK's proper time functions
const useTimeFilter = ctx.input('Enable Time Filter', false);
const sessionStart = ctx.input('Session Start (ET)', 0, { min: 0, max: 23 });
const sessionEnd = ctx.input('Session End (ET)', 23, { min: 0, max: 23 });
const activeDays = ctx.input('Active Days (0=Sun)', '1,2,3,4,5');
// Display options
const showBullish = ctx.input('Show Bullish Blocks', true);
const showBearish = ctx.input('Show Bearish Blocks', true);
const showMitigated = ctx.input('Show Mitigated Blocks', true);
const maxBlocksDisplay = ctx.input('Max Blocks to Display', 20, { min: 1, max: 100 });
const blockOpacity = ctx.input('Block Opacity', 0.5, { min: 0.05, max: 1, step: 0.05 });
// Text labels
const showLabels = ctx.input('Show Labels', true);
const labelSize = ctx.input('Label Size', 'small', {
options: ['small', 'normal', 'large']
});
// Color customization - using rgba() format for proper opacity
const bullishColor = ctx.input('Bullish Block Color', '#26a69a');
const bearishColor = ctx.input('Bearish Block Color', '#ef5350');
const mitigatedColor = ctx.input('Mitigated Block Color', '#666666');
const borderWidth = ctx.input('Border Width', 1, { min: 1, max: 3 });
// Mitigation method
const mitigationMethod = ctx.input('Mitigation Method', 'wick', {
options: ['wick', 'close']
});
// ========== PROPER TIME FILTERING ==========
// Using SXVNT SDK's ctx.time.inSession() for reliable time filtering
function isBarInSession(barIndex) {
if (!useTimeFilter) return true;
// Parse active days
const daySet = new Set(activeDays.split(',').map(d => parseInt(d.trim())));
const dow = ctx.time.dayOfWeek(barIndex);
if (!daySet.has(dow)) return false;
// Use ctx.time.inSession() which handles midnight wrapping automatically
return ctx.time.inSession(barIndex, sessionStart, sessionEnd, 'America/New_York');
}
// ========== CORRECT LOOKBACK DAYS CALCULATION ==========
let lookbackLimit = 0;
if (lookbackDays > 0) {
// Count actual days using day changes
let dayCount = 0;
let lastDay = -1;
for (let i = bars.length - 1; i >= 0; i--) {
const hour = ctx.time.hour(i);
const minute = ctx.time.minute(i);
// Simple approach: track day changes at midnight
if (hour === 0 && minute === 0) {
dayCount++;
}
if (dayCount >= lookbackDays) {
lookbackLimit = i;
break;
}
lastDay = hour;
}
}
// ========== SWING POINT DETECTION ==========
const swingHighs = new Array(bars.length).fill(false);
const swingLows = new Array(bars.length).fill(false);
for (let i = swingLookback; i < bars.length - swingLookback; i++) {
let isSwingHigh = true;
let isSwingLow = true;
// Check if current bar is higher/lower than surrounding bars
for (let j = 1; j <= swingLookback; j++) {
if (bars[i].high <= bars[i - j].high || bars[i].high <= bars[i + j].high) {
isSwingHigh = false;
}
if (bars[i].low >= bars[i - j].low || bars[i].low >= bars[i + j].low) {
isSwingLow = false;
}
}
if (isSwingHigh) swingHighs[i] = true;
if (isSwingLow) swingLows[i] = true;
}
// ========== LUXALGO ORDER BLOCK DETECTION ==========
// Based on search: 3-candle patterns with volume peaks
const bullishBlocks = [];
const bearishBlocks = [];
// We need at least 3 bars for pattern detection
for (let i = lookbackLimit; i < bars.length - 3; i++) {
// Apply time filter
if (!isBarInSession(i)) continue;
const bar1 = bars[i]; // Potential order block candle
const bar2 = bars[i + 1]; // First reversal candle
const bar3 = bars[i + 2]; // Confirmation candle
// Calculate candle size
const candleSize = Math.abs(bar1.high - bar1.low);
if (candleSize < minBlockSize * 0.25) continue;
// ===== BULLISH ORDER BLOCK DETECTION =====
// Last bearish candle before strong upward move (3-candle pattern)
if (bar1.close < bar1.open && // Bearish candle
bar2.close > bar2.open && // Bullish reversal
bar3.close > bar3.open && // Continuation bullish
bar2.close > bar1.high && // Engulfs the bearish candle
bar3.close > bar2.close) { // Upward momentum continues
// Check for swing low context (institutional accumulation)
let hasSwingLow = false;
for (let j = Math.max(0, i - 3); j <= Math.min(i + 3, bars.length - 1); j++) {
if (swingLows[j]) {
hasSwingLow = true;
break;
}
}
if (hasSwingLow) {
bullishBlocks.push({
index: i,
high: bar1.high,
low: bar1.low,
time: bar1.time,
mitigated: false,
label: "Bullish OB"
});
}
}
// ===== BEARISH ORDER BLOCK DETECTION =====
// Last bullish candle before strong downward move (3-candle pattern)
if (bar1.close > bar1.open && // Bullish candle
bar2.close < bar2.open && // Bearish reversal
bar3.close < bar3.open && // Continuation bearish
bar2.close < bar1.low && // Engulfs the bullish candle
bar3.close < bar2.close) { // Downward momentum continues
// Check for swing high context (institutional distribution)
let hasSwingHigh = false;
for (let j = Math.max(0, i - 3); j <= Math.min(i + 3, bars.length - 1); j++) {
if (swingHighs[j]) {
hasSwingHigh = true;
break;
}
}
if (hasSwingHigh) {
bearishBlocks.push({
index: i,
high: bar1.high,
low: bar1.low,
time: bar1.time,
mitigated: false,
label: "Bearish OB"
});
}
}
}
// ========== MITIGATION DETECTION ==========
// Check if blocks have been mitigated (price moved through them)
const lastBarIndex = bars.length - 1;
for (const block of bullishBlocks) {
if (block.mitigated) continue;
// Check subsequent bars for mitigation
for (let i = block.index + 1; i <= lastBarIndex; i++) {
const bar = bars[i];
if (mitigationMethod === 'wick') {
// Mitigated if wick goes below block low
if (bar.low < block.low) {
block.mitigated = true;
block.label = "Bullish OB (MIT)";
break;
}
} else { // 'close'
// Mitigated if close goes below block low
if (bar.close < block.low) {
block.mitigated = true;
block.label = "Bullish OB (MIT)";
break;
}
}
}
}
for (const block of bearishBlocks) {
if (block.mitigated) continue;
// Check subsequent bars for mitigation
for (let i = block.index + 1; i <= lastBarIndex; i++) {
const bar = bars[i];
if (mitigationMethod === 'wick') {
// Mitigated if wick goes above block high
if (bar.high > block.high) {
block.mitigated = true;
block.label = "Bearish OB (MIT)";
break;
}
} else { // 'close'
// Mitigated if close goes above block high
if (bar.close > block.high) {
block.mitigated = true;
block.label = "Bearish OB (MIT)";
break;
}
}
}
}
// ========== VISUALIZATION ==========
// Filter blocks based on showMitigated setting
let displayBullish = bullishBlocks;
let displayBearish = bearishBlocks;
if (!showMitigated) {
displayBullish = bullishBlocks.filter(b => !b.mitigated);
displayBearish = bearishBlocks.filter(b => !b.mitigated);
}
// Limit display count (show most recent)
displayBullish = displayBullish.slice(-maxBlocksDisplay);
displayBearish = displayBearish.slice(-maxBlocksDisplay);
// Helper function to convert hex color to rgba with opacity
function hexToRgba(hex, opacity) {
// Remove # if present
hex = hex.replace('#', '');
// Parse hex values
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
// Return rgba string
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
}
// Draw bullish order blocks (demand zones)
if (showBullish && displayBullish.length > 0) {
for (const block of displayBullish) {
const isMitigated = block.mitigated;
const baseColor = isMitigated ? mitigatedColor : bullishColor;
// Use rgba format for proper opacity handling
const fillColor = hexToRgba(baseColor, blockOpacity);
const borderColor = baseColor;
// Extend block to right edge of chart
ctx.box(
block.index,
block.high,
bars.length - 1,
block.low,
{
fillColor: fillColor,
borderColor: borderColor,
borderWidth: borderWidth,
text: showLabels ? block.label : '',
textColor: '#ffffff',
textBackground: borderColor,
textSize: labelSize
}
);
// Add marker at the block candle
ctx.shape(block.index, 'triangle_up', {
color: borderColor,
size: 'small',
location: 'belowBar',
text: ''
});
}
}
// Draw bearish order blocks (supply zones)
if (showBearish && displayBearish.length > 0) {
for (const block of displayBearish) {
const isMitigated = block.mitigated;
const baseColor = isMitigated ? mitigatedColor : bearishColor;
// Use rgba format for proper opacity handling
const fillColor = hexToRgba(baseColor, blockOpacity);
const borderColor = baseColor;
// Extend block to right edge of chart
ctx.box(
block.index,
block.high,
bars.length - 1,
block.low,
{
fillColor: fillColor,
borderColor: borderColor,
borderWidth: borderWidth,
text: showLabels ? block.label : '',
textColor: '#ffffff',
textBackground: borderColor,
textSize: labelSize
}
);
// Add marker at the block candle
ctx.shape(block.index, 'triangle_down', {
color: borderColor,
size: 'small',
location: 'aboveBar',
text: ''
});
}
}
// ========== STATISTICS DISPLAY ==========
const last = bars.length - 1;
const totalBullish = displayBullish.length;
const totalBearish = displayBearish.length;
const totalMitigated = bullishBlocks.filter(b => b.mitigated).length +
bearishBlocks.filter(b => b.mitigated).length;
if (totalBullish > 0 || totalBearish > 0) {
// Display statistics on chart using ctx.label()
ctx.label(last, bars[last].high * 1.002,
`Bullish: ${totalBullish} | Bearish: ${totalBearish} | Mitigated: ${totalMitigated}`, {
color: '#ffffff',
style: 'bubble',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
textSize: 'small'
});
}
}