Agent skill

ict-strategy

Inner Circle Trader (ICT) methodology - smart money concepts, liquidity pools, fair value gaps, order blocks, and market structure for trading bot implementation

Stars 2
Forks 0

Install this agent skill to your Project

npx add-skill https://github.com/Nice-Wolf-Studio/wolf-skills-marketplace/tree/main/ict-strategy

SKILL.md

ICT Trading Strategy

Overview

Inner Circle Trader (ICT) is a trading methodology developed by Michael J. Huddleston that focuses on identifying institutional "smart money" behavior in financial markets. Rather than relying on traditional technical indicators, ICT teaches traders to recognize how large institutions manipulate price to capture liquidity and fill their orders.

Use this skill when:

  • Building trading bots that implement ICT concepts
  • Understanding ICT analysis for Discord bot features
  • Coding pattern detection algorithms (FVG, Order Blocks, Liquidity Pools)
  • Implementing institutional trading logic in automated systems

Current as of: January 2025 (includes 2024 mentorship updates and new concepts like Suspension Blocks)


Core ICT Concepts

1. Fair Value Gaps (FVG)

What it is: An imbalance in price delivery shown by a gap between candles, indicating inefficient price movement. The market tends to return to these gaps to "fill" them.

Detection Logic:

Bullish FVG: candle[i-1].low > candle[i+1].high
Bearish FVG: candle[i-1].high < candle[i+1].low

TypeScript Implementation:

typescript
interface Candle {
  timestamp: number;
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
}

interface FairValueGap {
  type: 'bullish' | 'bearish';
  startIndex: number;
  gapHigh: number;
  gapLow: number;
  filled: boolean;
}

function detectFairValueGaps(candles: Candle[]): FairValueGap[] {
  const fvgs: FairValueGap[] = [];

  for (let i = 1; i < candles.length - 1; i++) {
    const prev = candles[i - 1];
    const current = candles[i];
    const next = candles[i + 1];

    // Bullish FVG: gap between prev candle low and next candle high
    if (prev.low > next.high) {
      fvgs.push({
        type: 'bullish',
        startIndex: i,
        gapHigh: prev.low,
        gapLow: next.high,
        filled: false
      });
    }

    // Bearish FVG: gap between prev candle high and next candle low
    if (prev.high < next.low) {
      fvgs.push({
        type: 'bearish',
        startIndex: i,
        gapHigh: next.low,
        gapLow: prev.high,
        filled: false
      });
    }
  }

  return fvgs;
}

// Check if FVG has been filled by subsequent price action
function updateFVGStatus(fvgs: FairValueGap[], candles: Candle[]): void {
  fvgs.forEach(fvg => {
    const candlesAfterGap = candles.slice(fvg.startIndex + 1);

    for (const candle of candlesAfterGap) {
      if (fvg.type === 'bullish' && candle.low <= fvg.gapLow) {
        fvg.filled = true;
        break;
      }
      if (fvg.type === 'bearish' && candle.high >= fvg.gapHigh) {
        fvg.filled = true;
        break;
      }
    }
  });
}

Key Points:

  • FVGs form during rapid price movement (displacement)
  • Most reliable during high-volume periods (killzones)
  • Institutions often return price to FVGs for liquidity
  • 50% fill of FVG is common entry point

2. Order Blocks (OB)

What it is: The last opposite-colored candle before a strong directional move. Represents where institutions accumulated/distributed positions before pushing price.

Detection Logic:

1. Identify displacement move (strong directional movement)
2. Find last candle of opposite color before displacement
3. Mark that candle's high/low as order block zone

TypeScript Implementation:

typescript
interface OrderBlock {
  type: 'bullish' | 'bearish';
  index: number;
  high: number;
  low: number;
  mitigated: boolean;
}

