Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8be5de41a | ||
|
|
b3e939ee47 | ||
|
|
29d6555803 | ||
|
|
a1a0280abd | ||
|
|
322348e50d | ||
|
|
04900aefc8 | ||
|
|
e1067f211a | ||
|
|
d5c8b8b34a | ||
|
|
8ae4a9148a | ||
|
|
41c60bf967 | ||
|
|
bfda6c2617 | ||
|
|
ef6fa0baeb | ||
|
|
7460d6bd48 | ||
|
|
f3875470e4 | ||
|
|
adc78c0644 | ||
|
|
01147e373a | ||
|
|
e4f11d252d | ||
|
|
5a13b79320 | ||
|
|
977f53dc75 | ||
|
|
9563f4d44f | ||
|
|
335ff232a3 | ||
|
|
79803d76d3 | ||
|
|
616ddbabe7 | ||
|
|
1b2f033fa3 | ||
|
|
ab2c1c5e9b | ||
|
|
bd6993d4f1 |
167
Bullmania_Money_Line_Strategy_v5.pine
Normal file
167
Bullmania_Money_Line_Strategy_v5.pine
Normal 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))
|
||||||
115
Bullmania_Money_Line_v2.pine
Normal file
115
Bullmania_Money_Line_v2.pine
Normal 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)
|
||||||
481
Bullmania_Money_Line_v4_plus.pine
Normal file
481
Bullmania_Money_Line_v4_plus.pine
Normal 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: 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
|
||||||
|
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}}")
|
||||||
175
Bullmania_Money_Line_v5.pine
Normal file
175
Bullmania_Money_Line_v5.pine
Normal 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.10–0.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))
|
||||||
140
Bullmania_Money_Line_v6.pine
Normal file
140
Bullmania_Money_Line_v6.pine
Normal 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))
|
||||||
207
Bullmania_Money_Line_v7.pine
Normal file
207
Bullmania_Money_Line_v7.pine
Normal 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.10–0.20 works well on intraday.")
|
||||||
|
confirmBars = input.int(0, "Bars to confirm after flip", minval=0, maxval=2, group=groupFilters, tooltip="0 = show on flip bar. 1 = wait one bar.")
|
||||||
|
useAdx = input.bool(false, "Use ADX trend-strength filter", group=groupFilters, tooltip="Require ADX above a threshold to reduce chop.")
|
||||||
|
adxLen = input.int(14, "ADX Length", minval=1, group=groupFilters)
|
||||||
|
adxMin = input.int(20, "ADX minimum", minval=0, maxval=100, group=groupFilters)
|
||||||
|
|
||||||
|
// Determine effective parameters based on selected mode/profile
|
||||||
|
var string activeProfile = ""
|
||||||
|
resSec = timeframe.in_seconds(timeframe.period)
|
||||||
|
isMinutes = resSec < 3600
|
||||||
|
isHours = resSec >= 3600 and resSec < 86400
|
||||||
|
isDaily = resSec >= 86400 and resSec < 604800
|
||||||
|
isWeeklyOrMore = resSec >= 604800
|
||||||
|
|
||||||
|
string profileBucket = "Single"
|
||||||
|
if paramMode == "Single"
|
||||||
|
profileBucket := "Single"
|
||||||
|
else
|
||||||
|
if profileOverride == "Minutes"
|
||||||
|
profileBucket := "Minutes"
|
||||||
|
else if profileOverride == "Hours"
|
||||||
|
profileBucket := "Hours"
|
||||||
|
else if profileOverride == "Daily"
|
||||||
|
profileBucket := "Daily"
|
||||||
|
else if profileOverride == "Weekly/Monthly"
|
||||||
|
profileBucket := "Weekly/Monthly"
|
||||||
|
else
|
||||||
|
profileBucket := isMinutes ? "Minutes" : isHours ? "Hours" : isDaily ? "Daily" : "Weekly/Monthly"
|
||||||
|
|
||||||
|
atrPeriod = profileBucket == "Single" ? atrPeriodSingle : profileBucket == "Minutes" ? atr_m : profileBucket == "Hours" ? atr_h : profileBucket == "Daily" ? atr_d : atr_w
|
||||||
|
multiplier = profileBucket == "Single" ? multiplierSingle : profileBucket == "Minutes" ? mult_m : profileBucket == "Hours" ? mult_h : profileBucket == "Daily" ? mult_d : mult_w
|
||||||
|
activeProfile := profileBucket
|
||||||
|
|
||||||
|
// Build selected source OHLC (for Money Line)
|
||||||
|
haTicker = ticker.heikinashi(syminfo.tickerid)
|
||||||
|
[haO, haH, haL, haC] = request.security(haTicker, timeframe.period, [open, high, low, close], lookahead=barmerge.lookahead_off)
|
||||||
|
calcH = srcMode == "Heikin Ashi" ? haH : high
|
||||||
|
calcL = srcMode == "Heikin Ashi" ? haL : low
|
||||||
|
calcC = srcMode == "Heikin Ashi" ? haC : close
|
||||||
|
|
||||||
|
// ATR on selected source (true range + RMA)
|
||||||
|
tr = math.max(calcH - calcL, math.max(math.abs(calcH - calcC[1]), math.abs(calcL - calcC[1])))
|
||||||
|
atr = ta.rma(tr, atrPeriod)
|
||||||
|
srcMid = (calcH + calcL) / 2
|
||||||
|
|
||||||
|
up = srcMid - (multiplier * atr)
|
||||||
|
dn = srcMid + (multiplier * atr)
|
||||||
|
|
||||||
|
var float up1 = na
|
||||||
|
var float dn1 = na
|
||||||
|
up1 := nz(up1[1], up)
|
||||||
|
dn1 := nz(dn1[1], dn)
|
||||||
|
up1 := calcC[1] > up1 ? math.max(up, up1) : up
|
||||||
|
dn1 := calcC[1] < dn1 ? math.min(dn, dn1) : dn
|
||||||
|
|
||||||
|
var int trend = 1
|
||||||
|
var float tsl = na
|
||||||
|
|
||||||
|
tsl := nz(tsl[1], up1)
|
||||||
|
if trend == 1
|
||||||
|
tsl := math.max(up1, tsl)
|
||||||
|
trend := calcC < tsl ? -1 : 1
|
||||||
|
else
|
||||||
|
tsl := math.min(dn1, tsl)
|
||||||
|
trend := calcC > tsl ? 1 : -1
|
||||||
|
|
||||||
|
supertrend = tsl
|
||||||
|
|
||||||
|
// Plot the Money Line
|
||||||
|
upTrend = trend == 1 ? supertrend : na
|
||||||
|
downTrend = trend == -1 ? supertrend : na
|
||||||
|
plot(upTrend, "Up Trend", color=color.new(color.green, 0), style=plot.style_linebr, linewidth=2)
|
||||||
|
plot(downTrend, "Down Trend", color=color.new(color.red, 0), style=plot.style_linebr, linewidth=2)
|
||||||
|
|
||||||
|
// Show active profile/RSI TF
|
||||||
|
showProfileLabel = input.bool(true, "Show active profile label", group="Profiles")
|
||||||
|
var label profLbl = na
|
||||||
|
if barstate.islast and showProfileLabel
|
||||||
|
if not na(profLbl)
|
||||||
|
label.delete(profLbl)
|
||||||
|
profLbl := label.new(bar_index, calcC, text="Profile: " + activeProfile + " | ATR=" + str.tostring(atrPeriod) + " Mult=" + str.tostring(multiplier) + " | RSI TF=" + rsiTf, yloc=yloc.price, style=label.style_label_upper_left, textcolor=color.white, color=color.new(color.blue, 20))
|
||||||
|
|
||||||
|
// RSI from selected timeframe (use regular close for RSI by default)
|
||||||
|
rsiTfVal = request.security(syminfo.tickerid, rsiTf, ta.rsi(close, rsiLen), lookahead=barmerge.lookahead_off)
|
||||||
|
|
||||||
|
// Build RSI conditions per selected mode
|
||||||
|
rsiLongNow = rsiTfVal <= rsiOversold
|
||||||
|
rsiShortNow = rsiTfVal >= rsiOverbought
|
||||||
|
rsiCrossUp = ta.crossover(rsiTfVal, rsiOversold) // crossed up from below Oversold
|
||||||
|
rsiCrossDown = ta.crossunder(rsiTfVal, rsiOverbought) // crossed down from above Overbought
|
||||||
|
rsiLongRecent = rsiLookback == 0 ? rsiLongNow : (ta.lowest(rsiTfVal, rsiLookback + 1) <= rsiOversold)
|
||||||
|
rsiShortRecent = rsiLookback == 0 ? rsiShortNow : (ta.highest(rsiTfVal, rsiLookback + 1) >= rsiOverbought)
|
||||||
|
rsiCrossUpRecent = rsiLookback == 0 ? rsiCrossUp : (ta.barssince(rsiCrossUp) <= rsiLookback)
|
||||||
|
rsiCrossDownRecent = rsiLookback == 0 ? rsiCrossDown : (ta.barssince(rsiCrossDown) <= rsiLookback)
|
||||||
|
|
||||||
|
longRsiOk = not useRsi or (rsiGateMode == "In zone now" ? rsiLongNow : rsiGateMode == "Cross within lookback" ? rsiCrossUpRecent : rsiLongRecent)
|
||||||
|
shortRsiOk = not useRsi or (rsiGateMode == "In zone now" ? rsiShortNow : rsiGateMode == "Cross within lookback" ? rsiCrossDownRecent : rsiShortRecent)
|
||||||
|
|
||||||
|
// MACD confirmation logic
|
||||||
|
[macdLine, macdSignal, macdHist] = ta.macd(macdSrc, macdFastLen, macdSlowLen, macdSigLen)
|
||||||
|
longMacdOk = not useMacd or (macdLine > macdSignal)
|
||||||
|
shortMacdOk = not useMacd or (macdLine < macdSignal)
|
||||||
|
|
||||||
|
// Combined confirmations
|
||||||
|
longOk = longRsiOk and longMacdOk
|
||||||
|
shortOk = shortRsiOk and shortMacdOk
|
||||||
|
|
||||||
|
// Flip detection
|
||||||
|
buyFlip = trend == 1 and trend[1] == -1
|
||||||
|
sellFlip = trend == -1 and trend[1] == 1
|
||||||
|
|
||||||
|
// ADX (manual) on selected OHLC if enabled
|
||||||
|
upMove = calcH - calcH[1]
|
||||||
|
downMove = calcL[1] - calcL
|
||||||
|
plusDM = (upMove > downMove and upMove > 0) ? upMove : 0.0
|
||||||
|
minusDM = (downMove > upMove and downMove > 0) ? downMove : 0.0
|
||||||
|
trADX = math.max(calcH - calcL, math.max(math.abs(calcH - calcC[1]), math.abs(calcL - calcC[1])))
|
||||||
|
atrADX = ta.rma(trADX, adxLen)
|
||||||
|
plusDI = atrADX == 0.0 ? 0.0 : 100.0 * ta.rma(plusDM, adxLen) / atrADX
|
||||||
|
minusDI = atrADX == 0.0 ? 0.0 : 100.0 * ta.rma(minusDM, adxLen) / atrADX
|
||||||
|
dx = (plusDI + minusDI == 0.0) ? 0.0 : 100.0 * math.abs(plusDI - minusDI) / (plusDI + minusDI)
|
||||||
|
adxVal = ta.rma(dx, adxLen)
|
||||||
|
adxOk = not useAdx or (adxVal > adxMin)
|
||||||
|
|
||||||
|
// Entry buffer and confirmation
|
||||||
|
longBufferOk = not useEntryBuffer or (calcC > supertrend + entryBufferATR * atr)
|
||||||
|
shortBufferOk = not useEntryBuffer or (calcC < supertrend - entryBufferATR * atr)
|
||||||
|
|
||||||
|
buyReady = ta.barssince(buyFlip) == confirmBars
|
||||||
|
sellReady = ta.barssince(sellFlip) == confirmBars
|
||||||
|
|
||||||
|
// Final signals
|
||||||
|
finalLongSignal = buyReady and longOk and adxOk and longBufferOk
|
||||||
|
finalShortSignal = sellReady and shortOk and adxOk and shortBufferOk
|
||||||
|
|
||||||
|
plotshape(finalLongSignal, title="Buy Signal", location=location.belowbar, color=color.green, style=shape.circle, size=size.small)
|
||||||
|
plotshape(finalShortSignal, title="Sell Signal", location=location.abovebar, color=color.red, style=shape.circle, size=size.small)
|
||||||
|
|
||||||
|
// Fill area between price and Money Line
|
||||||
|
fill(plot(calcC, display=display.none), plot(upTrend, display=display.none), color=color.new(color.green, 90))
|
||||||
|
fill(plot(calcC, display=display.none), plot(downTrend, display=display.none), color=color.new(color.red, 90))
|
||||||
|
|
||||||
|
// Alerts (set to Once per bar close) — message must be a const string
|
||||||
|
alertcondition(finalLongSignal, title="Bullmania v7 Buy", message="Bullmania v7 Buy | RSI gate")
|
||||||
|
alertcondition(finalShortSignal, title="Bullmania v7 Sell", message="Bullmania v7 Sell | RSI gate")
|
||||||
|
|
||||||
|
// Optional dynamic alert text for the "Any alert() function call" alert type
|
||||||
|
useDynAlert = input.bool(true, "Dynamic alert text (Any alert() call)", group="Alerts", tooltip="Enable runtime alert() with detailed message. Use Alerts: 'Any alert() function call' and Once per bar close.")
|
||||||
|
if barstate.isconfirmed and useDynAlert
|
||||||
|
if finalLongSignal
|
||||||
|
dynMsgL = "Buy " + syminfo.ticker + " " + timeframe.period + " | Profile=" + activeProfile + " ATR=" + str.tostring(atrPeriod) + " Mult=" + str.tostring(multiplier) + " | RSI TF=" + rsiTf + " <= " + str.tostring(rsiOversold)
|
||||||
|
alert(dynMsgL, alert.freq_once_per_bar_close)
|
||||||
|
if finalShortSignal
|
||||||
|
dynMsgS = "Sell " + syminfo.ticker + " " + timeframe.period + " | Profile=" + activeProfile + " ATR=" + str.tostring(atrPeriod) + " Mult=" + str.tostring(multiplier) + " | RSI TF=" + rsiTf + " >= " + str.tostring(rsiOverbought)
|
||||||
|
alert(dynMsgS, alert.freq_once_per_bar_close)
|
||||||
219
README.md
Normal file
219
README.md
Normal 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 (v1–v4):
|
||||||
|
- 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., 100–200)
|
||||||
|
- 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)
|
||||||
|
- 1–4h
|
||||||
|
- 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).
|
||||||
|
- 1–4h: 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 don’t 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)
|
||||||
|
|
||||||
|
- 1–4h:
|
||||||
|
- 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.)
|
||||||
Reference in New Issue
Block a user