← กลับหน้ารายการ

Reverse Pulse System FractalTP

Strategy ผู้เขียน: Lean_Trading Profit Factor: 1.678

ลิงก์ TradingView

เปิดใน TradingView

Equity Chart

Equity chart

เปิดรูปเต็มขนาด

คำอธิบาย

# Reverse Pulse System FractalTP – Strategy Description for TradingView

---

## Overview

**Reverse Pulse System FractalTP** is a variant of the Reverse Pulse sweep-and-reverse system with **Fractal-based Take-Profit**. Instead of a fixed R:R, the TP can target the next fractal level—the nearest resistance (long) or support (short).

- **Type:** Mean-reversion / Sweep-reversal
- **Timeframe:** M5 (recommended)
- **Instruments:** XAUUSD, EURUSD, and similar liquid pairs
- **Session:** 09:00–17:00 (Europe/Berlin, DST-safe)
- **Non-repainting:** Closed-bar logic, no lookahead bias

---

## Concept

1. **Trend Bias (H1):** Supertrend on H1 with body-break confirmation—long setups only when bias is long, short setups only when bias is short.
2. **POI Level:** Fractal highs (short bias) or fractal lows (long bias) define the key levels to watch.
3. **Sweep:** Price breaks the POI level (wick) but reverses back.
4. **Entry:** "Attempted Candle"—the candle that prints the new extreme and confirms reversal (either directly bullish/bearish or via a trigger break).
5. **TP (FractalTP):** Take-profit at the **next fractal**—long targets the nearest fractal high above entry, short targets the nearest fractal low below entry.

---

## Components

### Trend Bias (Supertrend H1)

- Supertrend on H1 with adjustable Factor and ATR Length.
- **Body-break:** Direction change only when the candle body breaks the Supertrend line (not just the wick).

### POI (Point of Interest)

- **MTF Fractals ** – CC BY-NC-SA 4.0
- Long bias: Fractal low as POI (lowest fractal low in lookback).
- Short bias: Fractal high as POI (highest fractal high in lookback).
- Fractal size: 3, 5, 7, or 9 bars.
- Optional: Higher timeframe for fractals (MTF).

### Sweep Detection

- **Long:** Price makes a new low below the POI level.
- **Short:** Price makes a new high above the POI level.
- While sweep is active, the extreme low/high is tracked.

### Entry Logic (Two Paths)

**1. Attempted Candle (direct)** – Sweep + failure on the same candle:
- **Long:** `low < poiLevel` (sweep), `close > open` (bullish), `low < low ` (new low).
- **Short:** `high > poiLevel` (sweep), `close < open` (bearish), `high > high ` (new high).
- SL: Low/High of this attempted candle. Entry: Close.

**2. Trigger + Break** – Candle prints the extreme but has the wrong color → wait for break:
- **Long:** Trigger candle = bearish, prints low. Trigger level = its high. Entry when a bullish candle closes above the trigger level.
- **Short:** Trigger candle = bullish, prints high. Trigger level = its low. Entry when a bearish candle closes below the trigger level.
- SL: Option A = Low/High of the break candle. Option B = Low/High of the trigger candle.

### Williams %R Filter (Optional)

- **Long:** Trade only if Williams %R (0–100) reached at least 80 (overbought) within the last X bars.
- **Short:** Trade only if Williams %R (0–100) reached at most 20 (oversold) within the last X bars.
- Default lookback: 20 bars. Default length: 14.
- When disabled, the filter is not applied.

---

## Exits – TP Mode: Fixed CRV vs Next Fractal

### TP Mode (Default: Next Fractal)

| Mode | Description |
|------|-------------|
| **Fixed CRV** | TP = Entry ± (Risk × Min R:R), same as base Reverse Pulse. |
| **Next Fractal** | TP at the next fractal level above (long) or below (short) entry. |

### Next Fractal Logic

- **Long:** TP = smallest fractal high **above** entry (nearest resistance).
- **Short:** TP = largest fractal low **below** entry (nearest support).
- **Fallback:** If no valid fractal exists, or the fractal is closer than "Min R:R (Fallback)", the CRV-based TP is used instead.

### SL

- Stop-Loss based on entry logic (Low/High of the relevant candle).

---

## Risk Management

- **Position sizing:** `qty = RiskAmount / (SL distance × USD per point)`.
- **XAUUSD:** USD per point = 1 (1 USD P&L per 1 USD price move per oz).
- **Daily stop:** After N stop-loss losses (default: 3), no further trades until session reset (09:00).
- **Max Qty:** Upper limit for position size (e.g. 500 oz for Gold).

---

## Session & Time

- **DST-safe:** Timestamps use IANA timezone (e.g. `Europe/Berlin`).
- **Default:** 09:00–17:00 Europe/Berlin. Timezone selectable via dropdown.
- **Session OFF:** Debug option to disable session filter (24/7 testing).

---

## Settings Overview

| Group | Parameter | Default |
|-------|-----------|---------|
| Session | Start/End Hour, Min, Timezone | 09:00–17:00, Europe/Berlin |
| Supertrend | Factor, ATR Length | 3.0, 10 |
| Fractals | Bars in Fractal, Timeframe | 5, Chart TF |
| Entry | Trigger-Break SL (A/B) | A |
| Filter Williams %R | Use Filter, Lookback, Length | Off, 20, 14 |
| Filter Williams %R | Min for Long, Max for Short | 80, 20 |
| Risk | Risk %, USD per Point, Max Qty | 1%, 1, 500 |
| Risk | **TP Mode** | **Next Fractal** |
| Risk | Min R:R (TP), Fractal TP: Min R:R (Fallback) | 2, 1 |
| Risk | Max SL per Day | 3 |
| Alerts | Alert Output, Telegram / PineConnector options | Off |
| Debug | Show Debug, Level Lines, Legend | true, true, true |

---

## Alerts – Telegram or PineConnector

### Alert Output

- **Off:** No alerts.
- **Telegram:** Webhook/Telegram, copier-friendly format.
- **PineConnector:** Format for PineConnector EA (MT4/MT5).

### Telegram / Webhook

- **Alert prefix:** First line (e.g. "Reverse Pulse").
- **Telegram Channel ID:** For webhook routing (e.g. `-1001234567890`).
- **Format:** `SYMBOL Buy/Sell now SL: X.XX TP: X.XX` – copier compatible.

### PineConnector

- **License ID:** Your PineConnector license (13–14 digits).
- **Volume Parameter:** `vol_lots` (strategy qty), `vol_pct_bal_loss` (% balance), `vol_dollar` ($ risk).
- **Symbol Override:** Broker symbol (e.g. GOLD instead of XAUUSD), empty = chart symbol.
- **Format:** `LicenseID,buy,Symbol,vol_lots=0.15,sl_price=2650.50,tp_price=2660.75`

---

## Recommended Instruments

- **XAUUSD** (Gold)
- **EURUSD**
- Other liquid forex and metal pairs with suitable tick size