function identifyOrderBlocks(
  candles: Candle[],
  displacementThreshold: number = 3 // Number of consecutive candles in same direction
): OrderBlock[] {
  const orderBlocks: OrderBlock[] = [];

  for (let i = displacementThreshold; i < candles.length; i++) {
    // Check for bullish displacement (upward move)
    const bullishDisplacement = candles
      .slice(i - displacementThreshold, i)
      .every(c => c.close > c.open);

    if (bullishDisplacement) {
      // Find last bearish candle before displacement
      for (let j = i - displacementThreshold - 1; j >= 0; j--) {
        if (candles[j].close < candles[j].open) {
          orderBlocks.push({
            type: 'bullish',
            index: j,
            high: candles[j].high,
            low: candles[j].low,
            mitigated: false
          });
          break;
        }
      }
    }

    // Check for bearish displacement (downward move)
    const bearishDisplacement = candles
      .slice(i - displacementThreshold, i)
      .every(c => c.close < c.open);

    if (bearishDisplacement) {
      // Find last bullish candle before displacement
      for (let j = i - displacementThreshold - 1; j >= 0; j--) {
        if (candles[j].close > candles[j].open) {
          orderBlocks.push({
            type: 'bearish',
            index: j,
            high: candles[j].high,
            low: candles[j].low,
            mitigated: false
          });
          break;
        }
      }
    }
  }

  return orderBlocks;
}

// Check if order block has been mitigated (price returned to it)
function updateOrderBlockStatus(obs: OrderBlock[], candles: Candle[]): void {
  obs.forEach(ob => {
    const candlesAfterOB = candles.slice(ob.index + 1);

    for (const candle of candlesAfterOB) {
      if (ob.type === 'bullish' && candle.low <= ob.low) {
        ob.mitigated = true;
        break;
      }
      if (ob.type === 'bearish' && candle.high >= ob.high) {
        ob.mitigated = true;
        break;
      }
    }
  });
}

3. Killzones

What it is: Specific time windows during the trading day when institutional activity is highest, providing optimal trade opportunities.

ICT Killzones (EST/New York Time):

  1. London Open: 2:00 AM - 5:00 AM EST
  2. New York AM Session: 7:00 AM - 10:00 AM EST (most important)
  3. New York PM Session: 1:00 PM - 3:00 PM EST (lunch hour manipulation)

TypeScript Implementation:

typescript
enum Killzone {
  LONDON_OPEN = 'london_open',
  NY_AM = 'ny_am',
  NY_PM = 'ny_pm',
  ASIAN = 'asian',
  NONE = 'none'
}

interface KillzoneConfig {
  timezone: string; // e.g., 'America/New_York'
  londonderOpenStart: number; // Hour in EST (2)
  londonOpenEnd: number; // Hour in EST (5)
  nyAMStart: number; // Hour in EST (7)
  nyAMEnd: number; // Hour in EST (10)
  nyPMStart: number; // Hour in EST (13)
  nyPMEnd: number; // Hour in EST (15)
}

function getCurrentKillzone(timestamp: Date, config: KillzoneConfig): Killzone {
  // Convert to EST if needed
  const est = new Date(timestamp.toLocaleString('en-US', {
    timeZone: 'America/New_York'
  }));

  const hour = est.getHours();

  if (hour >= config.londonOpenStart && hour < config.londonOpenEnd) {
    return Killzone.LONDON_OPEN;
  }

  if (hour >= config.nyAMStart && hour < config.nyAMEnd) {
    return Killzone.NY_AM;
  }

  if (hour >= config.nyPMStart && hour < config.nyPMEnd) {
    return Killzone.NY_PM;
  }

  // Asian session (low liquidity, avoid trading)
  if (hour >= 18 || hour < 2) {
    return Killzone.ASIAN;
  }

  return Killzone.NONE;
}

// Filter candles by killzone
function filterByKillzone(
  candles: Candle[],
  targetZone: Killzone,
  config: KillzoneConfig
): Candle[] {
  return candles.filter(candle => {
    const zone = getCurrentKillzone(new Date(candle.timestamp), config);
    return zone === targetZone;
  });
}

