Konatus Bearing · Pine Script v6 · free

Know the bearing.
Respect the conditions.

One bearing line, colored candles and a dashboard that says, in plain words, what the market is doing and what the plan is waiting for. It reads stocks, ETFs, futures, bonds, forex and crypto, and it writes the stop before you enter.

Get the Pine script New: Market Anatomy code ↓ Read the quick guide ↓

Experimental. Version 0.3.1 has not yet been compiled in TradingView, and no market edge has been measured. Plans on the chart are illustrations, not orders.

Brass navigation compass with a northeast-pointing needle on dark slate
Konatus Bearing
The dashboard as it sits in the corner of your chart. Example values, not a TradingView screenshot.

Set it up in five minutes

  1. InstallCopy the whole script into a blank Pine Editor indicator, save, and add it to an ordinary time-based price chart. Remove any older Konatus Bearing first.
  2. MarketLeave Instrument profile on Auto, or pick Stocks, Bonds, Futures, Forex, Crypto or Generic. Keep the chart's session (regular or extended) consistent.
  3. RiskEnter reference account, risk % and costs. Check point value and quantity increment for the symbol.
  4. FuturesEnter a per-contract margin allocation. Until you do, the panel reads SIZE UNAVAILABLE.
  5. SetupChoose Breakout, Pullback or Either. To enable shorts, open Settings → Inputs → 00 · Entry direction → Enable short entries. Shorts default off. If the control is missing, replace the older script with the complete 0.3.1 version below.
  6. AlertsUse “Any alert() function call”. Recreate alerts after changing settings.

Entry triangles and exit X marks

Long entry Green up-triangle below the candle
Short entry Red down-triangle above the candle
Exit X mark above the candle

Entry markers have no text. Triangles mark confirmed entry intentions; the illustrative fill is at the next open. Shorts must be enabled and pass the same setup and risk gates.

Candle colors show direction, not an order

Green Upward bearing with enough persistence
Coral Downward bearing with enough persistence
Gray Not enough evidence either way

Only closed candles are colored, and the dashboard reads the last closed bar. A color is never advice to enter.

Read the big word first

The large line on the panel is the plan. The small line under it says why.

LONG PLAN / SHORT PLAN
An illustrative position is open. Read its frozen stop and quantity.
ENTRY QUEUED
The setup fired at the close. The illustration fills at the next open.
EXIT QUEUED
A reversal queued an illustrative exit for the next open. Stop exits are marked with an X when reached in the illustration.
DATA UNAVAILABLE
The bar is invalid. Any open stop is kept on screen.

With no plan open, the big word is the New entry status: the first gate that is still closed.

READY
Every gate is open and the setup agrees with the bearing.
WAIT SETUP
The trend evidence is there; the breakout or pullback has not happened yet.
EXTENDED
The trend is real but price is too far from the bearing (1.5 ATR by default). Wait for it to come back.
WEAK EVIDENCE
Persistence is below the minimum. The direction is not settled.
CONGESTION
Price is moving without going anywhere; directional efficiency is too low.
SHOCK PAUSE
A bar moved more than 4 ATR. New plans wait a few bars.
LOW PARTICIPATION / VOLUME MISSING
The profile needs volume and the bar did not have enough of it.
VWAP CONTEXT
You asked for session VWAP alignment and price is on the wrong side.
SHORT DISABLED
The bearing points down and short plans are off. The analysis still shows.
SIZE UNAVAILABLE
Check the contract, margin allocation and risk inputs.
WARMUP / FLAT
Not enough history yet, or price has not moved.

Two ways in, one way out

Breakout waits for two closes beyond the prior 20-bar range. Pullback waits for a close that crosses back over the bearing in the direction of the trend. Either takes whichever comes first. Every other gate still applies. At entry the stop is set 3 ATR away and frozen; size comes from account, risk % and that distance, capped by your capacity limit, and never from how confident the reading looks.

Switch the panel to Detailed for drift, model uncertainty, run length and efficiency. Theme follows your chart on Auto.

Get the Pine script

Konatus Bearing 0.3.1, complete Pine v6 source, free to copy, use and share. Download or copy the entire file, paste it into a blank Pine Editor indicator, save and add it to your chart.

Download Konatus_Bearing_0.3.1.txt

View the full source
//@version=6
indicator("Konatus Bearing · Cross-market 0.3.1", "Konatus031", overlay = true, max_bars_back = 2000)
// Copyright (c) 2026 Kontaus. Experimental kb-0.3; no established market edge.
bool allowShort = input.bool(false, "Enable short entries", group = "00 · Entry direction", tooltip = "Long entries are enabled. Turn this on to also allow red short-entry triangles and short plans. Signals still require all setup and risk conditions. This control is in the cross-market 0.3.1 indicator; the old 0.2 baseline is long-only.")
string profileInput = input.string("Auto", "Instrument profile", options = ["Auto", "Stocks", "Bonds", "Futures", "Forex", "Crypto", "Generic"], group = "01 · Market")
string profile = profileInput == "Auto" ? (syminfo.type == "stock" or syminfo.type == "fund" ? "Stocks" : syminfo.type == "futures" ? "Futures" : syminfo.type == "forex" ? "Forex" : syminfo.type == "crypto" ? "Crypto" : "Generic") : profileInput
string volumePolicy = input.string("Profile", "Participation policy", options = ["Profile", "Required", "Price only"], group = "01 · Market", tooltip = "Profile requires volume for Stocks/Futures/Crypto. Bonds/Forex/Generic use price only. Missing required volume blocks new plans.")
string volumeMode = input.string("Prior EW", "Volume baseline", options = ["Prior EW", "Session slot"], group = "01 · Market", tooltip = "Prior EW uses 20 earlier volume observations. Session slot uses at least 5 earlier trading days at this exchange-local minute. Minute or hourly charts only; no current-bar volume in the baseline.")
bool vwapGate = input.bool(false, "Require session VWAP alignment", group = "01 · Market", tooltip = "Requires a fully observed trading-day anchor and positive current volume. VWAP is context, not order flow.")
int resetAt = input.time(0, "Reset model at contract/data boundary", group = "01 · Market", tooltip = "Optional timestamp. Resets analytical history once; never cancels an open illustrative stop. Does not construct or adjust a futures roll.")
string setupMode = input.string("Breakout", "Setup", options = ["Breakout", "Pullback", "Either"], group = "02 · Signals")
float decay = input.float(0.9, "Evidence memory", minval = 0.01, maxval = 0.99, step = 0.01, group = "02 · Signals")
float persistenceMin = input.float(20, "Minimum directional evidence", minval = 0, maxval = 100, group = "02 · Signals")
float extensionLimit = input.float(1.5, "Maximum entry extension · ATR", minval = 0.1, group = "02 · Signals")
float efficiencyMin = input.float(0.25, "Minimum directional efficiency", minval = 0, maxval = 1, group = "02 · Signals")
float shockAtr = input.float(4, "Shock threshold · prior ATR", minval = 0.1, group = "02 · Signals")
int cooldown = input.int(3, "Shock cooldown · bars", minval = 0, maxval = 100, group = "02 · Signals")
float account = input.float(100000, "Reference account", minval = 1, group = "03 · Risk")
float riskPct = input.float(1, "Planned risk %", minval = 0, maxval = 100, group = "03 · Risk")
float stopAtr = input.float(3, "Frozen distance · ATR", minval = 0.1, group = "03 · Risk")
float conversion = input.float(1, "Account currency per quote currency", minval = 0.00000001, group = "03 · Risk", tooltip = "Explicit fixed conversion for the illustration, not an FX feed. Use 1 only when account and quote currency match. Recompute when conversion changes.")
float pointOverride = input.float(0, "Point-value override · 0 = symbol", minval = 0, group = "03 · Risk")
float pointValue = pointOverride > 0 ? pointOverride : syminfo.pointvalue
float tick = syminfo.mintick
float lot = input.float(1, "Quantity increment", minval = 0.00000001, group = "03 · Risk", tooltip = "Quantity uses symbol units/contracts, not broker-defined FX lots. Confirm the point value and increment for this instrument.")
float margin = input.float(0, "Capacity per unit · account currency", minval = 0, group = "03 · Risk", tooltip = "Required for Futures and nonpositive-price plans. Enter a conservative per-contract allocation. It is not a live broker margin model. Zero uses cash notional for other positive-price instruments.")
float capitalFraction = input.float(1, "Maximum capacity · account fraction", minval = 0, maxval = 1, group = "03 · Risk")
float costBps = input.float(5, "One-way notional costs · bps", minval = 0, group = "03 · Risk")
float feePerUnit = input.float(0, "One-way fee per unit · quote currency", minval = 0, group = "03 · Risk")
int slipTicks = input.int(1, "Adverse slippage · ticks", minval = 0, maxval = 100, group = "03 · Risk")
string theme = input.string("Auto", "Theme", options = ["Auto", "Dark", "Light"], group = "04 · Appearance")
string density = input.string("Essential", "Dashboard", options = ["Essential", "Detailed", "Hidden"], group = "04 · Appearance")
bool candleColors = input.bool(true, "Color confirmed candles", group = "04 · Appearance")
bool showVwap = input.bool(false, "Show session VWAP", group = "04 · Appearance")
if not chart.is_standard or timeframe.isticks
    runtime.error("Konatus requires standard time-based OHLC charts.")
if na(pointValue) or na(tick) or math.sign(pointValue) <= 0 or math.sign(tick) <= 0
    runtime.error("A positive tick and point value are required.")
if volumeMode == "Session slot" and (not timeframe.isintraday or timeframe.in_seconds() < 60)
    runtime.error("Session-slot volume needs intraday time bars of at least one minute.")
bool needsVolume = volumePolicy == "Required" or (volumePolicy == "Profile" and (profile == "Stocks" or profile == "Futures" or profile == "Crypto"))

if vwapGate and not timeframe.isintraday
    runtime.error("Session VWAP gating requires intraday time bars. Disable it on daily and higher charts.")

// Shared confirmed-bar analytical engine. Price units allow zero/negative prices.
var float level = na
var float drift = 0
var float p00 = 0
var float p01 = 0
var float p11 = 0
var float atr = 0
var float trSum = 0
var float previous = na
var float evidence = 0
var int count = 0
var int direction = 0
var int runLength = 0
var int pause = 0
var array<float> highs = array.new_float()
var array<float> lows = array.new_float()
var array<float> closes = array.new_float()
var array<float> slotMean = array.new_float(1440, 0)
var array<int> slotCount = array.new_int(1440, 0)
var array<int> slotAnchor = array.new_int(1440, na)
var float volMean = 0
var int volCount = 0
var int anchor = na
var bool anchorComplete = false
var bool anchorValid = true
var float pv = 0
var float vSum = 0
var float vwap = na
var float relativeVolume = na
var float efficiency = 0
var float extension = na
var float surprise = na
var float predictiveSd = na
var float distance = 0
var float quantity = 0
var string volumeStatus = "WARMUP"
var string condition = "WARMUP"
var string entryStatus = "WARMUP"
var string setupName = "NONE"
var int breakoutSide = 0
var float breakoutLevel = na
var bool ready = false
int signal = 0
bool valid = not na(open) and not na(high) and not na(low) and not na(close) and (math.min(open, close) - low) / tick >= 0 and (high - math.max(open, close)) / tick >= 0
bool resetNow = resetAt > 0 and time >= resetAt and (na(time[1]) or time[1] < resetAt)
if barstate.isconfirmed
    if not valid or resetNow
        level := na
        drift := 0
        p00 := 0
        p01 := 0
        p11 := 0
        atr := 0
        trSum := 0
        previous := na
        evidence := 0
        count := 0
        direction := 0
        runLength := 0
        pause := 0
        array.clear(highs)
        array.clear(lows)
        array.clear(closes)
        array.fill(slotMean, 0)
        array.fill(slotCount, 0)
        array.fill(slotAnchor, na)
        volMean := 0
        volCount := 0
        anchor := na
        anchorComplete := false
        anchorValid := true
        pv := 0
        vSum := 0
        vwap := na
        breakoutSide := 0
        ready := false
        quantity := 0
        distance := 0
        extension := na
        efficiency := 0
        surprise := na
        predictiveSd := na
        relativeVolume := na
        volumeStatus := "UNAVAILABLE"
        condition := "INVALID DATA"
        entryStatus := "INVALID DATA"
        setupName := "NONE"
    if valid
        float priorLevel = level
        float tr = na(previous) ? high - low : math.max(high - low, math.abs(high - previous), math.abs(low - previous))
        float scale = math.max(count >= 14 ? atr : count > 0 ? trSum / count : tr, tick)
        float variance = scale * scale
        float observation = 0.25 * variance
        if na(level)
            level := close
            p00 := observation
            p11 := observation
            surprise := 0
            predictiveSd := math.sqrt(2 * observation)
        else
            float a = p00 + 2 * p01 + p11 + 0.01 * variance
            float b = p01 + p11
            float d = p11 + 0.0001 * variance
            float predictive = a + observation
            float innovation = close - level - drift
            surprise := innovation / math.sqrt(predictive)
            predictiveSd := math.sqrt(predictive)
            float k0 = a / predictive
            float k1 = b / predictive
            float m = 1 - k0
            level += drift + k0 * innovation
            drift += k1 * innovation
            p00 := m * m * a + k0 * k0 * observation
            p01 := m * (b - k1 * a) + k0 * k1 * observation
            p11 := d - 2 * k1 * b + k1 * k1 * a + k1 * k1 * observation
        count += 1
        if count <= 14
            trSum += tr
            atr := trSum / count
        else
            atr += (tr - atr) / 14
        int priorDirection = direction
        direction := drift / tick > 0.000001 ? 1 : drift / tick < -0.000001 ? -1 : 0
        runLength := direction != 0 and direction == priorDirection ? runLength + 1 : direction != 0 ? 1 : 0
        evidence := decay * evidence + (1 - decay) * math.max(-1, math.min(1, drift / (0.1 * scale)))
        array.push(closes, close)
        if array.size(closes) > 21
            array.shift(closes)
        float travel = 0
        if array.size(closes) > 1
            for j = 1 to array.size(closes) - 1
                travel += math.abs(array.get(closes, j) - array.get(closes, j - 1))
        efficiency := travel > 0 ? math.abs(close - array.get(closes, 0)) / travel : 0
        extension := math.abs(close - level) / math.max(atr, tick)
        bool shock = not na(previous) and math.abs(close - previous) / scale > shockAtr
        pause := shock ? cooldown : math.max(0, pause - 1)
        ready := count >= 40 and atr / tick > 0.000001
        condition := count < 40 ? "WARMUP" : not ready ? "FLAT" : shock or pause > 0 ? "SHOCK" : efficiency < efficiencyMin ? "CONGESTION" : "TREND"
        bool haveVolume = not na(volume) and volume >= 0
        int slot = hour(time, syminfo.timezone) * 60 + minute(time, syminfo.timezone)
        int dayAnchor = time_tradingday
        float baseline = volumeMode == "Session slot" ? array.get(slotMean, slot) : volMean
        int samples = volumeMode == "Session slot" ? array.get(slotCount, slot) : volCount
        int lastAnchor = array.get(slotAnchor, slot)
        bool duplicateSlot = volumeMode == "Session slot" and not na(lastAnchor) and lastAnchor == dayAnchor
        bool enough = samples >= (volumeMode == "Session slot" ? 5 : 20)
        relativeVolume := haveVolume and enough and baseline > 0 and not duplicateSlot ? volume / baseline : na
        volumeStatus := not haveVolume ? "VOLUME MISSING" : duplicateSlot ? "SLOT REPEATED" : volume == 0 ? "ZERO VOLUME" : na(relativeVolume) ? "VOLUME WARMUP" : "AVAILABLE"
        if haveVolume and not duplicateSlot
            float updated = samples == 0 ? volume : baseline + (volume - baseline) * 2 / 21
            if volumeMode == "Session slot"
                array.set(slotMean, slot, updated)
                array.set(slotCount, slot, samples + 1)
                array.set(slotAnchor, slot, dayAnchor)
            else
                volMean := updated
                volCount += 1
        if not needsVolume
            volumeStatus := "PRICE ONLY"
        bool volumeOk = not needsVolume or (volumeStatus == "AVAILABLE" and relativeVolume >= 1)
        if na(anchor) or dayAnchor != anchor
            anchorComplete := not na(anchor)
            anchor := dayAnchor
            anchorValid := true
            pv := 0
            vSum := 0
        if not haveVolume
            anchorValid := false
        if haveVolume and volume > 0
            pv += hlc3 * volume
            vSum += volume
        vwap := vSum > 0 ? pv / vSum : na
        bool contextOk = not vwapGate or (anchorValid and anchorComplete and not na(vwap) and haveVolume and volume > 0 and direction * (close - vwap) / tick > 0)
        int fire = 0
        if not ready or shock or pause > 0
            breakoutSide := 0
        else if breakoutSide != 0
            if breakoutSide * (close - breakoutLevel) / tick > 0
                fire := breakoutSide
            breakoutSide := 0
        else if array.size(highs) == 20
            if (close - array.max(highs)) / tick > 0
                breakoutSide := 1
                breakoutLevel := array.max(highs)
            else if (array.min(lows) - close) / tick > 0
                breakoutSide := -1
                breakoutLevel := array.min(lows)
        int pull = not na(previous) and not na(priorLevel) and direction * (previous - priorLevel) / tick <= 0 and direction * (close - level) / tick > 0 ? direction : 0
        int setupSide = setupMode == "Breakout" ? fire : setupMode == "Pullback" ? pull : fire != 0 ? fire : pull
        setupName := setupSide != 0 and setupSide == fire ? "BREAKOUT" : setupSide != 0 ? "PULLBACK" : "NONE"
        distance := math.ceil(stopAtr * atr / tick - 1e-9) * tick
        float slip = slipTicks * tick
        float fees = 2 * feePerUnit + (2 * math.abs(close) + distance + 2 * slip) * pointValue * costBps / 10000
        float unitRisk = ((distance + slip + tick) * pointValue + fees) * conversion
        float capacity = profile == "Futures" or math.sign(close) <= 0 ? margin : margin > 0 ? margin : (math.abs(close) + slip) * pointValue * conversion
        float capacityWithFees = capacity + (feePerUnit + (math.abs(close) + slip) * pointValue * costBps / 10000) * conversion
        quantity := math.sign(capacity) > 0 and math.sign(distance) > 0 and math.sign(unitRisk) > 0 ? math.max(0, math.floor(math.min(account * riskPct / 100 / unitRisk, account * capitalFraction / capacityWithFees) / lot) * lot) : 0
        entryStatus := count < 40 ? "WARMUP" : not ready ? "FLAT" : condition == "SHOCK" ? "SHOCK PAUSE" : condition == "CONGESTION" ? "CONGESTION" : direction == 0 or direction * evidence * 100 < persistenceMin ? "WEAK EVIDENCE" : direction < 0 and not allowShort ? "SHORT DISABLED" : not volumeOk and volumeStatus != "AVAILABLE" ? volumeStatus : not volumeOk ? "LOW PARTICIPATION" : not contextOk ? "VWAP CONTEXT" : extension > extensionLimit ? "EXTENDED" : quantity <= 0 ? "SIZE UNAVAILABLE" : setupSide == direction ? "READY" : "WAIT SETUP"
        signal := entryStatus == "READY" ? direction : 0
        array.push(highs, high)
        array.push(lows, low)
        if array.size(highs) > 20
            array.shift(highs)
            array.shift(lows)
        previous := close