---

## Technical Notes

- **process_orders_on_close:** Orders are executed at bar close.
- **pyramiding:** 0 (no pyramiding).
- **Float comparison:** `approxEq()` with `syminfo.mintick` for stable behavior.

---

## License Notice

- Fractals: MTF Fractals – CC BY-NC-SA 4.0
creativecommons.org

---

## Disclaimer

This system is for educational and research purposes only. There is no guarantee of profits. Trading involves substantial risk. Only trade with capital you can afford to lose.

รูป Preview

Preview

Pine Script Source

// @version=5
// Reverse Pulse System – FractalTP Version
// TP wählbar: Fixed CRV oder Next Fractal (Long = nächstes höheres Fractal-High, Short = nächstes tieferes Fractal-Low)
// Deterministic, closed-bar, non-repainting. Session 09:00–17:00. Stop after 3 SL losses per day.
// Fractals: MTF Fractals [RunRox] – CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
strategy("Reverse Pulse System FractalTP", overlay=true, process_orders_on_close=true, default_qty_type=strategy.fixed, default_qty_value=0.01, pyramiding=0, initial_capital=10000, max_lines_count=500, max_labels_count=500, max_polylines_count=100)

// ═══════════════════════════════════════════════════════════════════════════════
// INPUTS
// ═══════════════════════════════════════════════════════════════════════════════
grpSession = "Session (DST-safe)"
inputSessionStart = input.int(9, "Session Start Hour", minval=0, maxval=23, group=grpSession)
inputSessionStartMin = input.int(0, "Session Start Min", minval=0, maxval=59, group=grpSession)
inputSessionEnd = input.int(17, "Session End Hour", minval=0, maxval=23, group=grpSession, tooltip="Default 17 = bis 17:00.")
inputSessionEndMin = input.int(0, "Session End Min", minval=0, maxval=59, group=grpSession)
inputTz = input.string("Europe/Berlin", "Session Timezone", options=["Europe/Berlin", "Etc/UTC", "America/New_York", "America/Los_Angeles", "America/Chicago", "Europe/London", "Asia/Tokyo"], group=grpSession, tooltip="Europe/Berlin = DST-safe (CET/CEST). Etc/UTC = keine DST-Umstellung.")

grpST = "Supertrend (H1)"
inputStFactor = input.float(3.0, "Factor", minval=0.1, step=0.1, group=grpST)
inputStAtrLen = input.int(10, "ATR Length", minval=1, group=grpST)