Key Points:

  • Focus trading during killzones for best probability
  • NY AM session (7-10am EST) is considered most important
  • Avoid Asian session (low liquidity, choppy price action)
  • Use killzones to filter signals from other ICT concepts

4. Liquidity Pools

What it is: Areas where stop-loss orders cluster, typically above old swing highs or below old swing lows. Institutions "sweep" these levels to capture liquidity before reversing.

Detection Logic:

1. Identify swing highs and swing lows
2. Find equal highs or equal lows (within tolerance)
3. Mark as liquidity pool (buy-side or sell-side)
4. Watch for "liquidity grab" (sweep above/below then reverse)

TypeScript Implementation:

typescript
interface SwingPoint {
  type: 'high' | 'low';
  index: number;
  price: number;
}

interface LiquidityPool {
  type: 'buy_side' | 'sell_side'; // above highs or below lows
  price: number;
  swingIndices: number[]; // Indices of equal swings
  swept: boolean;
}

function findSwingPoints(
  candles: Candle[],
  lookback: number = 5
): SwingPoint[] {
  const swings: SwingPoint[] = [];

  for (let i = lookback; i < candles.length - lookback; i++) {
    const current = candles[i];
    const leftWindow = candles.slice(i - lookback, i);
    const rightWindow = candles.slice(i + 1, i + lookback + 1);

    // Swing high: higher than all candles in lookback window
    const isSwingHigh =
      leftWindow.every(c => current.high > c.high) &&
      rightWindow.every(c => current.high > c.high);

    if (isSwingHigh) {
      swings.push({ type: 'high', index: i, price: current.high });
    }

    // Swing low: lower than all candles in lookback window
    const isSwingLow =
      leftWindow.every(c => current.low < c.low) &&
      rightWindow.every(c => current.low < c.low);

    if (isSwingLow) {
      swings.push({ type: 'low', index: i, price: current.low });
    }
  }

  return swings;
}

function findLiquidityPools(
  swings: SwingPoint[],
  priceTolerance: number = 0.0005 // 0.05% tolerance for "equal" levels
): LiquidityPool[] {
  const pools: LiquidityPool[] = [];

  // Group swing highs (buy-side liquidity)
  const swingHighs = swings.filter(s => s.type === 'high');
  const highGroups = groupEqualPrices(swingHighs, priceTolerance);

  highGroups.forEach(group => {
    if (group.length >= 2) { // At least 2 equal highs
      pools.push({
        type: 'buy_side',
        price: group[0].price,
        swingIndices: group.map(s => s.index),
        swept: false
      });
    }
  });

  // Group swing lows (sell-side liquidity)
  const swingLows = swings.filter(s => s.type === 'low');
  const lowGroups = groupEqualPrices(swingLows, priceTolerance);

  lowGroups.forEach(group => {
    if (group.length >= 2) { // At least 2 equal lows
      pools.push({
        type: 'sell_side',
        price: group[0].price,
        swingIndices: group.map(s => s.index),
        swept: false
      });
    }
  });

  return pools;
}

function groupEqualPrices(
  swings: SwingPoint[],
  tolerance: number
): SwingPoint[][] {
  const groups: SwingPoint[][] = [];
  const used = new Set<number>();

  swings.forEach((swing, i) => {
    if (used.has(i)) return;

    const group: SwingPoint[] = [swing];
    used.add(i);

    for (let j = i + 1; j < swings.length; j++) {
      if (used.has(j)) continue;

      const priceDiff = Math.abs(swing.price - swings[j].price) / swing.price;
      if (priceDiff <= tolerance) {
        group.push(swings[j]);
        used.add(j);
      }
    }

    groups.push(group);
  });

  return groups;
}

