market-monitor-15min.pine
Copy
// ============================================================================
// Market Monitor — 15-Minute Optimized
// Timeframe: 15-minute (primary)
// Version: 1.0.0
// Author: Chris Thomas
// ============================================================================
// Directional-bias overlay purpose-built for 15-minute chart monitoring in
// multi-chart watchlist workflows. Serves as the reconnaissance bridge
// between the 15m 0DTE Scalper and the 15m Trend Compass.
//
// Enhancements tuned for the 26-bar RTH session:
// - Weighted bias scoring (structural 2x, confirmation 1x)
// - 1H Anchor EMA for immediate higher-timeframe institutional alignment
// - Session phase detection (Open Drive / Morning / Midday / Power Hour / Close)
// - NYSE TICK Index integration for market breadth
// - TTM Squeeze detection (20-bar default)
// - ATR regime classification with 260-bar lookback (~10 trading days)
// - 30-minute Opening Range (first 2 bars of the session)
// ============================================================================
// ============================================================================
// TradingView Pine Script v6
// ============================================================================
//@version=6
indicator(
title = "Market Monitor - 15m",
shorttitle = "MM-15m",
overlay = true,
max_labels_count = 100,
max_lines_count = 200,
max_boxes_count = 50,
max_bars_back = 5000
)
// ============================================================================
// CONSTANTS & COLORS
// ============================================================================
var string GP_EMA = "EMA Ribbon"
var string GP_VWAP = "VWAP & Levels"
var string GP_HTF = "Higher Timeframe"
var string GP_MOM = "Momentum & Volatility"
var string GP_SCORE = "Scoring Engine"
var string GP_DASH = "Dashboard"
var color C_BULL = #00E676
var color C_BEAR = #FF1744
var color C_NEUTRAL = #B0BEC5
var color C_YELLOW = #FFD600
var color C_ORANGE = #FF9100
var color C_CYAN = #00BCD4
var color C_PURPLE = #7C4DFF
var color C_DASH_BG = color.new(#1A1A2E, 5)
var color C_DASH_TEXT = #E0E0E0
var color C_DASH_HEADER = #7C4DFF
var color C_CLOUD_BULL = color.new(#00E676, 85)
var color C_CLOUD_BEAR = color.new(#FF1744, 85)
var color C_CLOUD_MIX = color.new(#FFD600, 90)
var color C_BG_BULL = color.new(#00E676, 95)
var color C_BG_BEAR = color.new(#FF1744, 95)
// ============================================================================
// INPUTS
// ============================================================================
// --- EMA Ribbon ---
int i_emaFastLen = input.int(9, "Fast EMA", minval=2, group=GP_EMA)
int i_emaMidLen = input.int(21, "Mid EMA", minval=2, group=GP_EMA)
int i_emaSlowLen = input.int(50, "Slow EMA", minval=2, group=GP_EMA)
bool i_showCloud = input.bool(true, "Show EMA Cloud", group=GP_EMA)
// --- VWAP & Levels ---
bool i_useVwap = input.bool(true, "Use Session VWAP", group=GP_VWAP)
float i_vwapMult = input.float(1.5, "VWAP Band Multiplier", minval=0.5, step=0.1, group=GP_VWAP)
bool i_showPrior = input.bool(true, "Show Prior Day Levels", group=GP_VWAP)
// --- Higher Timeframe ---
bool i_useHTF = input.bool(true, "Use 1H Anchor EMA", group=GP_HTF)
int i_htfEmaLen = input.int(50, "1H Anchor EMA Length", minval=10, group=GP_HTF)
// --- Momentum & Volatility ---
int i_rsiLen = input.int(14, "RSI Length", minval=2, group=GP_MOM)
int i_adxLen = input.int(14, "ADX Length", minval=2, group=GP_MOM)
int i_volSmaLen = input.int(20, "Volume SMA", minval=5, group=GP_MOM)
int i_atrPctlLB = input.int(260, "ATR Pctl Lookback", minval=50, group=GP_MOM, tooltip="260 bars = 10 days on 15m")
bool i_useTick = input.bool(true, "Use NYSE TICK", group=GP_MOM)
// --- Scoring Engine ---
bool i_useWeighted = input.bool(true, "Use Weighted Scoring (2x/1x)", group=GP_SCORE)
bool i_bgTint = input.bool(true, "Color Background on High Score", group=GP_SCORE)
// --- Dashboard ---
bool i_showDash = input.bool(true, "Show Dashboard", group=GP_DASH)
string i_dashPos = input.string("Bottom Right", "Position", options=["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group=GP_DASH)
string i_dashSize = input.string("Small", "Size", options=["Tiny", "Small", "Normal", "Large"], group=GP_DASH)
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
f1(float v) => str.tostring(v, "#.#")
f2(float v) => str.tostring(v, "#.##")
// ============================================================================
// TIME & SESSION PHASE
// ============================================================================
bool isRTH = time(timeframe.period, "0930-1600:1234567") != 0
var int sessionBar = 0
if isRTH and not isRTH[1]
sessionBar := 1
else if isRTH
sessionBar += 1
string sessionPhase = "PRE/POST"
if isRTH
if sessionBar <= 2
sessionPhase := "OPEN DRIVE"
else if sessionBar <= 8
sessionPhase := "MORNING"
else if sessionBar <= 18
sessionPhase := "MIDDAY"
else if sessionBar <= 25
sessionPhase := "POWER HOUR"
else
sessionPhase := "CLOSE"
// ============================================================================
// PRICE & EMA STRUCTURE
// ============================================================================
float emaFast = ta.ema(close, i_emaFastLen)
float emaMid = ta.ema(close, i_emaMidLen)
float emaSlow = ta.ema(close, i_emaSlowLen)
bool emaBull = emaFast > emaMid and emaMid > emaSlow
bool emaBear = emaFast < emaMid and emaMid < emaSlow
string emaStr = emaBull ? "FULL BULL" : emaBear ? "FULL BEAR" : emaFast > emaSlow ? "MIXED BULL" : "MIXED BEAR"
color emaClr = emaBull ? C_BULL : emaBear ? C_BEAR : C_YELLOW
// ============================================================================
// VWAP & ORB
// ============================================================================
var float vwapValue = na
var float vwapUpper = na
var float vwapLower = na
if i_useVwap and timeframe.isintraday
[v, u, l] = ta.vwap(close, i_vwapMult)
vwapValue := v
vwapUpper := u
vwapLower := l
float priceRef = i_useVwap and timeframe.isintraday ? vwapValue : emaSlow
string vwapPos = close > priceRef ? (close > vwapUpper ? "ABOVE BAND" : "ABOVE VWAP") : close < priceRef ? (close < vwapLower ? "BELOW BAND" : "BELOW VWAP") : "ON VWAP"
color vwapClr = close > vwapUpper ? C_ORANGE : close > priceRef ? C_BULL : close < vwapLower ? C_ORANGE : close < priceRef ? C_BEAR : C_NEUTRAL
// Opening Range (First 2 bars = 30 mins)
var float orHigh = na
var float orLow = na
if isRTH and not isRTH[1]
orHigh := high
orLow := low
else if isRTH and sessionBar == 2
orHigh := math.max(orHigh, high)
orLow := math.min(orLow, low)
// ============================================================================
// HIGHER TIMEFRAME (1H ANCHOR)
// ============================================================================
float htfEma = request.security(syminfo.tickerid, "60", ta.ema(close, i_htfEmaLen), lookahead=barmerge.lookahead_off)
string htfStr = close > htfEma ? "BULLISH" : close < htfEma ? "BEARISH" : "NEUTRAL"
color htfClr = close > htfEma ? C_BULL : close < htfEma ? C_BEAR : C_NEUTRAL
// ============================================================================
// MOMENTUM, VOLUME & SQUEEZE
// ============================================================================
float rsiVal = ta.rsi(close, i_rsiLen)
string rsiStr = rsiVal > 65 ? "OVERBOUGHT" : rsiVal > 55 ? "BULLISH" : rsiVal < 35 ? "OVERSOLD" : rsiVal < 45 ? "BEARISH" : "NEUTRAL"
color rsiClr = rsiVal > 65 ? C_ORANGE : rsiVal > 55 ? C_BULL : rsiVal < 35 ? C_ORANGE : rsiVal < 45 ? C_BEAR : C_NEUTRAL
[diPlus, diMinus, adxVal] = ta.dmi(i_adxLen, i_adxLen)
bool adxTrend = adxVal > 20
string dmiStr = adxTrend ? (diPlus > diMinus ? "BULL TREND" : "BEAR TREND") : "NO TREND"
color dmiClr = adxTrend ? (diPlus > diMinus ? C_BULL : C_BEAR) : C_NEUTRAL
float avgVol = ta.sma(volume, i_volSmaLen)
float relVol = avgVol != 0 ? volume / avgVol : 0.0
string volStr = relVol > 2.0 ? "SPIKE" : relVol > 1.2 ? "HIGH" : relVol > 0.8 ? "NORMAL" : "LOW"
color volClr = relVol > 2.0 ? C_ORANGE : relVol > 1.2 ? C_BULL : relVol > 0.8 ? C_NEUTRAL : C_YELLOW
// TTM Squeeze Logic (20, 2.0 BB, 1.5 KC)
float sqzLength = 20
[bbMid, bbUp, bbLow] = ta.bb(close, sqzLength, 2.0)
[kcMid, kcUp, kcLow] = ta.kc(close, sqzLength, 1.5)
bool squeezeOn = (bbUp < kcUp) and (bbLow > kcLow)
bool sqzFired = squeezeOn[1] and not squeezeOn
float sqzMom = ta.linreg(close - math.avg(math.avg(ta.highest(high, 20), ta.lowest(low, 20)), ta.sma(close, 20)), 20, 0)
string sqzStr = squeezeOn ? "COMPRESSED" : sqzFired ? "FIRED" : "EXPANDED"
color sqzClr = squeezeOn ? C_YELLOW : sqzFired ? (sqzMom > 0 ? C_BULL : C_BEAR) : C_NEUTRAL
// ATR Regime
float atrVal = ta.atr(14)
int atrRank = 0
for i = 1 to i_atrPctlLB
if atrVal > nz(atrVal[i])
atrRank += 1
float atrPctl = i_atrPctlLB > 0 ? atrRank / i_atrPctlLB * 100 : 50.0
string atrRegime = atrPctl >= 90 ? "EXTREME" : atrPctl >= 70 ? "HIGH" : atrPctl >= 30 ? "NORMAL" : "LOW"
color atrClr = atrPctl >= 90 ? C_ORANGE : atrPctl >= 70 ? C_YELLOW : atrPctl >= 30 ? C_NEUTRAL : C_CYAN
// NYSE TICK
float tickValue = i_useTick ? request.security("USI:TICK", timeframe.period, close) : na
string tickStr = na(tickValue) ? "N/A" : tickValue > 800 ? "EXT BULL" : tickValue > 400 ? "BULLISH" : tickValue < -800 ? "EXT BEAR" : tickValue < -400 ? "BEARISH" : "NEUTRAL"
color tickClr = na(tickValue) ? C_NEUTRAL : tickValue > 800 ? C_BULL : tickValue > 400 ? color.new(C_BULL, 40) : tickValue < -800 ? C_BEAR : tickValue < -400 ? color.new(C_BEAR, 40) : C_NEUTRAL
// ============================================================================
// SCORING ENGINE
// ============================================================================
int wStruct = i_useWeighted ? 2 : 1
int wConfirm = 1
int bias = 0
// Structural (Max +6 / -6)
bias += emaBull ? wStruct : emaBear ? -wStruct : 0
bias += close > priceRef ? wStruct : close < priceRef ? -wStruct : 0
if i_useHTF and not na(htfEma)
bias += close > htfEma ? wStruct : close < htfEma ? -wStruct : 0
// Confirmation (Max +5 / -5)
bias += rsiVal > 55 ? wConfirm : rsiVal < 45 ? -wConfirm : 0
if adxTrend
bias += diPlus > diMinus ? wConfirm : diMinus > diPlus ? -wConfirm : 0
bias += sqzMom > 0 ? wConfirm : sqzMom < 0 ? -wConfirm : 0
bias += close > emaFast ? wConfirm : close < emaFast ? -wConfirm : 0
if i_useTick and not na(tickValue)
bias += tickValue > 200 ? wConfirm : tickValue < -200 ? -wConfirm : 0
int maxBias = (3 * wStruct) + (5 * wConfirm)
float biasAvg = ta.sma(bias, 3)
string biasTrend = bias > biasAvg + 0.5 ? "IMPROVING" : bias < biasAvg - 0.5 ? "FADING" : "STABLE"
color biasClr = bias > 0 ? (bias >= (maxBias*0.6) ? C_BULL : color.new(C_BULL, 30)) : bias < 0 ? (bias <= -(maxBias*0.6) ? C_BEAR : color.new(C_BEAR, 30)) : C_NEUTRAL
if i_bgTint
bgcolor(bias >= (maxBias*0.7) ? C_BG_BULL : bias <= -(maxBias*0.7) ? C_BG_BEAR : na, title="Bias Background")
// ============================================================================
// DASHBOARD
// ============================================================================
if i_showDash and barstate.islast
string tblPos = switch i_dashPos
"Top Right" => position.top_right
"Top Left" => position.top_left
"Bottom Right" => position.bottom_right
"Bottom Left" => position.bottom_left
=> position.top_right
string cellSize = switch i_dashSize
"Tiny" => size.tiny
"Small" => size.small
"Normal" => size.normal
"Large" => size.large
=> size.small
var table dash = table.new(tblPos, 3, 19,
bgcolor = C_DASH_BG,
border_width = 0,
border_color = color.new(#FFFFFF, 85),
frame_width = 1,
frame_color = color.new(#FFFFFF, 80))
// === Row 0: Header ===
table.cell(dash, 0, 0, " Market Monitor - 15m ", text_color=#FFFFFF,
bgcolor=C_DASH_HEADER, text_size=cellSize, text_halign=text.align_center)
table.cell(dash, 1, 0, "", bgcolor=C_DASH_HEADER, text_size=cellSize)
table.cell(dash, 2, 0, "", bgcolor=C_DASH_HEADER, text_size=cellSize)
table.merge_cells(dash, 0, 0, 2, 0)
table.cell(dash, 0, 1, "Phase", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 1, sessionPhase, text_color=C_WHITE, text_size=cellSize, text_halign=text.align_right)
string biasValStr = (bias > 0 ? "+" : "") + str.tostring(bias)
table.cell(dash, 0, 2, "Bias Score", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 2, biasValStr, text_color=biasClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 2, biasTrend, text_color=biasClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 0, 3, "EMA Array", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 3, emaStr, text_color=emaClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 0, 4, "VWAP", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 4, vwapPos, text_color=vwapClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 0, 5, "1H Anchor", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 5, htfStr, text_color=htfClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 0, 6, "Squeeze", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 6, sqzStr, text_color=sqzClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 0, 7, "TICK", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 7, tickStr, text_color=tickClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 0, 8, "RSI", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 8, f1(rsiVal), text_color=rsiClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 0, 9, "DMI", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 9, dmiStr, text_color=dmiClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 0, 10, "Rel Vol", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 10, f2(relVol) + "x", text_color=volClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 0, 11, "ATR State", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 11, atrRegime, text_color=atrClr, text_size=cellSize, text_halign=text.align_right)
// ============================================================================
// PLOTS
// ============================================================================
plot(i_showCloud ? emaFast : na, "Fast EMA", color=C_BULL, linewidth=1)
plot(i_showCloud ? emaMid : na, "Mid EMA", color=C_YELLOW, linewidth=1)
plot(i_showCloud ? emaSlow : na, "Slow EMA", color=C_BEAR, linewidth=1)
p1 = plot(i_showCloud ? emaFast : na, color=color(na))
p2 = plot(i_showCloud ? emaSlow : na, color=color(na))
fill(p1, p2, color=emaBull ? C_CLOUD_BULL : emaBear ? C_CLOUD_BEAR : C_CLOUD_MIX)
plot(i_useVwap ? vwapValue : na, "VWAP", color=C_PURPLE, linewidth=2)
plot(i_useVwap ? vwapUpper : na, "VWAP Upper", color=color.new(C_PURPLE, 50), style=plot.style_circles)
plot(i_useVwap ? vwapLower : na, "VWAP Lower", color=color.new(C_PURPLE, 50), style=plot.style_circles)
// ============================================================================
// ALERTS
// ============================================================================
int strongThresh = i_useWeighted ? 7 : 5
alertcondition(bias >= strongThresh, "Strong Bullish Bias", "{{ticker}} bias score >= +7 on {{interval}}")
alertcondition(bias <= -strongThresh, "Strong Bearish Bias", "{{ticker}} bias score <= -7 on {{interval}}")
alertcondition(ta.cross(bias, 0), "Bias Crossed Neutral", "{{ticker}} bias crossed zero on {{interval}}")
alertcondition(relVol > 2.0, "Volume Spike (>2x)", "{{ticker}} relative volume > 2x on {{interval}}")
alertcondition(sqzFired, "Squeeze Fired", "{{ticker}} TTM Squeeze fired on {{interval}}")
Show all 334 lines