Compare commits

26 Commits

Author SHA1 Message Date
root
e8be5de41a v7: make RSI gate practical — add gate mode (In zone now / Cross within lookback / In zone within lookback) and lookback bars (default 3) 2025-10-22 10:11:04 +02:00
root
b3e939ee47 v7: fix alertcondition to const strings; add optional dynamic alert() messages with parameters 2025-10-21 19:57:39 +02:00
root
29d6555803 v7: add SuperTrend + RSI (15m) gate with profiles, HA/Chart source, entry filters, alerts; update README 2025-10-21 19:51:24 +02:00
root
a1a0280abd Remove v6 indicator; README cleaned up to reflect v5 as current and strategy v5 for backtesting 2025-10-21 19:44:35 +02:00
root
322348e50d v5: replace ta.adx() with manual ADX calculation using selected OHLC (calcH/L/C) 2025-10-19 20:02:53 +02:00
root
04900aefc8 v5: optional entry filters (ATR buffer, confirm bars, ADX) + alertconditions for final signals 2025-10-19 19:57:38 +02:00
root
e1067f211a v5: add Calculation source toggle (Chart vs Heikin Ashi) with ATR computed from selected OHLC; strategy uses HA signals but fills on chart bars 2025-10-18 20:59:41 +02:00
root
d5c8b8b34a Strategy v5: improve backtest realism (entry-based SL/TP, combined exits, pyramiding=0, commission/slippage) 2025-10-18 20:40:10 +02:00
root
8ae4a9148a Add strategy version v5 for backtesting; update README with backtesting guide 2025-10-18 20:27:23 +02:00
root
41c60bf967 v6: add optional RSI confirmation gate to v5 (SuperTrend + RSI combo); README updated 2025-10-18 20:22:38 +02:00
root
bfda6c2617 v5: add timeframe profiles with auto/override + default per-timeframe ATR/Multiplier; README updated 2025-10-18 14:21:43 +02:00
mindesbunister
ef6fa0baeb UX: add line width and label size inputs; set Flip Level to linebr so it sticks to candles; apply widths to next-flip lines 2025-10-17 12:25:30 +02:00
mindesbunister
7460d6bd48 Fix Pine parse errors: put label.new calls on single line (remove trailing commas) 2025-10-17 12:21:08 +02:00
mindesbunister
f3875470e4 Fix Pine syntax: correct indentation for label assignments in flip and next-flip sections 2025-10-17 12:17:23 +02:00
mindesbunister
adc78c0644 Flip label contrast: black text on green (bull), white on maroon (bear) 2025-10-17 12:15:57 +02:00
mindesbunister
01147e373a Labels: improve readability with high-contrast backgrounds (lime+black for next bull, maroon+white for bear) and adjust flip value label 2025-10-17 11:59:44 +02:00
mindesbunister
e4f11d252d Flip previews: plot Next Bull/Bear Flip thresholds with optional value labels; uses current tsl ± buffer*ATR and activates opposite to current trend 2025-10-17 11:45:29 +02:00
mindesbunister
5a13b79320 Flip Level: add live value label at current bar; keeps value fixed to last flip 2025-10-17 11:39:04 +02:00
mindesbunister
977f53dc75 v4+: add persistent Flip Level line with optional auto-coloring and price tracking 2025-10-17 11:31:25 +02:00
root
9563f4d44f docs: add per-profile presets and clarify toggles/thresholds behavior 2025-10-17 11:14:22 +02:00
root
335ff232a3 docs: clarify profiles ON = all settings per-profile (core + filters) 2025-10-17 11:07:54 +02:00
root
79803d76d3 feat(v4+): make all inputs per-profile when enabled (ATR period, multiplier, filter toggles, gates) and wire into logic 2025-10-17 11:03:12 +02:00
root
616ddbabe7 docs: add 'How to use profiles' checklist 2025-10-17 10:38:41 +02:00
root
1b2f033fa3 docs: document v4+ features (filters, gating, per-timeframe profiles) and Pine v6 2025-10-17 10:21:56 +02:00
root
ab2c1c5e9b feat(indicator): add Money Line v4+ (Pine v6) with optional ADX/MTF/session/cooldown, calc source, EMA trend filter, anti-chop (CHOP, retest, min body), gated markers, and per-timeframe profiles 2025-10-17 09:28:09 +02:00
mindesbunister
bd6993d4f1 docs: add README with v1 vs v4 overview, presets, and tuning guide 2025-10-16 18:13:16 +02:00
8 changed files with 1504 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
//@version=5
strategy(
"Bullmania Money Line Strategy v5",
overlay=true,
default_qty_type=strategy.percent_of_equity,
default_qty_value=100,
initial_capital=10000,
pyramiding=0,
commission_type=strategy.commission.percent,
commission_value=0.1, // 0.1% commission per trade
slippage=1,
calc_on_order_fills=true,
calc_on_every_tick=false
)
// Calculation source (Chart vs Heikin Ashi)
srcMode = input.string("Chart", "Calculation source", options=["Chart","Heikin Ashi"], tooltip="Use regular chart candles or Heikin Ashi for signal calculation. Fills occur on chart bars.")
// 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
// 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
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")
// Strategy settings
useStopLoss = input.bool(false, "Use Stop Loss", group="Strategy")
stopLossPct = input.float(1.0, "Stop Loss %", minval=0.1, step=0.1, group="Strategy")
useTakeProfit = input.bool(false, "Use Take Profit", group="Strategy")
takeProfitPct = input.float(2.0, "Take Profit %", minval=0.1, step=0.1, group="Strategy")
// 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
// Resolve profile bucket
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
// Core Money Line logic (with selectable source)
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
tr = math.max(calcH - calcL, math.max(math.abs(calcH - calcC[1]), math.abs(calcL - calcC[1])))
atr = ta.rma(tr, atrPeriod)
src = (calcH + calcL) / 2
up = src - (multiplier * atr)
dn = src + (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)
// MACD confirmation logic
[macdLine, macdSignal, macdHist] = ta.macd(macdSrc, macdFastLen, macdSlowLen, macdSigLen)
longOk = not useMacd or (macdLine > macdSignal)
shortOk = not useMacd or (macdLine < macdSignal)
// Strategy logic
buyFlip = trend == 1 and trend[1] == -1
sellFlip = trend == -1 and trend[1] == 1
if buyFlip and longOk
strategy.entry("Long", strategy.long)
if sellFlip and shortOk
strategy.entry("Short", strategy.short)
// Exits (based on entry price; combine SL/TP into a single exit per side)
// Long side
if strategy.position_size > 0
var string longExitId = "L-Exit"
longStop = useStopLoss ? strategy.position_avg_price * (1 - stopLossPct / 100.0) : na
longLimit = useTakeProfit ? strategy.position_avg_price * (1 + takeProfitPct / 100.0) : na
strategy.exit(longExitId, from_entry="Long", stop=longStop, limit=longLimit)
// Short side
if strategy.position_size < 0
var string shortExitId = "S-Exit"
shortStop = useStopLoss ? strategy.position_avg_price * (1 + stopLossPct / 100.0) : na
shortLimit = useTakeProfit ? strategy.position_avg_price * (1 - takeProfitPct / 100.0) : na
strategy.exit(shortExitId, from_entry="Short", stop=shortStop, limit=shortLimit)
// Plot buy/sell signals
plotshape(buyFlip and longOk, title="Buy Signal", location=location.belowbar, color=color.green, style=shape.circle, size=size.small)
plotshape(sellFlip and shortOk, title="Sell Signal", location=location.abovebar, color=color.red, style=shape.circle, size=size.small)
// Fill area between price and Money Line
fill(plot(close, display=display.none), plot(upTrend, display=display.none), color=color.new(color.green, 90))
fill(plot(close, display=display.none), plot(downTrend, display=display.none), color=color.new(color.red, 90))