// Check if liquidity pool has been swept
function updateLiquidityPoolStatus(
  pools: LiquidityPool[],
  candles: Candle[]
): void {
  pools.forEach(pool => {
    const lastSwingIndex = Math.max(...pool.swingIndices);
    const candlesAfter = candles.slice(lastSwingIndex + 1);

    for (const candle of candlesAfter) {
      if (pool.type === 'buy_side' && candle.high > pool.price) {
        pool.swept = true;
        break;
      }
      if (pool.type === 'sell_side' && candle.low < pool.price) {
        pool.swept = true;
        break;
      }
    }
  });
}

Key Patterns:

  • Liquidity Sweep + Reversal: Price spikes through liquidity pool then reverses (false breakout)
  • Displacement After Sweep: Strong move away from liquidity after capturing it
  • Entry: After liquidity sweep, enter on retracement to FVG or Order Block

5. Market Structure

What it is: The framework for understanding trend and reversals through higher highs/lows or lower highs/lows.

Key Concepts:

  • Break of Structure (BOS): Continuation signal (higher high in uptrend, lower low in downtrend)
  • Change of Character (CHoCH) / Market Structure Shift (MSS): Reversal signal
  • Higher Highs (HH) & Higher Lows (HL): Uptrend
  • Lower Highs (LH) & Lower Lows (LL): Downtrend

TypeScript Implementation:

typescript
enum MarketStructure {
  HIGHER_HIGH = 'HH',
  HIGHER_LOW = 'HL',
  LOWER_HIGH = 'LH',
  LOWER_LOW = 'LL'
}

enum Trend {
  UPTREND = 'uptrend',
  DOWNTREND = 'downtrend',
  RANGING = 'ranging'
}

interface StructurePoint {
  type: MarketStructure;
  index: number;
  price: number;
}

function analyzeMarketStructure(
  swings: SwingPoint[]
): { trend: Trend; structures: StructurePoint[] } {
  const structures: StructurePoint[] = [];
  let currentTrend: Trend = Trend.RANGING;

  for (let i = 1; i < swings.length; i++) {
    const prev = swings[i - 1];
    const current = swings[i];

    if (current.type === 'high' && prev.type === 'high') {
      if (current.price > prev.price) {
        structures.push({
          type: MarketStructure.HIGHER_HIGH,
          index: current.index,
          price: current.price
        });
        currentTrend = Trend.UPTREND;
      } else {
        structures.push({
          type: MarketStructure.LOWER_HIGH,
          index: current.index,
          price: current.price
        });
        currentTrend = Trend.DOWNTREND;
      }
    }

    if (current.type === 'low' && prev.type === 'low') {
      if (current.price > prev.price) {
        structures.push({
          type: MarketStructure.HIGHER_LOW,
          index: current.index,
          price: current.price
        });
        currentTrend = Trend.UPTREND;
      } else {
        structures.push({
          type: MarketStructure.LOWER_LOW,
          index: current.index,
          price: current.price
        });
        currentTrend = Trend.DOWNTREND;
      }
    }
  }

  return { trend: currentTrend, structures };
}

// Detect Break of Structure (BOS) or Change of Character (CHoCH)
function detectStructureBreaks(
  candles: Candle[],
  structures: StructurePoint[]
): { type: 'BOS' | 'CHoCH'; index: number; price: number }[] {
  const breaks: { type: 'BOS' | 'CHoCH'; index: number; price: number }[] = [];

  // Implementation depends on specific rules and context
  // BOS: Price breaks last swing high in uptrend (continuation)
  // CHoCH: Price breaks last swing low in uptrend (reversal)

  return breaks;
}

ICT Trading Model (2022 Model)

The ICT 2022 trading model provides a complete framework for entering trades:

Step-by-Step Workflow

1. Identify Market Bias (Higher Timeframe Analysis)

  • Analyze daily/4H charts for overall trend
  • Identify key liquidity pools and order blocks
  • Determine directional bias (bullish/bearish)

2. Wait for Killzone

  • Focus on NY AM session (7-10am EST) for best setups
  • London Open (2-5am EST) as secondary option

3. Look for Liquidity Sweep

  • Watch for price to sweep buy-side or sell-side liquidity
  • Confirm with displacement (strong move away from liquidity)