// Illustrative next-open ledger. Orders are not sent to a broker.
var int heldSide = 0
var int pendingSide = 0
var float pendingQuantity = 0
var float pendingDistance = 0
var float heldQuantity = 0
var float entryPrice = na
var float stopPrice = na
var float heldDistance = na
var int reversalCount = 0
var bool pendingExit = false
var bool capacityBreached = false
bool entryIntent = false
bool exitIntent = false
bool entered = false
bool exited = false
string exitReason = ""
if barstate.isconfirmed and valid
    bool dueExit = pendingExit
    pendingExit := false
    float slip = slipTicks * tick
    if heldSide != 0
        if heldSide * (open - stopPrice) / tick <= 0
            exited := true
            exitReason := "GAP_STOP"
        else if dueExit
            exited := true
            exitReason := "REVERSAL"
    if heldSide == 0 and pendingSide != 0
        heldSide := pendingSide
        heldQuantity := pendingQuantity
        heldDistance := pendingDistance
        entryPrice := open + heldSide * slip
        float rawStop = entryPrice - heldSide * heldDistance
        stopPrice := (heldSide > 0 ? math.floor(rawStop / tick + 1e-9) : math.ceil(rawStop / tick - 1e-9)) * tick
        float entryFee = heldQuantity * (feePerUnit + math.abs(entryPrice) * pointValue * costBps / 10000) * conversion
        float filledCapacity = margin > 0 ? margin : math.abs(entryPrice) * pointValue * conversion
        capacityBreached := filledCapacity * heldQuantity + entryFee > account * capitalFraction
        pendingSide := 0
        entered := true
    if heldSide != 0 and not exited and (heldSide > 0 ? (stopPrice - low) / tick >= 0 : (high - stopPrice) / tick >= 0)
        exited := true
        exitReason := "STOP"
    if exited
        heldSide := 0
        reversalCount := 0
    if heldSide != 0
        reversalCount := direction == -heldSide ? reversalCount + 1 : 0
        if reversalCount >= 2
            pendingExit := true
            exitIntent := true
    else if not exited and signal != 0
        pendingSide := signal
        pendingQuantity := quantity
        pendingDistance := distance
        entryIntent := true
// Alerts are decision events. Namespace separates configurations/alert instances.
string alertNamespace = input.string("default", "Alert instance ID", group = "05 · Alerts", tooltip = "Use a distinct ID for each configuration. Allowed characters: letters, numbers, dash and underscore. Recreate TradingView alerts after settings change.")
if str.length(alertNamespace) == 0 or str.match(alertNamespace, "^[A-Za-z0-9_-]+$") != alertNamespace
    runtime.error("Alert instance ID must contain only letters, numbers, dash or underscore.")
if entryIntent or exitIntent
    string eventName = entryIntent ? "ENTRY_INTENT" : "EXIT_INTENT"
    int eventSide = entryIntent ? pendingSide : heldSide
    string eventId = "kb-0.3:" + alertNamespace + ":" + syminfo.tickerid + ":" + timeframe.period + ":" + str.tostring(time_close) + ":" + eventName + ":" + str.tostring(eventSide)
    string payload = '{"version":"kb-0.3-experimental","id":"' + eventId + '","event":"' + eventName + '","symbol":"' + syminfo.tickerid + '","timeframe":"' + timeframe.period + '","time_close":' + str.tostring(time_close) + ',"side":' + str.tostring(eventSide) + ',"quantity":' + str.tostring(entryIntent ? pendingQuantity : heldQuantity, "#.########") + ',"distance":' + str.tostring(entryIntent ? pendingDistance : heldDistance, "#.########") + ',"illustrative":true}'
    alert(payload, alert.freq_once_per_bar_close)

bool dark = theme == "Dark" or (theme == "Auto" and color.r(chart.bg_color) + color.g(chart.bg_color) + color.b(chart.bg_color) < 384)
color surface = dark ? #151B18 : #FCFCF8
color ink = dark ? #E8ECE6 : #202E25
color secondary = dark ? #A5B0A7 : #546559
color up = dark ? #A6CF9A : #37672C
color down = dark ? #EDABA0 : #9E453A
color amber = dark ? #E7C184 : #835B1C
color edge = dark ? #344238 : #BDC7BB
color bearingInk = not ready or math.abs(evidence * 100) < persistenceMin ? secondary : direction > 0 ? up : down
plot(level, "Bearing price", color = bearingInk, linewidth = 2)
plot(showVwap ? vwap : na, "Session VWAP", color = secondary)
plot(heldSide != 0 ? entryPrice : na, "Illustrative entry", color = secondary, style = plot.style_linebr)
plot(heldSide != 0 ? stopPrice : na, "Frozen protective stop", color = amber, linewidth = 2, style = plot.style_linebr)
barcolor(candleColors and barstate.isconfirmed ? bearingInk : na)
plotshape(entryIntent and pendingSide == 1, "Long intent", style = shape.triangleup, location = location.belowbar, color = color.green, size = size.tiny)
plotshape(entryIntent and pendingSide == -1, "Short intent", style = shape.triangledown, location = location.abovebar, color = color.red, size = size.tiny)
plotshape(exited, "Illustrative exit", style = shape.xcross, location = location.abovebar, color = amber, size = size.tiny)
bool displayValid = barstate.isconfirmed ? valid : valid[1]
string state = not displayValid ? "DATA UNAVAILABLE" : pendingExit ? "EXIT QUEUED" : heldSide != 0 ? (heldSide > 0 ? "LONG PLAN" : "SHORT PLAN") : pendingSide != 0 ? "ENTRY QUEUED" : entryStatus
string reason = heldSide != 0 ? (not displayValid ? "Data invalid; last stop retained" : capacityBreached ? "Capacity exceeded at fill" : "Frozen stop active") : pendingSide != 0 ? "Illustrative next-open fill" : entryStatus == "EXTENDED" ? "Trend persists; wait for entry" : entryStatus == "SIZE UNAVAILABLE" ? "Check contract / margin / risk" : entryStatus == "SHOCK PAUSE" ? "New entries paused" : entryStatus == "WAIT SETUP" ? "Evidence ready; wait for setup" : "Experimental · closed-bar state"
var table panel = table.new(position.top_right, 2, 15, bgcolor = surface, frame_color = edge, frame_width = 1)
row(table t, int n, string label, string value, color valueInk, color bg, color labelInk) =>
    table.cell(t, 0, n, " " + label + " ", bgcolor = bg, text_color = labelInk, text_halign = text.align_left, text_size = size.small)
    table.cell(t, 1, n, " " + value + " ", bgcolor = bg, text_color = valueInk, text_halign = text.align_right, text_size = size.small)
if barstate.isfirst
    table.merge_cells(panel, 0, 1, 1, 1)
    table.merge_cells(panel, 0, 2, 1, 2)
    table.merge_cells(panel, 0, 14, 1, 14)
if barstate.islast
    table.clear(panel, 0, 0, 1, 14)
    if density != "Hidden" or (heldSide != 0 and (capacityBreached or not displayValid))
        row(panel, 0, "KONATUS BEARING", profile, ink, surface, secondary)
        table.cell(panel, 0, 1, " " + state + " ", text_color = heldSide != 0 and not pendingExit ? bearingInk : amber, bgcolor = surface, text_size = size.large, text_halign = text.align_left)
        table.cell(panel, 0, 2, " " + reason + " ", text_color = secondary, bgcolor = surface, text_size = size.small, text_halign = text.align_left)
        row(panel, 3, "DIRECTION", direction > 0 ? "Upward" : direction < 0 ? "Downward" : "Unclear", bearingInk, surface, secondary)
        row(panel, 4, "PERSISTENCE", str.tostring(evidence * 100, "0.0") + " / 100", ink, surface, secondary)
        row(panel, 5, "CONDITIONS", condition, ink, surface, secondary)
        row(panel, 6, "NEW ENTRY", entryStatus, ink, surface, secondary)
        row(panel, 7, "EXTENSION", na(extension) ? "—" : str.tostring(extension, "0.00") + " ATR", ink, surface, secondary)
        row(panel, 8, "PARTICIPATION", volumeStatus, secondary, surface, secondary)
        row(panel, 9, "FROZEN STOP", heldSide != 0 ? str.tostring(stopPrice, format.mintick) : "—", amber, surface, secondary)
        row(panel, 10, "ACTIVE / QUEUED QTY", str.tostring(heldSide != 0 ? heldQuantity : pendingSide != 0 ? pendingQuantity : 0, "#.########"), ink, surface, secondary)
        if density == "Detailed"
            row(panel, 11, "DRIFT / MODEL SD", str.tostring(drift, "#.########") + " / " + str.tostring(math.sqrt(math.max(p11, 0)), "#.########"), ink, surface, secondary)
            row(panel, 12, "RUN / EFFICIENCY", str.tostring(runLength) + " / " + str.tostring(efficiency, "0.00"), ink, surface, secondary)
            row(panel, 13, "QUOTE → ACCOUNT", syminfo.currency + " × " + str.tostring(conversion, "#.########"), secondary, surface, secondary)
        table.cell(panel, 0, 14, " kb-0.3 · experimental · illustrative ", text_color = secondary, bgcolor = surface, text_size = size.small)
plot(evidence * 100, "Persistence", display = display.data_window)
plot(direction, "Direction", display = display.data_window)
plot(extension, "Extension ATR", display = display.data_window)
plot(efficiency, "Efficiency", display = display.data_window)
plot(atr, "ATR", display = display.data_window)
plot(relativeVolume, "Prior relative volume", display = display.data_window)
plot(surprise, "Standardized forecast innovation", display = display.data_window)
plot(predictiveSd, "Prior predictive SD", display = display.data_window)
plot(drift, "Price drift", display = display.data_window)
plot(math.sqrt(math.max(p11, 0)), "Model drift SD", display = display.data_window)
plot(signal, "Setup signal", display = display.data_window)
plot(entryIntent ? pendingSide : 0, "Entry intent", display = display.data_window)
plot(exitIntent ? 1 : 0, "Exit intent", display = display.data_window)
plot(quantity, "Planned quantity", display = display.data_window)
plot(distance, "Planned distance", display = display.data_window)
plot(ready ? 1 : 0, "Ready", display = display.data_window)
// Raw observation exports allow an actual chart-to-Python parity check.
plot(bar_index, "Observation index", display = display.data_window)
plot(open, "Input open", display = display.data_window)
plot(high, "Input high", display = display.data_window)
plot(low, "Input low", display = display.data_window)
plot(close, "Input close", display = display.data_window)
plot(volume, "Input volume", display = display.data_window)
plot(time_tradingday, "Input anchor", display = display.data_window)
plot(hour(time, syminfo.timezone) * 60 + minute(time, syminfo.timezone), "Input slot", display = display.data_window)
plot(anchorComplete ? 1 : 0, "Input anchor complete", display = display.data_window)
plot(resetNow ? 1 : 0, "Input reset", display = display.data_window)

plot(heldSide, "Held side", display = display.data_window)
plot(heldSide != 0 ? heldQuantity : 0, "Held quantity", display = display.data_window)
plot(exited ? 1 : 0, "Illustrative exit flag", display = display.data_window)
plot(exitReason == "STOP" ? 1 : exitReason == "GAP_STOP" ? 2 : exitReason == "REVERSAL" ? 3 : 0, "Illustrative exit code", display = display.data_window)

What changed in 0.3.1

Version 0.3.1 moves Enable short entries to the first input group, 00 · Entry direction. Long entries use green triangles, shorts use red triangles, and exits keep their X marks. Entry text is removed. The underlying cross-market signal rules and sizing are unchanged. 68 tests pass on the Python reference implementation; TradingView compilation is still pending.

Why I built it

I built this overlay because the usual chart was teaching me the wrong lesson. A moving average, another moving average, MACD, RSI, a stochastic, a channel: the same prices recoded until they looked like a committee. Spinoza’s name for that error is inadequate ideas stacked until they feel like knowledge. When the stack disagreed I invented a reason. When it agreed I felt confirmed. Neither feeling was a plan.

What I needed was one readable heading, one named refusal, and one invalidation written at the moment of the event. Konatus Bearing is that instrument. It names the conditions, names what is missing, and writes the stop before hope can rewrite it. That is discipline, not a forecast.

How a “losing” indicator can still be profitable

People call a rule a loser when it is wrong most of the time. That is a hit-rate complaint. Money does not settle hit rate. Chapter 3 of The Spinozan Trader writes expectancy as pW − (1 − p)L − C: the chance of a win times the average win, minus the chance of a loss times the average loss, minus costs. A 90% rule that wins a little and loses a lot can drain an account. A 30% rule that wins enough and loses little can grow one.

What to look at before calling a rule a winner or a loser.
Look atWhat it answersWhat it does not answer
Win rateHow often the row is green.How much money moved.
Average winConditional size of a gain.Whether the next trade wins.
Average lossConditional size of a loss.Whether the next trade loses.
ExpectancypW − (1−p)L − C. The question that replaces “does it win often?”Whether buy-and-hold would have done better.

Full statement: proposition 4 in chapter 3. Why a useful indicator can still fail to produce a lasting edge: chapter 7.

What it does not do

  • It does not forecast the next bar or a target. Persistence is a signed score from −100 to +100, not a probability.
  • It does not place orders. ENTRY_INTENT and EXIT_INTENT alerts are notes to yourself, not broker fills.
  • It does not model borrow, locate, funding, financing, dividends, live margin or futures roll adjustments. Gaps can exceed planned risk.
  • It does not come with fitted settings. The defaults are research choices, not optimized parameters.
  • It does not replace an index fund for a person who does not need a timing rule.

Pair it with the Conatus Scanner

The Conatus Scanner ranks every US-listed company by its causes: power, persistence, self-funding, striving and independence. It tells you which businesses are worth watching. Konatus Bearing reads the chart and tells you when the conditions are right for a plan.

Open the scanner →

02 / New, separate indicator

Market Anatomy

A standalone KONATUS indicator with its own Pine script. Install it separately from Konatus Bearing.

Pine Script v6 · Research release 0.1.0

How to use it with Konatus Bearing →

Brass drafting compass and magnifying lens over geometric curves on dark paper
Market Anatomy

A common language for reading a market: its direction, pace, organization, volatility, location and participation. Built from The Spinozan Trader, this overlay explains the evidence behind its reading and separates observations from hypotheses about their economic causes.

Finite differences measure velocity and acceleration. Normalized curvature describes local bending; Euclidean path geometry compares distance traveled with progress made. A prior mean true range puts price movement on a common scale across instruments. These are measurements of the observed path, not a forecast.

Research release 0.1.0. 21 offline Python reference tests pass. Native TradingView compilation, chart behavior and alert delivery remain unverified. No trading edge has been measured.

Download Pine v6 sourceDownload research package (.zip)

View the complete Market Anatomy code
//@version=6
// Konatus Market Anatomy 0.1.0 — experimental interpretation tool.
// Copyright (c) 2026 Kutty Stauder. No profitability or causal identification claim.
// Formula/verification contract: SPEC.md and docs/VERIFICATION.md.
indicator("KONATUS | Market Anatomy [Research 0.1]", "K Anatomy", overlay = true, max_bars_back = 1200)

// One common measurement system; horizon is always measured in chart bars.
int horizon = input.int(20, "Observation horizon (bars)", minval = 5, maxval = 200, group = "Understanding")
string volumeInput = input.string("Auto", "Volume meaning", options = ["Auto", "Trade units", "Tick count", "Quote value", "Off"], group = "Data", tooltip = "Auto follows feed metadata. Only declared trade units enable VWAP. Unknown volume stays unverified; tick activity is not traded quantity.")
string anchorInput = input.string("Auto", "VWAP calendar anchor", options = ["Auto", "Day", "Week", "Month", "Year", "Off"], group = "Data", tooltip = "Exchange-calendar boundaries on the bars loaded in the chart. Auto: intraday/day, daily/month, weekly or monthly/year. First partial anchor is withheld. This is not a regular-session filter.")
bool detailed = input.bool(false, "Detailed explanation", group = "Display")
bool showPanel = input.bool(true, "Explanation panel", group = "Display")
bool showEnvelope = input.bool(true, "Descriptive extension envelope", group = "Display", tooltip = "Filtered price +/- 1.5 prior mean true ranges. This is not a probability interval or a stop.")
bool showVwap = input.bool(true, "Volume-weighted reference", group = "Display")
bool showScenario = input.bool(true, "Frozen scenario boundary", group = "Display")
bool paintBars = input.bool(false, "Color confirmed candles", group = "Display")
string corner = input.string("Top right", "Panel position", options = ["Top right", "Bottom right", "Top left", "Bottom left"], group = "Display")

f_number(float value) =>
    na(value) ? "Unavailable" : str.tostring(value, "#.###")

f_row(table panel, int row, string caption, string value, color tone) =>
    table.cell(panel, 0, row, caption, text_color = color.new(chart.fg_color, 30), text_size = size.small, text_halign = text.align_left)
    table.cell(panel, 1, row, value, text_color = tone, text_size = size.small, text_halign = text.align_left)

color upColor = color.rgb(38, 171, 145)
color downColor = color.rgb(224, 102, 108)
color cautionColor = color.rgb(213, 158, 66)
color neutralColor = color.new(chart.fg_color, 25)
bool timedChart = timeframe.isseconds or timeframe.isminutes or timeframe.isdaily or timeframe.isweekly or timeframe.ismonthly
bool supported = chart.is_standard and timedChart
bool validPrice = supported and not na(open) and not na(high) and not na(low) and not na(close) and low <= math.min(open, close) and high >= math.max(open, close)
var int priceCount = 0
priceCount := validPrice ? priceCount + 1 : 0
bool previousValid = validPrice[1]
float trueRange = validPrice ? (previousValid ? math.max(high - low, math.max(math.abs(high - close[1]), math.abs(low - close[1]))) : high - low) : na
float shortRange = ta.sma(trueRange, horizon)
float longRange = ta.sma(trueRange, 4 * horizon)
float scale = shortRange[1]
bool warm = priceCount >= 4 * horizon + 2
bool scaleKnown = warm and not na(scale) and scale > 0

// Two-pole SuperSmoother in signed price space. No log-price clipping.
float decay = math.exp(-math.sqrt(2.0) * math.pi / horizon)
float c2 = 2.0 * decay * math.cos(math.sqrt(2.0) * math.pi / horizon)
float c3 = -decay * decay
float c1 = 1.0 - c2 - c3
var float level = na
float level1 = previousValid ? nz(level[1], close) : close
float level2 = previousValid ? nz(level[2], level1) : close
level := validPrice ? c1 * (close + (previousValid ? close[1] : close)) / 2.0 + c2 * level1 + c3 * level2 : na
float velocity = scaleKnown ? (level - level1) / scale : na
float acceleration = scaleKnown ? (level - 2.0 * level1 + level2) / scale : na
float curvature = scaleKnown ? acceleration / math.pow(1.0 + velocity * velocity, 1.5) : na
float extension = scaleKnown ? (close - level) / scale : na
float distance = math.sum(math.abs(close - close[1]), horizon)
float efficiency = warm ? (distance > 0 ? math.abs(close - close[horizon]) / distance : 0.0) : na
float volatility = warm and longRange > 0 ? shortRange / longRange : na
float shockRatio = scaleKnown ? trueRange / scale : na
bool shock = scaleKnown and shockRatio >= 3.0
int direction = scaleKnown ? (velocity > 0.08 ? 1 : velocity < -0.08 ? -1 : 0) : 0

// Volume semantics remain separate from directional evidence.
string volumeKind = volumeInput == "Auto" ? (syminfo.volumetype == "base" ? "Trade units" : syminfo.volumetype == "tick" ? "Tick count" : syminfo.volumetype == "quote" ? "Quote value" : "Unverified") : volumeInput
bool validVolume = validPrice and not na(volume) and volume >= 0
var int volumeCount = 0
volumeCount := validVolume ? volumeCount + 1 : 0
float volumeMean = ta.sma(validVolume ? volume : na, horizon)[1]
float relativeVolume = volumeInput != "Off" and volumeCount >= horizon + 1 and volumeMean > 0 ? volume / volumeMean : na

string anchorTf = switch anchorInput
    "Day" => "1D"
    "Week" => "1W"
    "Month" => "1M"
    "Year" => "12M"
    => timeframe.isintraday ? "1D" : timeframe.isdaily ? "1M" : "12M"