View File

@@ -0,0 +1,115 @@
//@version=5
strategy("Bullmania Money Line v2", overlay=true,
initial_capital=10000, currency=currency.USD, pyramiding=0,
commission_type=strategy.commission.percent, commission_value=0.1, // 0.1% commission
slippage=1, process_orders_on_close=true)
// Inputs
atrPeriod = input.int(10, "ATR Period", minval=1)
multiplier = input.float(3.0, "Multiplier", minval=0.1, step=0.1)
// Backtest toggles
enableLongs = input.bool(true, "Enable Longs")
enableShorts = input.bool(true, "Enable Shorts")
// Date range filter
startYear = input.int(2020, "Start Year", minval=1970, maxval=2100)
startMonth = input.int(1, "Start Month", minval=1, maxval=12)
startDay = input.int(1, "Start Day", minval=1, maxval=31)
endYear = input.int(2100, "End Year", minval=1970, maxval=2100)
endMonth = input.int(12, "End Month", minval=1, maxval=12)
endDay = input.int(31, "End Day", minval=1, maxval=31)
startTime = timestamp(startYear, startMonth, startDay, 0, 0)
endTime = timestamp(endYear, endMonth, endDay, 23, 59)
inDateRange = time >= startTime and time <= endTime
// Position sizing
riskMode = input.string("Percent of equity", "Sizing Mode", options=["Percent of equity", "Fixed contracts"])
posSizePct = input.float(10.0, "Position Size % of Equity", minval=0.0, step=0.1)
fixedQty = input.float(1.0, "Fixed Contracts/Qty", minval=0.0, step=0.1)
allowFractional = input.bool(true, "Allow fractional quantity")
usePct = riskMode == "Percent of equity"
calcQty(price) =>
qtyCalc = usePct ? (strategy.equity * (posSizePct / 100.0)) / price : fixedQty
allowFractional ? qtyCalc : math.floor(qtyCalc)
// Stops/Targets
useMoneyLineStop = input.bool(true, "Use Money Line as Stop")
tpAtrMult = input.float(0.0, "Take Profit ATR Multiplier (0 = Off)", minval=0.0, step=0.1)
// Core calculations (same as v1)
atr = ta.atr(atrPeriod)
src = (high + low) / 2
up = src - (multiplier * atr)
dn = src + (multiplier * 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
tsl := nz(tsl[1], up1)
if trend == 1
tsl := math.max(up1, tsl)
trend := close < tsl ? -1 : 1
else
tsl := math.min(dn1, tsl)
trend := close > 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)
// Signals
longSignal = trend == 1 and trend[1] == -1
shortSignal = trend == -1 and trend[1] == 1
// Plot buy/sell signals
plotshape(longSignal, title="Buy Signal", location=location.belowbar, color=color.green, style=shape.circle, size=size.small)
plotshape(shortSignal, title="Sell Signal", location=location.abovebar, color=color.red, style=shape.circle, size=size.small)
// Fill area between price and Money Line
fill(plot(close, display=display.none), plot(upTrend, display=display.none), color=color.new(color.green, 90))
fill(plot(close, display=display.none), plot(downTrend, display=display.none), color=color.new(color.red, 90))
// Alerts
alertcondition(longSignal, title="Bullmania Long", message="Bullmania Money Line: Long signal")
alertcondition(shortSignal, title="Bullmania Short", message="Bullmania Money Line: Short signal")
// Backtest entries
canLong = enableLongs and inDateRange
canShort = enableShorts and inDateRange
// Issue entries on signal
if longSignal and canLong
strategy.entry(id="Long", direction=strategy.long, qty = calcQty(close))
if shortSignal and canShort
strategy.entry(id="Short", direction=strategy.short, qty = calcQty(close))
// Dynamic stops/targets via strategy.exit
longStop = useMoneyLineStop and trend == 1 ? supertrend : na
shortStop = useMoneyLineStop and trend == -1 ? supertrend : na
// Optional ATR-based take profits referenced from average fill price
longTP = tpAtrMult > 0 and strategy.position_size > 0 ? strategy.position_avg_price + atr * tpAtrMult : na
shortTP = tpAtrMult > 0 and strategy.position_size < 0 ? strategy.position_avg_price - atr * tpAtrMult : na
// Update exits every bar (works even when not in position)
strategy.exit("XL", from_entry="Long", stop=longStop, limit=longTP)
strategy.exit("XS", from_entry="Short", stop=shortStop, limit=shortTP)