4. Identify Entry Pattern

  • Wait for retracement to Fair Value Gap (FVG) or Order Block (OB)
  • Look for 50% fill of FVG as entry
  • Entry in the direction of bias after liquidity sweep

5. Confirmation

  • Market structure aligned with bias
  • During killzone
  • Displacement after liquidity sweep

Pseudocode:

typescript
interface ICTSignal {
  direction: 'long' | 'short';
  entry: number;
  stopLoss: number;
  takeProfit: number;
  confidence: 'high' | 'medium' | 'low';
  reasoning: string[];
}

async function generateICTSignal(
  symbol: string,
  timeframe: string
): Promise<ICTSignal | null> {
  // 1. Fetch data
  const candles = await fetchCandles(symbol, timeframe);

  // 2. Analyze market structure (higher timeframe)
  const swings = findSwingPoints(candles);
  const { trend } = analyzeMarketStructure(swings);

  // 3. Check if in killzone
  const killzone = getCurrentKillzone(new Date(), killzoneConfig);
  if (killzone !== Killzone.NY_AM && killzone !== Killzone.LONDON_OPEN) {
    return null; // Not in favorable time window
  }

  // 4. Detect liquidity pools and check for sweeps
  const pools = findLiquidityPools(swings);
  updateLiquidityPoolStatus(pools, candles);
  const recentSwepts = pools.filter(p => p.swept);

  if (recentSwepts.length === 0) {
    return null; // No liquidity sweep detected
  }

  // 5. Find FVGs and Order Blocks for entry
  const fvgs = detectFairValueGaps(candles);
  const orderBlocks = identifyOrderBlocks(candles);

  // 6. Check for displacement after sweep
  const hasDisplacement = checkForDisplacement(candles);

  if (!hasDisplacement) {
    return null; // No strong move after liquidity sweep
  }

  // 7. Generate signal
  if (trend === Trend.UPTREND && recentSwepts.some(p => p.type === 'sell_side')) {
    const fvg = findNearestUnfilledFVG(fvgs, 'bullish', candles);

    if (fvg) {
      return {
        direction: 'long',
        entry: fvg.gapLow + (fvg.gapHigh - fvg.gapLow) * 0.5, // 50% of FVG
        stopLoss: fvg.gapLow - calculateATR(candles),
        takeProfit: calculateTarget(candles, 'long'),
        confidence: 'high',
        reasoning: [
          'Uptrend confirmed by market structure',
          'Sell-side liquidity swept',
          'Currently in killzone',
          'Bullish FVG available for entry'
        ]
      };
    }
  }

  if (trend === Trend.DOWNTREND && recentSwepts.some(p => p.type === 'buy_side')) {
    const fvg = findNearestUnfilledFVG(fvgs, 'bearish', candles);

    if (fvg) {
      return {
        direction: 'short',
        entry: fvg.gapLow + (fvg.gapHigh - fvg.gapLow) * 0.5,
        stopLoss: fvg.gapHigh + calculateATR(candles),
        takeProfit: calculateTarget(candles, 'short'),
        confidence: 'high',
        reasoning: [
          'Downtrend confirmed by market structure',
          'Buy-side liquidity swept',
          'Currently in killzone',
          'Bearish FVG available for entry'
        ]
      };
    }
  }

  return null; // No setup found
}

Power of 3 (PO3) Framework

ICT's PO3 framework explains how the market unfolds each day in three phases:

1. Accumulation (Consolidation)

  • Smart money builds positions quietly
  • Low volatility, ranging price action
  • Occurs during Asian session or early London

2. Manipulation (Liquidity Sweep)

  • False breakout to trigger retail stops
  • Sweep liquidity pools above/below key levels
  • Often during killzone start

3. Distribution (True Move)

  • Displacement in true direction after manipulation
  • Smart money distributes positions at profit
  • Main trending move of the session