float chartSeconds = timedChart ? timeframe.in_seconds() : na
bool anchorLonger = timedChart and timeframe.in_seconds(anchorTf) > chartSeconds
bool anchorEnabled = supported and anchorInput != "Off" and volumeKind == "Trade units" and anchorLonger
bool anchorChanged = timeframe.change(anchorTf) and not barstate.isfirst
var bool anchorComplete = false
var bool anchorPoisoned = false
var float anchorWeight = 0.0
var float anchorMean = 0.0
if anchorChanged
    anchorComplete := true
    anchorPoisoned := false
    anchorWeight := 0.0
    anchorMean := 0.0
if not validVolume
    anchorPoisoned := true
else if volume > 0 and anchorEnabled
    float totalWeight = anchorWeight + volume
    anchorMean += volume / totalWeight * (hlc3 - anchorMean)
    anchorWeight := totalWeight
float vwapReference = anchorEnabled and anchorComplete and not anchorPoisoned and anchorWeight > 0 ? anchorMean : na

// A scenario freezes a previously observable boundary. It is not a trade ledger.
float priorHigh = ta.highest(high, horizon)[1]
float priorLow = ta.lowest(low, horizon)[1]
var int scenario = 0
var float boundary = na
bool invalidated = false
int breakout = 0
if not scaleKnown
    scenario := 0
    boundary := na
else if scenario != 0 and (close - boundary) * scenario < 0
    invalidated := true
    scenario := 0
    boundary := na
else if scenario == 0
    if close > priorHigh
        scenario := 1
        boundary := priorHigh
        breakout := 1
    else if close < priorLow
        scenario := -1
        boundary := priorLow
        breakout := -1

// Each message is a direct explanation of a measurement or a named hypothesis.
string state = not supported ? "UNSUPPORTED CHART" : not validPrice ? "INVALID PRICE DATA" : not warm ? "BUILDING HISTORY" : not scaleKnown ? (trueRange == 0 ? "FLAT" : "SCALE UNAVAILABLE") : direction > 0 ? "UPWARD" : direction < 0 ? "DOWNWARD" : "BALANCED"
string organization = not warm ? "Awaiting history" : efficiency >= 0.55 ? "Directional path" : efficiency < 0.25 ? "Backtracking path" : "Mixed path"
float directionalAcceleration = acceleration * direction
string pace = not scaleKnown ? "Unavailable" : direction == 0 ? "No directional pace" : directionalAcceleration > 0.015 ? "Gaining pace" : directionalAcceleration < -0.015 ? "Losing pace" : "Steady pace"
string volatilityText = not warm ? "Awaiting history" : shock ? "SHOCK / " + f_number(shockRatio) + " x prior range" : na(volatility) ? "No range" : volatility > 1.3 ? "Expanding" : volatility < 0.7 ? "Contracting" : "Near longer baseline"
string locationText = not scaleKnown ? "Unavailable" : (math.abs(extension) >= 1.5 ? "Extended / " : "Near reference / ") + f_number(extension) + " range units"
string participation = volumeInput == "Off" ? "Off / price-only context" : not validVolume ? "Missing volume" : na(relativeVolume) ? "Baseline unavailable" : f_number(relativeVolume) + " x / " + volumeKind
if timeframe.isintraday and volumeInput != "Off"
    participation += "\nNot time-of-day adjusted"
string anchorText = anchorInput == "Off" ? "Off" : volumeKind != "Trade units" ? "Requires declared trade units" : not anchorLonger ? "Anchor must exceed chart interval" : not anchorComplete ? "First partial anchor withheld" : anchorPoisoned ? "Anchor has missing data" : anchorWeight <= 0 ? "No positive anchor volume" : (close > vwapReference ? "Above " : close < vwapReference ? "Below " : "At ") + anchorTf + " VWAP"
string scenarioText = invalidated ? "Prior breakout failed" : scenario > 0 ? "Upward range breakout" : scenario < 0 ? "Downward range breakout" : "No active breakout scenario"
string failureText = scenario > 0 ? "Close below " + str.tostring(boundary, format.mintick) : scenario < 0 ? "Close above " + str.tostring(boundary, format.mintick) : "No scenario boundary"
string reading = not warm ? "Waiting for " + str.tostring(4 * horizon + 2) + " consecutive valid bars." : not scaleKnown ? "Normalized movement is unavailable." : shock ? "Range jumped relative to its prior baseline.\nThe previous pace may no longer describe conditions." : direction != 0 and efficiency < 0.25 ? "The smoother points one way; raw price backtracks.\nDirection and path organization conflict." : direction != 0 and math.abs(extension) >= 1.5 ? "Price is far from the smoothed reference.\nExtension alone does not imply reversal." : direction != 0 ? "The filtered path is directional.\nRead pace and participation separately." : "Directional movement is weak under this definition.\nA balanced reading does not predict a breakout."
string hypothesis = not scaleKnown ? "Insufficient normalized evidence." : shock ? "Information arrival or a liquidity change are possible.\nThe chart does not identify the cause." : direction != 0 and efficiency >= 0.55 ? "Persistent execution demand is one possible explanation.\nParticipant identity and intent are unobserved." : "Two-sided trade is one possible explanation.\nOHLCV cannot identify who supplied liquidity."
color stateColor = not warm or not scaleKnown ? neutralColor : direction > 0 ? upColor : direction < 0 ? downColor : neutralColor

// Developing bars carry no final plot/marker. The table holds the last closed bar.
bool confirmed = barstate.isconfirmed
plot(confirmed and warm ? level : na, "Filtered price", color = stateColor, linewidth = 2, style = plot.style_linebr)
upperPlot = plot(confirmed and scaleKnown and showEnvelope ? level + 1.5 * scale : na, "Upper descriptive envelope", color = color.new(neutralColor, 80), style = plot.style_linebr)
lowerPlot = plot(confirmed and scaleKnown and showEnvelope ? level - 1.5 * scale : na, "Lower descriptive envelope", color = color.new(neutralColor, 80), style = plot.style_linebr)
fill(upperPlot, lowerPlot, color = color.new(stateColor, 95), title = "Descriptive extension area")
plot(confirmed and showVwap ? vwapReference : na, "Calendar VWAP", color = color.new(cautionColor, 15), style = plot.style_linebr)
plot(confirmed and showScenario ? boundary : na, "Scenario boundary (not stop)", color = cautionColor, linewidth = 1, style = plot.style_circles)
barcolor(paintBars and confirmed and warm ? stateColor : na)
plotshape(confirmed and breakout == 1, "Upward breakout observation", shape.circle, location.belowbar, upColor, size = size.tiny)
plotshape(confirmed and breakout == -1, "Downward breakout observation", shape.circle, location.abovebar, downColor, size = size.tiny)
plotshape(confirmed and invalidated, "Scenario invalidated", shape.xcross, location.abovebar, cautionColor, size = size.tiny)

string panelPosition = corner == "Top right" ? position.top_right : corner == "Bottom right" ? position.bottom_right : corner == "Top left" ? position.top_left : position.bottom_left
var table panel = table.new(panelPosition, 2, detailed ? 18 : 12, bgcolor = color.new(chart.bg_color, 5), frame_color = color.new(chart.fg_color, 80), frame_width = 1)
if barstate.islast and showPanel
    int snapshot = confirmed ? 0 : 1
    color tone = stateColor[snapshot]
    string clockText = na(time_close[snapshot]) ? "No confirmed bar" : str.format_time(time_close[snapshot], "MM-dd HH:mm", syminfo.timezone)
    f_row(panel, 0, "KONATUS", "MARKET ANATOMY / 0.1", chart.fg_color)
    f_row(panel, 1, syminfo.ticker + " / " + timeframe.period, state[snapshot], tone)
    f_row(panel, 2, "Path / efficiency", organization[snapshot] + " / " + f_number(efficiency[snapshot]), tone)
    f_row(panel, 3, "Pace", pace[snapshot], tone)
    f_row(panel, 4, "Volatility", volatilityText[snapshot], shock[snapshot] ? cautionColor : chart.fg_color)
    f_row(panel, 5, "Location", locationText[snapshot], chart.fg_color)
    f_row(panel, 6, "Activity / rolling", participation[snapshot], chart.fg_color)
    f_row(panel, 7, "Anchor context", anchorText[snapshot], chart.fg_color)
    f_row(panel, 8, "Scenario", scenarioText[snapshot] + "\nInvalidation: " + failureText[snapshot], cautionColor)
    f_row(panel, 9, "Reading", reading[snapshot], chart.fg_color)
    if detailed
        f_row(panel, 10, "Possible mechanism", hypothesis[snapshot], neutralColor)
        f_row(panel, 11, "Velocity / acceleration", f_number(velocity[snapshot]) + " / " + f_number(acceleration[snapshot]), tone)
        f_row(panel, 12, "Local curvature", f_number(curvature[snapshot]) + " / defined coordinates", neutralColor)
        f_row(panel, 13, "Prior range scale", f_number(scale[snapshot]) + " / SMA true range", neutralColor)
        f_row(panel, 14, "Data meaning", volumeKind + " / " + syminfo.type, neutralColor)
        f_row(panel, 15, "Activity limitation", "Rolling baseline; no time-of-day adjustment.", neutralColor)
    f_row(panel, detailed ? 16 : 10, "Confirmed through", clockText + " / " + syminfo.timezone, neutralColor)
    f_row(panel, detailed ? 17 : 11, "Evidence", "Descriptive / parameters unvalidated", neutralColor)

// Exports enable a native TradingView versus Python fixture comparison.
plot(confirmed ? velocity : na, "Velocity", display = display.data_window)
plot(confirmed ? acceleration : na, "Acceleration", display = display.data_window)
plot(confirmed ? efficiency : na, "Efficiency", display = display.data_window)
plot(confirmed ? extension : na, "Extension", display = display.data_window)
plot(confirmed ? curvature : na, "Curvature", display = display.data_window)
plot(confirmed and warm ? volatility : na, "Volatility ratio", display = display.data_window)
plot(confirmed ? relativeVolume : na, "Relative activity", display = display.data_window)
plot(confirmed and warm ? scale : na, "Prior scale", display = display.data_window)
plot(confirmed and warm ? direction : na, "Direction", display = display.data_window)

bool directionChanged = scaleKnown and scaleKnown[1] and direction != direction[1]
alertcondition(confirmed and directionChanged, "Direction changed", "KONATUS {{ticker}} {{interval}}: confirmed direction changed. Read the explanation panel; this is not an entry order.")
alertcondition(confirmed and shock and not shock[1], "Volatility shock", "KONATUS {{ticker}} {{interval}}: confirmed range shock relative to prior history.")
alertcondition(confirmed and breakout != 0, "Range breakout observed", "KONATUS {{ticker}} {{interval}}: confirmed range breakout. A descriptive scenario boundary is now frozen.")
alertcondition(confirmed and invalidated, "Scenario invalidated", "KONATUS {{ticker}} {{interval}}: a confirmed close invalidated the frozen breakout scenario.")

Install in TradingView

  1. PasteOpen TradingView’s Pine Editor, create a blank indicator, replace its contents with the complete source, save, then select Add to chart.
  2. ChartUse ordinary time-based candles. Start with the 20-bar observation horizon and allow at least 82 consecutive valid bars for warmup.
  3. ReadEnable Detailed explanation for the mathematical readouts and mechanism hypotheses. The panel holds the last confirmed bar’s assessment.
  4. VolumeStart on Auto and check the feed’s volume meaning. Tick counts and quote-value activity stay distinct from trade units; missing volume leaves the price measurements available.
  5. AlertsChoose a direction change, shock, breakout or invalidation alert and use Once Per Bar Close. Recreate alerts after changing the code or inputs.

Designed for valid time-based OHLC data across stocks, ETFs, futures, forex and crypto; feed and timeframe coverage still need testing. Relative activity uses a rolling baseline, without time-of-day adjustment. The range scale is an SMA of true range, not Wilder’s ATR. A frozen breakout boundary is a scenario reference, not an order or stop.

The research package includes the script, setup guide, mathematics, specification and reference tests. Back to Konatus Bearing ↑

03 / New integrated edition

KONATUS
Atlas.

Understand the conditions.
See the plan.

Bearing’s planning engine and Anatomy’s market readings, already integrated in one Pine v6 indicator. One installation, one coordinated dashboard, and optional context filters.

Get the integrated code ↓
Brass armillary instrument surrounding a glass sphere with fine geometric arcs
KONATUS Atlas

Research release 0.1.0. 35 offline Python tests pass. Native TradingView compilation, chart behavior and alert delivery remain unverified. The optional combined policies have no established trading edge.

01 / Understand

Read the market.

Direction, velocity, acceleration, curvature, path efficiency, volatility and participation retain Anatomy’s explicit definitions.

02 / Plan

Keep the conditions.

Bearing retains its setup checks, illustrative next-open ledger, sizing assumptions, frozen stops and exit logic.

03 / Connect

Make context visible.

A shared dashboard explains agreement and conflict. Optional policies let Anatomy withhold new Bearing candidates.

A calmer chart. More explanation.

Choose compact or detailed readings, dark or light styling, text size and panel corner. One system owns candle colors. Extra Anatomy lines, the envelope and calendar VWAP are optional.

A solid gold line represents Bearing’s illustrative stop. A dotted teal line represents Anatomy’s descriptive scenario boundary. They have different jobs.

Dashboard design illustration with example values. Not a TradingView screenshot or live signal.

Choose how the engines work together

Observe only · default
Bearing’s candidates remain unchanged. Anatomy contributes explanation and a direction comparison. Agreement uses shared price data, not independent votes.
Require direction
New candidates also need a valid, warmed-up Anatomy scale, matching nonzero direction and no Anatomy shock.
Require organized direction
Adds a raw-path efficiency threshold of 0.55. This is an unvalidated research choice; fewer candidates does not establish better results.

The filters cannot create a candidate Bearing rejected or increase its position size. A context change does not cancel an already queued illustrative fill, erase a frozen stop or suppress an exit. Changing inputs recomputes the chart history.

Install Atlas by itself

  1. Paste the entire Atlas source into a new Pine Editor indicator, save and add it to an ordinary time-based candle chart. Remove the separate Bearing and Anatomy instances from that chart to avoid duplicate displays and alerts.
  2. Start with Anatomy entry policy → Observe only, Dashboard detail → Compact and Dashboard text → Small. Allow both engines to warm up; Anatomy needs 82 valid bars at its default horizon.
  3. Review the Bearing market, risk, contract and cost inputs. Check Anatomy’s volume meaning. The two engines retain different range scales and volume rules; their readings are not interchangeable.
  4. Use Any alert() function call for Bearing plan events inside Atlas. Select a named Atlas condition and Once Per Bar Close for Anatomy or context-block alerts. Recreate alerts when code, inputs, symbol or timeframe changes.

Get KONATUS Atlas 0.1.0

Download Atlas Pine v6Research package (.zip)

View the complete integrated Pine code
//@version=6
indicator("KONATUS Atlas · Integrated Research 0.1.0", "K Atlas", overlay = true, max_bars_back = 2000)
// Copyright (c) 2026 Kutty Stauder. Research software; no established trading edge.
// Built from frozen Bearing 0.3.1 and Anatomy 0.1.0 engines. See SPEC.md.
string atlasPolicy = input.string("Observe only", "Anatomy entry policy", options = ["Observe only", "Require direction", "Require organized direction"], group = "00 · Atlas", tooltip = "Observe preserves Bearing entries. Optional policies require valid Anatomy context, no Anatomy shock and matching direction. Organized also requires efficiency >= 0.55. Changes new entry candidates only; exits and queued fills remain active. These policies are unvalidated research rules.")
string atlasCorner = input.string("Top right", "Dashboard position", options = ["Top right", "Top left", "Bottom right", "Bottom left"], group = "00 · Atlas")
string atlasDensity = input.string("Compact", "Dashboard detail", options = ["Compact", "Detailed", "Hidden"], group = "00 · Atlas")
string atlasFont = input.string("Small", "Dashboard text", options = ["Small", "Tiny", "Normal"], group = "00 · Atlas")
bool atlasCandles = input.bool(true, "Color confirmed candles", group = "00 · Atlas")
bool atlasAnatomyLine = input.bool(false, "Show Anatomy filtered price", group = "00 · Atlas")
bool atlasEnvelope = input.bool(false, "Show Anatomy descriptive envelope", group = "00 · Atlas")
bool atlasVwap = input.bool(false, "Show Anatomy calendar VWAP", group = "00 · Atlas")
bool atlasScenario = input.bool(true, "Show Anatomy scenario boundary", group = "00 · Atlas")
bool atlasEvents = input.bool(false, "Show Anatomy observation markers", group = "00 · Atlas")

// === ANATOMY ENGINE ===
// One common measurement system; horizon is always measured in chart bars.
int a_horizon = input.int(20, "Observation horizon (bars)", minval = 5, maxval = 200, group = "A · Understanding")
string a_volumeInput = input.string("Auto", "Volume meaning", options = ["Auto", "Trade units", "Tick count", "Quote value", "Off"], group = "A · Volume and anchors", tooltip = "Auto follows feed metadata. Only declared trade units enable VWAP. Unknown volume stays unverified; tick activity is not traded quantity.")
string a_anchorInput = input.string("Auto", "VWAP calendar anchor", options = ["Auto", "Day", "Week", "Month", "Year", "Off"], group = "A · Volume and anchors", tooltip = "Exchange-calendar boundaries on the bars loaded in the chart. Auto: intraday/day, daily/month, weekly or monthly/year. First partial anchor is withheld. This is not a regular-session filter.")

a_f_number(float value) =>
    na(value) ? "Unavailable" : str.tostring(value, "#.###")

color a_upColor = color.rgb(38, 171, 145)
color a_downColor = color.rgb(224, 102, 108)
color a_cautionColor = color.rgb(213, 158, 66)
color a_neutralColor = color.new(chart.fg_color, 25)
bool a_timedChart = timeframe.isseconds or timeframe.isminutes or timeframe.isdaily or timeframe.isweekly or timeframe.ismonthly
bool a_supported = chart.is_standard and a_timedChart
bool a_validPrice = a_supported and not na(open) and not na(high) and not na(low) and not na(close) and low <= math.min(open, close) and high >= math.max(open, close)
var int a_priceCount = 0
a_priceCount := a_validPrice ? a_priceCount + 1 : 0
bool a_previousValid = a_validPrice[1]
float a_trueRange = a_validPrice ? (a_previousValid ? math.max(high - low, math.max(math.abs(high - close[1]), math.abs(low - close[1]))) : high - low) : na
float a_shortRange = ta.sma(a_trueRange, a_horizon)
float a_longRange = ta.sma(a_trueRange, 4 * a_horizon)
float a_scale = a_shortRange[1]
bool a_warm = a_priceCount >= 4 * a_horizon + 2
bool a_scaleKnown = a_warm and not na(a_scale) and a_scale > 0

// Two-pole SuperSmoother in signed price space. No log-price clipping.
float a_decay = math.exp(-math.sqrt(2.0) * math.pi / a_horizon)
float a_c2 = 2.0 * a_decay * math.cos(math.sqrt(2.0) * math.pi / a_horizon)
float a_c3 = -a_decay * a_decay
float a_c1 = 1.0 - a_c2 - a_c3
var float a_level = na
float a_level1 = a_previousValid ? nz(a_level[1], close) : close
float a_level2 = a_previousValid ? nz(a_level[2], a_level1) : close
a_level := a_validPrice ? a_c1 * (close + (a_previousValid ? close[1] : close)) / 2.0 + a_c2 * a_level1 + a_c3 * a_level2 : na
float a_velocity = a_scaleKnown ? (a_level - a_level1) / a_scale : na
float a_acceleration = a_scaleKnown ? (a_level - 2.0 * a_level1 + a_level2) / a_scale : na
float a_curvature = a_scaleKnown ? a_acceleration / math.pow(1.0 + a_velocity * a_velocity, 1.5) : na
float a_extension = a_scaleKnown ? (close - a_level) / a_scale : na
float a_distance = math.sum(math.abs(close - close[1]), a_horizon)
float a_efficiency = a_warm ? (a_distance > 0 ? math.abs(close - close[a_horizon]) / a_distance : 0.0) : na
float a_volatility = a_warm and a_longRange > 0 ? a_shortRange / a_longRange : na
float a_shockRatio = a_scaleKnown ? a_trueRange / a_scale : na
bool a_shock = a_scaleKnown and a_shockRatio >= 3.0
int a_direction = a_scaleKnown ? (a_velocity > 0.08 ? 1 : a_velocity < -0.08 ? -1 : 0) : 0

