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
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:
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:
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):
- London Open: 2:00 AM - 5:00 AM EST
- New York AM Session: 7:00 AM - 10:00 AM EST (most important)
- New York PM Session: 1:00 PM - 3:00 PM EST (lunch hour manipulation)
TypeScript Implementation:
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:
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:
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:
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
// 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:
- Implement data fetching (see
databentoskill for Databento integration) - Use
trading-bot-developmentskill for architecture patterns - Consider
trading-foundationsskill for shared utility functions
If implementing multiple strategies:
- Use
trading-bot-developmentskill for multi-strategy orchestration - Each strategy should implement same
Signalinterface 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
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
daily-summary
Use when preparing daily standups or status reports - automates PR summary generation with categorization, metrics, and velocity analysis; eliminates manual report compilation and ensures consistent format
wolf-scripts-core
Core automation scripts for archetype selection, evidence validation, quality scoring, and safe bash execution
wolf
Master skill for Wolf Agents institutional knowledge and behavioral patterns (v1.1.0 with skill-chaining)
wolf-archetypes
Behavioral archetypes for automatic agent adaptation based on work type
databento
Use when working with ES/NQ futures market data, before calling any Databento API - follow mandatory four-step workflow (cost check, availability check, fetch, validate); prevents costly API errors and ensures data quality
wolf-governance
Wolf's governance framework, compliance rules, quality gates, and process standards
Didn't find tool you were looking for?