ICT Smart Money Strategy
ICT strategy: trend filter, Order Block and FVG entries with confirmation; profit-taking at Fib levels or higher FVG.
What is ICT Smart Money?
Uses trend (EMA), Order Blocks and Fair Value Gaps (FVGs) for entries, with Fibonacci and higher FVG for profit-taking. Entries prefer OB retracement, then FVG retracement, and require a confirmation bar (no same-bar entry). Exits use Fib extension levels and/or the next higher (bull) or lower (bear) FVG as targets. Built for forex, crypto, and stocks.
Strategy Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| emaFast | number | 50 | Fast EMA period for trend |
| emaSlow | number | 200 | Slow EMA period for trend |
| fvgThreshold | number | 0.001 | Minimum FVG size (ratio) |
| confirmBars | number | 1 | Bars in zone before entry |
| atrMultiplier | number | 1.5 | ATR multiplier for displacement |
Use Cases
- ✓Trend-following entries at Order Block or FVG retracements
- ✓Profit targets at Fib extension or next FVG
- ✓Avoid chop with confirmation bar requirement
- ✓Works on higher timeframes (4H, Daily)
Strategy Script (JavaScript)
This strategy runs in VaultCharts using the built-in strategy engine. Below is the full script in a readable format. You can copy it or run it directly in VaultCharts.
module.exports = {
meta: {
name: "ICT Smart Money",
params: {
emaFast: { type: "number", default: 50 },
emaSlow: { type: "number", default: 200 },
fvgThreshold: { type: "number", default: 0.001 },
confirmBars: { type: "number", default: 1 },
atrMultiplier: { type: "number", default: 1.5 }
}
},
compute: (data, params, utils) => {
// Data sanitization
const cleanData = data.filter(d =>
d &&
Number.isFinite(d.high) &&
Number.isFinite(d.low) &&
Number.isFinite(d.close) &&
Number.isFinite(d.open) &&
Number.isFinite(d.time) &&
d.high >= d.low &&
d.close > 0
);
if (!cleanData || cleanData.length < 250) {
console.warn('[ICT Smart Money] Insufficient data:', cleanData?.length || 0);
return { signals: [] };
}
const { technicalindicators: TI } = utils;
if (!TI || !TI.EMA || !TI.ATR) {
console.warn('[ICT Smart Money] Technical indicators not available');
return { signals: [] };
}
const emaFast = params?.emaFast ?? 50;
const emaSlow = params?.emaSlow ?? 200;
const fvgThreshold = params?.fvgThreshold ?? 0.001;
const confirmBars = Math.max(0, params?.confirmBars ?? 1);
const atrMultiplier = params?.atrMultiplier ?? 1.5;
const closes = cleanData.map(d => d.close);
const highs = cleanData.map(d => d.high);
const lows = cleanData.map(d => d.low);
const emaFastValues = TI.EMA.calculate({ period: emaFast, values: closes });
const emaSlowValues = TI.EMA.calculate({ period: emaSlow, values: closes });
const atrValues = TI.ATR.calculate({ high: highs, low: lows, close: closes, period: 14 });
const getATR = (idx) => {
const atrIdx = Math.min(idx, atrValues.length - 1);
return Number.isFinite(atrValues[atrIdx]) ? atrValues[atrIdx] : (highs[idx] - lows[idx]) || 0.001;
};
// FVGs only when middle candle is displacement (body >= atrMultiplier * ATR, closes near extreme)
const detectFVGs = () => {
const fvgs = [];
for (let i = 2; i < cleanData.length; i++) {
const current = cleanData[i];
const prev1 = cleanData[i - 1];
const prev2 = cleanData[i - 2];
if (!current || !prev1 || !prev2) continue;
const atr = getATR(i - 1);
const body1 = Math.abs(prev1.close - prev1.open);
const isDisplacement = body1 >= atr * atrMultiplier;
const closesNearHigh = prev1.close >= prev1.open && (prev1.high - prev1.close) < body1 * 0.3;
const closesNearLow = prev1.close <= prev1.open && (prev1.close - prev1.low) < body1 * 0.3;
// Bullish FVG: current.low > prev2.high, middle candle displacement (bullish close near high)
if (current.low > prev2.high && prev1.close > prev2.high) {
const gapSize = (current.low - prev2.high) / prev2.high;
if (gapSize > fvgThreshold && isDisplacement && closesNearHigh) {
fvgs.push({
min: prev2.high,
max: current.low,
time: current.time,
index: i,
isBull: true
});
}
}
// Bearish FVG: current.high < prev2.low, middle candle displacement (bearish close near low)
if (current.high < prev2.low && prev1.close < prev2.low) {
const gapSize = (prev2.low - current.high) / current.high;
if (gapSize > fvgThreshold && isDisplacement && closesNearLow) {
fvgs.push({
min: current.high,
max: prev2.low,
time: current.time,
index: i,
isBull: false
});
}
}
}
return fvgs;
};
// Detect Order Blocks (simplified - last opposite candle before strong move)
const detectOrderBlocks = () => {
const orderBlocks = [];
const lookback = 20;
for (let i = lookback; i < cleanData.length - 5; i++) {
const candle = cleanData[i];
const next5 = cleanData.slice(i + 1, i + 6);
if (next5.length < 5) continue;
// Check for strong bullish move after bearish candle
const isBearishCandle = candle.close < candle.open;
const bodySize = Math.abs(candle.close - candle.open);
const avgBody = cleanData.slice(Math.max(0, i - 10), i)
.map(c => Math.abs(c.close - c.open))
.reduce((a, b) => a + b, 0) / Math.max(1, Math.min(10, i));
if (isBearishCandle && bodySize >= avgBody) {
// Check if next 5 candles show strong bullish move
const maxHigh = Math.max(...next5.map(c => c.high));
const strongMove = maxHigh > candle.high * 1.01; // 1% move up
if (strongMove) {
orderBlocks.push({
high: candle.open,
low: candle.low,
time: candle.time,
index: i,
isBull: true
});
}
}
// Check for strong bearish move after bullish candle
const isBullishCandle = candle.close > candle.open;
if (isBullishCandle && bodySize >= avgBody) {
// Check if next 5 candles show strong bearish move
const minLow = Math.min(...next5.map(c => c.low));
const strongMove = minLow < candle.low * 0.99; // 1% move down
if (strongMove) {
orderBlocks.push({
high: candle.high,
low: candle.open,
time: candle.time,
index: i,
isBull: false
});
}
}
}
return orderBlocks;
};
// Calculate Fibonacci levels from recent swing
const calculateFibLevels = (swingHigh, swingLow) => {
const range = swingHigh - swingLow;
return {
fib236: swingLow + range * 0.236,
fib382: swingLow + range * 0.382,
fib500: swingLow + range * 0.5,
fib618: swingLow + range * 0.618,
fib786: swingLow + range * 0.786
};
};
const fvgs = detectFVGs();
const orderBlocks = detectOrderBlocks();
const signals = [];
let checkedCount = 0;
let trendBullishCount = 0;
let trendBearishCount = 0;
let fvgTouchCount = 0;
let obTouchCount = 0;
// Find recent swing high and low for Fibonacci
let recentSwingHigh = cleanData[0].high;
let recentSwingLow = cleanData[0].low;
let swingHighIndex = 0;
let swingLowIndex = 0;
for (let i = 50; i < cleanData.length; i++) {
let isHigh = true;
let isLow = true;
for (let j = Math.max(0, i - 20); j < Math.min(cleanData.length, i + 20); j++) {
if (j === i) continue;
if (cleanData[j].high > cleanData[i].high) isHigh = false;
if (cleanData[j].low < cleanData[i].low) isLow = false;
}
if (isHigh && cleanData[i].high > recentSwingHigh) {
recentSwingHigh = cleanData[i].high;
swingHighIndex = i;
}
if (isLow && cleanData[i].low < recentSwingLow) {
recentSwingLow = cleanData[i].low;
swingLowIndex = i;
}
}
const fibLevels = calculateFibLevels(recentSwingHigh, recentSwingLow);
console.log('[ICT Smart Money] Setup:', {
dataLength: cleanData.length,
fvgCount: fvgs.length,
obCount: orderBlocks.length,
swingHigh: recentSwingHigh,
swingLow: recentSwingLow,
fibLevels
});
// Track position state
let currentPosition = null;
// Main loop
const startIdx = Math.max(emaFast, emaSlow, 50);
for (let i = startIdx; i < cleanData.length; i++) {
const candle = cleanData[i];
if (!candle || candle.time === undefined) continue;
const emaFastIdx = i - (emaFast - 1);
const emaSlowIdx = i - (emaSlow - 1);
if (emaFastIdx < 0 || emaSlowIdx < 0 || emaFastIdx >= emaFastValues.length || emaSlowIdx >= emaSlowValues.length) continue;
const emaFastVal = emaFastValues[emaFastIdx];
const emaSlowVal = emaSlowValues[emaSlowIdx];
if (!Number.isFinite(emaFastVal) || !Number.isFinite(emaSlowVal)) continue;
checkedCount++;
// Determine trend: bullish if EMA fast > EMA slow and price > EMA fast
const bullishTrend = emaFastVal > emaSlowVal && candle.close > emaFastVal;
const bearishTrend = emaFastVal < emaSlowVal && candle.close < emaFastVal;
if (bullishTrend) trendBullishCount++;
if (bearishTrend) trendBearishCount++;
// Exit only on take profit (Fib extension or higher FVG) or stop — no trend-reversal exit
if (currentPosition) {
let shouldExit = false;
if (currentPosition.type === 'long') {
if (currentPosition.targetFibExt1 != null && candle.close >= currentPosition.targetFibExt1 * 0.998) {
shouldExit = true;
}
if (currentPosition.targetFibExt2 != null && candle.high >= currentPosition.targetFibExt2 * 0.998) {
shouldExit = true;
}
if (currentPosition.targetFVG != null && candle.high >= currentPosition.targetFVG * 0.998) {
shouldExit = true;
}
if (candle.close < currentPosition.entryZone.min * 0.998) {
shouldExit = true;
}
} else if (currentPosition.type === 'short') {
if (currentPosition.targetFibExt1 != null && candle.close <= currentPosition.targetFibExt1 * 1.002) {
shouldExit = true;
}
if (currentPosition.targetFibExt2 != null && candle.low <= currentPosition.targetFibExt2 * 1.002) {
shouldExit = true;
}
if (currentPosition.targetFVG != null && candle.low <= currentPosition.targetFVG * 1.002) {
shouldExit = true;
}
if (candle.close > currentPosition.entryZone.max * 1.002) {
shouldExit = true;
}
}
if (shouldExit) {
signals.push({
type: "exit",
direction: currentPosition.type,
time: candle.time,
price: candle.close,
index: i
});
currentPosition = null;
}
}
const nextHigherBullFVG = (abovePrice) => {
let best = null;
for (const fvg of fvgs) {
if (!fvg.isBull || fvg.index >= i || fvg.min <= abovePrice) continue;
if (best == null || fvg.min < best.min) best = fvg;
}
return best ? best.max : null;
};
const nextLowerBearFVG = (belowPrice) => {
let best = null;
for (const fvg of fvgs) {
if (fvg.isBull || fvg.index >= i || fvg.max >= belowPrice) continue;
if (best == null || fvg.max > best.max) best = fvg;
}
return best ? best.min : null;
};
const swingHighBefore = (idx, lookback) => {
const start = Math.max(0, idx - lookback);
return Math.max(...cleanData.slice(start, idx).map(c => c.high));
};
const swingLowBefore = (idx, lookback) => {
const start = Math.max(0, idx - lookback);
return Math.min(...cleanData.slice(start, idx).map(c => c.low));
};
// Entry logic: prefer OB, then FVG; require confirmation (bars in zone before entry)
if (!currentPosition && bullishTrend) {
// 1) Order blocks first (preferred)
for (const ob of orderBlocks) {
if (!ob.isBull || ob.index >= i) continue;
let mitigated = false;
for (let j = ob.index; j < i; j++) {
if (cleanData[j].close < ob.low) { mitigated = true; break; }
}
if (mitigated) continue;
const inOBZone = candle.close >= ob.low && candle.close <= ob.high;
if (!inOBZone) continue;
let barsInZone = 1;
for (let k = i - 1; k >= Math.max(0, i - 5); k--) {
const c = cleanData[k];
if (c.close >= ob.low && c.close <= ob.high) barsInZone++; else break;
}
const confirmed = barsInZone >= confirmBars && candle.close > candle.open;
if (confirmed) {
obTouchCount++;
const entryP = candle.close;
const sh = swingHighBefore(i, 20);
const atr = getATR(i);
const rangeUp = sh - entryP;
const t1 = rangeUp > 0 ? entryP + rangeUp * 0.618 : entryP + atr * 1.272;
const t2 = rangeUp > 0 ? entryP + rangeUp * 1.272 : entryP + atr * 2;
currentPosition = {
type: 'long',
entryPrice: entryP,
entryIndex: i,
entryZone: { min: ob.low, max: ob.high },
targetFVG: nextHigherBullFVG(entryP),
targetFibExt1: t1,
targetFibExt2: t2
};
signals.push({ type: "entry", direction: "long", time: candle.time, price: candle.close, index: i });
break;
}
}
if (!currentPosition) {
for (const fvg of fvgs) {
if (!fvg.isBull || fvg.index >= i) continue;
let mitigated = false;
for (let j = fvg.index; j < i; j++) {
if (cleanData[j].close < fvg.min) { mitigated = true; break; }
}
if (mitigated) continue;
const inFVGZone = candle.close >= fvg.min && candle.close <= fvg.max;
if (!inFVGZone) continue;
let barsInZone = 1;
for (let k = i - 1; k >= Math.max(0, i - 5); k--) {
const c = cleanData[k];
if (c.close >= fvg.min && c.close <= fvg.max) barsInZone++; else break;
}
const confirmed = barsInZone >= confirmBars && candle.close > candle.open;
if (confirmed) {
fvgTouchCount++;
const entryP = candle.close;
const sh = swingHighBefore(i, 20);
const atr = getATR(i);
const rangeUp = sh - entryP;
const t1 = rangeUp > 0 ? entryP + rangeUp * 0.618 : entryP + atr * 1.272;
const t2 = rangeUp > 0 ? entryP + rangeUp * 1.272 : entryP + atr * 2;
currentPosition = {
type: 'long',
entryPrice: entryP,
entryIndex: i,
entryZone: { min: fvg.min, max: fvg.max },
targetFVG: nextHigherBullFVG(entryP),
targetFibExt1: t1,
targetFibExt2: t2
};
signals.push({ type: "entry", direction: "long", time: candle.time, price: candle.close, index: i });
break;
}
}
}
}
if (!currentPosition && bearishTrend) {
for (const ob of orderBlocks) {
if (ob.isBull || ob.index >= i) continue;
let mitigated = false;
for (let j = ob.index; j < i; j++) {
if (cleanData[j].close > ob.high) { mitigated = true; break; }
}
if (mitigated) continue;
const inOBZone = candle.close >= ob.low && candle.close <= ob.high;
if (!inOBZone) continue;
let barsInZone = 1;
for (let k = i - 1; k >= Math.max(0, i - 5); k--) {
const c = cleanData[k];
if (c.close >= ob.low && c.close <= ob.high) barsInZone++; else break;
}
const confirmed = barsInZone >= confirmBars && candle.close < candle.open;
if (confirmed) {
obTouchCount++;
const entryP = candle.close;
const sl = swingLowBefore(i, 20);
const atr = getATR(i);
const rangeDown = entryP - sl;
const t1 = rangeDown > 0 ? entryP - rangeDown * 0.618 : entryP - atr * 1.272;
const t2 = rangeDown > 0 ? entryP - rangeDown * 1.272 : entryP - atr * 2;
currentPosition = {
type: 'short',
entryPrice: entryP,
entryIndex: i,
entryZone: { min: ob.low, max: ob.high },
targetFVG: nextLowerBearFVG(entryP),
targetFibExt1: t1,
targetFibExt2: t2
};
signals.push({ type: "entry", direction: "short", time: candle.time, price: candle.close, index: i });
break;
}
}
if (!currentPosition) {
for (const fvg of fvgs) {
if (fvg.isBull || fvg.index >= i) continue;
let mitigated = false;
for (let j = fvg.index; j < i; j++) {
if (cleanData[j].close > fvg.max) { mitigated = true; break; }
}
if (mitigated) continue;
const inFVGZone = candle.close >= fvg.min && candle.close <= fvg.max;
if (!inFVGZone) continue;
let barsInZone = 1;
for (let k = i - 1; k >= Math.max(0, i - 5); k--) {
const c = cleanData[k];
if (c.close >= fvg.min && c.close <= fvg.max) barsInZone++; else break;
}
const confirmed = barsInZone >= confirmBars && candle.close < candle.open;
if (confirmed) {
fvgTouchCount++;
const entryP = candle.close;
const sl = swingLowBefore(i, 20);
const atr = getATR(i);
const rangeDown = entryP - sl;
const t1 = rangeDown > 0 ? entryP - rangeDown * 0.618 : entryP - atr * 1.272;
const t2 = rangeDown > 0 ? entryP - rangeDown * 1.272 : entryP - atr * 2;
currentPosition = {
type: 'short',
entryPrice: entryP,
entryIndex: i,
entryZone: { min: fvg.min, max: fvg.max },
targetFVG: nextLowerBearFVG(entryP),
targetFibExt1: t1,
targetFibExt2: t2
};
signals.push({ type: "entry", direction: "short", time: candle.time, price: candle.close, index: i });
break;
}
}
}
}
}
console.log('[ICT Smart Money] Analysis:', {
checkedCount,
trendBullishCount,
trendBearishCount,
fvgTouchCount,
obTouchCount,
signalsGenerated: signals.length
});
return { signals };
}
};Run ICT Smart Money in VaultCharts
VaultCharts includes this strategy as a built-in option. Backtest it, adjust parameters, and use it on your own data—all stored locally on your device.