// Volume semantics remain separate from directional evidence.
string a_volumeKind = a_volumeInput == "Auto" ? (syminfo.volumetype == "base" ? "Trade units" : syminfo.volumetype == "tick" ? "Tick count" : syminfo.volumetype == "quote" ? "Quote value" : "Unverified") : a_volumeInput
bool a_validVolume = a_validPrice and not na(volume) and volume >= 0
var int a_volumeCount = 0
a_volumeCount := a_validVolume ? a_volumeCount + 1 : 0
float a_volumeMean = ta.sma(a_validVolume ? volume : na, a_horizon)[1]
float a_relativeVolume = a_volumeInput != "Off" and a_volumeCount >= a_horizon + 1 and a_volumeMean > 0 ? volume / a_volumeMean : na

string a_anchorTf = switch a_anchorInput
    "Day" => "1D"
    "Week" => "1W"
    "Month" => "1M"
    "Year" => "12M"
    => timeframe.isintraday ? "1D" : timeframe.isdaily ? "1M" : "12M"
float a_chartSeconds = a_timedChart ? timeframe.in_seconds() : na
bool a_anchorLonger = a_timedChart and timeframe.in_seconds(a_anchorTf) > a_chartSeconds
bool a_anchorEnabled = a_supported and a_anchorInput != "Off" and a_volumeKind == "Trade units" and a_anchorLonger
bool a_anchorChanged = timeframe.change(a_anchorTf) and not barstate.isfirst
var bool a_anchorComplete = false
var bool a_anchorPoisoned = false
var float a_anchorWeight = 0.0
var float a_anchorMean = 0.0
if a_anchorChanged
    a_anchorComplete := true
    a_anchorPoisoned := false
    a_anchorWeight := 0.0
    a_anchorMean := 0.0
if not a_validVolume
    a_anchorPoisoned := true
else if volume > 0 and a_anchorEnabled
    float a_totalWeight = a_anchorWeight + volume
    a_anchorMean += volume / a_totalWeight * (hlc3 - a_anchorMean)
    a_anchorWeight := a_totalWeight
float a_vwapReference = a_anchorEnabled and a_anchorComplete and not a_anchorPoisoned and a_anchorWeight > 0 ? a_anchorMean : na

// A scenario freezes a previously observable boundary. It is not a trade ledger.
float a_priorHigh = ta.highest(high, a_horizon)[1]
float a_priorLow = ta.lowest(low, a_horizon)[1]
var int a_scenario = 0
var float a_boundary = na
bool a_invalidated = false
int a_breakout = 0
if not a_scaleKnown
    a_scenario := 0
    a_boundary := na
else if a_scenario != 0 and (close - a_boundary) * a_scenario < 0
    a_invalidated := true
    a_scenario := 0
    a_boundary := na
else if a_scenario == 0
    if close > a_priorHigh
        a_scenario := 1
        a_boundary := a_priorHigh
        a_breakout := 1
    else if close < a_priorLow
        a_scenario := -1
        a_boundary := a_priorLow
        a_breakout := -1

// Each message is a direct explanation of a measurement or a named hypothesis.
string a_state = not a_supported ? "UNSUPPORTED CHART" : not a_validPrice ? "INVALID PRICE DATA" : not a_warm ? "BUILDING HISTORY" : not a_scaleKnown ? (a_trueRange == 0 ? "FLAT" : "SCALE UNAVAILABLE") : a_direction > 0 ? "UPWARD" : a_direction < 0 ? "DOWNWARD" : "BALANCED"
string a_organization = not a_warm ? "Awaiting history" : a_efficiency >= 0.55 ? "Directional path" : a_efficiency < 0.25 ? "Backtracking path" : "Mixed path"
float a_directionalAcceleration = a_acceleration * a_direction
string a_pace = not a_scaleKnown ? "Unavailable" : a_direction == 0 ? "No directional pace" : a_directionalAcceleration > 0.015 ? "Gaining pace" : a_directionalAcceleration < -0.015 ? "Losing pace" : "Steady pace"
string a_volatilityText = not a_warm ? "Awaiting history" : a_shock ? "SHOCK / " + a_f_number(a_shockRatio) + " x prior range" : na(a_volatility) ? "No range" : a_volatility > 1.3 ? "Expanding" : a_volatility < 0.7 ? "Contracting" : "Near longer baseline"
string a_locationText = not a_scaleKnown ? "Unavailable" : (math.abs(a_extension) >= 1.5 ? "Extended / " : "Near reference / ") + a_f_number(a_extension) + " range units"
string a_participation = a_volumeInput == "Off" ? "Off / price-only context" : not a_validVolume ? "Missing volume" : na(a_relativeVolume) ? "Baseline unavailable" : a_f_number(a_relativeVolume) + " x / " + a_volumeKind
if timeframe.isintraday and a_volumeInput != "Off"
    a_participation += "\nNot time-of-day adjusted"
string a_anchorText = a_anchorInput == "Off" ? "Off" : a_volumeKind != "Trade units" ? "Requires declared trade units" : not a_anchorLonger ? "Anchor must exceed chart interval" : not a_anchorComplete ? "First partial anchor withheld" : a_anchorPoisoned ? "Anchor has missing data" : a_anchorWeight <= 0 ? "No positive anchor volume" : (close > a_vwapReference ? "Above " : close < a_vwapReference ? "Below " : "At ") + a_anchorTf + " VWAP"
string a_scenarioText = a_invalidated ? "Prior breakout failed" : a_scenario > 0 ? "Upward range breakout" : a_scenario < 0 ? "Downward range breakout" : "No active breakout scenario"
string a_failureText = a_scenario > 0 ? "Close below " + str.tostring(a_boundary, format.mintick) : a_scenario < 0 ? "Close above " + str.tostring(a_boundary, format.mintick) : "No scenario boundary"
string a_reading = not a_warm ? "Waiting for " + str.tostring(4 * a_horizon + 2) + " consecutive valid bars." : not a_scaleKnown ? "Normalized movement is unavailable." : a_shock ? "Range jumped relative to its prior baseline.\nThe previous pace may no longer describe conditions." : a_direction != 0 and a_efficiency < 0.25 ? "The smoother points one way; raw price backtracks.\nDirection and path organization conflict." : a_direction != 0 and math.abs(a_extension) >= 1.5 ? "Price is far from the smoothed reference.\nExtension alone does not imply reversal." : a_direction != 0 ? "The filtered path is directional.\nRead pace and participation separately." : "Directional movement is weak under this definition.\nA balanced reading does not predict a breakout."
string a_hypothesis = not a_scaleKnown ? "Insufficient normalized evidence." : a_shock ? "Information arrival or a liquidity change are possible.\nThe chart does not identify the cause." : a_direction != 0 and a_efficiency >= 0.55 ? "Persistent execution demand is one possible explanation.\nParticipant identity and intent are unobserved." : "Two-sided trade is one possible explanation.\nOHLCV cannot identify who supplied liquidity."
color a_stateColor = not a_warm or not a_scaleKnown ? a_neutralColor : a_direction > 0 ? a_upColor : a_direction < 0 ? a_downColor : a_neutralColor


// === BEARING ENGINE ===
// Copyright (c) 2026 Kontaus. Experimental kb-0.3; no established market edge.
bool b_allowShort = input.bool(false, "Enable short entries", group = "B · 00 · Entry direction", tooltip = "Long entries are enabled. Turn this on to also allow red short-entry triangles and short plans. Signals still require all setup and risk conditions. This control is in the cross-market 0.3.1 indicator; the old 0.2 baseline is long-only.")
string b_profileInput = input.string("Auto", "Instrument profile", options = ["Auto", "Stocks", "Bonds", "Futures", "Forex", "Crypto", "Generic"], group = "B · 01 · Market")
string b_profile = b_profileInput == "Auto" ? (syminfo.type == "stock" or syminfo.type == "fund" ? "Stocks" : syminfo.type == "futures" ? "Futures" : syminfo.type == "forex" ? "Forex" : syminfo.type == "crypto" ? "Crypto" : "Generic") : b_profileInput
string b_volumePolicy = input.string("Profile", "Participation policy", options = ["Profile", "Required", "Price only"], group = "B · 01 · Market", tooltip = "Profile requires volume for Stocks/Futures/Crypto. Bonds/Forex/Generic use price only. Missing required volume blocks new plans.")
string b_volumeMode = input.string("Prior EW", "Volume baseline", options = ["Prior EW", "Session slot"], group = "B · 01 · Market", tooltip = "Prior EW uses 20 earlier volume observations. Session slot uses at least 5 earlier trading days at this exchange-local minute. Minute or hourly charts only; no current-bar volume in the baseline.")
bool b_vwapGate = input.bool(false, "Require session VWAP alignment", group = "B · 01 · Market", tooltip = "Requires a fully observed trading-day anchor and positive current volume. VWAP is context, not order flow.")
int b_resetAt = input.time(0, "Reset model at contract/data boundary", group = "B · 01 · Market", tooltip = "Optional timestamp. Resets analytical history once; never cancels an open illustrative stop. Does not construct or adjust a futures roll.")
string b_setupMode = input.string("Breakout", "Setup", options = ["Breakout", "Pullback", "Either"], group = "B · 02 · Signals")
float b_decay = input.float(0.9, "Evidence memory", minval = 0.01, maxval = 0.99, step = 0.01, group = "B · 02 · Signals")
float b_persistenceMin = input.float(20, "Minimum directional evidence", minval = 0, maxval = 100, group = "B · 02 · Signals")
float b_extensionLimit = input.float(1.5, "Maximum entry extension · ATR", minval = 0.1, group = "B · 02 · Signals")
float b_efficiencyMin = input.float(0.25, "Minimum directional efficiency", minval = 0, maxval = 1, group = "B · 02 · Signals")
float b_shockAtr = input.float(4, "Shock threshold · prior ATR", minval = 0.1, group = "B · 02 · Signals")
int b_cooldown = input.int(3, "Shock cooldown · bars", minval = 0, maxval = 100, group = "B · 02 · Signals")
float b_account = input.float(100000, "Reference account", minval = 1, group = "B · 03 · Risk")
float b_riskPct = input.float(1, "Planned risk %", minval = 0, maxval = 100, group = "B · 03 · Risk")
float b_stopAtr = input.float(3, "Frozen distance · ATR", minval = 0.1, group = "B · 03 · Risk")
float b_conversion = input.float(1, "Account currency per quote currency", minval = 0.00000001, group = "B · 03 · Risk", tooltip = "Explicit fixed conversion for the illustration, not an FX feed. Use 1 only when account and quote currency match. Recompute when conversion changes.")
float b_pointOverride = input.float(0, "Point-value override · 0 = symbol", minval = 0, group = "B · 03 · Risk")
float b_pointValue = b_pointOverride > 0 ? b_pointOverride : syminfo.pointvalue
float b_tick = syminfo.mintick
float b_lot = input.float(1, "Quantity increment", minval = 0.00000001, group = "B · 03 · Risk", tooltip = "Quantity uses symbol units/contracts, not broker-defined FX lots. Confirm the point value and increment for this instrument.")
float b_margin = input.float(0, "Capacity per unit · account currency", minval = 0, group = "B · 03 · Risk", tooltip = "Required for Futures and nonpositive-price plans. Enter a conservative per-contract allocation. It is not a live broker margin model. Zero uses cash notional for other positive-price instruments.")
float b_capitalFraction = input.float(1, "Maximum capacity · account fraction", minval = 0, maxval = 1, group = "B · 03 · Risk")
float b_costBps = input.float(5, "One-way notional costs · bps", minval = 0, group = "B · 03 · Risk")
float b_feePerUnit = input.float(0, "One-way fee per unit · quote currency", minval = 0, group = "B · 03 · Risk")
int b_slipTicks = input.int(1, "Adverse slippage · ticks", minval = 0, maxval = 100, group = "B · 03 · Risk")
string b_theme = input.string("Auto", "Theme", options = ["Auto", "Dark", "Light"], group = "B · 04 · Appearance")
if not chart.is_standard or timeframe.isticks
    runtime.error("Konatus requires standard time-based OHLC charts.")
if na(b_pointValue) or na(b_tick) or math.sign(b_pointValue) <= 0 or math.sign(b_tick) <= 0
    runtime.error("A positive tick and point value are required.")
if b_volumeMode == "Session slot" and (not timeframe.isintraday or timeframe.in_seconds() < 60)
    runtime.error("Session-slot volume needs intraday time bars of at least one minute.")
bool b_needsVolume = b_volumePolicy == "Required" or (b_volumePolicy == "Profile" and (b_profile == "Stocks" or b_profile == "Futures" or b_profile == "Crypto"))

if b_vwapGate and not timeframe.isintraday
    runtime.error("Session VWAP gating requires intraday time bars. Disable it on daily and higher charts.")

// Shared confirmed-bar analytical engine. Price units allow zero/negative prices.
var float b_level = na
var float b_drift = 0
var float b_p00 = 0
var float b_p01 = 0
var float b_p11 = 0
var float b_atr = 0
var float b_trSum = 0
var float b_previous = na
var float b_evidence = 0
var int b_count = 0
var int b_direction = 0
var int b_runLength = 0
var int b_pause = 0
var array<float> b_highs = array.new_float()
var array<float> b_lows = array.new_float()
var array<float> b_closes = array.new_float()
var array<float> b_slotMean = array.new_float(1440, 0)
var array<int> b_slotCount = array.new_int(1440, 0)
var array<int> b_slotAnchor = array.new_int(1440, na)
var float b_volMean = 0
var int b_volCount = 0
var int b_anchor = na
var bool b_anchorComplete = false
var bool b_anchorValid = true
var float b_pv = 0
var float b_vSum = 0
var float b_vwap = na
var float b_relativeVolume = na
var float b_efficiency = 0
var float b_extension = na
var float b_surprise = na
var float b_predictiveSd = na
var float b_distance = 0
var float b_quantity = 0
var string b_volumeStatus = "WARMUP"
var string b_condition = "WARMUP"
var string b_entryStatus = "WARMUP"
var string b_setupName = "NONE"
var int b_breakoutSide = 0
var float b_breakoutLevel = na
var bool b_ready = false
int b_signal = 0
bool b_valid = not na(open) and not na(high) and not na(low) and not na(close) and (math.min(open, close) - low) / b_tick >= 0 and (high - math.max(open, close)) / b_tick >= 0
bool b_resetNow = b_resetAt > 0 and time >= b_resetAt and (na(time[1]) or time[1] < b_resetAt)
if barstate.isconfirmed
    if not b_valid or b_resetNow
        b_level := na
        b_drift := 0
        b_p00 := 0
        b_p01 := 0
        b_p11 := 0
        b_atr := 0
        b_trSum := 0
        b_previous := na
        b_evidence := 0
        b_count := 0
        b_direction := 0
        b_runLength := 0
        b_pause := 0
        array.clear(b_highs)
        array.clear(b_lows)
        array.clear(b_closes)
        array.fill(b_slotMean, 0)
        array.fill(b_slotCount, 0)
        array.fill(b_slotAnchor, na)
        b_volMean := 0
        b_volCount := 0
        b_anchor := na
        b_anchorComplete := false
        b_anchorValid := true
        b_pv := 0
        b_vSum := 0
        b_vwap := na
        b_breakoutSide := 0
        b_ready := false
        b_quantity := 0
        b_distance := 0
        b_extension := na
        b_efficiency := 0
        b_surprise := na
        b_predictiveSd := na
        b_relativeVolume := na
        b_volumeStatus := "UNAVAILABLE"
        b_condition := "INVALID DATA"
        b_entryStatus := "INVALID DATA"
        b_setupName := "NONE"
    if b_valid
        float b_priorLevel = b_level
        float b_tr = na(b_previous) ? high - low : math.max(high - low, math.abs(high - b_previous), math.abs(low - b_previous))
        float b_scale = math.max(b_count >= 14 ? b_atr : b_count > 0 ? b_trSum / b_count : b_tr, b_tick)
        float b_variance = b_scale * b_scale
        float b_observation = 0.25 * b_variance
        if na(b_level)
            b_level := close
            b_p00 := b_observation
            b_p11 := b_observation
            b_surprise := 0
            b_predictiveSd := math.sqrt(2 * b_observation)
        else
            float b_a = b_p00 + 2 * b_p01 + b_p11 + 0.01 * b_variance
            float b_b = b_p01 + b_p11
            float b_d = b_p11 + 0.0001 * b_variance
            float b_predictive = b_a + b_observation
            float b_innovation = close - b_level - b_drift
            b_surprise := b_innovation / math.sqrt(b_predictive)
            b_predictiveSd := math.sqrt(b_predictive)
            float b_k0 = b_a / b_predictive
            float b_k1 = b_b / b_predictive
            float b_m = 1 - b_k0
            b_level += b_drift + b_k0 * b_innovation
            b_drift += b_k1 * b_innovation
            b_p00 := b_m * b_m * b_a + b_k0 * b_k0 * b_observation
            b_p01 := b_m * (b_b - b_k1 * b_a) + b_k0 * b_k1 * b_observation
            b_p11 := b_d - 2 * b_k1 * b_b + b_k1 * b_k1 * b_a + b_k1 * b_k1 * b_observation
        b_count += 1
        if b_count <= 14
            b_trSum += b_tr
            b_atr := b_trSum / b_count
        else
            b_atr += (b_tr - b_atr) / 14
        int b_priorDirection = b_direction
        b_direction := b_drift / b_tick > 0.000001 ? 1 : b_drift / b_tick < -0.000001 ? -1 : 0
        b_runLength := b_direction != 0 and b_direction == b_priorDirection ? b_runLength + 1 : b_direction != 0 ? 1 : 0
        b_evidence := b_decay * b_evidence + (1 - b_decay) * math.max(-1, math.min(1, b_drift / (0.1 * b_scale)))
        array.push(b_closes, close)
        if array.size(b_closes) > 21
            array.shift(b_closes)
        float b_travel = 0
        if array.size(b_closes) > 1
            for j = 1 to array.size(b_closes) - 1
                b_travel += math.abs(array.get(b_closes, j) - array.get(b_closes, j - 1))
        b_efficiency := b_travel > 0 ? math.abs(close - array.get(b_closes, 0)) / b_travel : 0
        b_extension := math.abs(close - b_level) / math.max(b_atr, b_tick)
        bool b_shock = not na(b_previous) and math.abs(close - b_previous) / b_scale > b_shockAtr
        b_pause := b_shock ? b_cooldown : math.max(0, b_pause - 1)
        b_ready := b_count >= 40 and b_atr / b_tick > 0.000001
        b_condition := b_count < 40 ? "WARMUP" : not b_ready ? "FLAT" : b_shock or b_pause > 0 ? "SHOCK" : b_efficiency < b_efficiencyMin ? "CONGESTION" : "TREND"
        bool b_haveVolume = not na(volume) and volume >= 0
        int b_slot = hour(time, syminfo.timezone) * 60 + minute(time, syminfo.timezone)
        int b_dayAnchor = time_tradingday
        float b_baseline = b_volumeMode == "Session slot" ? array.get(b_slotMean, b_slot) : b_volMean
        int b_samples = b_volumeMode == "Session slot" ? array.get(b_slotCount, b_slot) : b_volCount
        int b_lastAnchor = array.get(b_slotAnchor, b_slot)
        bool b_duplicateSlot = b_volumeMode == "Session slot" and not na(b_lastAnchor) and b_lastAnchor == b_dayAnchor
        bool b_enough = b_samples >= (b_volumeMode == "Session slot" ? 5 : 20)
        b_relativeVolume := b_haveVolume and b_enough and b_baseline > 0 and not b_duplicateSlot ? volume / b_baseline : na
        b_volumeStatus := not b_haveVolume ? "VOLUME MISSING" : b_duplicateSlot ? "SLOT REPEATED" : volume == 0 ? "ZERO VOLUME" : na(b_relativeVolume) ? "VOLUME WARMUP" : "AVAILABLE"
        if b_haveVolume and not b_duplicateSlot
            float b_updated = b_samples == 0 ? volume : b_baseline + (volume - b_baseline) * 2 / 21
            if b_volumeMode == "Session slot"
                array.set(b_slotMean, b_slot, b_updated)
                array.set(b_slotCount, b_slot, b_samples + 1)
                array.set(b_slotAnchor, b_slot, b_dayAnchor)
            else
                b_volMean := b_updated
                b_volCount += 1
        if not b_needsVolume
            b_volumeStatus := "PRICE ONLY"
        bool b_volumeOk = not b_needsVolume or (b_volumeStatus == "AVAILABLE" and b_relativeVolume >= 1)
        if na(b_anchor) or b_dayAnchor != b_anchor
            b_anchorComplete := not na(b_anchor)
            b_anchor := b_dayAnchor
            b_anchorValid := true
            b_pv := 0
            b_vSum := 0
        if not b_haveVolume
            b_anchorValid := false
        if b_haveVolume and volume > 0
            b_pv += hlc3 * volume
            b_vSum += volume
        b_vwap := b_vSum > 0 ? b_pv / b_vSum : na
        bool b_contextOk = not b_vwapGate or (b_anchorValid and b_anchorComplete and not na(b_vwap) and b_haveVolume and volume > 0 and b_direction * (close - b_vwap) / b_tick > 0)
        int b_fire = 0
        if not b_ready or b_shock or b_pause > 0
            b_breakoutSide := 0
        else if b_breakoutSide != 0
            if b_breakoutSide * (close - b_breakoutLevel) / b_tick > 0
                b_fire := b_breakoutSide
            b_breakoutSide := 0
        else if array.size(b_highs) == 20
            if (close - array.max(b_highs)) / b_tick > 0
                b_breakoutSide := 1
                b_breakoutLevel := array.max(b_highs)
            else if (array.min(b_lows) - close) / b_tick > 0
                b_breakoutSide := -1
                b_breakoutLevel := array.min(b_lows)
        int b_pull = not na(b_previous) and not na(b_priorLevel) and b_direction * (b_previous - b_priorLevel) / b_tick <= 0 and b_direction * (close - b_level) / b_tick > 0 ? b_direction : 0
        int b_setupSide = b_setupMode == "Breakout" ? b_fire : b_setupMode == "Pullback" ? b_pull : b_fire != 0 ? b_fire : b_pull
        b_setupName := b_setupSide != 0 and b_setupSide == b_fire ? "BREAKOUT" : b_setupSide != 0 ? "PULLBACK" : "NONE"
        b_distance := math.ceil(b_stopAtr * b_atr / b_tick - 1e-9) * b_tick
        float b_slip = b_slipTicks * b_tick
        float b_fees = 2 * b_feePerUnit + (2 * math.abs(close) + b_distance + 2 * b_slip) * b_pointValue * b_costBps / 10000
        float b_unitRisk = ((b_distance + b_slip + b_tick) * b_pointValue + b_fees) * b_conversion
        float b_capacity = b_profile == "Futures" or math.sign(close) <= 0 ? b_margin : b_margin > 0 ? b_margin : (math.abs(close) + b_slip) * b_pointValue * b_conversion
        float b_capacityWithFees = b_capacity + (b_feePerUnit + (math.abs(close) + b_slip) * b_pointValue * b_costBps / 10000) * b_conversion
        b_quantity := math.sign(b_capacity) > 0 and math.sign(b_distance) > 0 and math.sign(b_unitRisk) > 0 ? math.max(0, math.floor(math.min(b_account * b_riskPct / 100 / b_unitRisk, b_account * b_capitalFraction / b_capacityWithFees) / b_lot) * b_lot) : 0
        b_entryStatus := b_count < 40 ? "WARMUP" : not b_ready ? "FLAT" : b_condition == "SHOCK" ? "SHOCK PAUSE" : b_condition == "CONGESTION" ? "CONGESTION" : b_direction == 0 or b_direction * b_evidence * 100 < b_persistenceMin ? "WEAK EVIDENCE" : b_direction < 0 and not b_allowShort ? "SHORT DISABLED" : not b_volumeOk and b_volumeStatus != "AVAILABLE" ? b_volumeStatus : not b_volumeOk ? "LOW PARTICIPATION" : not b_contextOk ? "VWAP CONTEXT" : b_extension > b_extensionLimit ? "EXTENDED" : b_quantity <= 0 ? "SIZE UNAVAILABLE" : b_setupSide == b_direction ? "READY" : "WAIT SETUP"
        b_signal := b_entryStatus == "READY" ? b_direction : 0
        array.push(b_highs, high)
        array.push(b_lows, low)
        if array.size(b_highs) > 20
            array.shift(b_highs)
            array.shift(b_lows)
        b_previous := close