Implementation Note: Use PO3 to understand intraday flow and avoid entering during manipulation phase. Wait for distribution (step 3) after liquidity sweep.


Common Pitfalls When Coding ICT

1. Timezone Handling

  • Always convert timestamps to EST/New York time for killzones
  • Use proper timezone libraries (moment-timezone, date-fns-tz)
  • Account for daylight saving time

2. FVG Validation

  • Not all 3-candle gaps are tradeable FVGs
  • Must occur during killzones for higher probability
  • Require displacement (momentum) for validity

3. Lookback Period for Swings

  • Too small: Noise and false swings
  • Too large: Miss important swing points
  • Recommended: 5-10 candles for 5m/15m charts

4. Order Block Mitigation

  • Don't immediately discard OBs after first touch
  • Some OBs get tested multiple times before failing
  • Track "respect count" rather than binary mitigated/not

5. Overtrading

  • Not every killzone produces setups
  • All criteria must align (structure, liquidity sweep, FVG/OB, killzone)
  • Quality over quantity

Integration with Discord Bot

Example: ICT Analysis Command

typescript
// Discord.js example
import { SlashCommandBuilder } from 'discord.js';

const ictAnalyzeCommand = new SlashCommandBuilder()
  .setName('ict-analyze')
  .setDescription('Analyze current market using ICT methodology')
  .addStringOption(option =>
    option.setName('symbol')
      .setDescription('Symbol to analyze (e.g., ES, NQ)')
      .setRequired(true))
  .addStringOption(option =>
    option.setName('timeframe')
      .setDescription('Timeframe (e.g., 5m, 15m, 1h)')
      .setRequired(true));

async function handleICTAnalyze(interaction) {
  const symbol = interaction.options.getString('symbol');
  const timeframe = interaction.options.getString('timeframe');

  await interaction.deferReply();

  try {
    const signal = await generateICTSignal(symbol, timeframe);

    if (!signal) {
      await interaction.editReply('No ICT setup detected currently.');
      return;
    }

    const embed = {
      title: `🎯 ICT Signal: ${symbol}`,
      color: signal.direction === 'long' ? 0x00FF00 : 0xFF0000,
      fields: [
        { name: 'Direction', value: signal.direction.toUpperCase(), inline: true },
        { name: 'Entry', value: signal.entry.toFixed(2), inline: true },
        { name: 'Confidence', value: signal.confidence.toUpperCase(), inline: true },
        { name: 'Stop Loss', value: signal.stopLoss.toFixed(2), inline: true },
        { name: 'Take Profit', value: signal.takeProfit.toFixed(2), inline: true },
        { name: 'Risk/Reward', value: calculateRR(signal).toFixed(2), inline: true },
        { name: 'Reasoning', value: signal.reasoning.join('\n'), inline: false }
      ],
      timestamp: new Date()
    };

    await interaction.editReply({ embeds: [embed] });
  } catch (error) {
    await interaction.editReply(`Error: ${error.message}`);
  }
}

After Using This Skill

If building ICT features for your Discord bot:

  1. Implement data fetching (see databento skill for Databento integration)
  2. Use trading-bot-development skill for architecture patterns
  3. Consider trading-foundations skill for shared utility functions

If implementing multiple strategies:

  • Use trading-bot-development skill for multi-strategy orchestration
  • Each strategy should implement same Signal interface for consistency

For testing:

  • Start with historical data (backtesting)
  • Test each concept individually (FVG detection, OB detection, etc.)
  • Combine concepts only after validating components

References & Further Learning

2024-2025 ICT Resources:

  • ICT 2024 Mentorship materials (free on YouTube)
  • ICT Trading concepts updated guides
  • Inner Circle Trader website tutorials

Key Updates (2025):

  • Suspension Blocks (new PD Array introduced September 2024)
  • Enhanced focus on TIME rather than just price
  • Refined killzone definitions

Last Updated: January 2025 Version: 1.0.0 Part of Wolf Skills Marketplace

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results