//@version=6 // ANALYSE_2 Swing-Breakout Strategy — Pine port of REGELN_2.md. // // Swings = Bill-Williams fractals, kept strictly ALTERNATING: consecutive // same-kind fractals collapse to their extreme (highest high / lowest low of the // run), so every "higher low" is a pullback low after a rally high. // // Long : last (nSteps+1) swing lows strictly rising (1 higher low by default) // AND close breaks above the last swing high. Exit when close < last // swing low (trailing stop) OR a confirmed lower high breaks the uptrend. // Short : mirror image (lower highs; enter on close < last swing low; exit on // close > last swing high OR a confirmed higher low). // // A fractal's center sits k bars back and is only KNOWN k bars later, so signals // use past/current bars only (no lookahead). The signal forms on the bar close; // the order fills at the NEXT bar's open (process_orders_on_close = false). // // Fractal length is parameterised by k: length = 2*k + 1 (k=1 → 3, k=2 → 5, // k=5 → 11). k is also the confirmation lag and the ANALYSE_{k} number. strategy( title = "ANALYSE_2 Swing-Breakout", shorttitle = "A2-SwingBO", overlay = true, calc_on_every_tick = false, // evaluate the signal once per closed bar process_orders_on_close = false, // fill at the NEXT bar's open, not this close pyramiding = 0, max_lines_count = 500, // ZigZag segments (TV keeps the most recent 500) default_qty_type = strategy.percent_of_equity, default_qty_value = 100, initial_capital = 10000, commission_type = strategy.commission.percent, commission_value = 0.0) // % per order. Pine needs a const here; set // costs via Strategy Tester → Properties → Commission. // ---- Inputs ------------------------------------------------------------- int k = input.int(2, "Fractal k (bars per side)", minval = 1, group = "Swings", tooltip = "Fractal length = 2*k + 1 (k=1 → 3 bars, k=2 → 5, k=5 → 11). k is also the confirmation lag.") int nSteps = input.int(1, "Confirmation steps", minval = 1, maxval = 5, group = "Swings", tooltip = "Long needs the last nSteps+1 swing lows strictly rising (1 = one higher low). Short mirrored.") bool allowLong = input.bool(true, "Allow long", group = "Direction") bool allowShort = input.bool(true, "Allow short", group = "Direction") bool showLevels = input.bool(true, "Show last high/low levels", group = "Display") bool showSwings = input.bool(true, "Mark confirmed swings", group = "Display") bool showZigZag = input.bool(true, "Draw ZigZag", group = "Display") color zzColor = input.color(#7e57c2, "ZigZag color", group = "Display") // ---- Fractal detection (center k bars back, confirmed now; strict) ------ f_fractalHigh(int kk) => bool ok = bar_index >= 2 * kk if ok float cc = high[kk] for i = 1 to kk if high[kk + i] >= cc or high[kk - i] >= cc ok := false ok f_fractalLow(int kk) => bool ok = bar_index >= 2 * kk if ok float cc = low[kk] for i = 1 to kk if low[kk + i] <= cc or low[kk - i] <= cc ok := false ok // Strictly increasing / decreasing over the last n elements of an array. f_incLast(array a, int n) => bool ok = array.size(a) >= n if ok for i = array.size(a) - n + 1 to array.size(a) - 1 if array.get(a, i) <= array.get(a, i - 1) ok := false ok f_decLast(array a, int n) => bool ok = array.size(a) >= n if ok for i = array.size(a) - n + 1 to array.size(a) - 1 if array.get(a, i) >= array.get(a, i - 1) ok := false ok // ---- State -------------------------------------------------------------- var array knownHighs = array.new() var array knownLows = array.new() var string lastKind = "" // kind of the most recent alternating swing var int cur = 0 // desired position (signal): +1 / -1 / 0 var bool highBroken = false // last-high level already taken out by a close var bool lowBroken = false // ZigZag drawing: previous locked pivot, the live tip, and the live segment. var int zzPrevX = na var float zzPrevY = na var int zzTipX = na var float zzTipY = na var line zzSeg = na int need = nSteps + 1 int keepN = math.max(need, 2) bool newLowerHigh = false bool newHigherLow = false bool bornHigh = false bool bornLow = false // Alternating-swing collapse on the fractal confirmed at this bar. if f_fractalHigh(k) float p = high[k] if lastKind == "H" // consecutive high -> keep the higher if p > array.last(knownHighs) array.set(knownHighs, array.size(knownHighs) - 1, p) highBroken := false zzTipX := bar_index - k // extend the live ZigZag tip zzTipY := p if showZigZag and not na(zzSeg) line.set_xy2(zzSeg, zzTipX, zzTipY) else array.push(knownHighs, p) lastKind := "H" highBroken := false bornHigh := true if array.size(knownHighs) >= 2 and array.get(knownHighs, array.size(knownHighs) - 1) < array.get(knownHighs, array.size(knownHighs) - 2) newLowerHigh := true if na(zzTipX) // first pivot: no segment yet zzTipX := bar_index - k zzTipY := p else // lock previous pivot, draw new leg zzPrevX := zzTipX zzPrevY := zzTipY zzTipX := bar_index - k zzTipY := p if showZigZag zzSeg := line.new(zzPrevX, zzPrevY, zzTipX, zzTipY, xloc = xloc.bar_index, color = zzColor, width = 2) if array.size(knownHighs) > keepN array.shift(knownHighs) if f_fractalLow(k) float p = low[k] if lastKind == "L" // consecutive low -> keep the lower if p < array.last(knownLows) array.set(knownLows, array.size(knownLows) - 1, p) lowBroken := false zzTipX := bar_index - k // extend the live ZigZag tip zzTipY := p if showZigZag and not na(zzSeg) line.set_xy2(zzSeg, zzTipX, zzTipY) else array.push(knownLows, p) lastKind := "L" lowBroken := false bornLow := true if array.size(knownLows) >= 2 and array.get(knownLows, array.size(knownLows) - 1) > array.get(knownLows, array.size(knownLows) - 2) newHigherLow := true if na(zzTipX) // first pivot: no segment yet zzTipX := bar_index - k zzTipY := p else // lock previous pivot, draw new leg zzPrevX := zzTipX zzPrevY := zzTipY zzTipX := bar_index - k zzTipY := p if showZigZag zzSeg := line.new(zzPrevX, zzPrevY, zzTipX, zzTipY, xloc = xloc.bar_index, color = zzColor, width = 2) if array.size(knownLows) > keepN array.shift(knownLows) float lastHigh = array.size(knownHighs) > 0 ? array.last(knownHighs) : na float lastLow = array.size(knownLows) > 0 ? array.last(knownLows) : na bool longPre = f_incLast(knownLows, need) bool shortPre = f_decLast(knownHighs, need) // ---- State machine (signal on close) ------------------------------------ // Exit first: trailing swing stop, or a structure break (lower high / higher low). if cur == 1 if (not na(lastLow) and close < lastLow) or newLowerHigh cur := 0 else if cur == -1 if (not na(lastHigh) and close > lastHigh) or newHigherLow cur := 0 // Entry only when flat (long has priority if both could trigger). if cur == 0 if allowLong and longPre and not na(lastHigh) and close > lastHigh cur := 1 else if allowShort and shortPre and not na(lastLow) and close < lastLow cur := -1 // ---- Orders (signal now, fill at next bar open) ------------------------- if cur > 0 strategy.entry("Long", strategy.long) else if cur < 0 strategy.entry("Short", strategy.short) else strategy.close_all() // ---- Level-break tracking (for the level display) ----------------------- if not na(lastHigh) and close > lastHigh highBroken := true if not na(lastLow) and close < lastLow lowBroken := true // ---- Visuals ------------------------------------------------------------ color posCol = strategy.position_size > 0 ? color.new(color.green, 88) : strategy.position_size < 0 ? color.new(color.red, 88) : na bgcolor(posCol) // Running last-high / last-low levels (hidden once a close takes them out). plot(showLevels and not highBroken ? lastHigh : na, "Last high level", color = color.yellow, style = plot.style_circles, linewidth = 1) plot(showLevels and not lowBroken ? lastLow : na, "Last low level", color = color.yellow, style = plot.style_circles, linewidth = 1) // New alternating swings, drawn at their center bar (offset back by k). plotshape(showSwings and bornHigh, title = "Swing high", style = shape.triangledown, location = location.abovebar, color = color.red, size = size.tiny, offset = -k) plotshape(showSwings and bornLow, title = "Swing low", style = shape.triangleup, location = location.belowbar, color = color.green, size = size.tiny, offset = -k)