grpFractal = "Fractals (POI) - MTF Fractals [RunRox]"
inputFractalPeriod = input.string("5", "Bars in Fractal", options=["3", "5", "7", "9"], group=grpFractal, tooltip="3/5/7/9-bar fractal")
inputFractalTF = input.timeframe("", "Fractals Timeframe", group=grpFractal, tooltip="Empty = chart TF. Higher TF = MTF fractals.")
inputPoiUseLowest = input.bool(true, "POI = tiefstes Fractal (untere Linie)", group=grpFractal, tooltip="An: Tiefstes Fractal-Low resp. höchstes Fractal-High im Lookback = die Linie, die gesweept werden muss.")
inputPoiLookback = input.int(50, "POI Lookback (Bars)", minval=5, maxval=200, group=grpFractal, tooltip="Wenn POI=tiefstes: Anzahl Bars für Min/Max der Fractals.")
// RunRox 1:1
maxFractalAge = input.int(2500, "Max Age, bars", minval=50, group=grpFractal)
showFractalLine = input.bool(true, "Show Fractal Line", group=grpFractal)
inFracLineStyle = input.string("sol", "Line Style", options=["sol", "dsh", "dot"], group=grpFractal)
fracLineWidth = input.int(1, "Line Width", options=[1,2,3,4], group=grpFractal)
highColor = input.color(color.green, "High Fractal", inline="frac", group=grpFractal)
inputHighStyle = input.string("triUp", " ", options=["arDn","arUp","cir","cross","xcross","diam","flag","sqr","triDn","triUp"], inline="frac", group=grpFractal)
lowColor = input.color(color.red, "Low Fractal", inline="frac2", group=grpFractal)
inputLowStyle = input.string("triDn", " ", options=["arDn","arUp","cir","cross","xcross","diam","flag","sqr","triDn","triUp"], inline="frac2", group=grpFractal)
PIV_LB_SIZE = input.string(size.auto, "Fractal Label Size", options=[size.auto, size.tiny, size.small, size.normal, size.large, size.huge], group=grpFractal)
showSweepLabel = input.bool(false, "Show Sweep Label", group=grpFractal)
lbBullColor = input.color(#00e67794, "Sweep Label Long", inline="lb", group=grpFractal)
lbBullTxtColor = input.color(color.silver, "", inline="lb", group=grpFractal)
lbBearColor = input.color(#ff525294, "Sweep Label Short", inline="lb", group=grpFractal)
lbBearTxtColor = input.color(color.silver, "", inline="lb", group=grpFractal)
enBgSweep = input.bool(false, "Shade Sweep Area", group=grpFractal)
bgBullColor = input.color(#7bda7e9f, "Sweep Area Long", inline="bg", group=grpFractal)
bgBearColor = input.color(#d375729f, "Sweep Area Short", inline="bg", group=grpFractal)

grpEntry = "Entry / SL"
inputTriggerSLOption = input.string("A", "Trigger-Break SL", options=["A", "B"], tooltip="A = SL = Low/High of break candle. B = SL = Low/High of original bearish/bullish trigger candle.", group=grpEntry)

grpAlerts = "Alerts"
inputAlertOutput = input.string("Off", "Alert Output", options=["Off", "Telegram", "PineConnector"], group=grpAlerts, tooltip="Off = keine Alerts. Telegram = Webhook/Telegram (Copier-Format). PineConnector = Format für PineConnector EA (MT4/MT5).")

grpTelegram = "Telegram / Webhook"
inputTelegramPrefix = input.string("Reverse Pulse", "Alert prefix", group=grpTelegram, tooltip="Erste Zeile der Nachricht (Strategiename).")
inputTelegramChannelId = input.string("", "Telegram Channel ID", group=grpTelegram, tooltip="Deine Chat/Channel-ID (z.B. -1001234567890). Für Webhook-Routing.")

grpPineConnector = "PineConnector"
inputPCLicenseId = input.string("", "License ID (13–14 Ziffern)", group=grpPineConnector, tooltip="Deine PineConnector License ID (z.B. 60123456789). Findest du im Portal.")
inputPCVolType = input.string("vol_lots", "Volume Parameter", options=["vol_lots", "vol_pct_bal_loss", "vol_dollar"], group=grpPineConnector, tooltip="vol_lots = Strategie-Qty. vol_pct_bal_loss = Risk % Balance. vol_dollar = Risk in $.")
inputPCSymbolOverride = input.string("", "Symbol Override (leer = Chart)", group=grpPineConnector, tooltip="Broker-Symbol überschreiben (z.B. GOLD statt XAUUSD). Leer = Chart-Symbol.")

grpFilter = "Filter Williams %R"
inputUseWilliamsFilter = input.bool(false, "Use Williams %R Filter", group=grpFilter, tooltip="Nur Trade wenn Williams %R kurz zuvor im Extrembereich war.")
inputWilliamsLookback = input.int(20, "Lookback (max. Bars)", minval=1, maxval=100, group=grpFilter, tooltip="Wie viele Bars zurück nach Extrembereich suchen.")
inputWilliamsLen = input.int(14, "Williams %R Length", minval=5, group=grpFilter)
inputWilliamsLongLevel = input.int(80, "Min für Long (0–100)", minval=0, maxval=100, group=grpFilter, tooltip="Long nur wenn Williams %R (0–100) innerhalb Lookback mind. so hoch war (Overbought).")
inputWilliamsShortLevel = input.int(20, "Max für Short (0–100)", minval=0, maxval=100, group=grpFilter, tooltip="Short nur wenn Williams %R (0–100) innerhalb Lookback mind. so tief war (Oversold).")

grpRisk = "Risk / TP"
inputRiskPct = input.float(1.0, "Risk % pro Trade", minval=0.1, step=0.1, group=grpRisk, tooltip="Verlust bei SL = dieser Prozentsatz der Equity. XAUUSD: Qty = RiskAmount / SL-Distanz.")
inputContractValue = input.float(1.0, "USD pro Punkt (Qty)", minval=0.01, step=0.01, group=grpRisk, tooltip="XAUUSD: 1 = 1 USD P&L pro 1 USD Preisbewegung pro Oz. Standard: 1.")
inputMaxQty = input.float(500.0, "Max Qty", minval=0.01, step=10, group=grpRisk, tooltip="Obergrenze (z.B. 500 oz).")
inputTPMode = input.string("Next Fractal", "TP Mode", options=["Fixed CRV", "Next Fractal"], group=grpRisk, tooltip="Fixed CRV = TP nach R:R. Next Fractal = TP am nächsten Fractal (Long= höheres, Short= tieferes).")
inputCrvMin = input.float(2.0, "Min R:R (TP)", minval=2.0, step=0.5, group=grpRisk, tooltip="Bei Fixed CRV: TP = Entry ± R:R. Bei Next Fractal: Fallback wenn kein gültiges Fractal.")
inputTPFractalMinRR = input.float(1.0, "Fractal TP: Min R:R (Fallback)", minval=0.5, step=0.5, group=grpRisk, tooltip="Nur bei Next Fractal: Wenn Fractal näher als dies, nutze CRV-Fallback.")
inputMaxSLPerDay = input.int(3, "Max SL Losses per Day", minval=1, maxval=20, group=grpRisk)

grpDebug = "Debug / Visuals"
inputShowDebug = input.bool(true, "Show Debug Plots", group=grpDebug)
inputShowLevelLines = input.bool(true, "Show Level Lines (POI, Extreme, etc.)", group=grpDebug, tooltip="Linien wie Fractals: line.new() statt plot().")
inputShowLegend = input.bool(true, "Show Legend", group=grpDebug, tooltip="Legende: Farbe = Linie.")
inputSkipSession = input.bool(false, "Session OFF (Debug)", group=grpDebug, tooltip="An: Session-Filter deaktiviert (24/7) zum Testen.")

// ═══════════════════════════════════════════════════════════════════════════════
// CONSTANTS
// ═══════════════════════════════════════════════════════════════════════════════
LONG = 1
SHORT = -1
IDLE = 0
TRACK_POI = 1
SWEEP_ACTIVE = 2
WAIT_TRIGGER = 3
IN_TRADE = 4
DAY_STOP = 5

// ═══════════════════════════════════════════════════════════════════════════════
// SESSION & TIME – DST-safe via timestamp()
// ═══════════════════════════════════════════════════════════════════════════════
// Session-Grenzen als Unix-Timestamps (IANA-TZ = automatisch CET/CEST)
y_ = year(time, inputTz)
m_ = month(time, inputTz)
d_ = dayofmonth(time, inputTz)
sessionStartTs = timestamp(inputTz, y_, m_, d_, inputSessionStart, inputSessionStartMin)
sessionEndTs = timestamp(inputTz, y_, m_, d_, inputSessionEnd, inputSessionEndMin)
inSession = inputSkipSession or (time >= sessionStartTs and time < sessionEndTs)
isNewDay = ta.change(time("D", inputTz)) != 0

// ═══════════════════════════════════════════════════════════════════════════════
// Bias() – H1 Supertrend with body-break confirmed flips
// ═══════════════════════════════════════════════════════════════════════════════
f_bias_get_h1_data() =>
    h1_o = request.security(syminfo.tickerid, "60", open[1], barmerge.gaps_off, barmerge.lookahead_off)
    h1_h = request.security(syminfo.tickerid, "60", high[1], barmerge.gaps_off, barmerge.lookahead_off)
    h1_l = request.security(syminfo.tickerid, "60", low[1], barmerge.gaps_off, barmerge.lookahead_off)
    h1_c = request.security(syminfo.tickerid, "60", close[1], barmerge.gaps_off, barmerge.lookahead_off)
    h1_atr = request.security(syminfo.tickerid, "60", ta.atr(inputStAtrLen)[1], barmerge.gaps_off, barmerge.lookahead_off)
    [h1_o, h1_h, h1_l, h1_c, h1_atr]

// Supertrend computed on H1 (last closed bar). Body-break: flip only when full body breaks the line.
var int stDir = 1
var float stTl = 0.0
var float stLine = 0.0

[h1_o, h1_h, h1_l, h1_c, h1_atr] = f_bias_get_h1_data()
newH1Close = ta.change(h1_c) != 0 and not na(h1_c) and not na(h1_atr) and h1_atr > 0

if newH1Close
    st_hl2 = (h1_h + h1_l) / 2.0
    upper = st_hl2 + inputStFactor * h1_atr
    lower = st_hl2 - inputStFactor * h1_atr
    oc = h1_o
    cc = h1_c
    if stDir == 1
        if lower > stTl
            stTl := lower
        if h1_l < stTl
            stTl := h1_l
        // Would flip to short: close < stTl. Body-break: max(O,C) < stTl
        bodyBreakShort = math.max(oc, cc) < stTl
        if bodyBreakShort
            stDir := -1
            stTl := upper
    else
        if upper < stTl or stTl == 0.0
            stTl := upper
        if h1_h > stTl
            stTl := h1_h
        // Would flip to long: close > stTl. Body-break: min(O,C) > stTl
        bodyBreakLong = math.min(oc, cc) > stTl
        if bodyBreakLong
            stDir := 1
            stTl := lower
    stLine := stTl

biasDir = stDir

// ═══════════════════════════════════════════════════════════════════════════════
// MTF Fractals [RunRox] – CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// 1:1 Übernahme des RunRox-Indikators
// ═══════════════════════════════════════════════════════════════════════════════
const int MAX_PIV_ARR_SIZE = 1000
const string SWEEP_HIGH_LB_STYLE = label.style_label_lower_left
const string SWEEP_LOW_LB_STYLE = label.style_label_upper_left

getLabelStyle(string style) =>
    switch style
        "arDn" => label.style_arrowdown
        "arUp" => label.style_arrowup
        "cir" => label.style_circle
        "cross" => label.style_cross
        "diam" => label.style_diamond
        "flag" => label.style_flag
        "sqr" => label.style_square
        "triDn" => label.style_triangledown
        "triUp" => label.style_triangleup
        "xcross" => label.style_xcross
        => label.style_triangleup

PIV_HIGH_LB_STYLE = getLabelStyle(inputHighStyle)
PIV_LOW_LB_STYLE = getLabelStyle(inputLowStyle)

inputTF = inputFractalTF
isHTF = inputTF != "" and timeframe.in_seconds(inputTF) > timeframe.in_seconds(timeframe.period)
htf = isHTF ? inputTF : timeframe.period
tfRatio = math.ceil(timeframe.in_seconds(htf) / timeframe.in_seconds(timeframe.period))

fracLineStyle = inFracLineStyle == "dot" ? line.style_dotted : inFracLineStyle == "dsh" ? line.style_dashed : line.style_solid
DT = 1000 * timeframe.in_seconds()

period = inputFractalPeriod == "3" ? 1 : inputFractalPeriod == "5" ? 2 : inputFractalPeriod == "7" ? 3 : 4

type Fractal
    bool        isHigh
    chart.point point
    line        ln
    label       lb
    int         brokenAt

var localHighs = array.new<chart.point>()
var localLows = array.new<chart.point>()
var fracHighs = array.new<Fractal>()
var fracLows = array.new<Fractal>()
var brokenHighs = array.new<Fractal>()
var brokenLows = array.new<Fractal>()

locPivLen = math.max(math.floor(tfRatio / 2), 1)
if isHTF
    locPh = ta.pivothigh(locPivLen, locPivLen)
    if not na(locPh)
        localHighs.push(chart.point.new(time[locPivLen], bar_index[locPivLen], locPh))
    locPl = ta.pivotlow(locPivLen, locPivLen)
    if not na(locPl)
        localLows.push(chart.point.new(time[locPivLen], bar_index[locPivLen], locPl))

getFractalPoint(bool isHigh, float price, int minTime) =>
    arr = isHigh ? localHighs : localLows
    chart.point res = na
    chart.point extremum = na
    i = arr.size() - 1
    while i >= 0
        p = arr.get(i)
        if na(extremum)
            extremum := p
        if p.price == price
            res := p
            break
        else
            if p.time < minTime
                break
            if isHigh ? extremum.price < p.price : extremum.price > p.price
                extremum := p
        i -= 1
    res := na(res) ? extremum : res

fractalHigh(int len) =>
    res = true
    for i = 1 to len
        res := res and high[len] > high[len - i] and high[len] > high[len + i]
    if res
        high[len]

fractalLow(int len) =>
    res = true
    for i = 1 to len
        res := res and low[len] < low[len - i] and low[len] < low[len + i]
    if res
        low[len]

method drawFractal(Fractal this) =>
    _style = this.isHigh ? PIV_HIGH_LB_STYLE : PIV_LOW_LB_STYLE
    loc = this.isHigh ? yloc.abovebar : yloc.belowbar
    clr = this.isHigh ? highColor : lowColor
    this.lb := label.new(this.point, style=_style, xloc=xloc.bar_time, yloc=loc, color=clr, size=PIV_LB_SIZE)
    end = chart.point.now(this.point.price)
    if showFractalLine
        this.ln := line.new(this.point, end, xloc=xloc.bar_time, color=clr, style=fracLineStyle, width=fracLineWidth)

method deleteFractal(Fractal this) =>
    if not na(this.lb)
        this.lb.delete()
    if not na(this.ln)
        this.ln.delete()

method extendFractals(array<Fractal> arr) =>
    i = arr.size() - 1
    while i >= 0
        piv = arr.get(i)
        if bar_index - piv.point.index > maxFractalAge
            arr.remove(i)
            piv.deleteFractal()
        else
            piv.ln.set_x2(time)
        i -= 1

addFractals(bool isHigh, float price, int openTime, bool isCurrentTF = false) =>
    res = false
    if not na(price)
        arr = isHigh ? fracHighs : fracLows
        point = isCurrentTF ? chart.point.from_time(openTime, price) : getFractalPoint(isHigh, price, openTime)
        if not na(point)
            piv = Fractal.new(isHigh, point)
            piv.drawFractal()
            arr.push(piv)
            if arr.size() > MAX_PIV_ARR_SIZE
                arr.shift()
            res := true
    res

markBroken(bool isHigh) =>
    arr = isHigh ? fracHighs : fracLows
    broken = isHigh ? brokenHighs : brokenLows
    i = arr.size() - 1
    while i >= 0
        frac = arr.get(i)
        if isHigh ? high > frac.point.price : low < frac.point.price
            frac.brokenAt := time
            arr.remove(i)
            broken.push(frac)
        else
            break
        i -= 1

newSweep(Fractal this) =>
    if na(this)
        false
    else
        endWasFound = false
        cpArr = array.new<chart.point>()
        price = this.point.price
        chart.point lbCp = na
        for i = 0 to tfRatio
            if not endWasFound
                if this.isHigh ? high[i] > price : low[i] < price
                    lbCp := i == 0 ? chart.point.new(time + DT, bar_index + 1, price) : chart.point.new(time[i-1], bar_index[i-1], price)
                    cpArr.push(lbCp)
                    endWasFound := true
                    if not enBgSweep
                        break
            if endWasFound
                if time[i] < this.brokenAt
                    cpArr.push(chart.point.new(time[i], bar_index[i], price))
                    break
                dotPrice = this.isHigh ? math.max(price, high[i]) : math.min(price, low[i])
                cpArr.push(chart.point.new(time[i], bar_index[i], dotPrice))
        if endWasFound
            if enBgSweep
                clr = this.isHigh ? bgBullColor : bgBearColor
                bgClr = color.new(clr, 80)
                if cpArr.size() == 3
                    p0 = cpArr.get(0)
                    peak = cpArr.get(1)
                    p1 = p0.copy()
                    p1.price := peak.price
                    p3 = cpArr.get(2)
                    p2 = p3.copy()
                    p2.price := peak.price
                    cpArr := array.from(p0, p1, p2, p3)
                polyline.new(cpArr, false, true, xloc.bar_time, clr, bgClr)
            if showSweepLabel
                if this.isHigh
                    label.new(lbCp, "Sweep", xloc=xloc.bar_time, yloc=yloc.price, color=lbBullColor, textcolor=lbBullTxtColor, style=SWEEP_HIGH_LB_STYLE)
                else
                    label.new(lbCp, "Sweep", xloc=xloc.bar_time, yloc=yloc.price, color=lbBearColor, textcolor=lbBearTxtColor, style=SWEEP_LOW_LB_STYLE)
            this.ln.set_x2(lbCp.time)
            true
        else
            false

getSweepFractals(bool isHigh, float extremum, float o, float c) =>
    arr = isHigh ? brokenHighs : brokenLows
    sign = isHigh ? 1 : -1
    toSweep = array.new<Fractal>()
    if not na(extremum)
        for frac in arr
            if sign * extremum > sign * frac.point.price and sign * o < sign * frac.point.price and sign * c < sign * frac.point.price
                toSweep.push(frac)
        arr.clear()
    toSweep


var htfTimestamps = array.new<int>()
var int prevHtfTs = 0

if barstate.isconfirmed
    markBroken(true)
    markBroken(false)

upFractal = fractalHigh(period)
dnFractal = fractalLow(period)

[htfOpen, htfHigh, htfLow, htfClose, htfTime, htfUpFractal, htfDnFractal] = request.security(syminfo.tickerid, htf, [open, high, low, close, time, fractalHigh(period), fractalLow(period)], barmerge.gaps_on, barmerge.lookahead_off)
isNewCandleHtf = not na(htfTime)

if isNewCandleHtf
    htfTimestamps.unshift(htfTime)
    if htfTimestamps.size() > period + 1
        prevHtfTs := htfTimestamps.get(period)

if isHTF
    if showFractalLine
        fracHighs.extendFractals()
        fracLows.extendFractals()
    addFractals(true, htfUpFractal, prevHtfTs)
    addFractals(false, htfDnFractal, prevHtfTs)
else
    if showFractalLine
        fracHighs.extendFractals()
        fracLows.extendFractals()
    addFractals(true, upFractal, time[period], true)
    addFractals(false, dnFractal, time[period], true)

// getSweepFractals + newSweep immer auf顶层 (Pine-Konsistenz)
detectSweepRun = (isHTF and isNewCandleHtf) or (not isHTF and barstate.isconfirmed)
detExtHigh = detectSweepRun ? (isHTF ? htfHigh : high) : na
detExtLow = detectSweepRun ? (isHTF ? htfLow : low) : na
detExtClose = detectSweepRun ? (isHTF ? htfClose : close) : na
detExtOpen = detectSweepRun ? (isHTF ? htfOpen : open) : na
sweepHighs = getSweepFractals(true, detExtHigh, detExtClose, detExtOpen)
sweepLows = getSweepFractals(false, detExtLow, detExtClose, detExtOpen)
newSweep(sweepHighs.size() > 0 ? sweepHighs.get(0) : na)
newSweep(sweepHighs.size() > 1 ? sweepHighs.get(1) : na)
newSweep(sweepHighs.size() > 2 ? sweepHighs.get(2) : na)
newSweep(sweepHighs.size() > 3 ? sweepHighs.get(3) : na)
newSweep(sweepHighs.size() > 4 ? sweepHighs.get(4) : na)
newSweep(sweepLows.size() > 0 ? sweepLows.get(0) : na)
newSweep(sweepLows.size() > 1 ? sweepLows.get(1) : na)
newSweep(sweepLows.size() > 2 ? sweepLows.get(2) : na)
newSweep(sweepLows.size() > 3 ? sweepLows.get(3) : na)
newSweep(sweepLows.size() > 4 ? sweepLows.get(4) : na)

fractalHighPrice = isHTF ? htfUpFractal : upFractal
fractalLowPrice = isHTF ? htfDnFractal : dnFractal

// POI: Long = tiefstes Fractal-Low (untere Linie) oder neuestes. Short = höchstes Fractal-High oder neuestes.
var float poiLevel = na
if inputPoiUseLowest
    lowestFractalLow = ta.lowest(nz(fractalLowPrice, 999999.0), inputPoiLookback)
    highestFractalHigh = ta.highest(nz(fractalHighPrice, 0.0), inputPoiLookback)
    if biasDir == LONG and lowestFractalLow < 999999.0
        poiLevel := lowestFractalLow
    else if biasDir == SHORT and highestFractalHigh > 0.0
        poiLevel := highestFractalHigh
else
    if biasDir == LONG and not na(fractalLowPrice)
        poiLevel := fractalLowPrice
    else if biasDir == SHORT and not na(fractalHighPrice)
        poiLevel := fractalHighPrice

// Invalidate POI on bias flip
var int prevBiasDir = 0
biasFlipped = prevBiasDir != 0 and biasDir != 0 and prevBiasDir != biasDir
if biasFlipped
    poiLevel := na
prevBiasDir := biasDir

// ═══════════════════════════════════════════════════════════════════════════════
// SWEEP DETECTION – price breaks POI (wick sufficient), track extreme
// Closed bar: evaluate on last closed bar [1] for sweep start; use current bar for entry
// ═══════════════════════════════════════════════════════════════════════════════
sweepLong = biasDir == LONG and not na(poiLevel) and low < poiLevel
sweepShort = biasDir == SHORT and not na(poiLevel) and high > poiLevel

var float extreme = na
var bool sweepActive = false
var float sweptPoiLevel = na  // POI-Level, das beim Sweep-Start gültig war – daran prüfen wir extreme

// Reset sweep state auf bias flip ODER wenn POI sich ändert (neues Fractal)
poiLevelChanged = not na(poiLevel) and not na(poiLevel[1]) and math.abs(poiLevel - poiLevel[1]) >= syminfo.mintick
if biasFlipped or poiLevelChanged
    sweepActive := false
    extreme := na
    sweptPoiLevel := na

if sweepLong and not sweepActive
    sweepActive := true
    extreme := low
    sweptPoiLevel := poiLevel  // Lock: dieses Level wurde gesweept
else if sweepShort and not sweepActive
    sweepActive := true
    extreme := high
    sweptPoiLevel := poiLevel

// Track extreme while sweep active (current bar contributes)
if sweepActive and biasDir == LONG
    extreme := math.min(nz(extreme), low)
else if sweepActive and biasDir == SHORT
    extreme := math.max(nz(extreme), high)

// ═══════════════════════════════════════════════════════════════════════════════
// SIGNAL & ENTRY – Attempted Candle logic (current bar = signal bar)
// ═══════════════════════════════════════════════════════════════════════════════
// Approx. float comparison (mintick tolerance) – avoids precision failure
approxEq(a, b) => math.abs(a - b) < syminfo.mintick
// Extreme muss unter POI liegen (Long) bzw. über POI (Short) – sowohl swept als auch AKTUELL
// (Wenn neues Fractal unter Extreme liegt, ist die Struktur anders – kein Entry)
extremeBelowPoiLong = not na(extreme) and not na(sweptPoiLevel) and extreme < sweptPoiLevel and not na(poiLevel) and extreme < poiLevel
extremeAbovePoiShort = not na(extreme) and not na(sweptPoiLevel) and extreme > sweptPoiLevel and not na(poiLevel) and extreme > poiLevel
barPrintsNewExtremeLong = sweepActive and biasDir == LONG and extremeBelowPoiLong and approxEq(low, extreme) and (na(extreme[1]) or low < extreme[1])
barPrintsNewExtremeShort = sweepActive and biasDir == SHORT and extremeAbovePoiShort and approxEq(high, extreme) and (na(extreme[1]) or high > extreme[1])

// Candle type: bullish = close > open, bearish = close < open
barBullish = close > open
barBearish = close < open

// Attempted Candle (klassisch): Sweep + Failure auf DERSELBEN Kerze – kein Trigger Level.
//   Long: low < poiLevel, close > open, low < low[1].
//   Short: high > poiLevel, close < open, high > high[1].
// Trigger + Break: Wenn Kerze das Extrem druckt aber falsche Farbe → Trigger Level, warten auf Break.

var float triggerLevel = na
var float triggerCandleLow = na
var float triggerCandleHigh = na
var bool waitTrigger = false

if biasFlipped or poiLevelChanged
    triggerLevel := na
    triggerCandleLow := na
    triggerCandleHigh := na
    waitTrigger := false

// Attempted Candle (klassisch): Sweep + bullish/bearish Close + neues Extrem auf derselben Kerze
// Extreme muss unter POI (Long) bzw. über POI (Short) – sonst kein gültiger Sweep
attemptedLongDirect = sweepLong and extremeBelowPoiLong and barBullish and (low < low[1])
attemptedShortDirect = sweepShort and extremeAbovePoiShort and barBearish and (high > high[1])

// Trigger nur, wenn Kerze Extrem druckt aber falsche Farbe (bearish für Long, bullish für Short)
if barPrintsNewExtremeLong and barBearish
    triggerLevel := high
    triggerCandleLow := low
    triggerCandleHigh := high
    waitTrigger := true

if barPrintsNewExtremeShort and barBullish
    triggerLevel := low
    triggerCandleLow := low
    triggerCandleHigh := high
    waitTrigger := true

// Reset Trigger bei Direct Entry (kein Warten nötig)
if attemptedLongDirect
    triggerLevel := na
    triggerCandleLow := na
    triggerCandleHigh := na
    waitTrigger := false
if attemptedShortDirect
    triggerLevel := na
    triggerCandleLow := na
    triggerCandleHigh := na
    waitTrigger := false

// Trigger Break: nachfolgende Kerze durchbricht Level mit richtiger Farbe
triggerBreakLong = waitTrigger and not na(triggerLevel) and close > triggerLevel and barBullish
triggerBreakShort = waitTrigger and not na(triggerLevel) and close < triggerLevel and barBearish

// ═══════════════════════════════════════════════════════════════════════════════
// Filter: Williams %R im Extrembereich (optional)
// Williams %R Standard: -100*(HH-close)/(HH-LL). 0–100: wrNorm = 100 + williamsR
// Long: darf nur wenn Williams innerhalb Lookback mind. inputWilliamsLongLevel war (Overbought)
// Short: darf nur wenn Williams innerhalb Lookback max. inputWilliamsShortLevel war (Oversold)
// ═══════════════════════════════════════════════════════════════════════════════
wrHH = ta.highest(high, inputWilliamsLen)
wrLL = ta.lowest(low, inputWilliamsLen)
wrRaw = wrHH == wrLL ? 0.0 : -100.0 * (wrHH - close) / (wrHH - wrLL)
wrNorm = 100.0 + wrRaw
wrLongOk = not inputUseWilliamsFilter or ta.highest(wrNorm, inputWilliamsLookback) >= inputWilliamsLongLevel
wrShortOk = not inputUseWilliamsFilter or ta.lowest(wrNorm, inputWilliamsLookback) <= inputWilliamsShortLevel

// Bias übergeordnet. Extreme MUSS unter POI (Long) bzw. über POI (Short) – letzter Check vor Entry
entryLong = (attemptedLongDirect or triggerBreakLong) and biasDir == LONG and extremeBelowPoiLong and wrLongOk
entryShort = (attemptedShortDirect or triggerBreakShort) and biasDir == SHORT and extremeAbovePoiShort and wrShortOk

// ═══════════════════════════════════════════════════════════════════════════════
// Risk() – SL, TP computation
// ═══════════════════════════════════════════════════════════════════════════════
// Nächstes Fractal für TP: Long = kleinstes Fractal-High über Entry, Short = größtes Fractal-Low unter Entry
getNextFractalTP(bool isHigh, float refPrice) =>
    float result = na
    sz = isHigh ? fracHighs.size() : fracLows.size()
    if sz > 0
        if isHigh
            for i = 0 to sz - 1
                p = fracHighs.get(i).point.price
                if p > refPrice
                    result := na(result) ? p : math.min(result, p)
        else
            for i = 0 to sz - 1
                p = fracLows.get(i).point.price
                if p < refPrice
                    result := na(result) ? p : math.max(result, p)
    result

f_risk_compute(entryPrice, slPrice, dir, crv) =>
    riskR = math.abs(entryPrice - slPrice)
    tp = dir == LONG ? entryPrice + riskR * crv : entryPrice - riskR * crv
    [riskR, tp]

// ═══════════════════════════════════════════════════════════════════════════════
// DAILY SL COUNT – block after 4 SL losses
// ═══════════════════════════════════════════════════════════════════════════════
var int slCount = 0
var int prevClosedTrades = 0
// Reset at session start (09:00)
sessionJustStarted = inSession and not inSession[1]
if sessionJustStarted
    slCount := 0
dayStopped = slCount >= inputMaxSLPerDay

if strategy.closedtrades > prevClosedTrades
    prevClosedTrades := strategy.closedtrades
    lastProfit = strategy.closedtrades.profit(strategy.closedtrades - 1)
    if lastProfit < 0
        slCount := slCount + 1

// ═══════════════════════════════════════════════════════════════════════════════
// STATE MACHINE
// ═══════════════════════════════════════════════════════════════════════════════
var int state = IDLE
inPos = strategy.position_size != 0

// Reset on new day
if isNewDay and state == DAY_STOP
    state := IDLE

if dayStopped and not inPos
    state := DAY_STOP

if inPos
    state := IN_TRADE

// Transition out of IN_TRADE when position closes
if ta.change(bar_index) != 0 and not inPos and state == IN_TRADE
    state := IDLE

if state != DAY_STOP and state != IN_TRADE and not inSession and not inPos
    state := IDLE

// State transitions
if state == IDLE and inSession and not dayStopped and biasDir != 0
    state := TRACK_POI

if state == TRACK_POI
    if biasFlipped
        state := IDLE
    else if sweepLong or sweepShort
        state := SWEEP_ACTIVE

if state == SWEEP_ACTIVE
    if biasFlipped
        state := IDLE
    else if entryLong or entryShort
        if waitTrigger and (triggerBreakLong or triggerBreakShort)
            state := WAIT_TRIGGER
        else
            state := WAIT_TRIGGER

if state == WAIT_TRIGGER
    if entryLong or entryShort
        state := IN_TRADE
    if biasFlipped
        state := IDLE

// ═══════════════════════════════════════════════════════════════════════════════
// Execute() – entry at bar close, SL/TP (process_orders_on_close=true)
// ═══════════════════════════════════════════════════════════════════════════════

// SL: Attempted Direct = Low/High der Attempted-Kerze. Trigger Break = Option A oder B
slDirectLong = low
slDirectShort = high
slTriggerLongA = low
slTriggerLongB = triggerCandleLow
slTriggerShortA = high
slTriggerShortB = triggerCandleHigh
slTriggerLong = inputTriggerSLOption == "A" ? slTriggerLongA : slTriggerLongB
slTriggerShort = inputTriggerSLOption == "A" ? slTriggerShortA : slTriggerShortB

entryPriceLong = close
entryPriceShort = close
slLong = attemptedLongDirect ? slDirectLong : slTriggerLong
slShort = attemptedShortDirect ? slDirectShort : slTriggerShort

[riskLong, tpLongCrv] = f_risk_compute(entryPriceLong, slLong, LONG, inputCrvMin)
[riskShort, tpShortCrv] = f_risk_compute(entryPriceShort, slShort, SHORT, inputCrvMin)

// TP: Fixed CRV oder Next Fractal
tpLongFractal = inputTPMode == "Next Fractal" ? getNextFractalTP(true, entryPriceLong) : na
tpShortFractal = inputTPMode == "Next Fractal" ? getNextFractalTP(false, entryPriceShort) : na
useTpLongFractal = not na(tpLongFractal) and tpLongFractal > entryPriceLong and (tpLongFractal - entryPriceLong) >= riskLong * inputTPFractalMinRR
useTpShortFractal = not na(tpShortFractal) and tpShortFractal < entryPriceShort and (entryPriceShort - tpShortFractal) >= riskShort * inputTPFractalMinRR
tpLong = inputTPMode == "Next Fractal" and useTpLongFractal ? tpLongFractal : tpLongCrv
tpShort = inputTPMode == "Next Fractal" and useTpShortFractal ? tpShortFractal : tpShortCrv

// Positionsgröße Gold-kompatibel: qty = RiskAmount / (SL-Distanz * USD pro Punkt)
riskAmount = strategy.equity * inputRiskPct / 100.0
slDistLong = math.abs(entryPriceLong - slLong)
slDistShort = math.abs(slShort - entryPriceShort)
qtyLongRaw = slDistLong > 0 and inputContractValue > 0 ? riskAmount / (slDistLong * inputContractValue) : 0.01
qtyShortRaw = slDistShort > 0 and inputContractValue > 0 ? riskAmount / (slDistShort * inputContractValue) : 0.01
qtyLong = math.max(0.01, math.min(inputMaxQty, math.round(qtyLongRaw * 100) / 100))
qtyShort = math.max(0.01, math.min(inputMaxQty, math.round(qtyShortRaw * 100) / 100))

// Execute – market order fills at bar close (process_orders_on_close)
// Bias als Filter: ST H1 bullish = nur Longs, ST H1 bearish = nur Shorts
if entryLong and biasDir == LONG and not inPos and inSession and not dayStopped and strategy.position_size == 0
    strategy.entry("Long", strategy.long, qty=qtyLong)
    strategy.exit("ExitLong", "Long", stop=slLong, limit=tpLong)
    sweepActive := false
    extreme := na
    sweptPoiLevel := na
    waitTrigger := false
    triggerLevel := na
    if inputAlertOutput == "Telegram"
        _ch = inputTelegramChannelId != "" ? "\nChannel: " + inputTelegramChannelId : ""
        alert(inputTelegramPrefix + "\n" + syminfo.ticker + " Buy now SL: " + str.tostring(slLong, "#.##") + " TP: " + str.tostring(tpLong, "#.##") + _ch, alert.freq_once_per_bar_close)
    else if inputAlertOutput == "PineConnector" and inputPCLicenseId != ""
        _sym = inputPCSymbolOverride != "" ? inputPCSymbolOverride : syminfo.ticker
        _vol = inputPCVolType == "vol_lots" ? "vol_lots=" + str.tostring(qtyLong, "#.##") : inputPCVolType == "vol_pct_bal_loss" ? "vol_pct_bal_loss=" + str.tostring(inputRiskPct, "#.#") : "vol_dollar=" + str.tostring(math.round(riskAmount, 2), "#.##")
        alert(inputPCLicenseId + ",buy," + _sym + "," + _vol + ",sl_price=" + str.tostring(slLong, "#.##") + ",tp_price=" + str.tostring(tpLong, "#.##"), alert.freq_once_per_bar_close)

if entryShort and biasDir == SHORT and not inPos and inSession and not dayStopped and strategy.position_size == 0
    strategy.entry("Short", strategy.short, qty=qtyShort)
    strategy.exit("ExitShort", "Short", stop=slShort, limit=tpShort)
    sweepActive := false
    extreme := na
    sweptPoiLevel := na
    waitTrigger := false
    triggerLevel := na
    if inputAlertOutput == "Telegram"
        _ch = inputTelegramChannelId != "" ? "\nChannel: " + inputTelegramChannelId : ""
        alert(inputTelegramPrefix + "\n" + syminfo.ticker + " Sell now SL: " + str.tostring(slShort, "#.##") + " TP: " + str.tostring(tpShort, "#.##") + _ch, alert.freq_once_per_bar_close)
    else if inputAlertOutput == "PineConnector" and inputPCLicenseId != ""
        _sym = inputPCSymbolOverride != "" ? inputPCSymbolOverride : syminfo.ticker
        _vol = inputPCVolType == "vol_lots" ? "vol_lots=" + str.tostring(qtyShort, "#.##") : inputPCVolType == "vol_pct_bal_loss" ? "vol_pct_bal_loss=" + str.tostring(inputRiskPct, "#.#") : "vol_dollar=" + str.tostring(math.round(riskAmount, 2), "#.##")
        alert(inputPCLicenseId + ",sell," + _sym + "," + _vol + ",sl_price=" + str.tostring(slShort, "#.##") + ",tp_price=" + str.tostring(tpShort, "#.##"), alert.freq_once_per_bar_close)

// ═══════════════════════════════════════════════════════════════════════════════
// Level Lines – Fractal-Style mit line.new() (POI, sweptPoi, extreme, trigger)
// ═══════════════════════════════════════════════════════════════════════════════
COLOR_POI = color.new(color.orange, 30)
COLOR_SWEPTPOI = color.new(color.orange, 0)
COLOR_EXTREME = color.new(color.purple, 30)
COLOR_TRIGGER = color.new(#00d4ff, 20)
COLOR_ST_LONG = color.new(color.green, 30)
COLOR_ST_SHORT = color.new(color.red, 30)

updateLevelLine(line ln, float level, float prevLevel, int startTime, color clr, int w) =>
    if na(level)
        if not na(ln)
            line.delete(ln)
        na
    else if na(ln) or not approxEq(level, prevLevel)
        if not na(ln)
            line.delete(ln)
        line.new(startTime, level, time, level, xloc=xloc.bar_time, color=clr, width=w)
    else
        line.set_x2(ln, time)
        ln

poiSwept = sweepLong or sweepShort
var bool poiWasSwept = false
if poiSwept
    poiWasSwept := true
if biasFlipped or (not na(poiLevel) and (na(poiLevel[1]) or poiLevel != poiLevel[1]))
    poiWasSwept := false
poiShow = not na(poiLevel) and not poiWasSwept
poiToDraw = inputShowDebug and inputShowLevelLines and poiShow ? poiLevel : na

var line poiLine = na
var line sweptPoiLine = na
var line extremeLine = na
var line triggerLine = na
sweptPoiToDraw = inputShowDebug and inputShowLevelLines and sweepActive and not na(sweptPoiLevel) ? sweptPoiLevel : na
extremeToDraw = inputShowDebug and inputShowLevelLines and sweepActive and not na(extreme) ? extreme : na
triggerToDraw = inputShowDebug and inputShowLevelLines and waitTrigger and not na(triggerLevel) ? triggerLevel : na

if barstate.isconfirmed and inputShowLevelLines
    poiLine := updateLevelLine(poiLine, poiToDraw, poiToDraw[1], time, COLOR_POI, 2)
    sweptPoiLine := updateLevelLine(sweptPoiLine, sweptPoiToDraw, sweptPoiToDraw[1], time, COLOR_SWEPTPOI, 1)
    extremeLine := updateLevelLine(extremeLine, extremeToDraw, extremeToDraw[1], time, COLOR_EXTREME, 1)
    triggerLine := updateLevelLine(triggerLine, triggerToDraw, triggerToDraw[1], time, COLOR_TRIGGER, 1)

plot(inputShowDebug ? stLine : na, "Supertrend H1", color=biasDir == LONG ? COLOR_ST_LONG : COLOR_ST_SHORT, linewidth=2)

// Sweep-Markierung (Strategy-POI): für Debug
sweepStartLong = sweepLong and not sweepActive[1]
sweepStartShort = sweepShort and not sweepActive[1]
plotshape(inputShowDebug and sweepStartLong, "Sweep L", shape.triangledown, location.abovebar, color.new(color.orange, 0), size=size.small)
plotshape(inputShowDebug and sweepStartShort, "Sweep S", shape.triangleup, location.belowbar, color.new(color.orange, 0), size=size.small)

// Fractal-Marker: RunRox zeichnet Labels via drawFractal()

stateStr = state == IDLE ? "IDLE" : state == TRACK_POI ? "TRACK_POI" : state == SWEEP_ACTIVE ? "SWEEP" : state == WAIT_TRIGGER ? "WAIT_TRIG" : state == IN_TRADE ? "IN_TRADE" : "DAY_STOP"
biasStr = biasDir == LONG ? "LONG" : "SHORT"
var table dbg = table.new(position.top_right, 2, 10, bgcolor=color.new(#1a1a2e, 5), border_width=1)
if inputShowDebug
    table.cell(dbg, 0, 0, "Reverse Pulse FractalTP", text_color=color.white, text_size=size.small, bgcolor=color.new(#16213e, 0))
    table.cell(dbg, 1, 0, "", bgcolor=color.new(#16213e, 0))
    table.cell(dbg, 0, 1, "State", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 1, 1, stateStr, text_color=#e2e8f0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 0, 2, "Bias", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 1, 2, biasStr, text_color=#e2e8f0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 0, 3, "poiLevel", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 1, 3, not na(poiLevel) ? str.tostring(poiLevel, "#.##") : "-", text_color=#e2e8f0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 0, 4, "extreme/sweptPoi", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 1, 4, (not na(extreme) ? str.tostring(extreme, "#.##") : "-") + " / " + (not na(sweptPoiLevel) ? str.tostring(sweptPoiLevel, "#.##") : "-"), text_color=#e2e8f0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 0, 5, "triggerLevel", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 1, 5, not na(triggerLevel) ? str.tostring(triggerLevel, "#.##") : "-", text_color=#e2e8f0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 0, 6, "slCount", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 1, 6, str.tostring(slCount), text_color=#e2e8f0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 0, 7, "dayStopped", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 1, 7, dayStopped ? "Y" : "N", text_color=#e2e8f0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 0, 8, "inSession", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    blockSession = (entryLong or entryShort) and not inSession
    sessColor = blockSession ? color.orange : (inSession ? #22c55e : #e2e8f0)
    table.cell(dbg, 1, 8, inSession ? "Y" : (blockSession ? "N (blocked!)" : "N"), text_color=sessColor, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 0, 9, "qty L/S", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(dbg, 1, 9, str.tostring(qtyLong, "#.##") + " / " + str.tostring(qtyShort, "#.##"), text_color=#e2e8f0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))

// Legende: Farbe = Linie
var table legendTbl = table.new(position.bottom_right, 2, 9, bgcolor=color.new(#1a1a2e, 5), border_width=1)
if inputShowLegend
    table.cell(legendTbl, 0, 0, "Legende", text_color=color.white, text_size=size.small, bgcolor=color.new(#16213e, 0))
    table.cell(legendTbl, 1, 0, "", bgcolor=color.new(#16213e, 0))
    table.cell(legendTbl, 0, 1, "POI", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 1, 1, "■", text_color=COLOR_POI, text_size=size.small, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 0, 2, "sweptPOI", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 1, 2, "■", text_color=COLOR_SWEPTPOI, text_size=size.small, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 0, 3, "Extreme", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 1, 3, "■", text_color=COLOR_EXTREME, text_size=size.small, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 0, 4, "Trigger", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 1, 4, "■", text_color=COLOR_TRIGGER, text_size=size.small, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 0, 5, "ST Long", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 1, 5, "■", text_color=COLOR_ST_LONG, text_size=size.small, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 0, 6, "ST Short", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 1, 6, "■", text_color=COLOR_ST_SHORT, text_size=size.small, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 0, 7, "Fractal High", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 1, 7, "▲", text_color=highColor, text_size=size.small, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 0, 8, "Fractal Low", text_color=#a0aec0, text_size=size.tiny, bgcolor=color.new(#0f0f1a, 10))
    table.cell(legendTbl, 1, 8, "▼", text_color=lowColor, text_size=size.small, bgcolor=color.new(#0f0f1a, 10))