View File

@@ -0,0 +1,481 @@
//@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")
flipLabelPosOpt = input.string("Flip bar", "Flip Level label position", options=["Current bar","Flip bar"], tooltip="Where to place the Flip Level value label. 'Flip bar' anchors the label to the candle where the last flip occurred so it moves with that candle when you pan the chart.")
extendLinesRight = input.bool(false, "Extend lines to right edge", tooltip="If enabled, flip and next-flip lines will extend to the right edge (HUD-style). Disable to keep them glued strictly to candles.")
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"])
// Global label size derived from option (used by all labels)
lblSize = labelSizeOpt == "tiny" ? size.tiny : labelSizeOpt == "small" ? size.small : size.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: 14h"
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
var int flipBarIndex = na
flipLevel := (flipUp or flipDn) ? supertrend : nz(flipLevel[1])
flipBarIndex := (flipUp or flipDn) ? bar_index : nz(flipBarIndex[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=extendLinesRight)
// Optional always-visible value label at current bar (high-contrast)
var label flipValLbl = na
if showFlipLevelValue and not na(flipLevel)
// 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
// Determine x position based on user preference
flipLblX = flipLabelPosOpt == "Flip bar" and not na(flipBarIndex) ? flipBarIndex : bar_index
if na(flipValLbl)
flipValLbl := label.new(flipLblX, flipLevel, text="Flip Level: " + str.tostring(flipLevel, format.mintick), xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, color=flipLblBg, textcolor=flipLblTxt, size=lblSize)
else
label.set_x(flipValLbl, flipLblX)
label.set_y(flipValLbl, flipLevel)
label.set_text(flipValLbl, "Flip Level: " + str.tostring(flipLevel, format.mintick))
label.set_color(flipValLbl, flipLblBg)
label.set_textcolor(flipValLbl, flipLblTxt)
label.set_style(flipValLbl, label.style_label_left)
label.set_size(flipValLbl, lblSize)
else
if not na(flipValLbl)
label.delete(flipValLbl)
flipValLbl := na
// 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=extendLinesRight)
plot(showNextBearFlip and nextBearActive ? nextBearLevel : na, title="Next Bear Flip Level", color=nsCol, linewidth=nextFlipLineWidth, style=plot.style_linebr, trackprice=extendLinesRight)
// Optional live value labels for next flip thresholds (high-contrast)
var label nextBullLbl = na
var label nextBearLbl = na
if showNextFlipValue
// Next Bull label lifecycle
if showNextBullFlip and nextBullActive and not na(nextBullLevel)
if na(nextBullLbl)
nextBullLbl := label.new(bar_index, nextBullLevel, text="Next Bull Flip: " + str.tostring(nextBullLevel, format.mintick) + (aConfirmBars > 1 ? " (" + str.tostring(aConfirmBars) + " closes)" : ""), xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, color=color.new(color.lime, 0), textcolor=color.black, size=lblSize)
else
label.set_x(nextBullLbl, bar_index)
label.set_y(nextBullLbl, nextBullLevel)
label.set_text(nextBullLbl, "Next Bull Flip: " + str.tostring(nextBullLevel, format.mintick) + (aConfirmBars > 1 ? " (" + str.tostring(aConfirmBars) + " closes)" : ""))
label.set_color(nextBullLbl, color.new(color.lime, 0))
label.set_textcolor(nextBullLbl, color.black)
label.set_style(nextBullLbl, label.style_label_left)
label.set_size(nextBullLbl, lblSize)
else
if not na(nextBullLbl)
label.delete(nextBullLbl)
nextBullLbl := na
// Next Bear label lifecycle
if showNextBearFlip and nextBearActive and not na(nextBearLevel)
if na(nextBearLbl)
nextBearLbl := label.new(bar_index, nextBearLevel, text="Next Bear Flip: " + str.tostring(nextBearLevel, format.mintick) + (aConfirmBars > 1 ? " (" + str.tostring(aConfirmBars) + " closes)" : ""), xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, color=color.new(color.maroon, 0), textcolor=color.white, size=lblSize)
else
label.set_x(nextBearLbl, bar_index)
label.set_y(nextBearLbl, nextBearLevel)
label.set_text(nextBearLbl, "Next Bear Flip: " + str.tostring(nextBearLevel, format.mintick) + (aConfirmBars > 1 ? " (" + str.tostring(aConfirmBars) + " closes)" : ""))
label.set_color(nextBearLbl, color.new(color.maroon, 0))
label.set_textcolor(nextBearLbl, color.white)
label.set_style(nextBearLbl, label.style_label_left)
label.set_size(nextBearLbl, lblSize)
else
if not na(nextBearLbl)
label.delete(nextBearLbl)
nextBearLbl := na
else
if not na(nextBullLbl)
label.delete(nextBullLbl)
nextBullLbl := na
if not na(nextBearLbl)
label.delete(nextBearLbl)
nextBearLbl := 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)
// 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}}")