// Integration changes candidate entries only; existing plans keep their original ledger.
var string atlasGateStatus = "OBSERVE ONLY"
var bool atlasBlocked = false
if barstate.isconfirmed
    atlasBlocked := false
    atlasGateStatus := atlasPolicy == "Observe only" ? "OBSERVE ONLY" : not a_scaleKnown ? "ANATOMY WARMUP / DATA" : a_shock ? "ANATOMY SHOCK" : a_direction == 0 or a_direction != b_direction ? "DIRECTION CONFLICT" : atlasPolicy == "Require organized direction" and (na(a_efficiency) or a_efficiency < 0.55) ? "PATH NOT ORGANIZED" : "CONTEXT ALIGNED"
    if atlasPolicy != "Observe only" and b_signal != 0 and atlasGateStatus != "CONTEXT ALIGNED"
        b_signal := 0
        b_entryStatus := atlasGateStatus
        atlasBlocked := true

// Illustrative next-open ledger. Orders are not sent to a broker.
var int b_heldSide = 0
var int b_pendingSide = 0
var float b_pendingQuantity = 0
var float b_pendingDistance = 0
var float b_heldQuantity = 0
var float b_entryPrice = na
var float b_stopPrice = na
var float b_heldDistance = na
var int b_reversalCount = 0
var bool b_pendingExit = false
var bool b_capacityBreached = false
bool b_entryIntent = false
bool b_exitIntent = false
bool b_entered = false
bool b_exited = false
string b_exitReason = ""
if barstate.isconfirmed and b_valid
    bool b_dueExit = b_pendingExit
    b_pendingExit := false
    float b_slip = b_slipTicks * b_tick
    if b_heldSide != 0
        if b_heldSide * (open - b_stopPrice) / b_tick <= 0
            b_exited := true
            b_exitReason := "GAP_STOP"
        else if b_dueExit
            b_exited := true
            b_exitReason := "REVERSAL"
    if b_heldSide == 0 and b_pendingSide != 0
        b_heldSide := b_pendingSide
        b_heldQuantity := b_pendingQuantity
        b_heldDistance := b_pendingDistance
        b_entryPrice := open + b_heldSide * b_slip
        float b_rawStop = b_entryPrice - b_heldSide * b_heldDistance
        b_stopPrice := (b_heldSide > 0 ? math.floor(b_rawStop / b_tick + 1e-9) : math.ceil(b_rawStop / b_tick - 1e-9)) * b_tick
        float b_entryFee = b_heldQuantity * (b_feePerUnit + math.abs(b_entryPrice) * b_pointValue * b_costBps / 10000) * b_conversion
        float b_filledCapacity = b_margin > 0 ? b_margin : math.abs(b_entryPrice) * b_pointValue * b_conversion
        b_capacityBreached := b_filledCapacity * b_heldQuantity + b_entryFee > b_account * b_capitalFraction
        b_pendingSide := 0
        b_entered := true
    if b_heldSide != 0 and not b_exited and (b_heldSide > 0 ? (b_stopPrice - low) / b_tick >= 0 : (high - b_stopPrice) / b_tick >= 0)
        b_exited := true
        b_exitReason := "STOP"
    if b_exited
        b_heldSide := 0
        b_reversalCount := 0
    if b_heldSide != 0
        b_reversalCount := b_direction == -b_heldSide ? b_reversalCount + 1 : 0
        if b_reversalCount >= 2
            b_pendingExit := true
            b_exitIntent := true
    else if not b_exited and b_signal != 0
        b_pendingSide := b_signal
        b_pendingQuantity := b_quantity
        b_pendingDistance := b_distance
        b_entryIntent := true
// Alerts are decision events. Namespace separates configurations/alert instances.
string b_alertNamespace = input.string("default", "Alert instance ID", group = "B · 05 · Alerts", tooltip = "Use a distinct ID for each configuration. Allowed characters: letters, numbers, dash and underscore. Recreate TradingView alerts after settings change.")
if str.length(b_alertNamespace) == 0 or str.match(b_alertNamespace, "^[A-Za-z0-9_-]+$") != b_alertNamespace
    runtime.error("Alert instance ID must contain only letters, numbers, dash or underscore.")
if b_entryIntent or b_exitIntent
    string b_eventName = b_entryIntent ? "ENTRY_INTENT" : "EXIT_INTENT"
    int b_eventSide = b_entryIntent ? b_pendingSide : b_heldSide
    string b_eventId = "atlas-0.1:" + b_alertNamespace + ":" + syminfo.tickerid + ":" + timeframe.period + ":" + str.tostring(time_close) + ":" + b_eventName + ":" + str.tostring(b_eventSide)
    string b_payload = '{"version":"atlas-0.1-experimental","id":"' + b_eventId + '","event":"' + b_eventName + '","symbol":"' + syminfo.tickerid + '","timeframe":"' + timeframe.period + '","time_close":' + str.tostring(time_close) + ',"side":' + str.tostring(b_eventSide) + ',"quantity":' + str.tostring(b_entryIntent ? b_pendingQuantity : b_heldQuantity, "#.########") + ',"distance":' + str.tostring(b_entryIntent ? b_pendingDistance : b_heldDistance, "#.########") + ',"illustrative":true}'
    alert(b_payload, alert.freq_once_per_bar_close)


// === ATLAS PRESENTATION: one dashboard, one candle-color owner ===
bool atlasConfirmed = barstate.isconfirmed
bool atlasDark = b_theme == "Dark" or (b_theme == "Auto" and color.r(chart.bg_color) + color.g(chart.bg_color) + color.b(chart.bg_color) < 384)
color atlasSurface = atlasDark ? #101B24 : #F8F6EF
color atlasInk = atlasDark ? #F3EAD6 : #202D35
color atlasMuted = atlasDark ? #A7B9BD : #4C6069
color atlasUp = atlasDark ? #63D4B7 : #096A55
color atlasDown = atlasDark ? #F1A394 : #9B322C
color atlasGold = atlasDark ? #E5C078 : #755308
color atlasBorder = atlasDark ? #34464C : #B6C4C2
color atlasBearingColor = not b_ready or math.abs(b_evidence * 100) < b_persistenceMin ? atlasMuted : b_direction > 0 ? atlasUp : atlasDown
color atlasAnatomyColor = a_direction > 0 ? atlasUp : a_direction < 0 ? atlasDown : atlasMuted
bool atlasDisplayValid = atlasConfirmed ? b_valid : b_valid[1]
string atlasState = not atlasDisplayValid ? "DATA UNAVAILABLE" : b_pendingExit ? "EXIT QUEUED" : b_heldSide != 0 ? (b_heldSide > 0 ? "LONG PLAN" : "SHORT PLAN") : b_pendingSide != 0 ? "ENTRY QUEUED" : b_entryStatus
string atlasReason = b_heldSide != 0 ? (not atlasDisplayValid ? "Data invalid; last stop retained" : b_capacityBreached ? "Capacity exceeded at illustrative fill" : "Illustrative stop remains active") : b_pendingSide != 0 ? "Illustrative next-open fill queued" : atlasBlocked ? "Anatomy policy blocked this entry candidate" : b_entryStatus == "EXTENDED" ? "Bearing: price exceeds entry extension limit" : b_entryStatus == "SIZE UNAVAILABLE" ? "Review contract, capacity and risk inputs" : b_entryStatus == "WAIT SETUP" ? "Bearing conditions available; wait for setup" : "Read conditions before interpreting a setup"
string atlasPosition = atlasCorner == "Top right" ? position.top_right : atlasCorner == "Top left" ? position.top_left : atlasCorner == "Bottom right" ? position.bottom_right : position.bottom_left
string atlasTextSize = atlasFont == "Tiny" ? size.tiny : atlasFont == "Normal" ? size.normal : size.small
bool atlasDetailed = atlasDensity == "Detailed"
var table atlasPanel = table.new(atlasPosition, 2, atlasDetailed ? 23 : 14, bgcolor = atlasSurface, frame_color = atlasBorder, frame_width = 1)
atlas_row(int n, string caption, string value, color tone) =>
    table.cell(atlasPanel, 0, n, " " + caption + " ", text_color = atlasMuted, bgcolor = atlasSurface, text_size = atlasTextSize, text_halign = text.align_left)
    table.cell(atlasPanel, 1, n, " " + value + " ", text_color = tone, bgcolor = atlasSurface, text_size = atlasTextSize, text_halign = text.align_right)
if barstate.isfirst
    table.merge_cells(atlasPanel, 0, 1, 1, 1)
    table.merge_cells(atlasPanel, 0, 2, 1, 2)
    table.merge_cells(atlasPanel, 0, atlasDetailed ? 22 : 13, 1, atlasDetailed ? 22 : 13)
if barstate.islast
    table.clear(atlasPanel, 0, 0, 1, atlasDetailed ? 22 : 13)
    if atlasDensity != "Hidden" or (b_heldSide != 0 and (b_capacityBreached or not atlasDisplayValid))
        int snap = atlasConfirmed ? 0 : 1
        bool anatomyAvailable = a_scaleKnown[snap]
        bool agreement = anatomyAvailable and b_ready and b_direction != 0 and b_direction == a_direction[snap]
        string alignment = not anatomyAvailable or not b_ready ? "Awaiting valid history" : b_direction == 0 or a_direction[snap] == 0 ? "At least one is balanced" : agreement ? "Directions agree / shared data" : "Directions disagree"
        string clockText = na(time_close[snap]) ? "No confirmed bar" : str.format_time(time_close[snap], "MM-dd HH:mm", syminfo.timezone)
        atlas_row(0, "KONATUS ATLAS", syminfo.ticker + " / " + timeframe.period, atlasGold)
        table.cell(atlasPanel, 0, 1, " " + atlasState + " ", text_color = b_heldSide != 0 ? atlasBearingColor : atlasGold, bgcolor = atlasSurface, text_size = atlasFont == "Tiny" ? size.normal : size.large, text_halign = text.align_left)
        table.cell(atlasPanel, 0, 2, " " + atlasReason + " ", text_color = atlasMuted, bgcolor = atlasSurface, text_size = atlasTextSize, text_halign = text.align_left)
        atlas_row(3, "BEARING / ANATOMY", (b_direction > 0 ? "Up" : b_direction < 0 ? "Down" : "Balanced") + " / " + a_state[snap], atlasInk)
        atlas_row(4, "ENTRY POLICY", atlasGateStatus, atlasGateStatus == "CONTEXT ALIGNED" ? atlasUp : atlasGold)
        atlas_row(5, "PACE / RAW PATH", a_pace[snap] + " / " + a_organization[snap], atlasInk)
        atlas_row(6, "ANATOMY VOLATILITY", a_volatilityText[snap], a_shock[snap] ? atlasGold : atlasInk)
        atlas_row(7, "BEARING NEW ENTRY", b_entryStatus, atlasInk)
        atlas_row(8, "ILLUSTRATIVE STOP", b_heldSide != 0 ? str.tostring(b_stopPrice, format.mintick) : "No open plan", atlasGold)
        atlas_row(9, "ACTIVE / QUEUED QTY", str.tostring(b_heldSide != 0 ? b_heldQuantity : b_pendingSide != 0 ? b_pendingQuantity : 0, "#.########"), atlasInk)
        atlas_row(10, "ALIGNMENT", alignment, agreement ? atlasUp : atlasMuted)
        atlas_row(11, "ACTIVITY / ROLLING", a_participation[snap], atlasMuted)
        atlas_row(12, "CONFIRMED THROUGH", clockText + " / " + syminfo.timezone, atlasMuted)
        if atlasDetailed
            atlas_row(13, "ANATOMY READING", a_reading[snap], atlasInk)
            atlas_row(14, "POSSIBLE MECHANISM", a_hypothesis[snap], atlasMuted)
            atlas_row(15, "VELOCITY / ACCELERATION", a_f_number(a_velocity[snap]) + " / " + a_f_number(a_acceleration[snap]), atlasInk)
            atlas_row(16, "CURVATURE / EFFICIENCY", a_f_number(a_curvature[snap]) + " / " + a_f_number(a_efficiency[snap]), atlasInk)
            atlas_row(17, "ANATOMY LOCATION", a_locationText[snap], atlasInk)
            atlas_row(18, "BEARING EXTENSION", str.tostring(b_extension, "0.00") + " ATR", atlasInk)
            atlas_row(19, "SCENARIO / NOT A STOP", a_scenarioText[snap] + "\n" + a_failureText[snap], atlasMuted)
            atlas_row(20, "ANATOMY ANCHOR", a_anchorText[snap], atlasMuted)
            atlas_row(21, "BEARING PARTICIPATION", b_volumeStatus, atlasMuted)
        table.cell(atlasPanel, 0, atlasDetailed ? 22 : 13, " ATLAS 0.1 / RESEARCH / ILLUSTRATIVE PLANS ", text_color = atlasMuted, bgcolor = atlasSurface, text_size = size.tiny)

// Restrain overlays. Gold = illustrative stop; dotted teal = descriptive scenario.
plot(b_level, "Bearing price", color = atlasBearingColor, linewidth = 2)
plot(atlasConfirmed and atlasAnatomyLine and a_warm ? a_level : na, "Anatomy filtered price", color = atlasMuted)
atlasUpper = plot(atlasConfirmed and atlasEnvelope and a_scaleKnown ? a_level + 1.5 * a_scale : na, "Anatomy upper envelope", color = color.new(atlasMuted, 80))
atlasLower = plot(atlasConfirmed and atlasEnvelope and a_scaleKnown ? a_level - 1.5 * a_scale : na, "Anatomy lower envelope", color = color.new(atlasMuted, 80))
fill(atlasUpper, atlasLower, color = color.new(atlasAnatomyColor, 94), title = "Anatomy descriptive range")
plot(atlasConfirmed and atlasVwap ? a_vwapReference : na, "Anatomy calendar VWAP", color = atlasMuted)
plot(atlasConfirmed and atlasScenario ? a_boundary : na, "Anatomy scenario / NOT stop", color = atlasUp, style = plot.style_circles)
plot(b_heldSide != 0 ? b_entryPrice : na, "Illustrative entry", color = atlasMuted, style = plot.style_linebr)
plot(b_heldSide != 0 ? b_stopPrice : na, "Bearing illustrative frozen stop", color = atlasGold, linewidth = 2, style = plot.style_linebr)
barcolor(atlasCandles and atlasConfirmed ? atlasBearingColor : na)
plotshape(b_entryIntent and b_pendingSide == 1, "Long plan intent", shape.triangleup, location.belowbar, atlasUp, size = size.tiny)
plotshape(b_entryIntent and b_pendingSide == -1, "Short plan intent", shape.triangledown, location.abovebar, atlasDown, size = size.tiny)
plotshape(b_exited, "Illustrative plan exit", shape.xcross, location.abovebar, atlasGold, size = size.tiny)
plotshape(atlasEvents and atlasConfirmed and a_breakout != 0, "Anatomy breakout observation", shape.circle, location.belowbar, atlasMuted, size = size.tiny)

