//@version=5 indicator("Bullmania Money Line v7 (SuperTrend + RSI 15m)", overlay=true) // Calculation source (Chart vs Heikin Ashi) for Money Line srcMode = input.string("Chart", "Calculation source", options=["Chart","Heikin Ashi"], tooltip="Use regular chart candles or Heikin Ashi for the line calculation.") // Parameter Mode paramMode = input.string("Profiles by timeframe", "Parameter Mode", options=["Single", "Profiles by timeframe"], tooltip="Choose whether to use one global set of parameters or timeframe-specific profiles.") // Single (global) parameters atrPeriodSingle = input.int(10, "ATR Period (Single mode)", minval=1, group="Single Mode") multiplierSingle = input.float(3.0, "Multiplier (Single mode)", minval=0.1, step=0.1, group="Single Mode") // Profile override when using profiles profileOverride = input.string("Auto", "Profile Override", options=["Auto", "Minutes", "Hours", "Daily", "Weekly/Monthly"], tooltip="When in 'Profiles by timeframe' mode, choose a fixed profile or let it auto-detect from the chart timeframe.", group="Profiles") // Timeframe profile parameters (defaults tuned from v5) // Minutes (<= 59m) atr_m = input.int(12, "ATR Period (Minutes)", minval=1, group="Profiles — Minutes") mult_m = input.float(3.3, "Multiplier (Minutes)", minval=0.1, step=0.1, group="Profiles — Minutes") // Hours (>=1h and <1d) atr_h = input.int(10, "ATR Period (Hours)", minval=1, group="Profiles — Hours") mult_h = input.float(3.0, "Multiplier (Hours)", minval=0.1, step=0.1, group="Profiles — Hours") // Daily (>=1d and <1w) atr_d = input.int(10, "ATR Period (Daily)", minval=1, group="Profiles — Daily") mult_d = input.float(2.8, "Multiplier (Daily)", minval=0.1, step=0.1, group="Profiles — Daily") // Weekly/Monthly (>=1w) atr_w = input.int(7, "ATR Period (Weekly/Monthly)", minval=1, group="Profiles — Weekly/Monthly") mult_w = input.float(2.5, "Multiplier (Weekly/Monthly)", minval=0.1, step=0.1, group="Profiles — Weekly/Monthly") // Optional MACD confirmation (same as v5) useMacd = input.bool(false, "Use MACD confirmation", inline="macd") macdSrc = input.source(close, "MACD Source", inline="macd") macdFastLen = input.int(12, "Fast", minval=1, inline="macdLens") macdSlowLen = input.int(26, "Slow", minval=1, inline="macdLens") macdSigLen = input.int(9, "Signal", minval=1, inline="macdLens") // RSI gate (designed for 15m by default) useRsi = input.bool(true, "Use RSI gate", group="RSI Gate", tooltip="Gate signals with RSI conditions from a selectable timeframe.") rsiTf = input.timeframe("15", "RSI timeframe", group="RSI Gate") rsiLen = input.int(14, "RSI Length", minval=1, group="RSI Gate") rsiOverbought = input.int(70, "RSI Overbought", minval=1, maxval=100, group="RSI Gate") rsiOversold = input.int(30, "RSI Oversold", minval=1, maxval=100, group="RSI Gate") rsiGateMode = input.string("Cross within lookback", "RSI gate mode", options=["In zone now","Cross within lookback","In zone within lookback"], group="RSI Gate", tooltip="How RSI should validate: require being in OB/OS now, a recent cross out of the zone, or having touched the zone within a lookback window.") rsiLookback = input.int(3, "RSI lookback bars", minval=0, maxval=20, group="RSI Gate", tooltip="Bars to look back for a cross/touch. 0 = only this bar.") // Entry filters (optional, same behavior as v5) groupFilters = "Entry filters" useEntryBuffer = input.bool(false, "Require entry buffer (ATR)", group=groupFilters, tooltip="Close must be beyond the Money Line by this buffer to avoid wick flips.") entryBufferATR = input.float(0.15, "Buffer size (in ATR)", minval=0.0, step=0.05, group=groupFilters, tooltip="0.10–0.20 works well on intraday.") confirmBars = input.int(0, "Bars to confirm after flip", minval=0, maxval=2, group=groupFilters, tooltip="0 = show on flip bar. 1 = wait one bar.") useAdx = input.bool(false, "Use ADX trend-strength filter", group=groupFilters, tooltip="Require ADX above a threshold to reduce chop.") adxLen = input.int(14, "ADX Length", minval=1, group=groupFilters) adxMin = input.int(20, "ADX minimum", minval=0, maxval=100, group=groupFilters) // Determine effective parameters based on selected mode/profile var string activeProfile = "" resSec = timeframe.in_seconds(timeframe.period) isMinutes = resSec < 3600 isHours = resSec >= 3600 and resSec < 86400 isDaily = resSec >= 86400 and resSec < 604800 isWeeklyOrMore = resSec >= 604800 string profileBucket = "Single" if paramMode == "Single" profileBucket := "Single" else if profileOverride == "Minutes" profileBucket := "Minutes" else if profileOverride == "Hours" profileBucket := "Hours" else if profileOverride == "Daily" profileBucket := "Daily" else if profileOverride == "Weekly/Monthly" profileBucket := "Weekly/Monthly" else profileBucket := isMinutes ? "Minutes" : isHours ? "Hours" : isDaily ? "Daily" : "Weekly/Monthly" atrPeriod = profileBucket == "Single" ? atrPeriodSingle : profileBucket == "Minutes" ? atr_m : profileBucket == "Hours" ? atr_h : profileBucket == "Daily" ? atr_d : atr_w multiplier = profileBucket == "Single" ? multiplierSingle : profileBucket == "Minutes" ? mult_m : profileBucket == "Hours" ? mult_h : profileBucket == "Daily" ? mult_d : mult_w activeProfile := profileBucket // Build selected source OHLC (for Money Line) haTicker = ticker.heikinashi(syminfo.tickerid) [haO, haH, haL, haC] = request.security(haTicker, timeframe.period, [open, high, low, close], lookahead=barmerge.lookahead_off) calcH = srcMode == "Heikin Ashi" ? haH : high calcL = srcMode == "Heikin Ashi" ? haL : low calcC = srcMode == "Heikin Ashi" ? haC : close // ATR on selected source (true range + RMA) tr = math.max(calcH - calcL, math.max(math.abs(calcH - calcC[1]), math.abs(calcL - calcC[1]))) atr = ta.rma(tr, atrPeriod) srcMid = (calcH + calcL) / 2 up = srcMid - (multiplier * atr) dn = srcMid + (multiplier * atr) var float up1 = na var float dn1 = na up1 := nz(up1[1], up) dn1 := nz(dn1[1], dn) up1 := calcC[1] > up1 ? math.max(up, up1) : up dn1 := calcC[1] < dn1 ? math.min(dn, dn1) : dn var int trend = 1 var float tsl = na tsl := nz(tsl[1], up1) if trend == 1 tsl := math.max(up1, tsl) trend := calcC < tsl ? -1 : 1 else tsl := math.min(dn1, tsl) trend := calcC > tsl ? 1 : -1 supertrend = tsl // Plot the Money Line upTrend = trend == 1 ? supertrend : na downTrend = trend == -1 ? supertrend : na plot(upTrend, "Up Trend", color=color.new(color.green, 0), style=plot.style_linebr, linewidth=2) plot(downTrend, "Down Trend", color=color.new(color.red, 0), style=plot.style_linebr, linewidth=2) // Show active profile/RSI TF showProfileLabel = input.bool(true, "Show active profile label", group="Profiles") var label profLbl = na if barstate.islast and showProfileLabel if not na(profLbl) label.delete(profLbl) profLbl := label.new(bar_index, calcC, text="Profile: " + activeProfile + " | ATR=" + str.tostring(atrPeriod) + " Mult=" + str.tostring(multiplier) + " | RSI TF=" + rsiTf, yloc=yloc.price, style=label.style_label_upper_left, textcolor=color.white, color=color.new(color.blue, 20)) // RSI from selected timeframe (use regular close for RSI by default) rsiTfVal = request.security(syminfo.tickerid, rsiTf, ta.rsi(close, rsiLen), lookahead=barmerge.lookahead_off) // Build RSI conditions per selected mode rsiLongNow = rsiTfVal <= rsiOversold rsiShortNow = rsiTfVal >= rsiOverbought rsiCrossUp = ta.crossover(rsiTfVal, rsiOversold) // crossed up from below Oversold rsiCrossDown = ta.crossunder(rsiTfVal, rsiOverbought) // crossed down from above Overbought rsiLongRecent = rsiLookback == 0 ? rsiLongNow : (ta.lowest(rsiTfVal, rsiLookback + 1) <= rsiOversold) rsiShortRecent = rsiLookback == 0 ? rsiShortNow : (ta.highest(rsiTfVal, rsiLookback + 1) >= rsiOverbought) rsiCrossUpRecent = rsiLookback == 0 ? rsiCrossUp : (ta.barssince(rsiCrossUp) <= rsiLookback) rsiCrossDownRecent = rsiLookback == 0 ? rsiCrossDown : (ta.barssince(rsiCrossDown) <= rsiLookback) longRsiOk = not useRsi or (rsiGateMode == "In zone now" ? rsiLongNow : rsiGateMode == "Cross within lookback" ? rsiCrossUpRecent : rsiLongRecent) shortRsiOk = not useRsi or (rsiGateMode == "In zone now" ? rsiShortNow : rsiGateMode == "Cross within lookback" ? rsiCrossDownRecent : rsiShortRecent) // MACD confirmation logic [macdLine, macdSignal, macdHist] = ta.macd(macdSrc, macdFastLen, macdSlowLen, macdSigLen) longMacdOk = not useMacd or (macdLine > macdSignal) shortMacdOk = not useMacd or (macdLine < macdSignal) // Combined confirmations longOk = longRsiOk and longMacdOk shortOk = shortRsiOk and shortMacdOk // Flip detection buyFlip = trend == 1 and trend[1] == -1 sellFlip = trend == -1 and trend[1] == 1 // ADX (manual) on selected OHLC if enabled upMove = calcH - calcH[1] downMove = calcL[1] - calcL plusDM = (upMove > downMove and upMove > 0) ? upMove : 0.0 minusDM = (downMove > upMove and downMove > 0) ? downMove : 0.0 trADX = math.max(calcH - calcL, math.max(math.abs(calcH - calcC[1]), math.abs(calcL - calcC[1]))) atrADX = ta.rma(trADX, adxLen) plusDI = atrADX == 0.0 ? 0.0 : 100.0 * ta.rma(plusDM, adxLen) / atrADX minusDI = atrADX == 0.0 ? 0.0 : 100.0 * ta.rma(minusDM, adxLen) / atrADX dx = (plusDI + minusDI == 0.0) ? 0.0 : 100.0 * math.abs(plusDI - minusDI) / (plusDI + minusDI) adxVal = ta.rma(dx, adxLen) adxOk = not useAdx or (adxVal > adxMin) // Entry buffer and confirmation longBufferOk = not useEntryBuffer or (calcC > supertrend + entryBufferATR * atr) shortBufferOk = not useEntryBuffer or (calcC < supertrend - entryBufferATR * atr) buyReady = ta.barssince(buyFlip) == confirmBars sellReady = ta.barssince(sellFlip) == confirmBars // Final signals finalLongSignal = buyReady and longOk and adxOk and longBufferOk finalShortSignal = sellReady and shortOk and adxOk and shortBufferOk plotshape(finalLongSignal, title="Buy Signal", location=location.belowbar, color=color.green, style=shape.circle, size=size.small) plotshape(finalShortSignal, title="Sell Signal", location=location.abovebar, color=color.red, style=shape.circle, size=size.small) // Fill area between price and Money Line fill(plot(calcC, display=display.none), plot(upTrend, display=display.none), color=color.new(color.green, 90)) fill(plot(calcC, display=display.none), plot(downTrend, display=display.none), color=color.new(color.red, 90)) // Alerts (set to Once per bar close) — message must be a const string alertcondition(finalLongSignal, title="Bullmania v7 Buy", message="Bullmania v7 Buy | RSI gate") alertcondition(finalShortSignal, title="Bullmania v7 Sell", message="Bullmania v7 Sell | RSI gate") // Optional dynamic alert text for the "Any alert() function call" alert type useDynAlert = input.bool(true, "Dynamic alert text (Any alert() call)", group="Alerts", tooltip="Enable runtime alert() with detailed message. Use Alerts: 'Any alert() function call' and Once per bar close.") if barstate.isconfirmed and useDynAlert if finalLongSignal dynMsgL = "Buy " + syminfo.ticker + " " + timeframe.period + " | Profile=" + activeProfile + " ATR=" + str.tostring(atrPeriod) + " Mult=" + str.tostring(multiplier) + " | RSI TF=" + rsiTf + " <= " + str.tostring(rsiOversold) alert(dynMsgL, alert.freq_once_per_bar_close) if finalShortSignal dynMsgS = "Sell " + syminfo.ticker + " " + timeframe.period + " | Profile=" + activeProfile + " ATR=" + str.tostring(atrPeriod) + " Mult=" + str.tostring(multiplier) + " | RSI TF=" + rsiTf + " >= " + str.tostring(rsiOverbought) alert(dynMsgS, alert.freq_once_per_bar_close)