How to Code the Money Flow Index (MFI) in Pine Script
Money flow, positive/negative periods, and overbought/oversold context
The Money Flow Index (MFI) is a volume-weighted oscillator that blends price and participation to score buying versus selling pressure over a window. Often described as a volume-augmented cousin to RSI, MFI uses typical price times volume to accumulate positive and negative money flow. This tutorial breaks down the formula, standard 80/20 reference bands, and a full Pine Script v5 script using ta.mfi() with histogram cues and alerts.
What Is the Money Flow Index?
MFI measures the ratio of positive money flow to total money flow across n periods, scaled to 0–100. It incorporates volume, unlike RSI, which only uses price changes.
Raw money flow is typical price multiplied by volume for each bar. Bars where typical price rises versus the prior bar contribute to positive flow; declines contribute to negative flow.
Interpretation snapshot
High MFI suggests aggressive buying pressure; low MFI suggests aggressive selling pressure—subject to liquidity and data quality constraints.
Why Traders Use This Indicator
- Gauge pressure with both price direction and volume participation.
- Spot potential exhaustion when MFI stretches to 80/20 extremes.
- Compare MFI peaks to price peaks for divergence analysis.
- Filter breakouts that lack volume-backed money flow support.
| Parameter | Default | Description |
|---|---|---|
| Length | 14 | Summation window for positive and negative money flow—mirrors common RSI periods for familiarity. |
| Overbought / oversold | 80 / 20 | Traditional bands; trending names may require stricter levels or confirmation. |
| Volume source | volume | Spot instruments use bar volume; thin markets may distort MFI—interpret carefully. |
| Typical price | hlc3 | Classic MFI uses typical price for money flow; deviations change sensitivity. |
How MFI Is Calculated
Typical Price: TP = (high + low + close) / 3
Raw Money Flow: RMF = TP × volume
Classify each bar: if TP > TP[1], add RMF to positive money flow; if TP < TP[1], add to negative money flow; if equal, handle per specification (often ignored or split).
Money Flow Ratio: MFR = sum(positive MF, n) / sum(negative MF, n)
MFI: 100 - (100 / (1 + MFR))—the same logistic form as RSI but driven by volume-weighted flows.
Pine’s ta.mfi(source, length) aligns with this construction using built-in volume.
Signal Interpretation
Above 80: Often treated as overbought; in strong trends MFI can remain elevated—context matters.
Below 20: Often treated as oversold; verify with structure to avoid catching falling knives.
Midline 50: Some traders treat crosses as broad pressure shifts—usually secondary to 80/20 events.
Divergence: Price higher high with MFI lower high warns bullish pressure may be fading (and vice versa).
Combining with Other Indicators
Pair MFI with VWAP or OBV to cross-check volume narratives.
RSI side-by-side highlights when price-only momentum disagrees with volume-weighted momentum—potential liquidity traps.
Bollinger Bands on price plus MFI extremes can frame volatility squeeze breakouts that actually show flow confirmation.
The Hard Way: Writing Pine Script Manually
Challenges of Manual Coding
Compute money flow sums manually and diff against ta.mfi() bar by bar.
Add a session filter so MFI ignores overnight thin prints on equities.
Plot MFI minus RSI to visualize volume-weighted disagreement.
Require two consecutive closes beyond 80 before signaling exhaustion.
Simulate MFI on tick-approximated volume for education (where data allows).
//@version=5
indicator("MFI Tutorial (ta.mfi)", shorttitle="MFI", overlay=false)
// -----------------------------------------------------------------------------
// Inputs
// -----------------------------------------------------------------------------
length = input.int(14, "MFI length", minval=1)
src = input.source(hlc3, "Source (typical price baseline)")
ob = input.float(80.0, "Overbought", step=1.0)
os = input.float(20.0, "Oversold", step=1.0)
showHist = input.bool(true, "Histogram around 50")
showMid = input.bool(true, "Plot 50 midline")
// -----------------------------------------------------------------------------
// Core
// -----------------------------------------------------------------------------
mfi = ta.mfi(src, length)
// -----------------------------------------------------------------------------
// States
// -----------------------------------------------------------------------------
stateOb = mfi > ob
stateOs = mfi < os
crossOb = ta.crossover(mfi, ob)
crossOs = ta.crossunder(mfi, os)
cross50Up = ta.crossover(mfi, 50)
cross50Dn = ta.crossunder(mfi, 50)
// -----------------------------------------------------------------------------
// Plots
// -----------------------------------------------------------------------------
plot(mfi, "MFI", color=stateOb ? color.new(color.red, 0) : stateOs ? color.new(color.green, 0) : color.new(color.teal, 0), linewidth=2)
hline(ob, "OB", color=color.new(color.red, 40))
hline(os, "OS", color=color.new(color.green, 40))
hline(50, "Mid", color=color.new(color.gray, 70), linestyle=hline.style_dotted)
plot(showMid ? 50 : na, "Mid plot", color=color.new(color.gray, 100), linewidth=1, display=display.all)
histColor = mfi >= 50 ? color.new(color.teal, 55) : color.new(color.orange, 55)
plot(showHist ? (mfi - 50) : na, "MFI-50 hist", style=plot.style_histogram, color=histColor)
plotshape(crossOb, "Enter OB", shape.triangledown, location.top, color.new(color.red, 0), size=size.tiny, text="80+")
plotshape(crossOs, "Enter OS", shape.triangleup, location.bottom, color.new(color.green, 0), size=size.tiny, text="20-")
bgcolor(cross50Up ? color.new(color.teal, 92) : cross50Dn ? color.new(color.orange, 92) : na)
// -----------------------------------------------------------------------------
// RSI companion for comparison (data window)
// -----------------------------------------------------------------------------
rsi = ta.rsi(close, length)
plot(rsi, "RSI(length) data window", display=display.data_window)
// -----------------------------------------------------------------------------
// Alerts
// -----------------------------------------------------------------------------
alertcondition(crossOb, "MFI overbought cross", "MFI crossed above overbought level")
alertcondition(crossOs, "MFI oversold cross", "MFI crossed below oversold level")
alertcondition(ta.cross(mfi, 50), "MFI midline cross", "MFI crossed the 50 midpoint")
// -----------------------------------------------------------------------------
// Summary table
// -----------------------------------------------------------------------------
var table tab = table.new(position.top_right, 1, 3, bgcolor=color.new(color.black, 78))
if barstate.islast
table.cell(tab, 0, 0, "MFI: " + str.tostring(mfi, "#.##"), text_color=color.white)
table.cell(tab, 0, 1, "RSI: " + str.tostring(rsi, "#.##"), text_color=color.silver)
table.cell(tab, 0, 2, stateOb ? "Zone: OB" : stateOs ? "Zone: OS" : "Zone: Mid", text_color=color.silver)Maintenance Note: Low-liquidity symbols can print erratic MFI; consider filtering sessions or increasing length. If volume data is inconsistent on a feed, validate MFI against another data provider before relying on it live.
The Easy Way: Build with Pineify Visual Editor
What if you could create the same indicator without writing a single line of code? Pineify is a visual editor designed for TradingView users who want professional-grade indicators through an intuitive interface.
| Feature | Manual Pine Script | Pineify Visual Editor |
|---|---|---|
| Volume nuance | Hand-coded money flow sums are verbose and error-prone. | Keeps MFI logic encapsulated and easier to audit. |
| RSI comparison | Maintaining twin oscillators clutters small scripts. | Structured blocks simplify multi-indicator layouts. |
| Alert wiring | Multiple thresholds multiply alertcondition boilerplate. | Reusable alert templates reduce copy errors. |
| Visualization | Histograms, bands, and tables require manual tuning. | Accelerates polished, investor-ready charts. |
| Iteration | Tweaking lengths breaks inline comments and spacing easily. | Centralized parameters propagate safely. |
Build MFI systems with less repetitive Pine.
Combine MFI with RSI, VWAP, and MA filters visually.
Export consistent v5 for publishing and collaboration.
Scale to multi-condition alerts without losing clarity.
Step-by-Step Tutorial
Create oscillator pane
Set overlay=false for MFI beneath price.
Insert ta.mfi()
Choose source and length; default hlc3 aligns with textbook TP flows.
Draw 80/20 bands
Add hlines and optional fill to emphasize extremes.
Add histogram optional
Plot MFI-50 to visualize pressure asymmetry around the midpoint.
Compare with RSI
Plot RSI in data window or pane to study disagreements.
Publish alerts
Set alertconditions for OB/OS crosses and midline transitions.
Trading Strategies & Pro Tips
Flow-confirmed breakout
Buy resistance breaks when MFI crosses above 50 on rising volume and stays above 50 for two bars.
Pro Tip: Avoid thin prints; require minimum relative volume if available.
Mean reversion with structure
Fade extremes below 20 or above 80 only when price stalls at prior support/resistance.
MFI vs RSI disagreement
When RSI is bullish but MFI lags, wait for MFI to confirm before adding size—signals potential weak participation.
Trend hold filter
In uptrends, ignore short signals unless MFI also breaks below 40, signaling sustained distribution.
Common Mistakes to Avoid
- Treating 80/20 as automatic reversal points in strong trends.
- Ignoring bad or missing volume data that distorts money flow sums.
- Using tiny lengths on illiquid tickers, amplifying noise.
- Confusing MFI with accumulation/distribution indicators that use different formulas.
- Overfitting levels to one backtest without forward validation.
Frequently Asked Questions
Compose MFI indicators faster with Pineify
- Wire ta.mfi() with thresholds, histograms, and alerts efficiently.
- Export clean Pine v5 for TradingView publishing.
- Blend MFI with RSI and volume tools in structured flows.
Disclaimer: Trading involves significant risk. The indicators, code, and strategies provided are for educational purposes only and do not constitute financial advice. Past performance is not indicative of future results.