// Selected Data Window diagnostics, deliberately within the plot budget.
plot(atlasConfirmed ? a_velocity : na, "Anatomy velocity", display = display.data_window)
plot(atlasConfirmed ? a_acceleration : na, "Anatomy acceleration", display = display.data_window)
plot(atlasConfirmed ? a_curvature : na, "Anatomy curvature", display = display.data_window)
plot(atlasConfirmed and a_warm ? a_efficiency : na, "Anatomy raw efficiency", display = display.data_window)
plot(atlasConfirmed ? a_scale : na, "Anatomy prior SMA range", display = display.data_window)
plot(atlasConfirmed ? a_relativeVolume : na, "Anatomy rolling activity", display = display.data_window)
plot(b_evidence * 100, "Bearing signed persistence", display = display.data_window)
plot(b_atr, "Bearing ATR", display = display.data_window)
plot(b_signal, "Atlas filtered candidate", display = display.data_window)
plot(b_heldSide, "Illustrative held side", display = display.data_window)
plot(b_quantity, "Bearing candidate quantity", display = display.data_window)
plot(atlasBlocked ? 1 : 0, "Atlas context blocked candidate", display = display.data_window)
bool atlasDirectionChanged = a_scaleKnown and a_scaleKnown[1] and a_direction != a_direction[1]
alertcondition(atlasConfirmed and atlasDirectionChanged, "Atlas: Anatomy direction changed", "ATLAS {{ticker}} {{interval}}: confirmed Anatomy direction changed; not an order.")
alertcondition(atlasConfirmed and a_shock and not a_shock[1], "Atlas: Anatomy volatility shock", "ATLAS {{ticker}} {{interval}}: new Anatomy range shock.")
alertcondition(atlasConfirmed and a_breakout != 0, "Atlas: Anatomy breakout observed", "ATLAS {{ticker}} {{interval}}: descriptive range breakout, not a plan entry.")
alertcondition(atlasConfirmed and a_invalidated, "Atlas: Anatomy scenario invalidated", "ATLAS {{ticker}} {{interval}}: descriptive scenario invalidated, not a plan exit.")
alertcondition(atlasConfirmed and atlasBlocked, "Atlas: candidate blocked by context", "ATLAS {{ticker}} {{interval}}: Anatomy entry policy blocked a Bearing candidate.")

The package includes frozen engine sources, the reproducible builder, reference tests and a native-verification checklist. Atlas is an integration of your KONATUS tools, not a claim to outperform every other indicator.

Prefer a standalone tool? Explore companion studies ↓

04 / Upgraded · 5-minute multitrend · v0.2

KONATUS
Flow.

The five-minute chart.
The bigger trend.

Read the local move against two higher timeframes. Adaptive structure, directional strength and volatility filters explain when a trend is strong, weak, conflicted or worth waiting out.

Get Flow 0.2 for TradingView ↓
A single brass ribbon curves above dark teal water, making delicate ripples
KONATUS Flow
05 / MINUTES

Read the move.

Fast and structural adaptive averages check price location, slope and local direction.

15 / MINUTES

Check the context.

The last completed 15-minute bar checks whether the surrounding trend agrees.

60 / MINUTES

Respect the larger trend.

The last completed hourly bar must also agree before a trend can qualify as strong.

Candle color tells you the condition.

Coloring is now on by default. Strong and weak are defined by the filter rules, not by the size of a candle or a promised outcome. The explanation panel names the first condition holding back a strong reading.

Strong up

Every upward filter agrees.

Weak up

Upward structure; a filter is still waiting.

Strong down

Every downward filter agrees.

Weak down

Downward structure; a filter is still waiting.

Timeframe conflict

A higher timeframe opposes the local trend.

Volatility pause

A range shock calls for a cooling period.

Neutral

Local structure does not establish a direction.

An open candle keeps its native chart color. Unsupported charts and incomplete or stale data are left unpainted; the panel explains why. The lines and panel hold the last closed reading. Colors are editable in Settings.

A stricter definition of strong.

  1. Structure first. The fast adaptive average, structural average, price and their slopes must agree.
  2. All three timeframes. Both 15m and 1h must match the 5m direction. Neutral context withholds Strong; opposing context produces amber Conflict.
  3. Strength and efficiency. ADX, directional DI spread and path efficiency must pass. High ADX cannot override an opposing direction.
  4. Do not chase. Too much distance from the adaptive line withholds Strong. A range shock pauses the shock candle and the next two closes.
  5. Wait for confirmation. By default, every strong condition must pass on two consecutive 5-minute closes. A failed condition resets the count.
Two presets. The same transparent rules.
FilterBalanced · defaultStrict
Minimum efficiency0.300.40
Minimum ADX2025
Directional DI spread58
Maximum extension1.5 × mean range1.0 × mean range

Strict admits fewer candidates; it has not been shown to produce better returns. These are unoptimized research settings. More filters add delay and can still whipsaw.

Install on your 5-minute chart.

  1. 1 · ReplacePaste the complete source below into a blank TradingView Pine Editor indicator, save and add it to the chart. Remove the old Flow instance when upgrading.
  2. 2 · Set 5 minutesUse standard candles. This edition deliberately blocks other timeframes and synthetic charts. Start with Balanced and 2 confirmation closes.
  3. 3 · Load historyFlow needs 120 valid 5m bars and 90 valid bars on each higher timeframe. Loaded history counts; you do not need to wait 90 hours after installation.
  4. 4 · Read the reasonReview the state, 5m/15m/1h directions, ADX and efficiency in the bottom-right panel. Triangles mark new strong states, not filled trades. Keep your own entry, stop and size rules.
  5. 5 · Recreate alertsSelect a new Flow 0.2 alert and Once Per Bar Close. Old alerts keep their saved script and settings. Use either individual alerts or the combined state-change alert to avoid overlapping notifications.
Timing, calculations and limitations

Higher-timeframe inputs use previous completed bars. An hourly bar ending at 10:00 first contributes to the closed 10:00–10:05 candle; it is not used on the 09:55–10:00 candle. Context older than 30 minutes for 15m or 120 minutes for 1h is blocked. After a session gap, Flow may wait for new context and withhold the opening portion of the session.

Fast KAMA uses ER10 and fast/slow constants 2/30; structural KAMA uses ER30 and 5/60. The quiet zone is 0.15 × the 20-bar simple mean of true range. The fast 3-bar slope must exceed 0.02 × mean range per bar and the structural 5-bar slope must agree. Higher-timeframe direction uses EMA21/55 and a normalized EMA21 slope. Wilder-style ADX/DI use 14-bar seeded smoothing, reset on invalid data. The research package documents the complete formulas and initialization.

These are correlated readings of price, not independent votes or economic causes. No volume, spread or scheduled-news filter is included. Sessions, futures rolls, revised data and loaded history affect readings. Flow carries history across sessions and does not close positions at session end. The lines and quiet zone are not protective stops.

Method references: Kaufman adaptive average, directional movement, and confirmed higher-timeframe requests.

Give candle colors one owner.

Flow can stand alone as your trend filter. Add session VWAP for location or Market Anatomy for more context. When using Bearing or Atlas, turn off their candle coloring if Flow owns the colors. The scripts do not communicate or change one another’s signals, stops or sizing. Turn off Flow’s panel if another study occupies the bottom-right corner.

Research release 0.2.0. 30 offline reference and timing tests pass. Native TradingView compilation, chart behavior and alert delivery remain unverified. No trading edge has been measured; Strong is a rule-based description, not a prediction.

Get the complete Flow 0.2 script

Download Flow 0.2 Pine v6Research package (.zip)

View the complete Flow 0.2 code
//@version=6
// KONATUS Flow 0.2.0 — Kutty Stauder. 5-minute multitrend research edition.
// Native TradingView compilation, chart execution and alert delivery unverified.
// A descriptive filter, not an order, protective stop or profitability claim.
indicator("KONATUS Flow · 5m Multitrend 0.2.0", shorttitle="Flow 5m", overlay=true, max_bars_back=500, dynamic_requests=true)

