//@version=6 indicator("Bullmania Money Line v4+ multi time frame profiles (optional filters)", overlay=true) // ============================= // Base inputs (match v4 defaults) // ============================= atrPeriod = input.int(10, "ATR Period", minval=1) multiplier = input.float(3.0, "Multiplier", minval=0.1, step=0.1) confirmBars = input.int(1, "Flip confirmation bars", minval=1, maxval=5, tooltip="Require this many consecutive closes beyond the Money Line before flipping trend. 1 = v4 behavior.") bufferATR = input.float(0.0, "Flip buffer (×ATR)", minval=0.0, step=0.05, tooltip="Extra distance beyond the Money Line required to flip. Example: 0.3 means close must cross by 0.3 × ATR.") // Optional smoothing (applied to src only; ATR remains unchanged) smoothLen = input.int(0, "Smoothing EMA Length (0 = off)", minval=0) // Price source selection (independent of chart type) srcMode = input.string("Chart", "Calculation source", options=["Chart","Heikin Ashi"]) // ============================= // Optional filters (all default off to preserve v4 behavior) // ============================= useAdxFilt = input.bool(false, "Use ADX/DMI filter (gates labels/alerts)") adxLength = input.int(14, "ADX Length", minval=1) minAdx = input.float(20.0, "Min ADX", minval=1.0, step=0.5) useMTFConf = input.bool(false, "Use higher timeframe confirmation") higherTF = input.timeframe("", "Higher timeframe (e.g. 60, 1D)") sessionStr = input.session("0000-2400", "Active session (labels/alerts only)") cooldownBars= input.int(0, "Cooldown bars after flip", minval=0) alertsCloseOnly = input.bool(true, "Alerts/labels on bar close only") useTrendMA = input.bool(false, "Use EMA trend filter") trendMALen = input.int(200, "EMA length", minval=1) // Anti-chop filters (optional) useChopFilt = input.bool(false, "Use Choppiness filter (gates)") chopLength = input.int(14, "Choppiness Length", minval=2) maxChop = input.float(55.0, "Max CHOP (allow if <=)", minval=0.0, maxval=100.0, step=0.5, tooltip="Lower = stronger trend; typical thresholds 38-61") useRetest = input.bool(false, "Require retest after flip") retestWindow = input.int(3, "Retest window (bars)", minval=1, maxval=20) retestTolATR = input.float(0.20, "Retest tolerance (×ATR)", minval=0.0, step=0.05) useBodyFilter = input.bool(false, "Min candle body fraction") minBodyFrac = input.float(0.35, "Min body as fraction of range", minval=0.0, maxval=1.0, step=0.05) showLabels = input.bool(true, "Show flip labels") showCircles = input.bool(true, "Show flip circles (v4 style)") showFill = input.bool(true, "Shade price-to-line area") gateCircles = input.bool(false, "Gate flip circles with filters") showGatedMarkers = input.bool(true, "Show gated markers (triangles)") // Flip level line (persistent) visuals showFlipLevelLine = input.bool(true, "Show Flip Level line") flipLineAutoColor = input.bool(true, "Flip line color by trend") flipLineCustomCol = input.color(color.new(color.silver, 0), "Flip line custom color") showFlipLevelValue = input.bool(true, "Show Flip Level value label") flipLineWidth = input.int(3, "Flip line width", minval=1, maxval=6) nextFlipLineWidth = input.int(2, "Next flip line width", minval=1, maxval=6) labelSizeOpt = input.string("small", "Value label size", options=["tiny","small","normal"]) // Preview of the next flip thresholds showNextBullFlip = input.bool(true, "Show Next Bull Flip level") showNextBearFlip = input.bool(false, "Show Next Bear Flip level") showNextFlipValue = input.bool(true, "Show Next Flip value label") // ============================= // Per-timeframe profiles (optional) // ============================= useProfiles = input.bool(false, "Use per-timeframe profiles") grpI = "Profile: Intraday (<60m)" pI_atrPeriod = input.int(10, "ATR Period", minval=1, group=grpI) pI_multiplier = input.float(3.0, "Multiplier", minval=0.1, step=0.1, group=grpI) pI_confirmBars = input.int(2, "Confirm bars", minval=1, group=grpI) pI_bufferATR = input.float(0.15, "Buffer (×ATR)", minval=0.0, step=0.05, group=grpI) pI_smoothLen = input.int(1, "Smoothing EMA", minval=0, group=grpI) pI_useAdxFilt = input.bool(false, "Use ADX/DMI filter", group=grpI) pI_adxLength = input.int(18, "ADX Length", minval=1, group=grpI) pI_minAdx = input.float(20.0, "Min ADX", minval=1.0, step=0.5, group=grpI) pI_useMTFConf = input.bool(false, "Use higher timeframe confirmation", group=grpI) pI_higherTF = input.timeframe("", "Higher timeframe (e.g. 60, 1D)", group=grpI) pI_sessionStr = input.session("0000-2400", "Active session", group=grpI) pI_cooldownBars = input.int(0, "Cooldown bars after flip", minval=0, group=grpI) pI_alertsClose = input.bool(true, "Alerts/labels on bar close only", group=grpI) pI_useTrendMA = input.bool(false, "Use EMA trend filter", group=grpI) pI_chopLength = input.int(14, "CHOP Length", minval=2, group=grpI) pI_maxChop = input.float(55.0, "Max CHOP", minval=0.0, maxval=100.0, step=0.5, group=grpI) pI_useChopFilt = input.bool(false, "Use Choppiness filter", group=grpI) pI_useRetest = input.bool(false, "Require retest after flip", group=grpI) pI_retestWindow = input.int(3, "Retest window (bars)", minval=1, group=grpI) pI_retestTolATR = input.float(0.20, "Retest tol (×ATR)", minval=0.0, step=0.05, group=grpI) pI_useBodyFilter = input.bool(false, "Min candle body fraction", group=grpI) pI_minBodyFrac = input.float(0.35, "Min body fraction", minval=0.0, maxval=1.0, step=0.05, group=grpI) pI_trendMALen = input.int(100, "EMA trend length", minval=1, group=grpI) grpH = "Profile: 1–4h" pH_atrPeriod = input.int(10, "ATR Period", minval=1, group=grpH) pH_multiplier = input.float(3.0, "Multiplier", minval=0.1, step=0.1, group=grpH) pH_confirmBars = input.int(2, "Confirm bars", minval=1, group=grpH) pH_bufferATR = input.float(0.10, "Buffer (×ATR)", minval=0.0, step=0.05, group=grpH) pH_smoothLen = input.int(1, "Smoothing EMA", minval=0, group=grpH) pH_useAdxFilt = input.bool(false, "Use ADX/DMI filter", group=grpH) pH_adxLength = input.int(20, "ADX Length", minval=1, group=grpH) pH_minAdx = input.float(19.0, "Min ADX", minval=1.0, step=0.5, group=grpH) pH_useMTFConf = input.bool(false, "Use higher timeframe confirmation", group=grpH) pH_higherTF = input.timeframe("", "Higher timeframe (e.g. 60, 1D)", group=grpH) pH_sessionStr = input.session("0000-2400", "Active session", group=grpH) pH_cooldownBars = input.int(0, "Cooldown bars after flip", minval=0, group=grpH) pH_alertsClose = input.bool(true, "Alerts/labels on bar close only", group=grpH) pH_useTrendMA = input.bool(false, "Use EMA trend filter", group=grpH) pH_chopLength = input.int(14, "CHOP Length", minval=2, group=grpH) pH_maxChop = input.float(57.0, "Max CHOP", minval=0.0, maxval=100.0, step=0.5, group=grpH) pH_useChopFilt = input.bool(false, "Use Choppiness filter", group=grpH) pH_useRetest = input.bool(false, "Require retest after flip", group=grpH) pH_retestWindow = input.int(3, "Retest window (bars)", minval=1, group=grpH) pH_retestTolATR = input.float(0.20, "Retest tol (×ATR)", minval=0.0, step=0.05, group=grpH) pH_useBodyFilter = input.bool(false, "Min candle body fraction", group=grpH) pH_minBodyFrac = input.float(0.30, "Min body fraction", minval=0.0, maxval=1.0, step=0.05, group=grpH) pH_trendMALen = input.int(200, "EMA trend length", minval=1, group=grpH) grpD = "Profile: 1D+" pD_atrPeriod = input.int(10, "ATR Period", minval=1, group=grpD) pD_multiplier = input.float(3.0, "Multiplier", minval=0.1, step=0.1, group=grpD) pD_confirmBars = input.int(1, "Confirm bars", minval=1, group=grpD) pD_bufferATR = input.float(0.10, "Buffer (×ATR)", minval=0.0, step=0.05, group=grpD) pD_smoothLen = input.int(0, "Smoothing EMA", minval=0, group=grpD) pD_useAdxFilt = input.bool(false, "Use ADX/DMI filter", group=grpD) pD_adxLength = input.int(14, "ADX Length", minval=1, group=grpD) pD_minAdx = input.float(18.0, "Min ADX", minval=1.0, step=0.5, group=grpD) pD_useMTFConf = input.bool(false, "Use higher timeframe confirmation", group=grpD) pD_higherTF = input.timeframe("", "Higher timeframe (e.g. 60, 1D)", group=grpD) pD_sessionStr = input.session("0000-2400", "Active session", group=grpD) pD_cooldownBars = input.int(0, "Cooldown bars after flip", minval=0, group=grpD) pD_alertsClose = input.bool(true, "Alerts/labels on bar close only", group=grpD) pD_useTrendMA = input.bool(false, "Use EMA trend filter", group=grpD) pD_chopLength = input.int(14, "CHOP Length", minval=2, group=grpD) pD_maxChop = input.float(60.0, "Max CHOP", minval=0.0, maxval=100.0, step=0.5, group=grpD) pD_useChopFilt = input.bool(false, "Use Choppiness filter", group=grpD) pD_useRetest = input.bool(false, "Require retest after flip", group=grpD) pD_retestWindow = input.int(2, "Retest window (bars)", minval=1, group=grpD) pD_retestTolATR = input.float(0.20, "Retest tol (×ATR)", minval=0.0, step=0.05, group=grpD) pD_useBodyFilter = input.bool(false, "Min candle body fraction", group=grpD) pD_minBodyFrac = input.float(0.30, "Min body fraction", minval=0.0, maxval=1.0, step=0.05, group=grpD) pD_trendMALen = input.int(200, "EMA trend length", minval=1, group=grpD) // Compute active profile based on timeframe tfSec = timeframe.in_seconds(timeframe.period) isIntraday = tfSec < 60 * 60 isHto4h = tfSec >= 60 * 60 and tfSec <= 4 * 60 * 60 // else daily+ aConfirmBars = useProfiles ? (isIntraday ? pI_confirmBars : isHto4h ? pH_confirmBars : pD_confirmBars) : confirmBars aBufferATR = useProfiles ? (isIntraday ? pI_bufferATR : isHto4h ? pH_bufferATR : pD_bufferATR) : bufferATR aSmoothLen = useProfiles ? (isIntraday ? pI_smoothLen : isHto4h ? pH_smoothLen : pD_smoothLen) : smoothLen aAtrPeriod = useProfiles ? (isIntraday ? pI_atrPeriod : isHto4h ? pH_atrPeriod : pD_atrPeriod) : atrPeriod aMultiplier = useProfiles ? (isIntraday ? pI_multiplier : isHto4h ? pH_multiplier : pD_multiplier) : multiplier aAdxLength = useProfiles ? (isIntraday ? pI_adxLength : isHto4h ? pH_adxLength : pD_adxLength) : adxLength aMinAdx = useProfiles ? (isIntraday ? pI_minAdx : isHto4h ? pH_minAdx : pD_minAdx) : minAdx aUseAdxFilt = useProfiles ? (isIntraday ? pI_useAdxFilt : isHto4h ? pH_useAdxFilt : pD_useAdxFilt) : useAdxFilt aUseMTFConf = useProfiles ? (isIntraday ? pI_useMTFConf : isHto4h ? pH_useMTFConf : pD_useMTFConf) : useMTFConf aHigherTF = useProfiles ? (isIntraday ? pI_higherTF : isHto4h ? pH_higherTF : pD_higherTF) : higherTF aSessionStr = useProfiles ? (isIntraday ? pI_sessionStr : isHto4h ? pH_sessionStr : pD_sessionStr) : sessionStr aCooldownBars = useProfiles ? (isIntraday ? pI_cooldownBars : isHto4h ? pH_cooldownBars : pD_cooldownBars) : cooldownBars aAlertsClose = useProfiles ? (isIntraday ? pI_alertsClose : isHto4h ? pH_alertsClose : pD_alertsClose) : alertsCloseOnly aUseTrendMA = useProfiles ? (isIntraday ? pI_useTrendMA : isHto4h ? pH_useTrendMA : pD_useTrendMA) : useTrendMA aChopLength = useProfiles ? (isIntraday ? pI_chopLength : isHto4h ? pH_chopLength : pD_chopLength) : chopLength aMaxChop = useProfiles ? (isIntraday ? pI_maxChop : isHto4h ? pH_maxChop : pD_maxChop) : maxChop aUseChopFilt = useProfiles ? (isIntraday ? pI_useChopFilt : isHto4h ? pH_useChopFilt : pD_useChopFilt) : useChopFilt aUseRetest = useProfiles ? (isIntraday ? pI_useRetest : isHto4h ? pH_useRetest : pD_useRetest) : useRetest aRetestWindow = useProfiles ? (isIntraday ? pI_retestWindow : isHto4h ? pH_retestWindow : pD_retestWindow) : retestWindow aRetestTolATR = useProfiles ? (isIntraday ? pI_retestTolATR : isHto4h ? pH_retestTolATR : pD_retestTolATR) : retestTolATR aUseBodyFilter= useProfiles ? (isIntraday ? pI_useBodyFilter: isHto4h ? pH_useBodyFilter: pD_useBodyFilter) : useBodyFilter aMinBodyFrac = useProfiles ? (isIntraday ? pI_minBodyFrac : isHto4h ? pH_minBodyFrac : pD_minBodyFrac) : minBodyFrac aTrendMALen = useProfiles ? (isIntraday ? pI_trendMALen : isHto4h ? pH_trendMALen : pD_trendMALen) : trendMALen // ============================= // Build selected source (Chart vs Heikin Ashi) // ============================= haTicker = ticker.heikinashi(syminfo.tickerid) haH = request.security(haTicker, timeframe.period, high) haL = request.security(haTicker, timeframe.period, low) haC = request.security(haTicker, timeframe.period, close) haO = request.security(haTicker, timeframe.period, open) calcH = srcMode == "Heikin Ashi" ? haH : high calcL = srcMode == "Heikin Ashi" ? haL : low calcC = srcMode == "Heikin Ashi" ? haC : close calcO = srcMode == "Heikin Ashi" ? haO : open // ============================= // Core Money Line logic (v4-compatible on selected source) // ============================= atr_custom(len) => tr1 = calcH - calcL tr2 = math.abs(calcH - nz(calcC[1], calcC)) tr3 = math.abs(calcL - nz(calcC[1], calcC)) tr = math.max(tr1, math.max(tr2, tr3)) ta.rma(tr, len) atr = atr_custom(aAtrPeriod) srcBase = (calcH + calcL) / 2.0 src = aSmoothLen > 0 ? ta.ema(srcBase, aSmoothLen) : srcBase up = src - (aMultiplier * atr) dn = src + (aMultiplier * 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 var int buyCount = 0 var int sellCount = 0 tsl := nz(tsl[1], up1) if trend == 1 tsl := math.max(up1, tsl) sellCross = calcC < (tsl - aBufferATR * atr) sellCount := sellCross ? sellCount + 1 : 0 trend := sellCount >= aConfirmBars ? -1 : 1 if trend == -1 buyCount := 0 else tsl := math.min(dn1, tsl) buyCross = calcC > (tsl + aBufferATR * atr) buyCount := buyCross ? buyCount + 1 : 0 trend := buyCount >= aConfirmBars ? 1 : -1 if trend == 1 sellCount := 0 supertrend = tsl // ============================= // Flip signals (v4 style) // ============================= flipUp = trend == 1 and nz(trend[1], -1) == -1 flipDn = trend == -1 and nz(trend[1], 1) == 1 // Track bars since last flip (for cooldown) var int barsSinceFlip = 100000 barsSinceFlip := (flipUp or flipDn) ? 0 : nz(barsSinceFlip[1], 100000) + 1 cooldownOk = barsSinceFlip >= aCooldownBars // Choppiness Index (on selected source) chop_tr(len) => t1 = calcH - calcL t2 = math.abs(calcH - nz(calcC[1], calcC)) t3 = math.abs(calcL - nz(calcC[1], calcC)) math.max(t1, math.max(t2, t3)) f_chop(len) => // sum(x, len) equivalent for wide compatibility: sma(x, len) * len sumTR = ta.sma(chop_tr(len), len) * len hh = ta.highest(calcH, len) ll = ta.lowest(calcL, len) denom = math.max(hh - ll, syminfo.mintick) 100 * math.log10(sumTR / denom) / math.log10(len) chop = f_chop(aChopLength) chopOk = not aUseChopFilt or (chop <= aMaxChop) // Retest-after-flip gate var bool retestSatisfied = true if flipUp or flipDn retestSatisfied := not aUseRetest ? true : false retestTouchLong = (trend == 1) and (barsSinceFlip > 0) and (barsSinceFlip <= aRetestWindow) and (calcL <= supertrend + aRetestTolATR * atr) retestTouchShort = (trend == -1) and (barsSinceFlip > 0) and (barsSinceFlip <= aRetestWindow) and (calcH >= supertrend - aRetestTolATR * atr) if aUseRetest and not retestSatisfied and (retestTouchLong or retestTouchShort) retestSatisfied := true retestOk = not aUseRetest or retestSatisfied // Minimum body fraction gate barRange = math.max(calcH - calcL, syminfo.mintick) barBody = math.abs(calcC - calcO) bodyOk = not aUseBodyFilter or (barBody / barRange >= aMinBodyFrac) // ============================= // Optional ADX/DMI filter (self-contained implementation on selected source) // ============================= f_adx(len) => upMove = calcH - nz(calcH[1], calcH) downMove = nz(calcL[1], calcL) - calcL plusDM = (upMove > downMove and upMove > 0) ? upMove : 0.0 minusDM = (downMove > upMove and downMove > 0) ? downMove : 0.0 tr1 = calcH - calcL tr2 = math.abs(calcH - nz(calcC[1], calcC)) tr3 = math.abs(calcL - nz(calcC[1], calcC)) tr = math.max(tr1, math.max(tr2, tr3)) trRma = ta.rma(tr, len) pdmR = ta.rma(plusDM, len) mdmR = ta.rma(minusDM, len) pdi = trRma == 0 ? 0.0 : 100.0 * pdmR / trRma mdi = trRma == 0 ? 0.0 : 100.0 * mdmR / trRma dx = (pdi + mdi == 0) ? 0.0 : 100.0 * math.abs(pdi - mdi) / (pdi + mdi) adxVal = ta.rma(dx, len) [adxVal, pdi, mdi] [adx, diPlus, diMinus] = f_adx(aAdxLength) adxOk = not aUseAdxFilt or (adx >= aMinAdx and ((trend == 1 and diPlus > diMinus) or (trend == -1 and diMinus > diPlus))) // ============================= // Optional higher timeframe confirmation // ============================= f_dir(_atrLen, _mult, _confBars, _bufAtr) => _atr = ta.atr(_atrLen) _srcBase = (high + low) / 2.0 _up = _srcBase - (_mult * _atr) _dn = _srcBase + (_mult * _atr) var float _up1 = na var float _dn1 = na _up1 := nz(_up1[1], _up) _dn1 := nz(_dn1[1], _dn) _up1 := close[1] > _up1 ? math.max(_up, _up1) : _up _dn1 := close[1] < _dn1 ? math.min(_dn, _dn1) : _dn var int _trend = 1 var float _tsl = na var int _buyCount = 0 var int _sellCount = 0 _tsl := nz(_tsl[1], _up1) if _trend == 1 _tsl := math.max(_up1, _tsl) _sellCross = close < (_tsl - _bufAtr * _atr) _sellCount := _sellCross ? _sellCount + 1 : 0 _trend := _sellCount >= _confBars ? -1 : 1 if _trend == -1 _buyCount := 0 else _tsl := math.min(_dn1, _tsl) _buyCross = close > (_tsl + _bufAtr * _atr) _buyCount := _buyCross ? _buyCount + 1 : 0 _trend := _buyCount >= _confBars ? 1 : -1 if _trend == 1 _sellCount := 0 _trend htfDir = aUseMTFConf and (aHigherTF != "") ? request.security(syminfo.tickerid, aHigherTF, f_dir(aAtrPeriod, aMultiplier, aConfirmBars, aBufferATR), lookahead=barmerge.lookahead_off) : na mtfOk = not aUseMTFConf or (trend == htfDir) // ============================= // Session & trend filters and final gating (per-profile when enabled) // ============================= inSession = not na(time(timeframe.period, aSessionStr)) emaTrend = ta.ema(calcC, aTrendMALen) trendMAOk = not aUseTrendMA or ((trend == 1 and calcC >= emaTrend) or (trend == -1 and calcC <= emaTrend)) barOk = not aAlertsClose or barstate.isconfirmed signalsOk = adxOk and mtfOk and inSession and cooldownOk and trendMAOk and chopOk and retestOk and bodyOk and barOk // ============================= // Plots // ============================= upTrend = trend == 1 ? supertrend : na downTrend = trend == -1 ? supertrend : na // Persistent Flip Level (holds value from last flip until next) var float flipLevel = na flipLevel := (flipUp or flipDn) ? supertrend : nz(flipLevel[1]) flipLineColor = flipLineAutoColor ? (trend == 1 ? color.new(color.green, 0) : color.new(color.red, 0)) : flipLineCustomCol plot(showFlipLevelLine ? flipLevel : na, title="Flip Level", color=flipLineColor, linewidth=flipLineWidth, style=plot.style_linebr, trackprice=true) // Optional always-visible value label at current bar (high-contrast) var label flipValLbl = na if showFlipLevelValue and not na(flipLevel) if not na(flipValLbl) label.delete(flipValLbl) // High-contrast: black text on green (bull), white text on maroon (bear) flipLblBg = flipLineAutoColor ? (trend == 1 ? color.new(color.green, 0) : color.new(color.maroon, 0)) : flipLineCustomCol flipLblTxt = flipLineAutoColor ? (trend == 1 ? color.black : color.white) : color.white lblSize = labelSizeOpt == "tiny" ? size.tiny : labelSizeOpt == "small" ? size.small : size.normal flipValLbl := label.new(bar_index, flipLevel, text="Flip Level: " + str.tostring(flipLevel, format.mintick), style=label.style_label_left, color=flipLblBg, textcolor=flipLblTxt, yloc=yloc.price, size=lblSize) // Next flip preview levels (dynamic thresholds) nextBullLevel = tsl + aBufferATR * atr // price needed to start bull flip counting nextBearLevel = tsl - aBufferATR * atr // price needed to start bear flip counting nextBullActive = trend == -1 nextBearActive = trend == 1 nbCol = color.new(color.lime, 0) nsCol = color.new(color.red, 0) plot(showNextBullFlip and nextBullActive ? nextBullLevel : na, title="Next Bull Flip Level", color=nbCol, linewidth=nextFlipLineWidth, style=plot.style_linebr, trackprice=true) plot(showNextBearFlip and nextBearActive ? nextBearLevel : na, title="Next Bear Flip Level", color=nsCol, linewidth=nextFlipLineWidth, style=plot.style_linebr, trackprice=true) // Optional live value labels for next flip thresholds (high-contrast) var label nextBullLbl = na var label nextBearLbl = na if showNextFlipValue if showNextBullFlip and nextBullActive and not na(nextBullLevel) if not na(nextBullLbl) label.delete(nextBullLbl) // Bright lime background with black text for readability nextBullLbl := label.new(bar_index, nextBullLevel, text="Next Bull Flip: " + str.tostring(nextBullLevel, format.mintick) + (aConfirmBars > 1 ? " (" + str.tostring(aConfirmBars) + " closes)" : ""), style=label.style_label_left, color=color.new(color.lime, 0), textcolor=color.black, yloc=yloc.price, size=lblSize) if showNextBearFlip and nextBearActive and not na(nextBearLevel) if not na(nextBearLbl) label.delete(nextBearLbl) // Darker red background for contrast nextBearLbl := label.new(bar_index, nextBearLevel, text="Next Bear Flip: " + str.tostring(nextBearLevel, format.mintick) + (aConfirmBars > 1 ? " (" + str.tostring(aConfirmBars) + " closes)" : ""), style=label.style_label_left, color=color.new(color.maroon, 0), textcolor=color.white, yloc=yloc.price, size=lblSize) 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) // v4-style flip circles (optionally gated) rawUpCond = showCircles and (gateCircles ? (flipUp and signalsOk) : flipUp) rawDownCond = showCircles and (gateCircles ? (flipDn and signalsOk) : flipDn) plotshape(rawUpCond, title="Buy Signal (circle)", location=location.belowbar, color=color.green, style=shape.circle, size=size.small) plotshape(rawDownCond, title="Sell Signal (circle)", location=location.abovebar, color=color.red, style=shape.circle, size=size.small) // Distinct gated markers for passes (triangles) plotshape(showGatedMarkers and signalsOk and flipUp, title="Gated Buy", location=location.belowbar, color=color.lime, style=shape.triangleup, size=size.tiny, offset=0) plotshape(showGatedMarkers and signalsOk and flipDn, title="Gated Sell", location=location.abovebar, color=color.maroon, style=shape.triangledown, size=size.tiny, offset=0) // Labels (gated) if showLabels and signalsOk and flipUp label.new(bar_index, supertrend, text="bullish", style=label.style_label_up, color=color.new(color.green, 0), textcolor=color.white, yloc=yloc.belowbar) if showLabels and signalsOk and flipDn label.new(bar_index, supertrend, text="bearish", style=label.style_label_down, color=color.new(color.red, 0), textcolor=color.white, yloc=yloc.abovebar) // Fills pClose = plot(calcC, title="Close (hidden)", display=display.none) fill(pClose, plot(upTrend, display=display.none), color=showFill ? color.new(color.green, 90) : color.new(color.green, 100)) fill(pClose, plot(downTrend, display=display.none), color=showFill ? color.new(color.red, 90) : color.new(color.red, 100)) // Alerts (gated) alertcondition(signalsOk and flipUp, title="Bullmania v4+ Flip Up", message="Bullmania Money Line v4+: Bullish flip on {{ticker}} @ {{close}}") alertcondition(signalsOk and flipDn, title="Bullmania v4+ Flip Down", message="Bullmania Money Line v4+: Bearish flip on {{ticker}} @ {{close}}")