View File

@@ -0,0 +1,175 @@
//@version=5
indicator("Bullmania Money Line v5", overlay=true)
// Calculation source (Chart vs Heikin Ashi)
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
// 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
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")
// Entry filters (optional)
groupFilters = "Entry filters"
useEntryBuffer = input.bool(false, "Require entry buffer (ATR)", group=groupFilters, tooltip="If enabled, the close must be beyond the Money Line by the buffer amount 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 1h.")
confirmBars = input.int(0, "Bars to confirm after flip", minval=0, maxval=2, group=groupFilters, tooltip="0 = signal on flip bar. 1 = wait one bar.")
useAdx = input.bool(false, "Use ADX trend-strength filter", group=groupFilters, tooltip="If enabled, require ADX to be 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
// Resolve profile bucket
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
// Core Money Line logic (with selectable source)
// Build selected source OHLC
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
tr = math.max(calcH - calcL, math.max(math.abs(calcH - calcC[1]), math.abs(calcL - calcC[1])))
atr = ta.rma(tr, atrPeriod)
src = (calcH + calcL) / 2
up = src - (multiplier * atr)
dn = src + (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 on chart as a label (once per bar close)
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, close, text="Profile: " + activeProfile + " | ATR=" + str.tostring(atrPeriod) + " Mult=" + str.tostring(multiplier), yloc=yloc.price, style=label.style_label_upper_left, textcolor=color.white, color=color.new(color.blue, 20))
// MACD confirmation logic
[macdLine, macdSignal, macdHist] = ta.macd(macdSrc, macdFastLen, macdSlowLen, macdSigLen)
longOk = not useMacd or (macdLine > macdSignal)
shortOk = not useMacd or (macdLine < macdSignal)
// Plot buy/sell signals (gated by optional MACD)
buyFlip = trend == 1 and trend[1] == -1
sellFlip = trend == -1 and trend[1] == 1
// ADX computation (manual) and gate on selected source
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 gates relative to current Money Line
longBufferOk = not useEntryBuffer or (calcC > supertrend + entryBufferATR * atr)
shortBufferOk = not useEntryBuffer or (calcC < supertrend - entryBufferATR * atr)
// Confirmation bars after flip
buyReady = ta.barssince(buyFlip) == confirmBars
sellReady = ta.barssince(sellFlip) == confirmBars
// Final gated 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)
// Alerts for final signals (set to 'Once per bar close' in the alert dialog)
alertcondition(finalLongSignal, title="Bullmania Buy", message="Buy {{ticker}} {{interval}} | Profile={{activeProfile}} ATR={{atrPeriod}} Mult={{multiplier}}")
alertcondition(finalShortSignal, title="Bullmania Sell", message="Sell {{ticker}} {{interval}} | Profile={{activeProfile}} ATR={{atrPeriod}} Mult={{multiplier}}")
// Fill area between price and Money Line
fill(plot(close, display=display.none), plot(upTrend, display=display.none), color=color.new(color.green, 90))
fill(plot(close, display=display.none), plot(downTrend, display=display.none), color=color.new(color.red, 90))

View File

@@ -0,0 +1,140 @@
//@version=5
indicator("Bullmania Money Line v6 (SuperTrend + RSI)", overlay=true)
// 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
// 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 RSI confirmation
useRsi = input.bool(false, "Use RSI confirmation", inline="rsi")
rsiLen = input.int(14, "RSI Length", minval=1, inline="rsi")
rsiOverbought = input.int(70, "RSI Overbought", minval=1, maxval=100, inline="rsi")
rsiOversold = input.int(30, "RSI Oversold", minval=1, maxval=100, inline="rsi")
// Optional MACD confirmation
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")
// 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
// Resolve profile bucket
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
// Core Money Line logic (unchanged from v1)
atr = ta.atr(atrPeriod)
src = (high + low) / 2
up = src - (multiplier * atr)
dn = src + (multiplier * 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
tsl := nz(tsl[1], up1)
if trend == 1
tsl := math.max(up1, tsl)
trend := close < tsl ? -1 : 1
else
tsl := math.min(dn1, tsl)
trend := close > 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 on chart as a label (once per bar close)
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, close, text="Profile: " + activeProfile + " | ATR=" + str.tostring(atrPeriod) + " Mult=" + str.tostring(multiplier), yloc=yloc.price, style=label.style_label_upper_left, textcolor=color.white, color=color.new(color.blue, 20))
// RSI confirmation logic
rsi = ta.rsi(close, rsiLen)
longRsiOk = not useRsi or rsi < rsiOverbought
shortRsiOk = not useRsi or rsi > rsiOversold
// 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 confirmation
longOk = longRsiOk and longMacdOk
shortOk = shortRsiOk and shortMacdOk
// Plot buy/sell signals (gated by optional RSI and MACD)
buyFlip = trend == 1 and trend[1] == -1
sellFlip = trend == -1 and trend[1] == 1
plotshape(buyFlip and longOk, title="Buy Signal", location=location.belowbar, color=color.green, style=shape.circle, size=size.small)
plotshape(sellFlip and shortOk, title="Sell Signal", location=location.abovebar, color=color.red, style=shape.circle, size=size.small)
// Fill area between price and Money Line
fill(plot(close, display=display.none), plot(upTrend, display=display.none), color=color.new(color.green, 90))
fill(plot(close, display=display.none), plot(downTrend, display=display.none), color=color.new(color.red, 90))

View File

@@ -0,0 +1,207 @@
//@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)

219
README.md Normal file
View File

@@ -0,0 +1,219 @@
# Bullmania Money Line
## Bullmania Money Line v2 (strategy)
`Bullmania_Money_Line_v2.pine` is a TradingView Strategy version of the original indicator with built-in backtesting.
- Convert the original Money Line into a strategy with entries on trend flips.
- Long/Short toggles and date range filter for backtests.
- Commission/slippage controls via strategy() header.
- Two sizing modes: Percent of equity (qty computed from equity and price) or fixed quantity.
- Optional Money Line-based stop and ATR-multiple take profit.
How to use:
1. Open the file in TradingView Pine Editor and click Add to chart.
2. In the Inputs tab, set your ATR Period and Multiplier just like v1.
3. Set backtest range (Start/End date) and toggles (Enable Longs/Shorts).
4. Choose sizing mode: Percent of equity (qty_percent) or Fixed contracts (qty).
5. Optionally enable Money Line as Stop and ATR-based Take Profit.
Notes:
- Entries occur on trend flips (when the Money Line switches from down to up for longs and vice versa for shorts).
- Exits are managed using `strategy.exit` with the Money Line as trailing stop when enabled. TP is optional.
- Default initial capital is 10,000 and commission is 0.1% with 1 tick slippage; adjust as needed.
ATR-based trend line and flip signals for TradingView (Pine v6). The indicator draws a “Money Line” that trails price and flips direction when price crosses it. Green dots mark bullish flips (trend turns up), red dots mark bearish flips (trend turns down).
This repo contains multiple versions; v4 is the stable baseline and `v4+` adds optional filters, alerts, and per-timeframe profiles while preserving v4 defaults (off by default).
## Files
- `Bullmania_Money_Line_v1.pine` — Baseline behavior (original). Two inputs: ATR Period, Multiplier.
- `Bullmania_Money_Line_v2.pine` — Historical variant.
- `Bullmania_Money_Line_v3.pine` — Historical variant.
- `Bullmania_Money_Line_v4.pine` — v1 + optional noise reduction (confirmation bars, ATR buffer). Tagged `v4.0.0`.
- `Bullmania_Money_Line_v4_plus.pine` — v4-compatible but with optional filters and quality-of-life features (Pine v6).
- `Bullmania_Money_Line_v5.pine` — v1 + optional MACD confirmation gate and optional timeframe profiles (Single mode or auto profile by chart TF).
- `Bullmania_Money_Line_Strategy_v5.pine` — Strategy version of v5 for backtesting with entry/exit rules, optional SL/TP.
- `Bullmania_Money_Line_v7.pine` — v5 core + RSI gate using a selectable timeframe (default 15m oversold/overbought) with alerts.
## How it works (all versions)
- Money Line is derived from a mid-price `(high + low) / 2` and `ATR(atrPeriod)` scaled by `multiplier`.
- The line trails in the direction of the current trend and flips when price closes across it.
- Plots:
- Green line when trend is up, red line when trend is down.
- Green dot below bar when trend flips up; red dot above bar when trend flips down.
- Optional green/red fill between price and the line for quick context.
## Inputs
Common inputs (v1v4):
- ATR Period — ATR length in bars.
- Multiplier — distance from source to the line in ATRs.
Additional inputs (v4 only):
- Flip confirmation bars — require N consecutive closes beyond the Money Line to flip (default 1 = same as v1).
- Flip buffer (×ATR) — require the cross to exceed the line by X × ATR to flip (default 0.0 = same as v1).
Defaults in v4 match v1, so you can upgrade without changing results and then enable the new knobs as needed.
### v4+ extras (all optional; defaults preserve v4)
- Calculation source: Chart or Heikin Ashi
- Flip confirmation bars and Flip buffer (×ATR)
- ADX/DMI gate (length, min ADX)
- Higher timeframe confirmation (request.security with lookahead_off)
- Session/time gating and cooldown bars after flip
- EMA trend filter (e.g., 100200)
- Anti-chop filters:
- Choppiness Index (length and max allowed)
- Retest-after-flip (window and tolerance in ATR)
- Minimum candle body fraction
- Visualization:
- Gate flip circles with filters (optional)
- Gated markers (triangles) for approved flips
- Alerts/labels on bar close only
- Price-to-line shading
### Per-timeframe profiles (v4+)
Enable "Use per-timeframe profiles" to automatically switch parameters by chart timeframe:
- Intraday (<60m)
- 14h
- 1D+
Each profile has its own confirm bars, ATR buffer, smoothing, ADX length/min, CHOP length/max, retest window/tolerance, min body fraction, and trend EMA. When profiles are OFF, the single global set of inputs is used.
Note
- When profiles are ON, ALL relevant settings are per-profile: ATR Period, Multiplier, and all filter toggles/parameters (ADX, higher TF + its timeframe, session, cooldown, alerts on close, EMA trend filter enable and length, CHOP, Retest, Body filter). You always see exactly which profile is active by your chart timeframe.
#### How to use profiles
1) In the indicator Inputs, enable "Use per-timeframe profiles".
2) Change the chart timeframe; the active profile is selected automatically:
- Intraday: under 60 minutes (e.g., 1, 3, 5, 15, 30, 45).
- 14h: from 60 to 240 minutes inclusive (1h, 2h, 3h, 4h).
- 1D+: daily and above (1D, 2D, 1W, 1M).
3) Tune the controls in that profile group; they dont affect the other profiles.
4) Optional: Save as default or save an Indicator Template in TradingView to reuse your setup.
5) Turn profiles OFF to revert to one global set of inputs across all timeframes.
Tips
- With profiles ON, toggles and thresholds are per-profile (e.g., ADX on/off and its length/min are tied to the active profile). With profiles OFF, a single global set applies to all timeframes.
- For consistent behavior across TFs, start by scaling ATR Period with timeframe and then adjust Confirm bars and Buffer (×ATR) per profile.
- Triangles, labels, and alerts follow the same gating and will only appear when all enabled filters pass.
## Quick presets
Use these as starting points and adjust per symbol/volatility. Values are for Heikin Ashi unless noted.
- 30m Conservative (quiet):
- ATR Period: 8
- Multiplier: 3.5
- Flip confirmation bars: 2
- Flip buffer (×ATR): 0.30
- 30m Balanced:
- ATR Period: 6
- Multiplier: 3.2
- Flip confirmation bars: 2
- Flip buffer (×ATR): 0.25
#### Per-profile presets (v4+)
Balanced starting points that map directly to the three profiles. Tweak per market.
- Intraday (<60m):
- ATR Period: 10
- Multiplier: 3.0
- Confirm bars: 2
- Flip buffer (×ATR): 0.15
- Smoothing EMA: 1
- ADX: on (len 18, min 20)
- Choppiness: on (len 14, max 55)
- Retest: optional (window 3, tol 0.20)
- Body filter: optional (min 0.35)
- Trend EMA: optional (len 100)
- 14h:
- ATR Period: 10
- Multiplier: 3.0
- Confirm bars: 2
- Flip buffer (×ATR): 0.10
- Smoothing EMA: 1
- ADX: on (len 20, min 19)
- Choppiness: on (len 14, max 57)
- Retest: optional (window 3, tol 0.20)
- Body filter: optional (min 0.30)
- Trend EMA: optional (len 200)
- 1D+:
- ATR Period: 10
- Multiplier: 3.0
- Confirm bars: 1
- Flip buffer (×ATR): 0.10
- Smoothing EMA: 0
- ADX: on (len 14, min 18)
- Choppiness: on (len 14, max 60)
- Retest: optional (window 2, tol 0.20)
- Body filter: optional (min 0.30)
- Trend EMA: optional (len 200)
### Tuning cheat sheet
- Too noisy (too many reversals):
1) Increase confirmation bars (e.g., 2 → 3)
2) Raise buffer by +0.05 to +0.10
3) Widen Multiplier by +0.3 to +0.5 if needed
- Too late / too few signals:
1) Lower buffer by -0.05
2) Reduce confirmation bars (3 → 2)
3) Tighten Multiplier by -0.3 to -0.5
### Timeframe scaling tip
If you want a similar time horizon across timeframes, pick a target window `T` in minutes (e.g., ~150 minutes). Then
```
ATR Period ≈ round(T / timeframe_minutes)
```
Keep Multiplier roughly stable and fine-tune by small increments; HA candles allow slightly tighter settings than regular candles.
## Backtesting with Strategy Version
For automated testing of different settings, use `Bullmania_Money_Line_Strategy_v5.pine`:
- Load it as a strategy in TradingView.
- In the Strategy Tester, set date ranges and use "Optimize" to sweep parameters like ATR Period, Multiplier, or MACD lengths.
- Review performance metrics (win rate, profit factor) to find optimal combos.
- Note: Indicators show signals but don't backtest; strategies do.
## Changelog
- v7 — v5 core plus RSI oversold/overbought gate from a selectable timeframe (default 15m). Includes alerts; works with MACD gate and entry filters.
- v5 — v1 + optional MACD gate (buy dot requires MACD line > signal; sell dot requires MACD line < signal). Adds optional timeframe profiles: choose "Single" or "Profiles by timeframe". Profiles buckets: Minutes, Hours, Daily, Weekly/Monthly with independent ATR Period and Multiplier. Defaults keep v1 behavior.
- v4+ — Pine v6 variant with optional ADX/MTF/session/cooldown, calc source toggle, EMA trend filter, anti-chop (CHOP, retest, min body), gated markers, and per-timeframe profiles.
- v4.0.0 — Adds Flip confirmation bars and Flip buffer (×ATR). Defaults keep v1 behavior. Tag: `v4.0.0`.
- v1 — Initial baseline.
## v5 timeframe profiles (quick guide)
In `Bullmania_Money_Line_v5.pine` you can switch "Parameter Mode" between:
- Single — one global ATR Period and Multiplier (same as v1 behavior).
- Profiles by timeframe — separate inputs per bucket with auto-selection from chart TF or via "Profile Override":
- Minutes (<= 59m)
- Hours (>= 1h and < 1d)
- Daily (>= 1d and < 1w)
- Weekly/Monthly (>= 1w)
Inputs added in v5 when profiles are enabled:
- ATR Period (Minutes/Hours/Daily/Weekly-Monthly)
- Multiplier (Minutes/Hours/Daily/Weekly-Monthly)
- Profile Override: Auto, or force a specific bucket
- Optional: show active profile label on the chart
Note: Only ATR Period and Multiplier are profiled in v5; filters remain limited to the optional MACD confirmation gate.
### Default values (v5 profiles)
Out of the box defaults meant to be robust across popular markets; adjust per symbol/volatility:
- Minutes: ATR 12, Multiplier 3.3
- Hours: ATR 10, Multiplier 3.0
- Daily: ATR 10, Multiplier 2.8
- Weekly/Monthly: ATR 7, Multiplier 2.5
## Notes
- v4 is designed to reduce whips without changing the core Money Line logic.
- Optional future add-ons (can be toggled if requested):
- RSI centerline or ADX filter to gate flips in weak trends.
- Timeframe-adaptive ATR period (auto scales with chart TF).
- Pivot-anchored dots (plot flip dot at prior swing high/low).
(If you want a LICENSE file added, tell me which license you prefer.)