Files
Tradingview/Bullmania_Money_Line_v7.pine

208 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//@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.100.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)