//@version=6
// ============================================================================
// Commodity FlowTrend [WavesUnchained]
// Version: 1.0.6
// Author: WavesUnchained
// Build: 2026-10-12
// ============================================================================
// MFI + CCI composite oscillator tuned for commodity markets. Combines
// money-flow momentum (MFI) with cycle-sensitive trend context (CCI) to
// identify flow regimes: expansion, accumulation, distribution, and reversal.
//
// Features:
// ✓ Manual MFI with pluggable smoothing (EMA, SMA, RMA, WMA)
// ✓ CCI confirmation layer (normalized to MFI scale for visual alignment)
// ✓ 4-state flow background: bull/bear expansion vs. accumulation/distribution
// ✓ 4-state color-coded MFI histogram
// ✓ Extreme-zone reversal signals with optional CCI gating
// ✓ Midline confirmation dots (MFI crosses 50 with aligned CCI)
// ✓ Alert conditions for all signal types
//
// Data Contract:
// Price: REQUIRED chart symbol
// Volume: REQUIRED real trade volume — feed must deliver volume and
// syminfo.volumetype must not be "tick"; otherwise not available
// OI: NO
// Reference: NO
// Verdict: Exchange-only
// ============================================================================
indicator("Commodity FlowTrend [WavesUnchained]", "CFT", format=format.price, precision=2, overlay=false, max_labels_count=500)
// ─────────────────────────────────────────────────────────────────────────────
// INPUTS
// ─────────────────────────────────────────────────────────────────────────────
g_base = "1 · Base"
src = input.source(hlc3, "Source", group=g_base)
mfiLen = input.int(14, "MFI Length", minval=1, group=g_base)
cciLen = input.int(20, "CCI Length", minval=1, group=g_base)
sigLen = input.int(5, "Signal Length", minval=1, group=g_base)
g_smooth = "2 · Smoothing"
smoothType = input.string("EMA", "Type",
options=["EMA", "SMA", "RMA", "WMA", "SuperSmoother", "T3", "KAMA", "JMA Approx"],
group=g_smooth)
jmaPhase = input.int(50, " JMA Phase", minval=-100, maxval=100, group=g_smooth)
jmaPower = input.float(2.0, " JMA Power", minval=1.0, maxval=4.0, step=0.25, group=g_smooth)
g_levels = "3 · Levels"
mfiOB = input.float(80, "MFI Overbought", group=g_levels)
mfiOS = input.float(20, "MFI Oversold", group=g_levels)
mfiMid = input.float(50, "MFI Midline", group=g_levels)
g_filter = "4 · Signal Filters"
triggerMode = input.string("MFI Lead", "Trigger Mode", options=["MFI Lead", "CCI Lead"], group=g_filter,
tooltip="MFI Lead: MFI cross triggers, CCI confirms. CCI Lead is a legacy timing option and still requires MFI momentum confirmation.")
useCCIConfirm = input.bool(true, "Require CCI Confirmation", group=g_filter)
useExtremeOnly = input.bool(true, "Only Signal From OB/OS Zone", group=g_filter)
lookbackExtreme = input.int(8, "OB/OS Lookback", minval=1, group=g_filter)
usePriceTrendFilter = input.bool(true, "Use Price Trend Filter (EMA)", group=g_filter,
tooltip="If enabled, bullish signals are blocked below the EMA, and bearish signals are blocked above it.")
priceTrendLen = input.int(200, "Price Trend EMA Length", minval=10, group=g_filter)
g_col = "5 · Colors"
colBull = input.color(#26a69a, "Bull", group=g_col)
colBear = input.color(#ef5350, "Bear", group=g_col)
g_display = "6 · Display"
showBg = input.bool(false, "Show Flow Background", group=g_display)
showHist = input.bool(true, "Show MFI Histogram", group=g_display)
showCCI = input.bool(true, "Show CCI Normalized", group=g_display)
showSignals = input.bool(true, "Show Signals", group=g_display)
showCCITurn = input.bool(true, "Show CCI Turn Markers", group=g_display,
tooltip="Small diamonds when CCI turns from an extreme zone (±100) — fires before the MFI cross. Bull turn = CCI was below -100 and now reversing up. Bear turn = CCI was above +100 and now reversing down.")
// Echtes Handelsvolumen? Nur "tick" schließt es ausdrücklich aus (Anzahl
// Transaktionen, keine Menge). "n/a" heißt laut Referenz "kein Volumen ODER Art
// nicht angegeben" — die meisten Feeds außer Krypto melden das, auch Futures mit
// echten Kontrakten. Dort entscheidet, ob der Feed überhaupt Volumen liefert.
var bool feedHasVolume = false
if nz(volume) > 0
feedHasVolume := true
bool volumeIsReal = syminfo.volumetype != "tick" and feedHasVolume
float vol = volumeIsReal ? volume : na
// ─────────────────────────────────────────────────────────────────────────────
// SMOOTHING
// ─────────────────────────────────────────────────────────────────────────────
f_supersmoother(series float x, simple int len) =>
float a1 = math.exp(-1.414 * math.pi / len)
float b1 = 2.0 * a1 * math.cos(1.414 * math.pi / len)
float c2 = b1
float c3 = -(a1 * a1)
float c1 = 1.0 - c2 - c3
var float ss = na
ss := c1 * (x + nz(x[1])) / 2.0 + c2 * nz(ss[1]) + c3 * nz(ss[2])
ss
f_t3(series float x, simple int len) =>
float b = 0.7
float e1 = ta.ema(x, len)
float e2 = ta.ema(e1, len)
float e3 = ta.ema(e2, len)
float e4 = ta.ema(e3, len)
float e5 = ta.ema(e4, len)
float e6 = ta.ema(e5, len)
float c1 = -(b * b * b)
float c2 = 3.0 * b * b + 3.0 * b * b * b
float c3 = -6.0 * b * b - 3.0 * b - 3.0 * b * b * b
float c4 = 1.0 + 3.0 * b + b * b * b + 3.0 * b * b
c1 * e6 + c2 * e5 + c3 * e4 + c4 * e3
f_kama(series float x, simple int len) =>
float change = math.abs(x - x[len])
float vol = math.sum(math.abs(x - x[1]), len)
float er = vol != 0.0 ? change / vol : 0.0
float sc = math.pow(er * (2.0 / 3.0 - 2.0 / 31.0) + 2.0 / 31.0, 2)
var float kama = na
kama := nz(kama[1], x) + sc * (x - nz(kama[1], x))
kama
f_jma(series float x, simple int len, simple int phase, simple float power) =>
var float jma = na
var float e0 = na
var float e1 = na
var float e2 = na
float phaseRatio = phase < -100 ? 0.5 : phase > 100 ? 2.5 : phase / 100.0 + 1.5
float beta = 0.45 * (len - 1) / (0.45 * (len - 1) + 2.0)
float alpha = math.pow(beta, power)
e0 := (1.0 - alpha) * x + alpha * nz(e0[1], x)
e1 := (x - e0) * (1.0 - beta) + beta * nz(e1[1], 0.0)
e2 := (e0 + phaseRatio * e1 - nz(jma[1], x)) * math.pow(1.0 - alpha, 2.0) + math.pow(alpha, 2.0) * nz(e2[1], 0.0)
jma := nz(jma[1], x) + e2
jma
f_smooth(series float x, simple int len, simple string mode) =>
switch mode
"SMA" => ta.sma(x, len)
"RMA" => ta.rma(x, len)
"WMA" => ta.wma(x, len)
"SuperSmoother" => f_supersmoother(x, len)
"T3" => f_t3(x, len)
"KAMA" => f_kama(x, len)
"JMA Approx" => f_jma(x, len, jmaPhase, jmaPower)
=> ta.ema(x, len)
// ─────────────────────────────────────────────────────────────────────────────
// MFI
// ─────────────────────────────────────────────────────────────────────────────
rawMoneyFlow = src * vol
positiveFlow = src > src[1] ? rawMoneyFlow : 0.0
negativeFlow = src < src[1] ? rawMoneyFlow : 0.0
positiveSum = math.sum(positiveFlow, mfiLen)
negativeSum = math.sum(negativeFlow, mfiLen)
mfiRaw = not volumeIsReal ? na :
negativeSum == 0.0 ? 100.0 :
positiveSum == 0.0 ? 0.0 :
100.0 - (100.0 / (1.0 + positiveSum / negativeSum))
mfi = f_smooth(mfiRaw, sigLen, smoothType)
mfiSignal = f_smooth(mfi, sigLen, smoothType)
hist = mfi - mfiSignal
// ─────────────────────────────────────────────────────────────────────────────
// CCI (normalized to 0–100 for visual alignment with MFI)
// ─────────────────────────────────────────────────────────────────────────────
cciRaw = ta.cci(src, cciLen)
cci = f_smooth(cciRaw, sigLen, smoothType)
cciNorm = math.max(math.min(50.0 + cci / 4.0, 100.0), 0.0)
// ─────────────────────────────────────────────────────────────────────────────
// FLOW STATE
// ─────────────────────────────────────────────────────────────────────────────
mfiBull = mfi > mfiMid
mfiBear = mfi < mfiMid
mfiRising = mfi > mfi[1]
mfiFalling = mfi < mfi[1]
cciBullish = cci > 0.0
cciBearish = cci < 0.0
bullExpansion = mfiBull and cciBullish and mfiRising
bearExpansion = mfiBear and cciBearish and mfiFalling
bullAccumulation = not mfiBull and mfiRising and cci > cci[1]
bearDistribution = not mfiBear and mfiFalling and cci < cci[1]
// ─────────────────────────────────────────────────────────────────────────────
// SIGNALS
// ─────────────────────────────────────────────────────────────────────────────
wasOversold = ta.lowest(mfi, lookbackExtreme) <= mfiOS
wasOverbought = ta.highest(mfi, lookbackExtreme) >= mfiOB
bullCross = ta.crossover(mfi, mfiSignal)
bearCross = ta.crossunder(mfi, mfiSignal)
bullConfirm = not useCCIConfirm or cciBullish or cci > cci[1]
bearConfirm = not useCCIConfirm or cciBearish or cci < cci[1]
// Price-based Trend Filter (EMA)
priceTrend = ta.ema(close, priceTrendLen)
priceTrendBull = not usePriceTrendFilter or close > priceTrend
priceTrendBear = not usePriceTrendFilter or close < priceTrend
// MFI Lead: MFI cross triggers, CCI confirms, price trend filters
bool _bullMFI = bullCross and bullConfirm and (not useExtremeOnly or wasOversold) and priceTrendBull
bool _bearMFI = bearCross and bearConfirm and (not useExtremeOnly or wasOverbought) and priceTrendBear
// CCI Lead: CCI cross triggers, MFI momentum confirms, price trend filters
// (the CCI confirm term is always true on a CCI zero cross, so MFI direction is
// the actual confirmation; without real volume MFI is na and CCI Lead stays silent
// like every other MFI-based signal)
bool cciCrossUp = ta.crossover(cci, 0.0)
bool cciCrossDown = ta.crossunder(cci, 0.0)
bool cciWasOS = ta.lowest(cci, lookbackExtreme) <= -100.0
bool cciWasOB = ta.highest(cci, lookbackExtreme) >= 100.0
bool _bullCCI = cciCrossUp and mfiRising and (not useExtremeOnly or cciWasOS) and priceTrendBull
bool _bearCCI = cciCrossDown and mfiFalling and (not useExtremeOnly or cciWasOB) and priceTrendBear
bool bullSignal = triggerMode == "CCI Lead" ? _bullCCI : _bullMFI
bool bearSignal = triggerMode == "CCI Lead" ? _bearCCI : _bearMFI
// MFI soft quality filter
bullQuality = mfi > mfiMid
bearQuality = mfi < mfiMid
bullMidConfirm = ta.crossover(mfi, mfiMid) and cciBullish
bearMidConfirm = ta.crossunder(mfi, mfiMid) and cciBearish
// CCI turn from extreme: local inflection while still in OB/OS zone → early warning
bool cciTurningUp = cci > cci[1] and cci[1] <= cci[2] and cci[1] < -100.0
bool cciTurningDown = cci < cci[1] and cci[1] >= cci[2] and cci[1] > 100.0
// ─────────────────────────────────────────────────────────────────────────────
// COLORS
// ─────────────────────────────────────────────────────────────────────────────
mfiLineColor = color.from_gradient(mfi, mfiOS, mfiOB, colBull, colBear)
sigColor = mfi >= mfiSignal ? color.new(colBull, 30) : color.new(colBear, 30)
// 4-state histogram
histRising = hist >= hist[1]
histColor = hist >= 0.0
? (histRising ? color.new(colBull, 20) : color.new(colBull, 60))
: (not histRising ? color.new(colBear, 20) : color.new(colBear, 60))
bgColor =
bullExpansion ? color.new(colBull, 84) :
bullAccumulation ? color.new(colBull, 91) :
bearExpansion ? color.new(colBear, 84) :
bearDistribution ? color.new(colBear, 91) :
na
// ─────────────────────────────────────────────────────────────────────────────
// HLINES
// ─────────────────────────────────────────────────────────────────────────────
hline(100, "100", color=color.new(color.gray, 80))
hline(mfiOB, "Overbought", color=color.new(colBear, 45), linestyle=hline.style_dashed)
hline(mfiMid,"Midline", color=color.new(color.gray, 50), linestyle=hline.style_dotted)
hline(mfiOS, "Oversold", color=color.new(colBull, 45), linestyle=hline.style_dashed)
hline(0, "0", color=color.new(color.gray, 80))
// ─────────────────────────────────────────────────────────────────────────────
// PLOTS
// ─────────────────────────────────────────────────────────────────────────────
bgcolor(showBg ? bgColor : na, title="Flow Background")
// Histogram centered on midline
plot(showHist ? hist + mfiMid : na, "MFI Histogram",
style=plot.style_columns, color=histColor)
// CCI normalized (behind MFI line)
plot(showCCI ? cciNorm : na, "CCI Normalized",
color=color.new(color.orange, 15), linewidth=1)
// MFI lines
pSig = plot(mfiSignal, "MFI Signal", color=sigColor, linewidth=1)
pMFI = plot(mfi, "MFI", color=mfiLineColor, linewidth=2)
// Fill between MFI and signal
fill(pMFI, pSig,
color=mfi >= mfiSignal ? color.new(colBull, 88) : color.new(colBear, 88),
title="MFI Fill")
// ─────────────────────────────────────────────────────────────────────────────
// SIGNAL MARKERS
// ─────────────────────────────────────────────────────────────────────────────
plotshape(showSignals and bullSignal ? mfi - 6.0 : na, "Bull Signal",
style=shape.triangleup, location=location.absolute,
color=bullQuality ? colBull : color.new(colBull, 50), size=size.tiny)
plotshape(showSignals and bearSignal ? mfi + 6.0 : na, "Bear Signal",
style=shape.triangledown, location=location.absolute,
color=bearQuality ? colBear : color.new(colBear, 50), size=size.tiny)
// Midline confirmation dots — placed on the MFI line
plotshape(showSignals and bullMidConfirm ? mfi : na, "Bull Mid Confirm",
style=shape.circle, location=location.absolute,
color=color.new(colBull, 0), size=size.tiny)
plotshape(showSignals and bearMidConfirm ? mfi : na, "Bear Mid Confirm",
style=shape.circle, location=location.absolute,
color=color.new(colBear, 0), size=size.tiny)
// CCI turn markers — small diamonds on the CCI line, fires before MFI cross
plotshape(showCCITurn and cciTurningUp ? cciNorm : na, "CCI Bull Turn",
style=shape.diamond, location=location.absolute,
color=color.new(colBull, 20), size=size.tiny)
plotshape(showCCITurn and cciTurningDown ? cciNorm : na, "CCI Bear Turn",
style=shape.diamond, location=location.absolute,
color=color.new(colBear, 20), size=size.tiny)
// ─────────────────────────────────────────────────────────────────────────────
// DATA VALIDITY WARNING
// ─────────────────────────────────────────────────────────────────────────────
// Kurzer Hinweis unten rechts; was fehlt, steht im Data Contract oben.
var table volWarn = table.new(position.bottom_right, 1, 1)
if barstate.islast and not volumeIsReal
table.cell(volWarn, 0, 0, "CFT · MFI not available",
bgcolor = color.new(color.orange, 20), text_color = color.white, text_size = size.small)
// ─────────────────────────────────────────────────────────────────────────────
// ALERTS
// ─────────────────────────────────────────────────────────────────────────────
bool alertsOnBarClose = input.bool(true, "Alerts only on bar close (confirmed)", group="Alerts")
bool alertOk = not alertsOnBarClose or barstate.isconfirmed
alertcondition(alertOk and (bullSignal), "CFT Bull Signal", "CFT · LONG · {{ticker}} {{interval}}")
alertcondition(alertOk and (bearSignal), "CFT Bear Signal", "CFT · SHORT · {{ticker}} {{interval}}")
alertcondition(alertOk and (bullMidConfirm), "CFT Bull Midline Confirm", "CFT · BULL MIDLINE CONFIRM · {{ticker}} {{interval}}")
alertcondition(alertOk and (bearMidConfirm), "CFT Bear Midline Confirm", "CFT · BEAR MIDLINE CONFIRM · {{ticker}} {{interval}}")
Overview
Commodity FlowTrend [WavesUnchained] calculates a manually smoothed Money Flow Index from price and real trade volume, overlays a CCI reading scaled to the same range, and colors a histogram plus an optional background by expansion, accumulation, distribution, or reversal regimes. Signals fire when MFI or CCI leads a cross, optionally only after an overbought or oversold visit, with CCI confirmation and a price EMA trend filter. Lengths, smoothing type, levels, and display toggles are adjustable. It is unavailable when volume is tick-based.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Source
source
hlc3
src
MFI Length
int
14
mfiLen
CCI Length
int
20
cciLen
Signal Length
int
5
sigLen
Type
string
EMA
smoothType
JMA Phase
int
50
jmaPhase
JMA Power
float
2.0
jmaPower
MFI Overbought
float
80
mfiOB
MFI Oversold
float
20
mfiOS
MFI Midline
float
50
mfiMid
Trigger Mode
string
MFI Lead
triggerMode
Require CCI Confirmation
bool
true
useCCIConfirm
Show all 24 settings
Setting
Type
Default
Variable
Only Signal From OB/OS Zone
bool
true
useExtremeOnly
OB/OS Lookback
int
8
lookbackExtreme
Use Price Trend Filter (EMA)
bool
true
usePriceTrendFilter
Price Trend EMA Length
int
200
priceTrendLen
Bull
color
#26a69a
colBull
Bear
color
#ef5350
colBear
Show Flow Background
bool
false
showBg
Show MFI Histogram
bool
true
showHist
Show CCI Normalized
bool
true
showCCI
Show Signals
bool
true
showSignals
Show CCI Turn Markers
bool
true
showCCITurn
Alerts only on bar close (confirmed)
bool
true
alertsOnBarClose
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.
Calculates the standard Money Flow Index (MFI) oscillator alongside an exponential moving average signal line in a separate pane. The script visually highlights trend conditions through dynamic ribbon filling, optional difference histogram bars, and overbought
This indicator measures directional momentum by evaluating candlestick body proportions and volume over a user-defined lookback window. It computes the ratio of candle body to total range, weights the cumulative value by volume and absolute body size, and dete
This indicator builds an aggregate oscillator in a separate pane. It scales open, high, low, close, and volume into a 0 to 1 range, then averages scaled readings from MACD, a MACD histogram, RSI, stochastic, CCI, momentum, OBV, a directional index, volume-weig
This indicator calculates the Bill Williams Market Facilitation Index as the high-low range divided by volume, or zero when volume is zero. It compares the index and volume with the previous bar and assigns one of four zones: green for both rising, fade for bo
PPO ATR Bands plots a percentage price oscillator in a separate pane. It compares a Hull average of typical price with a volume-weighted exponential average of Parabolic SAR, then scales three true-range bands around the slow average into matching oscillator l
Calculates the Chaikin Oscillator by taking the difference between a fast exponential moving average and a slow exponential moving average of the Accumulation/Distribution line. It plots the resulting line in a separate pane alongside a dashed zero reference l