Changes: - moneyline_v11_2_indicator.pinescript: Alert format now includes SCORE:100 - parse_signal_enhanced.json: Added indicatorScore parsing (SCORE:X regex) - execute/route.ts: Added hasIndicatorScore bypass (score >= 90 bypasses quality check) - Money_Machine.json: Both Execute Trade nodes now pass indicatorScore to API Rationale: v11.2 indicator filters already optimized (2.544 PF, +51.80% return). Bot-side quality scoring was blocking proven profitable signals (e.g., quality 75). Now indicator passes SCORE:100, bot respects it and executes immediately. This completes the signal chain: Indicator (SCORE:100) → n8n parser (indicatorScore) → workflow → bot endpoint (bypass)
160 lines
9.4 KiB
Plaintext
160 lines
9.4 KiB
Plaintext
//@version=6
|
|
indicator("Bullmania Breaker v1 LOOSE", shorttitle="Breaker v1L", overlay=true)
|
|
// LOOSENED VERSION: More signals, less strict filters
|
|
// Changes from strict version:
|
|
// - ADX min: 18 → 12 (allow weaker trends)
|
|
// - Volume spike: 1.2 → 1.0 (disabled)
|
|
// - RSI bands: wider (40-72 long, 28-65 short)
|
|
// - Price position: looser (92% long max, 8% short min)
|
|
// - Confirm move: 0.25% → 0.15% (easier confirmation)
|
|
// - Squeeze threshold: 0.25 → 0.30 (more squeeze detection)
|
|
// - Release threshold: 0.35 → 0.30 (easier release)
|
|
// - Cooldown: 5 → 3 bars (more signals)
|
|
|
|
// Trend filters
|
|
emaFastLen = input.int(50, "EMA Fast", minval=1, group="Trend")
|
|
emaSlowLen = input.int(200, "EMA Slow", minval=1, group="Trend")
|
|
trendBuffer = input.float(0.05, "Trend buffer %", minval=0.0, step=0.05, group="Trend", tooltip="Reduced from 0.10 to catch more trend alignments.")
|
|
|
|
// Squeeze and release (LOOSENED)
|
|
bbLen = input.int(20, "BB Length", minval=1, group="Squeeze")
|
|
bbMult = input.float(2.0, "BB Multiplier", minval=0.1, step=0.1, group="Squeeze")
|
|
squeezeLookback = input.int(100, "Squeeze lookback", minval=10, group="Squeeze", tooltip="Reduced from 120 for more responsive squeeze detection.")
|
|
squeezeThreshold = input.float(0.30, "Squeeze threshold (0-1)", minval=0.0, maxval=1.0, step=0.05, group="Squeeze", tooltip="Increased from 0.25 to detect more squeezes.")
|
|
releaseThreshold = input.float(0.30, "Release threshold (0-1)", minval=0.0, maxval=1.0, step=0.05, group="Squeeze", tooltip="Reduced from 0.35 to trigger releases easier.")
|
|
releaseWindow = input.int(40, "Release window bars", minval=1, group="Squeeze", tooltip="Increased from 30 to allow more time for release.")
|
|
|
|
// Confirmation and filters (LOOSENED)
|
|
confirmPct = input.float(0.15, "Confirm move % (next bar)", minval=0.0, maxval=3.0, step=0.05, group="Confirmation", tooltip="Reduced from 0.25 for easier confirmation.")
|
|
volLookback = input.int(20, "Volume MA length", minval=1, group="Confirmation")
|
|
volSpike = input.float(1.0, "Volume spike x", minval=0.5, step=0.05, group="Confirmation", tooltip="DISABLED (was 1.2) - volume filter too restrictive.")
|
|
adxLen = input.int(14, "ADX length", minval=1, group="Confirmation")
|
|
adxMin = input.int(12, "ADX minimum", minval=0, maxval=100, group="Confirmation", tooltip="Reduced from 18 to allow weaker trends.")
|
|
rsiLen = input.int(14, "RSI length", minval=2, group="Confirmation")
|
|
rsiLongMin = input.float(40, "RSI long min", minval=0, maxval=100, group="Confirmation", tooltip="Reduced from 48 for more long signals.")
|
|
rsiLongMax = input.float(72, "RSI long max", minval=0, maxval=100, group="Confirmation", tooltip="Increased from 68 for more long signals.")
|
|
rsiShortMin = input.float(28, "RSI short min", minval=0, maxval=100, group="Confirmation", tooltip="Reduced from 32 for more short signals.")
|
|
rsiShortMax = input.float(65, "RSI short max", minval=0, maxval=100, group="Confirmation", tooltip="Increased from 60 for more short signals.")
|
|
priceRangeLookback = input.int(100, "Price position lookback", minval=10, group="Confirmation")
|
|
longPosMax = input.float(92, "Long max position %", minval=0, maxval=100, group="Confirmation", tooltip="Increased from 88 to allow longs closer to top.")
|
|
shortPosMin = input.float(8, "Short min position %", minval=0, maxval=100, group="Confirmation", tooltip="Reduced from 12 to allow shorts closer to bottom.")
|
|
cooldownBars = input.int(3, "Bars between signals", minval=0, maxval=100, group="Confirmation", tooltip="Reduced from 5 for more frequent signals.")
|
|
|
|
// Core series
|
|
emaFast = ta.ema(close, emaFastLen)
|
|
emaSlow = ta.ema(close, emaSlowLen)
|
|
trendGap = (emaSlow == 0.0) ? 0.0 : math.abs((emaFast - emaSlow) / emaSlow) * 100
|
|
trendLong = emaFast > emaSlow and trendGap >= trendBuffer
|
|
trendShort = emaFast < emaSlow and trendGap >= trendBuffer
|
|
|
|
basis = ta.sma(close, bbLen)
|
|
dev = bbMult * ta.stdev(close, bbLen)
|
|
upper = basis + dev
|
|
lower = basis - dev
|
|
widthPct = basis == 0.0 ? 0.0 : (upper - lower) / basis * 100.0
|
|
lowW = ta.lowest(widthPct, squeezeLookback)
|
|
highW = ta.highest(widthPct, squeezeLookback)
|
|
denom = math.max(highW - lowW, 0.0001)
|
|
normWidth = (widthPct - lowW) / denom
|
|
|
|
squeeze = normWidth < squeezeThreshold
|
|
barsSinceSqueeze = ta.barssince(squeeze)
|
|
recentSqueeze = not na(barsSinceSqueeze) and barsSinceSqueeze <= releaseWindow
|
|
release = normWidth > releaseThreshold and recentSqueeze
|
|
|
|
volMA = ta.sma(volume, volLookback)
|
|
volumeRatio = volMA == 0.0 ? 0.0 : volume / volMA
|
|
rsi = ta.rsi(close, rsiLen)
|
|
[_, _, adxVal] = ta.dmi(adxLen, adxLen)
|
|
|
|
highestRange = ta.highest(high, priceRangeLookback)
|
|
lowestRange = ta.lowest(low, priceRangeLookback)
|
|
rangeSpan = math.max(highestRange - lowestRange, 0.0001)
|
|
pricePos = ((close - lowestRange) / rangeSpan) * 100
|
|
|
|
// Breakout candidates
|
|
breakoutLong = trendLong and release and close > upper and volumeRatio >= volSpike and adxVal >= adxMin and rsi >= rsiLongMin and rsi <= rsiLongMax and pricePos <= longPosMax
|
|
breakoutShort = trendShort and release and close < lower and volumeRatio >= volSpike and adxVal >= adxMin and rsi >= rsiShortMin and rsi <= rsiShortMax and pricePos >= shortPosMin
|
|
|
|
// Cooldown gate
|
|
var int lastSignalBar = na
|
|
cooldownOk = na(lastSignalBar) or (bar_index - lastSignalBar > cooldownBars)
|
|
|
|
// Two-stage confirmation
|
|
var int pendingDir = 0
|
|
var float pendingPrice = na
|
|
var int pendingBar = na
|
|
var int pendingCount = na
|
|
finalLong = false
|
|
finalShort = false
|
|
|
|
if cooldownOk
|
|
if breakoutLong
|
|
pendingDir := 1
|
|
pendingPrice := close
|
|
pendingBar := bar_index
|
|
pendingCount := nz(pendingCount, 0) + 1
|
|
if breakoutShort
|
|
pendingDir := -1
|
|
pendingPrice := close
|
|
pendingBar := bar_index
|
|
pendingCount := nz(pendingCount, 0) + 1
|
|
|
|
if pendingDir != 0 and bar_index == pendingBar + 1
|
|
longPass = pendingDir == 1 and (confirmPct <= 0 or close >= pendingPrice * (1 + confirmPct / 100.0))
|
|
shortPass = pendingDir == -1 and (confirmPct <= 0 or close <= pendingPrice * (1 - confirmPct / 100.0))
|
|
if longPass
|
|
finalLong := true
|
|
lastSignalBar := bar_index
|
|
if shortPass
|
|
finalShort := true
|
|
lastSignalBar := bar_index
|
|
pendingDir := 0
|
|
pendingPrice := na
|
|
pendingBar := na
|
|
|
|
// Plots
|
|
plot(emaFast, "EMA Fast", color=color.new(color.green, 0), linewidth=1)
|
|
plot(emaSlow, "EMA Slow", color=color.new(color.red, 0), linewidth=1)
|
|
plot(basis, "BB Basis", color=color.new(color.gray, 50))
|
|
plot(upper, "BB Upper", color=color.new(color.teal, 40))
|
|
plot(lower, "BB Lower", color=color.new(color.teal, 40))
|
|
plotshape(squeeze, title="Squeeze", location=location.bottom, color=color.new(color.blue, 10), style=shape.square, size=size.tiny)
|
|
plotshape(release, title="Release", location=location.bottom, color=color.new(color.orange, 0), style=shape.triangleup, size=size.tiny)
|
|
|
|
plotshape(finalLong, title="Buy", location=location.belowbar, color=color.lime, style=shape.circle, size=size.small, text="B")
|
|
plotshape(finalShort, title="Sell", location=location.abovebar, color=color.red, style=shape.circle, size=size.small, text="S")
|
|
|
|
// Debug table showing filter status on current bar
|
|
var table debugTbl = table.new(position.top_right, 2, 8, bgcolor=color.new(color.black, 80))
|
|
if barstate.islast
|
|
table.cell(debugTbl, 0, 0, "Filter", text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 1, 0, "Status", text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 0, 1, "Trend", text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 1, 1, trendLong ? "LONG ✓" : trendShort ? "SHORT ✓" : "FLAT ✗", text_color=trendLong or trendShort ? color.lime : color.red, text_size=size.small)
|
|
table.cell(debugTbl, 0, 2, "Squeeze", text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 1, 2, squeeze ? "YES" : "no", text_color=squeeze ? color.lime : color.gray, text_size=size.small)
|
|
table.cell(debugTbl, 0, 3, "Release", text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 1, 3, release ? "YES ✓" : "no", text_color=release ? color.lime : color.gray, text_size=size.small)
|
|
table.cell(debugTbl, 0, 4, "ADX " + str.tostring(adxMin), text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 1, 4, str.tostring(adxVal, "#.#") + (adxVal >= adxMin ? " ✓" : " ✗"), text_color=adxVal >= adxMin ? color.lime : color.red, text_size=size.small)
|
|
table.cell(debugTbl, 0, 5, "RSI", text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 1, 5, str.tostring(rsi, "#.#"), text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 0, 6, "PricePos", text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 1, 6, str.tostring(pricePos, "#.#") + "%", text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 0, 7, "Vol Ratio", text_color=color.white, text_size=size.small)
|
|
table.cell(debugTbl, 1, 7, str.tostring(volumeRatio, "#.##") + "x", text_color=volumeRatio >= volSpike ? color.lime : color.gray, text_size=size.small)
|
|
|
|
// Alerts
|
|
baseCurrency = str.replace(syminfo.ticker, "USDT", "")
|
|
baseCurrency := str.replace(baseCurrency, "USD", "")
|
|
baseCurrency := str.replace(baseCurrency, "PERP", "")
|
|
ver = "breaker_v1L"
|
|
common = " | ADX:" + str.tostring(adxVal, "#.##") + " | RSI:" + str.tostring(rsi, "#.##") + " | VOL:" + str.tostring(volumeRatio, "#.##") + " | POS:" + str.tostring(pricePos, "#.##") + " | IND:" + ver
|
|
longMsg = baseCurrency + " buy " + timeframe.period + common
|
|
shortMsg = baseCurrency + " sell " + timeframe.period + common
|
|
if finalLong
|
|
alert(longMsg, alert.freq_once_per_bar_close)
|
|
if finalShort
|
|
alert(shortMsg, alert.freq_once_per_bar_close)
|