OB Detector

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

Volume#ict#orderblock#free

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'
    });
  }
}