| Inicio | Bolsa |
| Fordumen //@version=5 indicator("Fordumen", overlay=true) //---------------------------------------------------- // PARAMETROS //---------------------------------------------------- volPeriod = input.int(15, "Media de Volumen") rsiPeriod = input.int(14, "Periodo RSI") rsiMaPeriod = input.int(21, "Media RSI") macdFast = input.int(12) macdSlow = input.int(26) macdSignal = input.int(9) diLength = input.int(14, "DI Length") adxSmoothing = input.int(14, "ADX Smoothing") //---------------------------------------------------- // VOLUMEN //---------------------------------------------------- volMA = ta.sma(volume, volPeriod) volScore = volume < volMA * 0.8 ? 1 : volume <= volMA * 1.2 ? 2 : 3 //---------------------------------------------------- // RSI //---------------------------------------------------- rsi = ta.rsi(close, rsiPeriod) rsiMA = ta.ema(rsi, rsiMaPeriod) rsiScore = rsi < 50 and ta.crossover(rsi, rsiMA) ? 3 : rsi > rsiMA ? 2 : 1 //---------------------------------------------------- // MACD //---------------------------------------------------- [macdLine, macdSignalLine, hist] = ta.macd(close, macdFast, macdSlow, macdSignal) macdScore = macdLine > macdSignalLine and hist > 0 ? 3 : macdLine > macdSignalLine ? 2 : 1 //---------------------------------------------------- // TENDENCIA //---------------------------------------------------- ema9 = ta.ema(close, 9) emaSlope = ema9 - ema9[5] slopeScore = emaSlope > 0 ? 3 : emaSlope > -0.1 ? 2 : 1 //---------------------------------------------------- // KONCORDE (simplificado) //---------------------------------------------------- nvi = ta.nvi nvim = ta.ema(nvi, 15) bigMoneyBuying = nvi > nvim //---------------------------------------------------- // ADX / DI //---------------------------------------------------- [plusDI, minusDI, adx] = ta.dmi(diLength, adxSmoothing) adxRising = adx > adx[1] bullishDI = plusDI > minusDI bearishDI = minusDI > plusDI //---------------------------------------------------- // SCORE BASE //---------------------------------------------------- rawScore = volScore + rsiScore + macdScore + slopeScore baseScore = math.round(((rawScore - 4) / 8) * 9 + 1) //---------------------------------------------------- // BONUS ADX //---------------------------------------------------- adxBonus = (bullishDI and adxRising) ? 1 : 0 scoreWithADX = math.min(baseScore + adxBonus, 10) //---------------------------------------------------- // FILTROS ESTRUCTURALES //---------------------------------------------------- scoreAfterKoncorde = bigMoneyBuying ? scoreWithADX : math.min(scoreWithADX, 6) finalScore = bearishDI ? math.min(scoreAfterKoncorde, 6) : scoreAfterKoncorde //---------------------------------------------------- // SCORE ANTERIOR //---------------------------------------------------- prevScore = finalScore[1] //---------------------------------------------------- // COLOR SCORE //---------------------------------------------------- scoreColor = finalScore >= 9 ? color.lime : finalScore >= 7 ? color.green : finalScore >= 5 ? color.yellow : finalScore >= 3 ? color.orange : color.red //---------------------------------------------------- // LABEL //---------------------------------------------------- var label scoreLabel = na if barstate.islast label.delete(scoreLabel) labelText = str.tostring(prevScore) + " | " + str.tostring(finalScore) + " | " + str.tostring(math.round(rsi)) scoreLabel := label.new( bar_index + 3, close, labelText, style = label.style_label_left, color = scoreColor, textcolor = color.black, size = size.large) //---------------------------------------------------- // MEDIAS //---------------------------------------------------- plot(ta.ema(close,10), color=color.aqua, linewidth=2) plot(ta.ema(close,21), color=color.black, linewidth=2) plot(ta.sma(close,150), color=color.yellow, linewidth=2) plot(ta.sma(close,200), color=color.orange, linewidth=2) |
| Canales (Nombre original: "SRChannels") //@version=6 // ══════════════════════════════════════════════════════════ // AUTO SUPPORT & RESISTANCE CHANNELS [WillyAlgoTrader] // ══════════════════════════════════════════════════════════ // Author: Willy | WillyAlgoTrader // Version: 1.4.3 indicator( title = "Auto S/R Channels [WillyAlgoTrader]", shorttitle = "Canales", overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500, max_bars_back = 5000) // ══════════════════════════════════════ // CONSTANTS // ══════════════════════════════════════ GRP_MAIN = "⚙️ Main Settings" GRP_VISUAL = "🎨 Visual Settings" GRP_CHAN = "📏 Channels" GRP_STYLE_BASE = "📐 Base Line Style" GRP_STYLE_PAR = "📐 Parallel Line Style" GRP_STYLE_MID = "📐 Midline Style" GRP_DASH = "📊 Dashboard" GRP_ALERT = "🔔 Alerts" GRP_COLORS = "🎨 Colors" INDICATOR_VERSION = "v1.4.3" int MAX_PIVOTS = 40 // ══════════════════════════════════════ // INPUTS // ══════════════════════════════════════ pivotLenInput = input.int(21, "Pivot Length", minval = 2, maxval = 50, group = GRP_MAIN, tooltip = "Lookback/forward
bars for pivot detection.\n• Higher = fewer, stronger pivots\n• Lower =
more pivots, noisier\n• Recommended: 3–10") atrLenInput = input.int(14, "ATR Length", minval = 5, maxval = 100, group = GRP_MAIN, tooltip = "ATR period for calculations.\nRecommended: 14") minChannelBarsInput = input.int(10, "Min Channel Bars", minval = 3, maxval = 200, group = GRP_MAIN, tooltip = "Minimum distance between two base pivots.\nRecommended: 10–30") maxChannelBarsInput = input.int(400, "Max Channel Bars", minval = 30, maxval = 2000, group = GRP_MAIN, tooltip = "Maximum lookback for channel base pivots.\nRecommended: 200–500") channelQualityInput = input.float(0.55, "Min Channel Quality", minval = 0.2, maxval = 1.0, step = 0.05, group = GRP_MAIN, tooltip = "Min fraction of bars contained in channel.\n• Higher = stricter, fewer channels\n• Recommended: 0.5–0.8") themeInput = input.string("Auto", "Theme", options = ["Auto", "Dark", "Light"], group = GRP_VISUAL, tooltip = "Chart theme.\n• Auto: detect from background\n• Dark/Light: manual override") showMidlineInput = input.bool(true, "Show Midlines", group = GRP_VISUAL, tooltip = "Dotted midline inside channels") showFillInput = input.bool(true, "Show Channel Fill", group = GRP_VISUAL, tooltip = "Very subtle channel fill") showWatermarkInput = input.bool(true, "Show Watermark", group = GRP_VISUAL, tooltip = "Display 'WillyAlgoTrader - by Willy' watermark") extendInput = input.string("Right", "Extend Channels", options = ["None", "Right", "Both"], group = GRP_VISUAL, tooltip = "Extend channel lines beyond pivot range") deletePrevInput = input.bool(true, "Delete Previous", group = GRP_VISUAL, tooltip = "Remove old channels when new ones form") showBreakoutLblInput = input.bool(true, "Show Breakout Label", group = GRP_VISUAL, tooltip = "Label on the last channel breakout bar") showUpInput = input.bool(true, "Show Up Channels", group = GRP_CHAN, tooltip = "Ascending channels from pivot lows") showDownInput = input.bool(true, "Show Down Channels", group = GRP_CHAN, tooltip = "Descending channels from pivot highs") baseWidthInput = input.int(2, "Width", minval = 1, maxval = 5, group = GRP_STYLE_BASE) baseStyleInput = input.string("Solid", "Style", options = ["Solid", "Dashed", "Dotted"], group = GRP_STYLE_BASE) parWidthInput = input.int(2, "Width", minval = 1, maxval = 5, group = GRP_STYLE_PAR) parStyleInput = input.string("Solid", "Style", options = ["Solid", "Dashed", "Dotted"], group = GRP_STYLE_PAR) midWidthInput = input.int(1, "Width", minval = 1, maxval = 3, group = GRP_STYLE_MID) midStyleInput = input.string("Dashed", "Style", options = ["Solid", "Dashed", "Dotted"], group = GRP_STYLE_MID) bullColorInput = input.color(#00E676, "Bull / Up", inline = "col1", group = GRP_COLORS) bearColorInput = input.color(#FF5252, "Bear / Down", inline = "col1", group = GRP_COLORS) fillTranspInput = input.int(95, "Fill Transparency", minval = 80, maxval = 99, group = GRP_COLORS) showDashInput = input.bool(false, "Show Dashboard", group = GRP_DASH) dashPosStr = input.string("Top Right", "Position", options = ["Top Left", "Top Right", "Bottom Left", "Bottom Right"], group = GRP_DASH) alertBreakInput = input.bool(true, "Alert on Signal", group = GRP_ALERT) alertBreakoutInput = input.bool(true, "Alert on Breakout", group = GRP_ALERT) alertReactInput = input.bool(true, "Alert on React", group = GRP_ALERT) webhookInput = input.bool(false, "Webhook JSON", group = GRP_ALERT) // ══════════════════════════════════════ // THEME // ══════════════════════════════════════ isDark = switch themeInput "Dark" => true "Light" => false => color.r(chart.bg_color) < 128 TEXT_COLOR = isDark ? #E0E0E0 : #1A1A1A TEXT_MUTED = isDark ? color.new(#9E9E9E, 0) : color.new(#757575, 0) TABLE_BG = isDark ? color.new(#131722, 5) : color.new(#FFFFFF, 5) TABLE_BORDER = isDark ? color.new(#2A2E39, 0) : color.new(#D0D0D0, 0) TABLE_ROW_ALT = isDark ? color.new(#1C2030, 0) : color.new(#F0F4F8, 0) HEADER_BG = color.new(#2962FF, 0) HEADER_TEXT = #FFFFFF WM_COLOR = isDark ? color.new(#FFFFFF, 80) : color.new(#000000, 80) // ══════════════════════════════════════ // FUNCTIONS // ══════════════════════════════════════ safeDiv(float num, float den, float fallback = 0.0) => den != 0 and not na(num) and not na(den) ? num / den : fallback getLineStyle(string s) => switch s "Dashed" => line.style_dashed "Dotted" => line.style_dotted => line.style_solid getExtend(string s) => switch s "Right" => extend.right "Both" => extend.both => extend.none linePrice(int x1, float y1, int x2, float y2, int bx) => x2 == x1 ? y1 : y1 + (y2 - y1) / (x2 - x1) * (bx - x1) clearLabels(array<label> arr) => if array.size(arr) > 0 for i = array.size(arr) - 1 to 0 label.delete(array.get(arr, i)) array.clear(arr) // Direction-agnostic "is price inside channel?" isInside(float p, float baseLine, float off) => float upper = math.max(baseLine, baseLine + off) float lower = math.min(baseLine, baseLine + off) p >= lower and p <= upper // ══════════════════════════════════════ // PIVOT DETECTION // ══════════════════════════════════════ int WARMUP_BARS = math.max(pivotLenInput * 4, 50) bool isWarmedUp = bar_index >= WARMUP_BARS float atrVal = nz(ta.atr(atrLenInput), high - low) float pivotHi = ta.pivothigh(high, pivotLenInput, pivotLenInput) float pivotLo = ta.pivotlow(low, pivotLenInput, pivotLenInput) var array<float> hiPrices = array.new_float() var array<int> hiIndices = array.new_int() var array<float> loPrices = array.new_float() var array<int> loIndices = array.new_int() if not na(pivotHi) array.push(hiPrices, pivotHi) array.push(hiIndices, bar_index - pivotLenInput) if array.size(hiPrices) > MAX_PIVOTS array.shift(hiPrices) array.shift(hiIndices) if not na(pivotLo) array.push(loPrices, pivotLo) array.push(loIndices, bar_index - pivotLenInput) if array.size(loPrices) > MAX_PIVOTS array.shift(loPrices) array.shift(loIndices) // ══════════════════════════════════════════════════════════ // CHANNEL SEARCH // ══════════════════════════════════════════════════════════ findUpChannel(array<float> basePrices, array<int> baseIndices, array<float> oppPrices, array<int> oppIndices) => int bestX1 = na float bestY1 = na int bestX2 = na float bestY2 = na float bestOffset = na float bestQual = 0.0 int sz = array.size(basePrices) if sz >= 2 int pairsToCheck = math.min(sz, 8) for i = sz - 1 to math.max(0, sz - pairsToCheck) for j = i - 1 to math.max(0, i - 5) int ix1 = array.get(baseIndices, j) float iy1 = array.get(basePrices, j) int ix2 = array.get(baseIndices, i) float iy2 = array.get(basePrices, i) int span = ix2 - ix1 if span < minChannelBarsInput or span > maxChannelBarsInput continue if iy2 <= iy1 continue float maxOff = 0.0 int oppSz = array.size(oppPrices) if oppSz > 0 for k = 0 to oppSz - 1 int oppBar = array.get(oppIndices, k) if oppBar >= ix1 and oppBar <= bar_index float oppVal = array.get(oppPrices, k) float baseAtOpp = linePrice(ix1, iy1, ix2, iy2, oppBar) float diff = oppVal - baseAtOpp if diff > maxOff maxOff := diff if maxOff <= 0 continue int totalBars = bar_index - ix1 if totalBars <= 0 continue int checkLen = math.min(totalBars, 300) int contained = 0 float tol = atrVal * 0.05 for k = 0 to checkLen - 1 int bx = bar_index - k if bx < ix1 break float bLine = linePrice(ix1, iy1, ix2, iy2, bx) float pLine = bLine + maxOff if low[k] >= bLine - tol and high[k] <= pLine + tol contained += 1 float qual = safeDiv(float(contained), float(checkLen), 0.0) if qual >= channelQualityInput and qual > bestQual bestQual := qual bestX1 := ix1 bestY1 := iy1 bestX2 := ix2 bestY2 := iy2 bestOffset := maxOff [bestX1, bestY1, bestX2, bestY2, bestOffset, bestQual] findDownChannel(array<float> basePrices, array<int> baseIndices, array<float> oppPrices, array<int> oppIndices) => int bestX1 = na float bestY1 = na int bestX2 = na float bestY2 = na float bestOffset = na float bestQual = 0.0 int sz = array.size(basePrices) if sz >= 2 int pairsToCheck = math.min(sz, 8) for i = sz - 1 to math.max(0, sz - pairsToCheck) for j = i - 1 to math.max(0, i - 5) int ix1 = array.get(baseIndices, j) float iy1 = array.get(basePrices, j) int ix2 = array.get(baseIndices, i) float iy2 = array.get(basePrices, i) int span = ix2 - ix1 if span < minChannelBarsInput or span > maxChannelBarsInput continue if iy2 >= iy1 continue float minOff = 0.0 int oppSz = array.size(oppPrices) if oppSz > 0 for k = 0 to oppSz - 1 int oppBar = array.get(oppIndices, k) if oppBar >= ix1 and oppBar <= bar_index float oppVal = array.get(oppPrices, k) float baseAtOpp = linePrice(ix1, iy1, ix2, iy2, oppBar) float diff = oppVal - baseAtOpp if diff < minOff minOff := diff if minOff >= 0 continue int totalBars = bar_index - ix1 if totalBars <= 0 continue int checkLen = math.min(totalBars, 300) int contained = 0 float tol = atrVal * 0.05 for k = 0 to checkLen - 1 int bx = bar_index - k if bx < ix1 break float bLine = linePrice(ix1, iy1, ix2, iy2, bx) float pLine = bLine + minOff if high[k] <= bLine + tol and low[k] >= pLine - tol contained += 1 float qual = safeDiv(float(contained), float(checkLen), 0.0) if qual >= channelQualityInput and qual > bestQual bestQual := qual bestX1 := ix1 bestY1 := iy1 bestX2 := ix2 bestY2 := iy2 bestOffset := minOff [bestX1, bestY1, bestX2, bestY2, bestOffset, bestQual] // ══════════════════════════════════════ // CHANNEL STATE // ══════════════════════════════════════ var int chUpX1 = na, var float chUpY1 = na var int chUpX2 = na, var float chUpY2 = na var float chUpOff = na, var float chUpQual = na var bool chUpOn = false var int chDnX1 = na, var float chDnY1 = na var int chDnX2 = na, var float chDnY2 = na var float chDnOff = na, var float chDnQual = na var bool chDnOn = false var line chUpLnBase = na, var line chUpLnPar = na, var line chUpLnMid = na var line chDnLnBase = na, var line chDnLnPar = na, var line chDnLnMid = na var linefill chUpFill1 = na, var linefill chUpFill2 = na var linefill chDnFill1 = na, var linefill chDnFill2 = na // ══════════════════════════════════════ // BREAKOUT STATE & LABEL ARRAYS // ══════════════════════════════════════ var bool upBullFired = false var bool upBearFired = false var bool dnBullFired = false var bool dnBearFired = false var array<label> upLabels = array.new_label() var array<label> dnLabels = array.new_label() // ══════════════════════════════════════ // CHANNEL SEARCH TRIGGER // ══════════════════════════════════════ // FIX: Both channels are searched on ANY new pivot (newHi or newLo). // Previously down-channel was only searched on newHi, causing it // to go stale and never produce breakouts. bool newHi = not na(pivotHi) bool newLo = not na(pivotLo) if (newHi or newLo) and isWarmedUp // ── Up channel (needs ≥2 lows) ── if array.size(loPrices) >= 2 [bx1, by1, bx2, by2, boff, bq] = findUpChannel(loPrices, loIndices, hiPrices, hiIndices) if not na(bx1) and bq > nz(chUpQual, 0.0) * 0.7 // Check if channel actually changed (different pivot bars) bool upChanged = bx1 != chUpX1 or bx2 != chUpX2 chUpX1 := bx1 chUpY1 := by1 chUpX2 := bx2 chUpY2 := by2 chUpOff := boff chUpQual := bq chUpOn := true if upChanged clearLabels(upLabels) float newB = linePrice(bx1, by1, bx2, by2, bar_index) if isInside(close, newB, boff) upBullFired := false upBearFired := false else upBullFired := close > math.max(newB, newB + boff) upBearFired := close < math.min(newB, newB + boff) // ── Down channel (needs ≥2 highs) ── if array.size(hiPrices) >= 2 [bx1, by1, bx2, by2, boff, bq] = findDownChannel(hiPrices, hiIndices, loPrices, loIndices) if not na(bx1) and bq > nz(chDnQual, 0.0) * 0.7 // Check if channel actually changed (different pivot bars) bool dnChanged = bx1 != chDnX1 or bx2 != chDnX2 chDnX1 := bx1 chDnY1 := by1 chDnX2 := bx2 chDnY2 := by2 chDnOff := boff chDnQual := bq chDnOn := true if dnChanged clearLabels(dnLabels) float newB = linePrice(bx1, by1, bx2, by2, bar_index) if isInside(close, newB, boff) dnBullFired := false dnBearFired := false else dnBullFired := close > math.max(newB, newB + boff) dnBearFired := close < math.min(newB, newB + boff) // ══════════════════════════════════════ // DRAWING // ══════════════════════════════════════ lineExt = getExtend(extendInput) baseStyl = getLineStyle(baseStyleInput) parStyl = getLineStyle(parStyleInput) midStyl = getLineStyle(midStyleInput) drawCh(int x1, float y1, int x2, float y2, float off, bool active, bool show, line prevBase, line prevPar, line prevMid, linefill prevF1, linefill prevF2, color col, bool del) => line nBase = prevBase line nPar = prevPar line nMid = prevMid linefill nF1 = prevF1 linefill nF2 = prevF2 if active and show and not na(x1) and not na(x2) and not na(off) if del line.delete(prevBase) line.delete(prevPar) line.delete(prevMid) linefill.delete(prevF1) linefill.delete(prevF2) nBase := line.new(x1, y1, x2, y2, color = col, width = baseWidthInput, style = baseStyl, extend = lineExt) nPar := line.new(x1, y1 + off, x2, y2 + off, color = col, width = parWidthInput, style = parStyl, extend = lineExt) if showMidlineInput nMid := line.new(x1, y1 + off / 2, x2, y2 + off / 2, color = col, width = midWidthInput, style = midStyl, extend = lineExt) if showFillInput color fc = color.new(col, fillTranspInput) if not na(nMid) nF1 := linefill.new(nBase, nMid, fc) nF2 := linefill.new(nMid, nPar, fc) else nF1 := linefill.new(nBase, nPar, fc) [nBase, nPar, nMid, nF1, nF2] if (newHi or newLo) and isWarmedUp [a1, a2, a3, a4, a5] = drawCh(chUpX1, chUpY1, chUpX2, chUpY2, chUpOff, chUpOn, showUpInput, chUpLnBase, chUpLnPar, chUpLnMid, chUpFill1, chUpFill2, bullColorInput, deletePrevInput) chUpLnBase := a1 chUpLnPar := a2 chUpLnMid := a3 chUpFill1 := a4 chUpFill2 := a5 [b1, b2, b3, b4, b5] = drawCh(chDnX1, chDnY1, chDnX2, chDnY2, chDnOff, chDnOn, showDownInput, chDnLnBase, chDnLnPar, chDnLnMid, chDnFill1, chDnFill2, bearColorInput, deletePrevInput) chDnLnBase := b1 chDnLnPar := b2 chDnLnMid := b3 chDnFill1 := b4 chDnFill2 := b5 // ══════════════════════════════════════════════════════════ // BREAKOUT & SIGNAL DETECTION // ══════════════════════════════════════════════════════════ // Direction-agnostic upper/lower boundaries: // Up channel: upper = base + off (off > 0), lower = base //
Down channel: upper = base,
lower = base + off (off < 0) // // GUARD 1: max extrapolation = maxChannelBarsInput beyond X2 // GUARD 2: crossover — close[1] within 1.5 ATR of boundary // GUARD 3: smart flag reset on channel rebuild (price-aware) // GUARD 4: all old labels deleted on channel rebuild bool upBreakSup = false bool upBreakRes = false bool upReactSup = false bool upReactRes = false bool dnBreakSup = false bool dnBreakRes = false bool dnReactSup = false bool dnReactRes = false int upBoDir = 0 int dnBoDir = 0 if barstate.isconfirmed and isWarmedUp // ══ Up channel (off > 0: base = support, par = resistance) ══ if chUpOn and not na(chUpX1) and not na(chUpX2) and not na(chUpOff) if (bar_index - chUpX2) <= maxChannelBarsInput // GUARD 1 float bNow = linePrice(chUpX1, chUpY1, chUpX2, chUpY2, bar_index) float pNow = bNow + chUpOff
// resistance float bPrev = linePrice(chUpX1, chUpY1, chUpX2, chUpY2, bar_index - 1) float pPrev = bPrev + chUpOff float tol = atrVal * 0.12 float crossTol = atrVal * 1.5
// GUARD 2 if close >= bNow and close <= pNow upBullFired := false upBearFired := false // Bull breakout: close > resistance if close > pNow and not upBullFired and close[1] <= pPrev + crossTol upBoDir := 1 upBullFired := true upBreakRes := true if showBreakoutLblInput array.push(upLabels, label.new(bar_index, low, "Breakout ▲", color = bullColorInput, textcolor = #FFFFFF, style = label.style_label_up, size = size.small)) // Bear breakout: close < support if close < bNow and not upBearFired and close[1] >= bPrev - crossTol upBoDir := -1 upBearFired := true upBreakSup := true if showBreakoutLblInput array.push(upLabels, label.new(bar_index, high, "Breakout ▼", color = bearColorInput, textcolor = #FFFFFF, style = label.style_label_down, size = size.small)) if not upBreakSup and low <= bNow + tol and close > bNow and open > bNow upReactSup := true if not upBreakRes and high >= pNow - tol and close < pNow and open < pNow upReactRes := true // ══ Down channel (off < 0: base = resistance [upper], par = support [lower]) ══ if chDnOn and not na(chDnX1) and not na(chDnX2) and not na(chDnOff) if (bar_index - chDnX2) <= maxChannelBarsInput // GUARD 1 float bNow = linePrice(chDnX1, chDnY1, chDnX2, chDnY2, bar_index) // upper boundary float pNow = bNow + chDnOff
// lower boundary (off < 0) float bPrev = linePrice(chDnX1, chDnY1, chDnX2, chDnY2, bar_index - 1) float pPrev = bPrev + chDnOff float tol = atrVal * 0.12 float crossTol = atrVal * 1.5
// GUARD 2 if close <= bNow and close >= pNow dnBullFired := false dnBearFired := false // Bull breakout: close > base (upper boundary / resistance) if close > bNow and not dnBullFired and close[1] <= bPrev + crossTol dnBoDir := 1 dnBullFired := true dnBreakRes := true if showBreakoutLblInput array.push(dnLabels, label.new(bar_index, low, "Breakout ▲", color = bullColorInput, textcolor = #FFFFFF, style = label.style_label_up, size = size.small)) // Bear breakout: close < parallel (lower boundary / support) if close < pNow and not dnBearFired and close[1] >= pPrev - crossTol dnBoDir := -1 dnBearFired := true dnBreakSup := true if showBreakoutLblInput array.push(dnLabels, label.new(bar_index, high, "Breakout ▼", color = bearColorInput, textcolor = #FFFFFF, style = label.style_label_down, size = size.small)) // React at resistance (upper = base) if not dnBreakRes and high >= bNow - tol and close < bNow and open < bNow dnReactRes := true // React at support (lower = parallel) if not dnBreakSup and low <= pNow + tol and close > pNow and open > pNow dnReactSup := true // ═══ Aggregated signals ═══ bool bullSig = upReactSup or dnBreakRes bool bearSig = upBreakSup or dnReactRes bool hasBullBreakout = upBoDir == 1 or dnBoDir == 1 bool hasBearBreakout = upBoDir == -1 or dnBoDir == -1 bool hasBreakout = hasBullBreakout or hasBearBreakout bool anyReact = upReactSup or upReactRes or dnReactSup or dnReactRes // ══════════════════════════════════════ // TREND & DASHBOARD DATA // ══════════════════════════════════════ float upBase = chUpOn and not na(chUpX1) ? linePrice(chUpX1, chUpY1, chUpX2, chUpY2, bar_index) : na float dnBase = chDnOn and not na(chDnX1) ? linePrice(chDnX1, chDnY1, chDnX2, chDnY2, bar_index) : na int trendDir = 0 if not na(upBase) and close > upBase trendDir := 1 if not na(dnBase) and close < dnBase trendDir := -1 string trendStr = trendDir > 0 ? "Bullish" : trendDir < 0 ? "Bearish" : "Neutral" color trendCol = trendDir > 0 ? bullColorInput : trendDir < 0 ? bearColorInput : TEXT_MUTED string sigStr = bullSig ? "Buy" : bearSig ? "Sell" : "Wait" color sigCol = bullSig ? bullColorInput : bearSig ? bearColorInput : TEXT_MUTED float bestQ = math.max(nz(chUpQual, 0.0), nz(chDnQual, 0.0)) float sigPower = (bullSig or bearSig) ? bestQ * 100 : 0.0 string strStr = sigPower >= 70 ? "Strong" : sigPower >= 40 ? "Medium" : sigPower > 0 ? "Weak" : "—" color strCol = sigPower >= 70 ? bullColorInput : sigPower >= 40 ? #FFEB3B : sigPower > 0 ? bearColorInput : TEXT_MUTED // ══════════════════════════════════════ // DASHBOARD // ══════════════════════════════════════ dashPos = switch dashPosStr "Top Left" => position.top_left "Top Right" => position.top_right "Bottom Left" => position.bottom_left "Bottom Right" => position.bottom_right => position.top_right if showDashInput and barstate.islast var dashTable = table.new(dashPos, 2, 7, TABLE_BG, TABLE_BORDER, 1, TABLE_BORDER, 1) table.cell(dashTable, 0, 0, "WillyAlgoTrader", text_color = HEADER_TEXT, bgcolor = HEADER_BG, text_size = size.small, text_halign = text.align_center) table.merge_cells(dashTable, 0, 0, 1, 0) table.cell(dashTable, 0, 1, "Trend", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_BG) table.cell(dashTable, 1, 1, trendStr, text_color = trendCol, text_size = size.small, bgcolor = TABLE_BG) table.cell(dashTable, 0, 2, "Signal", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_ROW_ALT) table.cell(dashTable, 1, 2, sigStr, text_color = sigCol, text_size = size.small, bgcolor = TABLE_ROW_ALT) table.cell(dashTable, 0, 3, "Strength", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_BG) table.cell(dashTable, 1, 3, strStr, text_color = strCol, text_size = size.small, bgcolor = TABLE_BG) int actCh = (chUpOn ? 1 : 0) + (chDnOn ? 1 : 0) table.cell(dashTable, 0, 4, "Channels", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_ROW_ALT) table.cell(dashTable, 1, 4, str.tostring(actCh), text_color = TEXT_COLOR, text_size = size.small, bgcolor = TABLE_ROW_ALT) string qStr = bestQ > 0 ? str.tostring(bestQ * 100, "#.0") + "%" : "—" table.cell(dashTable, 0, 5, "Quality", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_BG) table.cell(dashTable, 1, 5, qStr, text_color = TEXT_COLOR, text_size = size.small, bgcolor = TABLE_BG) table.cell(dashTable, 0, 6, "TF", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_ROW_ALT) table.cell(dashTable, 1, 6, timeframe.period, text_color = TEXT_COLOR, text_size = size.small, bgcolor = TABLE_ROW_ALT) // ══════════════════════════════════════ // WATERMARK // ══════════════════════════════════════ if barstate.islast and showWatermarkInput var wmTable = table.new(position = position.bottom_center, columns = 1, rows = 1, bgcolor = color.new(color.black, 100), border_color = color.new(color.black, 100), border_width = 0, frame_color = color.new(color.black, 100), frame_width = 0) table.cell(wmTable, 0, 0, "WillyAlgoTrader", text_color = WM_COLOR, text_size = size.normal, text_halign = text.align_center, bgcolor = color.new(color.black, 100)) // ══════════════════════════════════════ // ALERTS // ══════════════════════════════════════ string ticker = syminfo.tickerid string tf = timeframe.period string priceS = str.tostring(close, format.mintick) if hasBreakout and alertBreakoutInput string dir = hasBullBreakout ? "bull" : "bear" string ico = hasBullBreakout ? "🟢" : "🔴" string jm = '{"action":"breakout","direction":"' + dir + '","ticker":"' + ticker + '","price":' + priceS + ',"tf":"' + tf + '"}' string tm = ico + " BREAKOUT " + str.upper(dir) + " | " + ticker + " | TF: " + tf + " | Price: " + priceS alert(webhookInput ? jm : tm, alert.freq_once_per_bar_close) else if bullSig and alertBreakInput string jm = '{"action":"buy","ticker":"' + ticker + '","price":' + priceS + ',"tf":"' + tf + '"}' string tm = "🟢 BULL | " + ticker + " | TF: " + tf + " | Price: " + priceS + " | Channel Signal" alert(webhookInput ? jm : tm, alert.freq_once_per_bar_close) else if bearSig and alertBreakInput string jm = '{"action":"sell","ticker":"' + ticker + '","price":' + priceS + ',"tf":"' + tf + '"}' string tm = "🔴 BEAR | " + ticker + " | TF: " + tf + " | Price: " + priceS + " | Channel Signal" alert(webhookInput ? jm : tm, alert.freq_once_per_bar_close) else if anyReact and alertReactInput string jm = '{"action":"react","ticker":"' + ticker + '","price":' + priceS + ',"tf":"' + tf + '"}' string tm = "🟡 REACT | " + ticker + " | TF: " + tf + " | Price: " + priceS + " | Channel Reaction" alert(webhookInput ? jm : tm, alert.freq_once_per_bar_close) |