What it does
The dashboard puts the trend of five timeframes in one small table on your chart. For each timeframe it shows whether the trend is up, down or mixed, based on two moving averages, plus the current RSI. A score row adds them up so you can see at a glance whether the timeframes agree.
How it works
For each timeframe the script requests the last closed bar's close, fast EMA, slow EMA and RSI, using lookahead off and a one-bar offset. That means every value comes from a completed bar of that timeframe and will not change later.
- Up: close above the fast EMA and the fast EMA above the slow EMA.
- Down: close below the fast EMA and the fast EMA below the slow EMA.
- Mixed: anything else.
Each timeframe scores +1 for up, −1 for down and 0 for mixed. The score row shows the total from −5 to +5, and reads "All up" or "All down" when every timeframe agrees. The RSI cell is tinted green at 50 or above and red below 50.
The table is updated on the last bar only, so it adds very little load to the chart. Five data requests are used.
How to use it
- A common approach is to trade in the direction of the higher timeframes and use the lower ones for timing. For example, only look for long setups on M15 when H4 and D are both up.
- A score of +5 or −5 shows strong agreement. Scores near 0 suggest a range or a transition, where trend-following methods often struggle.
- RSI adds a momentum read. Uptrends with RSI well above 50 on several timeframes show strong momentum; an uptrend with RSI drifting below 50 may be weakening. See the RSI and moving averages lessons.
- Choose timeframes equal to or above your chart timeframe for the clearest results.
Alerts: all five timeframes turn up, all five turn down. They are checked on candle close.
Settings
| Input | Default | What it changes |
|---|---|---|
| Timeframe 1-5 | 15, 60, 240, D, W | Timeframes shown |
| Fast EMA | 20 | Fast trend average |
| Slow EMA | 50 | Slow trend average |
| RSI length | 14 | RSI period |
| Table position | Top right | Placement |
| Text size | Small | Table text size |
| Up / down / mixed colors | Teal / red / gray | Appearance only |
Limitations
Because only closed bars are used, the higher timeframes lag: the weekly row reflects last week's close until the new week ends. Historical bars can show values that are one higher-timeframe bar older than the live display, a side effect of avoiding lookahead. Choosing a timeframe lower than the chart timeframe gives less meaningful values. EMA trends lag at turning points, and agreement across timeframes does not guarantee the move will continue.
Pine Script source
In TradingView open Pine Editor, paste the code, click Save and then Add to chart.
FXP_MtfTrendDashboard.pine
98 lines
// © FXPIPS ONLINE — https://fxpips.online/tradingview-indicators/mtf-trend-dashboard/
// License: MIT. Educational tool — not financial advice. Trading involves substantial risk.
//@version=6
// Shows a table with the EMA trend direction and RSI on five configurable timeframes.
// Every request uses lookahead off and the previous (closed) bar of each timeframe, so
// values never change after the fact.
indicator("FXP MTF Trend Dashboard", shorttitle = "FXP MTF Trend", overlay = true)
// ── Inputs ─────────────────────────────────────────────────────────────
grpTf = "Timeframes"
tf1 = input.timeframe("15", "Timeframe 1", group = grpTf)
tf2 = input.timeframe("60", "Timeframe 2", group = grpTf)
tf3 = input.timeframe("240", "Timeframe 3", group = grpTf)
tf4 = input.timeframe("D", "Timeframe 4", group = grpTf)
tf5 = input.timeframe("W", "Timeframe 5", group = grpTf)
grpCalc = "Calculation"
fastLen = input.int(20, "Fast EMA", minval = 1, group = grpCalc)
slowLen = input.int(50, "Slow EMA", minval = 2, group = grpCalc)
rsiLen = input.int(14, "RSI length", minval = 2, group = grpCalc)
grpDisp = "Display"
posIn = input.string("Top right", "Table position", options = ["Top right", "Top left", "Bottom right", "Bottom left", "Middle right"], group = grpDisp)
sizeIn = input.string("Small", "Text size", options = ["Tiny", "Small", "Normal"], group = grpDisp)
colUp = input.color(#26a69a, "Up", group = grpDisp)
colDn = input.color(#ef5350, "Down", group = grpDisp)
colMix = input.color(color.gray, "Mixed", group = grpDisp)
// ── Data (closed bars only) ────────────────────────────────────────────
getTf(string tf) =>
[ef, es, r, c] = request.security(syminfo.tickerid, tf, [ta.ema(close, fastLen)[1], ta.ema(close, slowLen)[1], ta.rsi(close, rsiLen)[1], close[1]], lookahead = barmerge.lookahead_off)
int dir = na(es) ? 0 : c > ef and ef > es ? 1 : c < ef and ef < es ? -1 : 0
[dir, r]
[d1, r1] = getTf(tf1)
[d2, r2] = getTf(tf2)
[d3, r3] = getTf(tf3)
[d4, r4] = getTf(tf4)
[d5, r5] = getTf(tf5)
int score = d1 + d2 + d3 + d4 + d5
// ── Table ──────────────────────────────────────────────────────────────
tablePos = switch posIn
"Top left" => position.top_left
"Bottom right" => position.bottom_right
"Bottom left" => position.bottom_left
"Middle right" => position.middle_right
=> position.top_right
txtSize = switch sizeIn
"Tiny" => size.tiny
"Normal" => size.normal
=> size.small
tfLabel(string tf) =>
string t = tf == "" ? timeframe.period : tf
int secs = timeframe.in_seconds(t)
secs < 3600 ? str.tostring(secs / 60) + "m" : secs < 86400 ? str.tostring(secs / 3600) + "H" : t
dirText(int d) =>
d == 1 ? "Up" : d == -1 ? "Down" : "Mixed"
dirColor(int d) =>
d == 1 ? colUp : d == -1 ? colDn : colMix
rsiColor(float r) =>
na(r) ? colMix : r >= 50 ? color.new(colUp, 30) : color.new(colDn, 30)
var table dash = table.new(tablePos, 3, 7, bgcolor = color.new(color.black, 20), frame_color = color.new(color.gray, 60), frame_width = 1, border_color = color.new(color.gray, 80), border_width = 1)
fillRow(int row, string tf, int d, float r) =>
table.cell(dash, 0, row, tfLabel(tf), text_color = color.white, text_size = txtSize)
table.cell(dash, 1, row, dirText(d), text_color = color.white, bgcolor = color.new(dirColor(d), 40), text_size = txtSize)
table.cell(dash, 2, row, na(r) ? "-" : str.tostring(r, "#.0"), text_color = color.white, bgcolor = rsiColor(r), text_size = txtSize)
true
if barstate.islast
table.cell(dash, 0, 0, "TF", text_color = color.gray, text_size = txtSize)
table.cell(dash, 1, 0, "Trend", text_color = color.gray, text_size = txtSize)
table.cell(dash, 2, 0, "RSI", text_color = color.gray, text_size = txtSize)
fillRow(1, tf1, d1, r1)
fillRow(2, tf2, d2, r2)
fillRow(3, tf3, d3, r3)
fillRow(4, tf4, d4, r4)
fillRow(5, tf5, d5, r5)
int scoreDir = score == 5 ? 1 : score == -5 ? -1 : 0
table.cell(dash, 0, 6, "Score", text_color = color.white, text_size = txtSize)
table.cell(dash, 1, 6, (score > 0 ? "+" : "") + str.tostring(score) + " / 5", text_color = color.white, bgcolor = color.new(dirColor(scoreDir), 40), text_size = txtSize)
table.cell(dash, 2, 6, scoreDir == 1 ? "All up" : scoreDir == -1 ? "All down" : "", text_color = color.white, text_size = txtSize)
// ── Alerts ─────────────────────────────────────────────────────────────
bool allUp = barstate.isconfirmed and score == 5 and score[1] != 5
bool allDown = barstate.isconfirmed and score == -5 and score[1] != -5
alertcondition(allUp, "All timeframes up", "{{ticker}}: all five timeframes are in an up trend")
alertcondition(allDown, "All timeframes down", "{{ticker}}: all five timeframes are in a down trend")