string quality = input.string("Balanced", "Filtering", options=["Balanced", "Strict"], tooltip="5-minute charts only. Both presets require closed 15m and 1h agreement. Strict raises strength/efficiency thresholds and reduces allowed extension. Settings are not performance-optimized.")
int confirmation = input.int(2, "Confirmation closes", minval=1, maxval=5)
bool paintBars = input.bool(true, "Color closed candles", group="Display")
bool showStructure = input.bool(true, "Show structural line and ribbon", group="Display")
bool showZone = input.bool(false, "Show quiet zone", group="Display")
bool showMarks = input.bool(true, "Show new strong trends", group="Display")
bool showPanel = input.bool(true, "Show explanation panel", group="Display")
color strongUp = input.color(#21CE91, "Strong up", group="Candle colors")
color weakUp = input.color(#689C91, "Weak up", group="Candle colors")
color strongDown = input.color(#F05F6D, "Strong down", group="Candle colors")
color weakDown = input.color(#AE858D, "Weak down", group="Candle colors")
color conflictColor = input.color(#E9B85D, "Timeframe conflict", group="Candle colors")
color pauseColor = input.color(#AE91E8, "Volatility pause", group="Candle colors")
color neutralColor = input.color(#939DAA, "Neutral", group="Candle colors")

f_valid() =>
    not na(open) and not na(high) and not na(low) and not na(close) and high >= math.max(open, close) and low <= math.min(open, close) and high >= low

f_range(bool valid, int count) =>
    valid ? (count > 1 ? math.max(high - low, math.max(math.abs(high - close[1]), math.abs(low - close[1]))) : high - low) : na

f_kama(int length, float fastLength, float slowLength, bool valid, int count) =>
    float step = valid and count > 1 ? math.abs(close - close[1]) : 0.0
    float path = math.sum(step, length)
    float er = count > length and path > 0 ? math.max(0.0, math.min(1.0, math.abs(close - close[length]) / path)) : 0.0
    float alpha = math.pow(er * (2.0 / (fastLength + 1.0) - 2.0 / (slowLength + 1.0)) + 2.0 / (slowLength + 1.0), 2)
    var float average = na
    average := not valid ? na : na(average[1]) ? close : average[1] + alpha * (close - average[1])
    [average, er]

// Seed and reset explicitly so missing OHLC never carries an old strength estimate.
f_wilder(float value, int length, int count) =>
    float total = math.sum(value, length)
    var float result = na
    result := count < length or na(value) ? na : count == length ? total / length : (result[1] * (length - 1) + value) / length
    result

f_context() =>
    bool valid = f_valid()
    var int count = 0
    count := valid ? count + 1 : 0
    var float fast = na
    var float slow = na
    fast := not valid ? na : na(fast[1]) ? close : fast[1] + (2.0 / 22.0) * (close - fast[1])
    slow := not valid ? na : na(slow[1]) ? close : slow[1] + (2.0 / 56.0) * (close - slow[1])
    float scale = ta.sma(f_range(valid, count), 14)
    float slope = (fast - fast[3]) / 3.0
    bool ready = count >= 90 and not na(scale) and not na(slope)
    int direction = not ready or scale <= 0 ? 0 : fast > slow and close > fast and slope > 0.02 * scale ? 1 : fast < slow and close < fast and slope < -0.02 * scale ? -1 : 0
    // Every field is offset: lookahead_on must NEVER expose an unfinished HTF bar.
    [direction[1], ready[1], time_close[1]]

f_direction(int direction) =>
    direction == 1 ? "UP" : direction == -1 ? "DOWN" : "NEUTRAL"

f_state(int state) =>
    switch state
        2 => "STRONG UP"
        1 => "WEAK UP"
        -2 => "STRONG DOWN"
        -1 => "WEAK DOWN"
        3 => "TIMEFRAME CONFLICT"
        4 => "VOLATILITY PAUSE"
        => "NEUTRAL"

f_color(int state) =>
    switch state
        2 => strongUp
        1 => weakUp
        -2 => strongDown
        -1 => weakDown
        3 => conflictColor
        4 => pauseColor
        => neutralColor

bool supported = chart.is_standard and timeframe.isminutes and timeframe.multiplier == 5
int direction15 = 0
int direction60 = 0
bool ready15 = false
bool ready60 = false
int closed15 = na
int closed60 = na
// Unsupported charts do not issue accidental lower-timeframe requests.
if supported
    [d15, r15, t15] = request.security(syminfo.tickerid, "15", f_context(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_on)
    [d60, r60, t60] = request.security(syminfo.tickerid, "60", f_context(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_on)
    direction15 := d15
    direction60 := d60
    ready15 := r15
    ready60 := r60
    closed15 := t15
    closed60 := t60

bool valid = f_valid()
var int count = 0
count := valid ? count + 1 : 0
[adaptive, efficiency] = f_kama(10, 2.0, 30.0, valid, count)
[structure, _] = f_kama(30, 5.0, 60.0, valid, count)
float trueRange = f_range(valid, count)
float scale = ta.sma(trueRange, 20)
float fastSlope = (adaptive - adaptive[3]) / 3.0
float slowSlope = (structure - structure[5]) / 5.0
float halfZone = 0.15 * scale
float upward = count > 1 ? high - high[1] : 0.0
float downward = count > 1 ? low[1] - low : 0.0
float plusDM = valid ? (upward > downward and upward > 0 ? upward : 0.0) : na
float minusDM = valid ? (downward > upward and downward > 0 ? downward : 0.0) : na
float smoothTR = f_wilder(trueRange, 14, count)
float smoothPlus = f_wilder(plusDM, 14, count)
float smoothMinus = f_wilder(minusDM, 14, count)
float plusDI = smoothTR > 0 ? 100.0 * smoothPlus / smoothTR : 0.0
float minusDI = smoothTR > 0 ? 100.0 * smoothMinus / smoothTR : 0.0
float dx = plusDI + minusDI > 0 ? 100.0 * math.abs(plusDI - minusDI) / (plusDI + minusDI) : 0.0
float adx = f_wilder(count >= 14 ? dx : na, 14, math.max(0, count - 13))
bool localReady = count >= 120 and not na(adx) and not na(scale) and not na(slowSlope)
int localDirection = not localReady or scale <= 0 ? 0 : adaptive > structure and close > adaptive + halfZone and fastSlope > 0.02 * scale and slowSlope > 0 ? 1 : adaptive < structure and close < adaptive - halfZone and fastSlope < -0.02 * scale and slowSlope < 0 ? -1 : 0
bool fresh15 = not na(closed15) and closed15 <= time and time - closed15 <= 30 * 60 * 1000
bool fresh60 = not na(closed60) and closed60 <= time and time - closed60 <= 120 * 60 * 1000
bool ready = supported and localReady and ready15 and ready60 and fresh15 and fresh60
bool shock = valid and count > 20 and scale[1] > 0 and trueRange > 3.0 * scale[1]
var int cooldown = 0
cooldown := not valid ? 0 : shock ? 3 : math.max(0, cooldown - 1)
bool paused = cooldown > 0
bool strict = quality == "Strict"
float minimumER = strict ? 0.40 : 0.30
float minimumADX = strict ? 25.0 : 20.0
float minimumSpread = strict ? 8.0 : 5.0
float maxExtension = strict ? 1.0 : 1.5
float extension = scale > 0 ? math.abs(close - adaptive) / scale : 0.0
bool opposing = localDirection != 0 and (direction15 == -localDirection or direction60 == -localDirection)
bool aligned = localDirection != 0 and direction15 == localDirection and direction60 == localDirection
bool efficient = efficiency >= minimumER
bool strength = adx >= minimumADX and localDirection * (plusDI - minusDI) >= minimumSpread
bool stretched = extension > maxExtension
int qualified = ready and not paused and aligned and efficient and strength and not stretched ? localDirection : 0
var int run = 0
run := qualified == 0 ? 0 : qualified == qualified[1] ? run + 1 : 1
int nextState = not ready ? 0 : paused ? 4 : localDirection == 0 ? 0 : opposing ? 3 : qualified != 0 and run >= confirmation ? 2 * localDirection : localDirection
string nextReason = not supported ? "Use standard 5-minute candles" : not valid ? "Invalid OHLC: rebuilding history" : not localReady ? "Warming up: 120 valid 5m bars" : not ready15 or not ready60 ? "Warming up: 90 bars on 15m and 1h" : not fresh15 or not fresh60 ? "Waiting for fresh closed higher-timeframe bars" : paused ? "Range shock: " + str.tostring(cooldown) + " pause bars including this close" : localDirection == 0 ? "Local structure is mixed or inside quiet zone" : opposing ? "Higher timeframe opposes the 5m direction" : not aligned ? "Higher timeframe neutral: strong withheld" : not efficient ? "Price path too inefficient" : not strength ? "ADX / directional spread below threshold" : stretched ? "Too far from adaptive line" : run < confirmation ? "Confirming: " + str.tostring(run) + "/" + str.tostring(confirmation) + " closes" : "All filters aligned"

// Publish one snapshot at each close. The open candle is intentionally not painted.
var int state = 0
var string reason = "Waiting for first closed bar"
var string snapshotDirections = "5m — / 15m — / 1h —"
var string snapshotStrength = "ADX — / ER —"
var string snapshotFilter = "Waiting for data"
if barstate.isconfirmed
    state := nextState
    reason := nextReason
    snapshotDirections := "5m " + f_direction(localDirection) + " / 15m " + (ready15 and fresh15 ? f_direction(direction15) : "WAIT") + " / 1h " + (ready60 and fresh60 ? f_direction(direction60) : "WAIT")
    snapshotStrength := "ADX " + (localReady ? str.tostring(adx, "#.0") : "—") + " / ER " + (localReady ? str.tostring(efficiency, "#.00") : "—")
    snapshotFilter := quality + " · " + str.tostring(confirmation) + " closes · last closed bar"

bool eligibleEvent = barstate.isconfirmed and ready and ready[1]
bool strongUpEvent = eligibleEvent and state == 2 and state[1] != 2
bool strongDownEvent = eligibleEvent and state == -2 and state[1] != -2
bool lostEvent = eligibleEvent and math.abs(state[1]) == 2 and math.abs(state) != 2
bool conflictEvent = eligibleEvent and state == 3 and state[1] != 3
bool pauseEvent = eligibleEvent and state == 4 and state[1] != 4
bool changeEvent = eligibleEvent and state != state[1]
bool shownLocalReady = supported and (barstate.isconfirmed ? localReady : localReady[1])
bool shownReady = supported and (barstate.isconfirmed ? ready : ready[1])
float shownAdaptive = shownLocalReady ? (barstate.isconfirmed ? adaptive : adaptive[1]) : na
float shownStructure = shownLocalReady ? (barstate.isconfirmed ? structure : structure[1]) : na
float shownZone = barstate.isconfirmed ? halfZone : halfZone[1]
color stateColor = f_color(state)
fastPlot = plot(shownAdaptive, "Adaptive trend", color=stateColor, linewidth=3, style=plot.style_linebr)
slowPlot = plot(showStructure ? shownStructure : na, "Structural trend", color=color.new(#D9BD7F, 15), linewidth=1, style=plot.style_linebr)
fill(fastPlot, slowPlot, color=showStructure ? color.new(stateColor, 93) : na, title="Trend structure ribbon", fillgaps=false)
zoneUpper = plot(showZone ? shownAdaptive + shownZone : na, "Quiet zone upper", color=color.new(neutralColor, 80), style=plot.style_linebr)
zoneLower = plot(showZone ? shownAdaptive - shownZone : na, "Quiet zone lower", color=color.new(neutralColor, 80), style=plot.style_linebr)
fill(zoneUpper, zoneLower, color=color.new(neutralColor, 94), title="Quiet zone", fillgaps=false)
barcolor(paintBars and ready and barstate.isconfirmed ? stateColor : na, title="Confirmed trend strength")
plotshape(showMarks and strongUpEvent, title="New strong up", style=shape.triangleup, location=location.belowbar, color=strongUp, text="S+", textcolor=strongUp, size=size.tiny)
plotshape(showMarks and strongDownEvent, title="New strong down", style=shape.triangledown, location=location.abovebar, color=strongDown, text="S-", textcolor=strongDown, size=size.tiny)

var table panel = table.new(position.bottom_right, 1, 6, bgcolor=#11191D, frame_color=#404D4E, frame_width=1)
if barstate.islast and showPanel
    table.cell(panel, 0, 0, "KONATUS FLOW / 5m · 0.2", text_color=#CFB98A, text_size=size.small)
    table.cell(panel, 0, 1, shownReady ? f_state(state) : "WAIT / DATA", text_color=stateColor, text_size=size.large)
    table.cell(panel, 0, 2, reason, text_color=#D5DEDF, text_size=size.small)
    table.cell(panel, 0, 3, snapshotDirections, text_color=#D5DEDF, text_size=size.small)
    table.cell(panel, 0, 4, snapshotStrength, text_color=#B4BEC0, text_size=size.small)
    table.cell(panel, 0, 5, snapshotFilter, text_color=#B4BEC0, text_size=size.tiny)

alertcondition(strongUpEvent, "Flow: strong up", "Flow 0.2: new confirmed STRONG UP on {{ticker}} / 5m. Descriptive state, not an order.")
alertcondition(strongDownEvent, "Flow: strong down", "Flow 0.2: new confirmed STRONG DOWN on {{ticker}} / 5m. Descriptive state, not an order.")
alertcondition(lostEvent, "Flow: strong trend lost", "Flow 0.2: strong conditions no longer hold on {{ticker}} / 5m. Review the panel; not an exit order.")
alertcondition(conflictEvent, "Flow: timeframe conflict", "Flow 0.2: 5m direction opposed by a closed higher-timeframe reading on {{ticker}}.")
alertcondition(pauseEvent, "Flow: volatility pause", "Flow 0.2: confirmed volatility pause on {{ticker}} / 5m.")
alertcondition(changeEvent, "Flow: any state change", "Flow 0.2: confirmed state change on {{ticker}} / 5m. Check the explanation panel.")

Prefer the original simpler version? Download the Flow 0.1 archive.

05 / Macro price trends · Research v0.1

KONATUS
Horizon.

Know the direction.
Understand the condition.

A daily and weekly view of the market you trade. Horizon separates a lasting directional bias from a temporary pullback, a stretched move or a genuine breakdown.

Get Horizon for TradingView ↓
An aged brass telescope looks across a dark sea toward a golden horizon
KONATUS Horizon
+ / LONG BIAS

The upward case holds.

Daily pressure, weekly evidence and range position established an upward regime. Its holding rules still pass.

0 / NEUTRAL

No side has earned it.

Evidence is conflicted, a new direction is still confirming, the old bias broke, or a shock calls for a pause.

− / SHORT BIAS

The downward case holds.

The same rules work in reverse. A short bias needs its own agreement and confirmation.

Illustrative reading · Not live market data

LONG BIASPULLBACK

The last five days moved against the long bias, but the macro holding rules remain intact. That describes a condition to investigate—not a promise that the dip will recover.

Direction and condition are different questions.

A single red candle should not have to mean a new macro short. Horizon uses separate rules for establishing and retaining a bias, then tells you what kind of move is happening inside it.

Trend

The bias holds and the recent move agrees.

Pullback

The five-day move opposes the bias; the holding rules still pass.

Stretched

Price is more than three mean ranges beyond its recent median in the direction of the bias. Direction is not an entry price.

Conflict or transition

Daily and weekly evidence disagree, confirmation is incomplete, or the previous bias has broken.

Shock pause

A daily range shock clears the bias for that observation and the next two. Fresh confirmation is required afterward.

Unavailable

History, freshness or chart requirements are not met. Missing information is kept separate from a neutral reading.

Three measurements. One explicit decision.

  1. Daily pressure. Signed path efficiency over 21, 63 and 126 daily observations measures net progress relative to back-and-forth travel.
  2. Weekly backbone. Completed 13- and 26-week paths establish the slower directional evidence.
  3. Range structure. The latest daily close is located within the preceding 63-day high–low range. The current bar cannot move its own reference boundaries.

Every entry gate must agree. The pane’s −100 to +100 evidence index summarizes these measurements; it is not a probability or a standalone trade trigger. The inputs are correlated readings of price, not independent economic evidence.

Balanced requires two qualifying daily observations. Patient raises the thresholds and requires three. Both use the same holding rules. If a holding rule fails, the bias clears to neutral; a reversal must build a fresh confirmation sequence on later days.

Set it up once. Read the bigger picture.

  1. 1 · InstallPaste the complete code into a blank TradingView Pine Editor indicator, save and add to chart. Horizon opens in its own pane.
  2. 2 · Start dailyUse standard candles on a minute chart below one day, or exactly 1D. Start with Balanced. Weekly, multi-day, seconds and synthetic charts are unsupported.
  3. 3 · Load historyAllow at least 127 valid daily bars and 27 weekly bars, plus the next daily interval to display the result. Loaded historical data counts; confirmation may require extra days.
  4. 4 · Read bothCheck the bias, condition, explanation and snapshot date. Entries, stops, size and session rules remain your separate decisions.
  5. 5 · Add alertsChoose long, short, bias cleared or any change, with Once Per Bar Close. Use an intraday chart for an earlier notification of a newly published daily snapshot.
Exact timing, formulas and limitations

Horizon publishes the previous completed daily assessment in the next daily interval, including on a daily chart. Weekly evidence is what was already confirmed at that assessment’s daily open. For a normal Monday–Friday market, Friday’s new weekly bar enters Monday’s calculation and is displayed Tuesday. This deliberate delay prevents developing weekly data from being treated as confirmed.

The regime engine and confirmation counters run inside the daily context. Moving to a five-minute chart does not speed them up. Alerts fire at the first chart-bar close carrying a new snapshot; on a daily chart that means the close, although the snapshot is visible from its open. Initial availability does not trigger a bias-change alert.

With signed efficiency E, daily pressure D = .20 E21 + .35 E63 + .45 E126; weekly backbone W = .50 E13 + .50 E26. Range structure S = 2 × (close − prior low)/(prior high − low) − 1, clipped to [−1,+1]. Index = 100 × (.45 D + .35 W + .20 S). Flat denominators produce zero.

Balanced entry thresholds for directional D/W/S are .20/.15/.20; Patient uses .30/.25/.30. Holding thresholds are D > −.05, W > 0 and S > −.20, after multiplying by the bias direction. A daily true range above three times the prior 20-day simple mean range triggers the shock pause. Stretch compares the close to the prior 21-day median, using the current 20-day mean range. These defaults are unoptimized research choices.

Daily snapshots older than seven calendar days and weekly context older than 21 days are blocked. A daily gap over seven days restarts the daily warmup. Invalid data resets history. Revised feeds, adjustments, futures rolls, sessions and loaded history can change results. Slow trend rules lag, can hold through losses and can miss fast reversals.

“Macro” here means the charted asset’s larger price trend. There are no inflation, interest-rate, news, volume, liquidity or execution-cost inputs. Research on diversified time-series momentum does not validate this specific model. The source follows TradingView’s confirmed higher-timeframe request patterns.

Horizon for perspective. Flow for the local move.

Use Horizon to frame the broader directional case, then Flow to inspect five-minute conditions. A local pullback can coexist with an intact macro trend. The scripts do not communicate, submit orders or override each other’s readings. Horizon leaves price candles untouched.

Research release 0.1.0. 28 offline reference tests pass. Native TradingView compilation, chart behavior and alert delivery remain unverified. No trading edge has been measured.

Get the complete Horizon script

Download Horizon Pine v6Research package (.zip)

View the complete Horizon code
//@version=6
// KONATUS Horizon 0.1.0 — Kutty Stauder. Macro price-trend research edition.
// Native TradingView compilation, chart behavior and alerts remain unverified.
// Bias is descriptive, not an entry, exit, protective stop or profit forecast.
indicator("KONATUS Horizon · Macro Regime 0.1.0", shorttitle="Horizon", overlay=false, max_bars_back=600, dynamic_requests=true)

string mode = input.string("Balanced", "Confirmation", options=["Balanced", "Patient"], tooltip="Balanced: 2 daily observations; Patient: 3 and higher entry thresholds. Both use identical holding rules. Neither preset is performance-optimized.")
bool showComponents = input.bool(false, "Show the three components", group="Display")
bool showBackground = input.bool(true, "Shade the active bias", group="Display")
bool showMarks = input.bool(true, "Mark bias changes", group="Display")
bool showPanel = input.bool(true, "Show explanation panel", group="Display")
color longColor = input.color(#36C9A0, "Long bias", group="Colors")
color shortColor = input.color(#F0717C, "Short bias", group="Colors")
color waitColor = input.color(#C3AC78, "Neutral / transition", group="Colors")

f_valid() =>
    not na(open) and not na(high) and not na(low) and not na(close) and high >= math.max(open, close) and low <= math.min(open, close) and high >= low

f_efficiency(int length, bool valid, int count) =>
    float step = valid and count > 1 ? math.abs(close - close[1]) : 0.0
    float path = math.sum(step, length)
    count > length and path > 0 ? math.max(-1.0, math.min(1.0, (close - close[length]) / path)) : 0.0

f_weekly() =>
    bool valid = f_valid()
    var int count = 0
    count := valid ? count + 1 : 0
    float backbone = 0.5 * f_efficiency(13, valid, count) + 0.5 * f_efficiency(26, valid, count)
    bool ready = count >= 27
    // Every requested field is shifted; no developing weekly vote.
    [backbone[1], ready[1], time_close[1]]

f_daily() =>
    // Executed inside the DAILY request, so counters cannot accelerate on 5m.
    // Weekly evidence here is what was confirmed at this daily bar's OPEN.
    [weekly, weeklyReady, weeklyClosed] = request.security(syminfo.tickerid, "1W", f_weekly(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_on)
    bool valid = f_valid()
    bool longGap = not na(time_close[1]) and time - time_close[1] > 7 * 86400000
    var int count = 0
    count := not valid ? 0 : longGap ? 1 : count + 1
    float tr = valid ? (count > 1 ? math.max(high - low, math.max(math.abs(high - close[1]), math.abs(low - close[1]))) : high - low) : na
    float scale = ta.sma(tr, 20)
    float pressure = 0.20 * f_efficiency(21, valid, count) + 0.35 * f_efficiency(63, valid, count) + 0.45 * f_efficiency(126, valid, count)
    float priorHigh = ta.highest(high[1], 63)
    float priorLow = ta.lowest(low[1], 63)
    float structure = priorHigh > priorLow ? math.max(-1.0, math.min(1.0, 2.0 * (close - priorLow) / (priorHigh - priorLow) - 1.0)) : 0.0
    float balance = ta.median(close[1], 21)
    float extension = scale > 0 ? (close - balance) / scale : 0.0
    float impulse = close - close[5]
    bool freshWeek = not na(weeklyClosed) and time >= weeklyClosed and time - weeklyClosed <= 21 * 86400000
    bool ready = count >= 127 and weeklyReady and freshWeek and not na(scale) and not na(balance) and not na(priorHigh) and not na(priorLow)
    bool shock = valid and count >= 21 and not na(scale[1]) and tr > 3.0 * scale[1]
    float td = mode == "Patient" ? 0.30 : 0.20
    float tw = mode == "Patient" ? 0.25 : 0.15
    float ts = mode == "Patient" ? 0.30 : 0.20
    int required = mode == "Patient" ? 3 : 2
    var int bias = 0
    var int candidate = 0
    var int run = 0
    var int pause = 0
    bool invalidated = false
    if not ready
        bias := 0
        candidate := 0
        run := 0
        pause := 0
    else
        pause := shock ? 3 : math.max(0, pause - 1)
        if pause > 0
            bias := 0
            candidate := 0
            run := 0
        else
            invalidated := bias != 0 and (bias * pressure <= -0.05 or bias * weekly <= 0 or bias * structure <= -0.20)
            if invalidated
                bias := 0
                candidate := 0
                run := 0
            else if bias == 0
                int next = pressure >= td and weekly >= tw and structure >= ts ? 1 : pressure <= -td and weekly <= -tw and structure <= -ts ? -1 : 0
                run := next != 0 ? (next == candidate ? run + 1 : 1) : 0
                candidate := next
                if run >= required
                    bias := candidate
                    candidate := 0
                    run := 0
    // 0 transition, 1 trend, 2 pullback, 3 stretched, 4 conflict, 5 pause, 6 unavailable.
    int condition = not ready ? 6 : pause > 0 ? 5 : bias != 0 ? (bias * extension > 3 ? 3 : bias * impulse < 0 ? 2 : 1) : invalidated ? 0 : pressure * weekly < 0 ? 4 : 0
    float score = ready ? 100.0 * (0.45 * pressure + 0.35 * weekly + 0.20 * structure) : na
    // A daily result is published in the NEXT daily interval on every chart.
    [bias[1], condition[1], score[1], pressure[1], weekly[1], structure[1], ready[1], time_close[1], weeklyClosed[1], run[1], invalidated[1]]

f_condition(int value) =>
    switch value
        1 => "TREND"
        2 => "PULLBACK"
        3 => "STRETCHED"
        4 => "CONFLICT"
        5 => "SHOCK PAUSE"
        6 => "UNAVAILABLE"
        => "TRANSITION"

f_reason(int value, bool invalidated) =>
    switch value
        1 => "Macro hold rules intact"
        2 => "5-day counter-move; macro bias still intact"
        3 => "Extended from median; bias is not an entry"
        4 => "Daily and weekly evidence oppose"
        5 => "Range shock; wait for fresh confirmation"
        6 => "Need 127 daily and 27 weekly valid bars"
        => invalidated ? "Prior bias broke; fresh confirmation required" : "Waiting for agreement and daily confirmation"

bool supported = chart.is_standard and ((timeframe.isminutes and timeframe.multiplier < 1440) or (timeframe.isdaily and timeframe.multiplier == 1))
int sourceBias = 0
int condition = 6
float score = na
float dailyPressure = na
float weeklyBackbone = na
float structure = na
bool sourceReady = false
int dailyClosed = na
int weeklyClosed = na
int confirmationRun = 0
bool invalidated = false
if supported
    [b, c, s, d, w, r, ready, dc, wc, n, invalid] = request.security(syminfo.tickerid, "1D", f_daily(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_on)
    sourceBias := b
    condition := c
    score := s
    dailyPressure := d
    weeklyBackbone := w
    structure := r
    sourceReady := ready
    dailyClosed := dc
    weeklyClosed := wc
    confirmationRun := n
    invalidated := invalid

bool fresh = not na(dailyClosed) and time >= dailyClosed and time - dailyClosed <= 7 * 86400000
bool available = supported and sourceReady and fresh
int bias = available ? sourceBias : 0
color biasColor = bias == 1 ? longColor : bias == -1 ? shortColor : waitColor
string title = not supported ? "USE STANDARD 1m–1D" : not available ? "UNAVAILABLE" : bias == 1 ? "LONG BIAS" : bias == -1 ? "SHORT BIAS" : "NEUTRAL"
string detail = not supported ? "Standard minute candles or exactly 1D" : not fresh and not na(dailyClosed) ? "Daily data stale; no active bias" : f_reason(available ? condition : 6, invalidated)

hline(0, "Balance", color=color.new(color.gray, 55))
hline(100, "Index ceiling", color=color.new(color.gray, 85))
hline(-100, "Index floor", color=color.new(color.gray, 85))
plot(available ? score : na, "Macro evidence index (not probability)", color=biasColor, linewidth=3, style=plot.style_stepline)
plot(showComponents and available ? 100 * dailyPressure : na, "Daily pressure", color=#5AA8D1)
plot(showComponents and available ? 100 * weeklyBackbone : na, "Weekly backbone", color=#B19AE1)
plot(showComponents and available ? 100 * structure : na, "Range structure", color=#C4A46B)
bgcolor(showBackground and available ? color.new(biasColor, bias == 0 ? 96 : 91) : na)

bool newSnapshot = not na(dailyClosed[1]) and dailyClosed != dailyClosed[1]
bool changed = available and available[1] and newSnapshot and sourceBias != sourceBias[1]
bool newLong = changed and sourceBias == 1
bool newShort = changed and sourceBias == -1
bool cleared = changed and sourceBias == 0
plotshape(showMarks and newLong, title="New long bias", style=shape.triangleup, location=location.bottom, color=longColor, size=size.tiny, text="L")
plotshape(showMarks and newShort, title="New short bias", style=shape.triangledown, location=location.top, color=shortColor, size=size.tiny, text="S")
plotshape(showMarks and cleared, title="Bias cleared", style=shape.xcross, location=location.bottom, color=waitColor, size=size.tiny)
alertcondition(newLong and barstate.isconfirmed, "New macro long bias", "KONATUS Horizon: new confirmed macro LONG bias on {{ticker}}. Review condition; this is not an entry order.")
alertcondition(newShort and barstate.isconfirmed, "New macro short bias", "KONATUS Horizon: new confirmed macro SHORT bias on {{ticker}}. Review condition; this is not an entry order.")
alertcondition(cleared and barstate.isconfirmed, "Macro bias cleared", "KONATUS Horizon: prior bias cleared to NEUTRAL on {{ticker}}.")
alertcondition(changed and barstate.isconfirmed, "Any macro bias change", "KONATUS Horizon: confirmed macro bias changed on {{ticker}}. Review the Horizon panel.")

var table panel = table.new(position.top_right, 2, 9, bgcolor=color.new(#101D29, 6), border_width=1, border_color=color.new(color.gray, 70))
if barstate.islast
    if showPanel
        table.cell(panel, 0, 0, "KONATUS HORIZON", text_color=color.white, text_size=size.small)
        table.cell(panel, 1, 0, title, text_color=available ? biasColor : color.gray, text_size=size.small)
        table.cell(panel, 0, 1, "Condition", text_color=color.silver)
        table.cell(panel, 1, 1, available ? f_condition(condition) : "UNAVAILABLE", text_color=color.white)
        table.cell(panel, 0, 2, "Why", text_color=color.silver)
        table.cell(panel, 1, 2, detail, text_color=color.white, text_size=size.small)
        table.cell(panel, 0, 3, "Daily / Weekly / Range", text_color=color.silver)
        table.cell(panel, 1, 3, available ? str.tostring(100*dailyPressure, "#.0") + " / " + str.tostring(100*weeklyBackbone, "#.0") + " / " + str.tostring(100*structure, "#.0") : "—", text_color=color.white)
        table.cell(panel, 0, 4, "Daily snapshot", text_color=color.silver)
        table.cell(panel, 1, 4, not na(dailyClosed) ? str.format_time(dailyClosed, "yyyy-MM-dd HH:mm", syminfo.timezone) : "—", text_color=color.white, text_size=size.small)
        table.cell(panel, 0, 5, "Weekly evidence closed", text_color=color.silver)
        table.cell(panel, 1, 5, not na(weeklyClosed) ? str.format_time(weeklyClosed, "yyyy-MM-dd", syminfo.timezone) : "—", text_color=color.white)
        table.cell(panel, 0, 6, "Confirmation", text_color=color.silver)
        table.cell(panel, 1, 6, mode + (available and bias == 0 ? " · " + str.tostring(confirmationRun) + "/" + (mode == "Patient" ? "3" : "2") : ""), text_color=color.white)
        table.cell(panel, 0, 7, "Timing", text_color=color.silver)
        table.cell(panel, 1, 7, "Previous daily close; weekly at its open", text_color=color.silver, text_size=size.small)
        table.cell(panel, 0, 8, "RESEARCH 0.1.0", text_color=waitColor, text_size=size.small)
        table.cell(panel, 1, 8, "Bias ≠ entry · Native validation pending", text_color=color.silver, text_size=size.small)
    else
        table.clear(panel, 0, 0, 1, 8)

06 / Daily breakouts · Pine Script v6 · Research 0.1

KONATUSVela.

Let the base form.
Let price prove it.

A quiet chart. A level that stays put. Follow a tightening base into a confirmed breakout—and see when its exit rules call time.

A golden brass sail rises over dark teal water as three fine lines converge and lift into the wind
Vela · A study in compression and release. Original generated artwork.

One setup. One attempt.

Coil. Break. Ride. Release.

Inspired by Kristjan Kullamägi, known as Qullamaggie: prior momentum, a tightening base, and rising moving averages. Vela adds a frozen breakout level and explicit rules for waiting, accepting, or rejecting the next move.

01 / Coil

A gold dot marks preparation. The base qualifies; its release level stops moving.

02 / Break

A teal triangle marks a close above the level with enough volume and a controlled extension.

03 / Ride

Follow the 10/20-day ribbon. A thin coral line holds the breakout day’s low.

04 / Exit

A coral cross marks a later risk touch or close below your chosen average.

READ THE SEQUENCESynthetic candles · Rule demonstration · Not a backtest
77828793C

A tightening base qualifies at the close. The release level is sealed at 91.96725 for the next five bars.

77828893CBREAK

The next close reaches 92 with 1.5× prior average volume. A single BREAK appears; the initial risk reference is 91.

77849299CBREAK

The campaign stays active above its initial reference and the selected moving average. No repeated entry triangles.

778593101CBREAKEXIT

A later close falls below SMA20. EXIT ends this observation; it does not open a short position.

Gold: SMA10 and frozen release. Teal: SMA20. Dashed coral: initial risk reference. The examples contain no live market data.

From source to chart

Four steps to begin.

  1. Install on a daily chart.Open TradingView’s Pine Editor, create a blank indicator, paste the complete source below, save, and add it to your chart. Select 1D and standard candles. Defaults need 55 valid daily bars of loaded history.
  2. Wait for a gold coil.Start with defaults on a stock chart. Review liquidity, earnings dates and the broader market yourself. COIL means watch; it is not an instruction to buy. WAIT explains the first missing condition.
  3. Let the close confirm.BREAK appears only after the daily close. Do not assume you could enter at an earlier intraday price—or that the signal close is still available. Decide your entry, stop and position size independently.
  4. Track the ride.Use SMA20 by default, or select SMA10 for a faster close-based exit. Create Vela alerts with Once Per Bar Close. Recreate them after changing inputs or code. Paper-test before relying on them.

This is long-only. EXIT is not a short signal. The script places no orders; a risk-touch alert arrives at the daily close and cannot replace an intraday protective stop. Gaps and slippage can exceed your intended risk.

The precise rules, without a black box

Trend + prior advanceClose > SMA10 > SMA20 > SMA50; SMA20 is higher than five bars earlier. The 40-bar advance ending before the 10-bar base must be at least 20%.

Contraction + proximityBase span ≤ 3 MTR. Last five bars’ mean true range ≤ 0.75× the pre-base 20-bar mean. Close within 1 MTR of SMA10 or SMA20. MTR is the simple 20-day mean true range, not Wilder ATR or percentage ADR.

A sealed release levelBase high + 0.10 MTR, frozen on the arming close. Valid for the next five closes. A low beneath the base or lost trend cancels it. A new coil needs the readiness condition to turn off and then on.

First attempt onlyFirst close above the level needs ≥ 1.5× prior 20-day average volume, close in the top 35% of its range, extension from SMA10 ≤ 2 frozen MTR, and positive close-to-low distance ≤ 1.5 frozen MTR. A failed attempt is consumed.

Two ways to finishFrom the bar after BREAK: low at or below the breakout-day low, or close below the chosen SMA10/20. Initial-risk touch takes precedence. No automatic partial sales or break-even adjustment.

Missing informationMissing volume blocks setups by default; disabling that filter changes the rules. Price exits continue without volume. Invalid price/history clears the campaign and shows UNAVAILABLE. Standard daily candles only.

Open source · No account required to download

A small instrument.
The complete code.

Three moving averages, a restrained ribbon, and clear event markers. Candle coloring is optional and off by default. Use the compact panel to read the state without covering the chart.

Research release. 24 offline reference tests pass, including a complete synthetic lifecycle. Independent source review completed. Native TradingView compilation, chart replay and alert delivery remain unverified. No trading edge or improvement in returns has been measured.

View the full Pine Script
//@version=6
// Konatus Vela 0.1.0 | Kutty Stauder | https://kuttystauder.com/trading/#konatus-vela
// Inspired by Qullamaggie's published breakout framework; independent adaptation.
// Research indicator. No orders, position sizing or demonstrated trading edge.
// Standard daily candles. Events at close; no future data or intrabar entries.
indicator("KONATUS Vela · Breakout Trend", shorttitle="VELA", overlay=true, max_bars_back=500)

string setupGroup = "01 · Coil"
int baseLen = input.int(10, "Base length", minval=5, maxval=30, group=setupGroup)
int impulseLen = input.int(40, "Prior advance lookback", minval=20, maxval=120, group=setupGroup)
float minImpulse = input.float(20.0, "Minimum prior advance (%)", minval=0, maxval=200, step=5, group=setupGroup)
float contraction = input.float(0.75, "Range contraction ratio", minval=0.1, maxval=1, step=0.05, group=setupGroup)
int expiry = input.int(5, "Coil expires after bars", minval=1, maxval=20, group=setupGroup)
string releaseGroup = "02 · Release and ride"
bool requireVolume = input.bool(true, "Require volume expansion", group=releaseGroup)
float volMultiple = input.float(1.5, "Volume / prior 20-day average", minval=1, maxval=5, step=0.1, group=releaseGroup)
int trailLen = input.int(20, "Close-based trailing average", options=[10, 20], group=releaseGroup)
string styleGroup = "03 · Appearance"
bool showMAs = input.bool(true, "Show 10 / 20 / 50 averages", group=styleGroup)
bool showPanel = input.bool(true, "Show compact status", group=styleGroup)
bool paintBars = input.bool(false, "Color confirmed campaign candles", group=styleGroup)
color gold = input.color(#D7B777, "Coil / SMA10", group=styleGroup)
color teal = input.color(#72CCB1, "Breakout / SMA20", group=styleGroup)
color coral = input.color(#E58C7A, "Exit / initial risk", group=styleGroup)
color muted = #8D969A

// Rolling series are evaluated unconditionally. MTR uses simple, not Wilder smoothing.
bool supported = chart.is_standard and timeframe.isdaily and timeframe.multiplier == 1
bool priceOK = not na(open) and not na(high) and not na(low) and not na(close) and low > 0 and high >= math.max(open, close) and low <= math.min(open, close)
var int validRun = 0
validRun := priceOK ? validRun + 1 : 0
int warmup = math.max(55, math.max(baseLen + impulseLen + 1, baseLen + 21))
float ma10 = ta.sma(close, 10)
float ma20 = ta.sma(close, 20)
float ma50 = ta.sma(close, 50)
float trueRange = ta.tr(true)
float mtr = ta.sma(trueRange, 20)
float shortRange = ta.sma(trueRange, 5)
float oldRange = mtr[baseLen]
float top = ta.highest(high, baseLen)
float bottom = ta.lowest(low, baseLen)
float impulse = 100 * (close[baseLen] / close[baseLen + impulseLen] - 1)
float priorVolume = ta.sma(volume, 20)[1]
float volumeValid = ta.sma(not na(volume) and volume > 0 ? 1.0 : 0.0, 20)[1]
bool volumeData = not na(volume) and volume > 0 and volumeValid == 1 and priorVolume > 0
bool volumeOK = not requireVolume or (volumeData and volume >= volMultiple * priorVolume)
bool dataOK = supported and priceOK and validRun >= warmup and not na(mtr)
bool trend = close > ma10 and ma10 > ma20 and ma20 > ma50 and ma20 > ma20[5]
bool advance = impulse >= minImpulse
bool narrow = top - bottom <= 3 * mtr
bool contracted = shortRange <= contraction * oldRange
bool nearMA = math.min(math.abs(close - ma10), math.abs(close - ma20)) <= mtr
bool ready = dataOK and mtr > 0 and trend and advance and narrow and contracted and nearMA and (not requireVolume or volumeData)
string waiting = not trend ? "Wait for rising averages" : not advance ? "Need a prior advance" : not narrow ? "Base is too wide" : not contracted ? "Wait for contraction" : not nearMA ? "Price away from averages" : requireVolume and not volumeData ? "Volume unavailable" : "Wait for a fresh coil"

// 0 WAIT, 1 COIL, 2 RIDE. One attempt per fresh readiness edge.
var int state = 0
var int armedBar = na
var float level = na
var float floor = na
var float frozenRange = na
var float initialRisk = na
var bool previousReady = false
var string reason = "Load history"
var bool available = false
bool coilEvent = false
bool breakEvent = false
bool exitEvent = false
bool rejectEvent = false

if barstate.isconfirmed
    available := dataOK
    if not dataOK
        state := 0
        level := na
        floor := na
        frozenRange := na
        initialRisk := na
        previousReady := false
        reason := not supported ? "Use standard 1D candles" : "Load valid price history"
    else
        if state == 2
            float trail = trailLen == 10 ? ma10 : ma20
            if low <= initialRisk or close < trail
                reason := low <= initialRisk ? "Initial risk touched" : "Close below trail"
                state := 0
                exitEvent := true
                level := na
                initialRisk := na
            else
                reason := "Follow the moving average"
        else if state == 1
            int age = bar_index - armedBar
            if age > expiry
                state := 0
                reason := "Coil expired"
            else if low < floor or not trend
                state := 0
                reason := "Base or trend lost"
            else if close > level
                float location = high > low ? (close - low) / (high - low) : 0
                bool quality = volumeOK and location >= 0.65 and close - ma10 <= 2 * frozenRange and close - low > 0 and close - low <= 1.5 * frozenRange
                if quality
                    state := 2
                    breakEvent := true
                    initialRisk := low
                    reason := "Confirmed breakout"
                else
                    state := 0
                    rejectEvent := true
                    reason := "Breakout filters failed"
            else
                reason := "Waiting for the release"
            if state == 0
                level := na
        else if ready and not previousReady
            state := 1
            armedBar := bar_index
            level := top + 0.10 * mtr
            floor := bottom
            frozenRange := mtr
            initialRisk := na
            coilEvent := true
            reason := "Range sealed"
        else
            reason := waiting
        previousReady := ready

// A live candle shows the previous completed averages, never a provisional event.
float confirmed10 = barstate.isconfirmed ? ma10 : ma10[1]
float confirmed20 = barstate.isconfirmed ? ma20 : ma20[1]
float confirmed50 = barstate.isconfirmed ? ma50 : ma50[1]
p10 = plot(supported and showMAs ? confirmed10 : na, "SMA10 · fast", color=color.new(gold, 15), linewidth=1)
p20 = plot(supported and showMAs ? confirmed20 : na, "SMA20 · trend", color=color.new(teal, 15), linewidth=2)
plot(supported and showMAs ? confirmed50 : na, "SMA50 · structure", color=color.new(muted, 45), linewidth=1)
fill(p10, p20, color=available and state > 0 ? color.new(state == 1 ? gold : teal, 93) : na, title="Campaign ribbon")
plot(available and (state == 1 or breakEvent) ? level : na, "Frozen release level", color=gold, linewidth=2, style=plot.style_linebr)
plot(available and state == 2 ? initialRisk : na, "Initial risk reference · not an order", color=color.new(coral, 20), linewidth=1, style=plot.style_linebr)
plotshape(coilEvent, title="New coil", style=shape.circle, location=location.belowbar, color=gold, size=size.tiny, text="C", textcolor=gold)
plotshape(breakEvent, title="Confirmed breakout", style=shape.triangleup, location=location.belowbar, color=teal, size=size.small, text="BREAK", textcolor=teal)
plotshape(exitEvent, title="Campaign exit", style=shape.xcross, location=location.abovebar, color=coral, size=size.tiny, text="EXIT", textcolor=coral)
barcolor(paintBars and barstate.isconfirmed and available ? exitEvent ? coral : state == 2 ? teal : state == 1 ? gold : na : na)

var table panel = table.new(position.bottom_right, 2, 3, bgcolor=color.new(#101D20, 7), frame_color=color.new(gold, 65), frame_width=1)
if barstate.islast
    if showPanel
        string label = not supported ? "1D ONLY" : not available ? "UNAVAILABLE" : breakEvent ? "BREAKOUT" : exitEvent ? "EXIT" : state == 2 ? "RIDE" : state == 1 ? "COIL" : "WAIT"
        color accent = exitEvent ? coral : state == 2 ? teal : gold
        table.cell(panel, 0, 0, "VELA", text_color=gold, text_size=size.small)
        table.cell(panel, 1, 0, label, text_color=accent, text_size=size.small)
        table.cell(panel, 0, 1, "At last close", text_color=muted, text_size=size.tiny)
        table.cell(panel, 1, 1, not supported ? "Use standard 1D candles" : reason, text_color=color.white, text_size=size.tiny)
        table.cell(panel, 0, 2, state == 2 ? "Initial risk" : "Release", text_color=muted, text_size=size.tiny)
        table.cell(panel, 1, 2, state == 2 ? str.tostring(initialRisk, format.mintick) : state == 1 ? str.tostring(level, format.mintick) : "—", text_color=accent, text_size=size.small)
    else
        table.clear(panel, 0, 0, 1, 2)

alertcondition(coilEvent, "Vela · New coil", "VELA: new daily coil on {{ticker}}. Preparation, not an entry.")
alertcondition(breakEvent, "Vela · Breakout", "VELA: confirmed daily breakout on {{ticker}} at close {{close}}. Review risk before acting.")
alertcondition(exitEvent, "Vela · Exit", "VELA: initial risk touched or close below the trailing average on {{ticker}}. Daily-close observation, not an executed order.")
alertcondition(rejectEvent, "Vela · Rejected release", "VELA: first release failed quality filters on {{ticker}}. Wait for a fresh coil.")

The idea and its boundaries

Inspired by a method.
Explicit about the differences.

Qullamaggie describes discretionary stock selection and often uses intraday opening-range entries, then partial profit-taking and moving-average exits. Vela’s daily-close confirmation, exact filters, five-bar expiry and frozen-level lifecycle are our own adaptation. They are not his complete system or a claim to his results.

Vela does not evaluate fundamentals, market breadth, news, liquidity or the visual quality of higher lows. Filters can delay good entries and still admit failed breakouts. Price history, sessions and data revisions can change readings. Not affiliated with or endorsed by Qullamaggie.

One chart · Two different jobs

Using both together

Read Market Anatomy for context, then Konatus Bearing for its trading-plan conditions. The purpose of pairing them is to understand what you are looking at before interpreting a setup. You can also use either one on its own.

Understand the market

Market Anatomy

What is price doing? Read direction, pace, path organization, volatility and participation. Its explanations make conflicting evidence visible. Economic causes remain hypotheses.

Explore Market Anatomy →

Review the plan

Konatus Bearing

What is the setup waiting for? Read its entry status, named restrictions and any illustrative position, frozen stop and quantity.

Explore Konatus Bearing →

Two standalone tools, shared evidence. Both analyze much of the same price history. Agreement is not two independent confirmations or a calibrated probability. The standalone scripts do not communicate: adding Anatomy does not change Bearing’s signals, stops or sizing. This manual pairing is a reading workflow, not a tested combined strategy.

Set up one readable chart

  1. Add two separate scripts. Copy the Bearing source and the Anatomy source into separate Pine Editor indicators. Save and add each to the same ordinary time-based candle chart, with the same symbol, timeframe and session.
  2. Separate the dashboards. Bearing uses the top right. In Anatomy, open Settings → Inputs → Display, set Panel position to Bottom left, and enable Detailed explanation.
  3. Give candle colors one owner. Leave Bearing’s Color confirmed candles on and Anatomy’s off. For a simpler view, switch off Anatomy’s Descriptive extension envelope and Volume-weighted reference. These are display choices.
  4. Check the inputs before reading. Review Bearing’s instrument profile and risk inputs, and Anatomy’s volume meaning. Allow both to finish warming up; Anatomy needs 82 consecutive valid bars at its default 20-bar horizon. Compare their last confirmed readings, not a developing candle.

Read in this order

  1. 1 · ContextRead Anatomy’s direction and pace, then check whether the raw path is organized, volatility has changed, and participation is available. State what you observe before inventing a cause.
  2. 2 · ConditionsRead Bearing’s plan and reason. WAIT SETUP, EXTENDED or SHOCK PAUSE still means that condition is unresolved. Anatomy’s favorable reading does not remove it.
  3. 3 · RecordIn replay or paper trading, record both readings and the decision under your written rules. If you use Bearing’s illustrative plan, inspect its frozen stop and quantity. Anatomy’s scenario boundary is a different reference, not a replacement stop.

When they disagree

Check the symbol, session, warmup and settings, then name the disagreement: direction, pace, extension or participation. Their formulas, thresholds and volume rules differ. Anatomy’s range scale is an SMA of true range; Bearing labels its distances in ATR. Their numbers are not interchangeable. Record the conflict rather than changing settings until they agree.

Keep alerts separate

For Bearing, select Any alert() function call. For Anatomy, choose the individual direction, shock, breakout or invalidation condition and Once Per Bar Close. Label alerts with the indicator name. They report separate events; neither is a broker order.

Recreate alerts after changing the script, inputs, symbol or timeframe. Existing alerts keep the configuration saved when they were created. See TradingView’s alert documentation.

Start as a research exercise. Native TradingView compilation, chart behavior and alert delivery are still unverified. Once the scripts compile and run correctly, use replay or paper trading to assess the workflow. No trading edge or benefit from combining them has been established.

Build a readable workspace

Use either standalone with other indicators.

You do not need Atlas, or both KONATUS tools, on every chart. Keep Bearing when you want its illustrative planning framework. Keep Anatomy when you want context for a method you already use. Add a companion only when it answers a specific question still missing from your workflow.

Location by price level

Bearing + Volume Profile

Bearing supplies its setup and plan conditions; Volume Profile shows how activity is distributed across price levels over a selected range. Define that range before interpreting the setup.

Activity depends on feed coverage and volume meaning. A high-volume area is not a guaranteed support level or independent proof of an entry.

About Volume Profile ↗

Context for your trend method

Anatomy + Supertrend

Use Anatomy to read pace, path organization and shocks alongside an existing ATR-based trend method. Let your written method define decisions; Anatomy explains the observed conditions.

Both depend on price. This is a manual pairing, not an automated connection. If you use Supertrend instead of Bearing, you do not inherit Bearing’s sizing or ledger.

About Supertrend ↗

A reference from a chosen event

Either + Anchored VWAP

An event-anchored reference answers a different question from a calendar reset: where is volume-weighted price since a specified point? Record an objective anchor rule, such as the first bar after a scheduled release.

Check volume semantics and avoid moving the anchor to fit the result. Hide an existing VWAP line if it only duplicates the reference.

About Anchored VWAP ↗

An alternate definition to compare

Either + RSI or Bollinger Bands

RSI provides a price-momentum oscillator. Bollinger Bands use a moving-average and standard-deviation envelope. Choose one for a defined comparison with pace or relative location.

These overlap with existing price-derived evidence. An extreme reading is not automatically a reversal, and agreement is not another independent vote.

About RSI ↗ · About Bollinger Bands ↗

Give each study one job.

  1. Write the unanswered question and the companion’s role before adding it. Start with one extra study.
  2. Keep the symbol, timeframe and session consistent. Move Anatomy’s panel if needed and allow only one study to color candles.
  3. Decide in advance how conflicts affect your written rules. Keep the tools’ alerts separately named; loading studies together does not link their logic.
  4. In replay or paper trading, compare the pairing with each tool alone. Record changed decisions, costs and failure cases before claiming an improvement.

These are research workflow suggestions, not tested combination strategies. Companion availability and data coverage depend on your TradingView access and feed. Native validation of the KONATUS research scripts remains pending.