Read the market.
Direction, velocity, acceleration, curvature, path efficiency, volatility and participation retain Anatomy’s explicit definitions.
Konatus Bearing · Pine Script v6 · free
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.

KONATUS BEARINGStocks
WAIT SETUP
Evidence ready; wait for setup
kb-0.3 · experimental · illustrative
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.
Only closed candles are colored, and the dashboard reads the last closed bar. A color is never advice to enter.
The large line on the panel is the plan. The small line under it says why.
With no plan open, the big word is the New entry status: the first gate that is still closed.
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.
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
//@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)
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.
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.
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.
| Look at | What it answers | What it does not answer |
|---|---|---|
| Win rate | How often the row is green. | How much money moved. |
| Average win | Conditional size of a gain. | Whether the next trade wins. |
| Average loss | Conditional size of a loss. | Whether the next trade loses. |
| Expectancy | pW − (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.
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.
02 / New, separate indicator
A standalone KONATUS indicator with its own Pine script. Install it separately from Konatus Bearing.
Pine Script v6 · Research release 0.1.0

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)
//@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.")
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
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 ↓
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.
Direction, velocity, acceleration, curvature, path efficiency, volatility and participation retain Anatomy’s explicit definitions.
Bearing retains its setup checks, illustrative next-open ledger, sizing assumptions, frozen stops and exit logic.
A shared dashboard explains agreement and conflict. Optional policies let Anatomy withhold new Bearing candidates.
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.
KONATUS ATLAS / 0.1
WAIT SETUP
Conditions available. Wait for setup.
RESEARCH · ILLUSTRATIVE PLANS
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.
Download Atlas Pine v6Research package (.zip)
//@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.
04 / Upgraded · 5-minute multitrend · v0.2
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 ↓
Fast and structural adaptive averages check price location, slope and local direction.
The last completed 15-minute bar checks whether the surrounding trend agrees.
The last completed hourly bar must also agree before a trend can qualify as strong.
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.
Every upward filter agrees.
Upward structure; a filter is still waiting.
Every downward filter agrees.
Downward structure; a filter is still waiting.
A higher timeframe opposes the local trend.
A range shock calls for a cooling period.
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.
| Filter | Balanced · default | Strict |
|---|---|---|
| Minimum efficiency | 0.30 | 0.40 |
| Minimum ADX | 20 | 25 |
| Directional DI spread | 5 | 8 |
| Maximum extension | 1.5 × mean range | 1.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.
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.
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.
Download Flow 0.2 Pine v6Research package (.zip)
//@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
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 ↓
Daily pressure, weekly evidence and range position established an upward regime. Its holding rules still pass.
Evidence is conflicted, a new direction is still confirming, the old bias broke, or a shock calls for a pause.
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.
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.
The bias holds and the recent move agrees.
The five-day move opposes the bias; the holding rules still pass.
Price is more than three mean ranges beyond its recent median in the direction of the bias. Direction is not an entry price.
Daily and weekly evidence disagree, confirmation is incomplete, or the previous bias has broken.
A daily range shock clears the bias for that observation and the next two. Fresh confirmation is required afterward.
History, freshness or chart requirements are not met. Missing information is kept separate from a neutral reading.
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.
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.
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.
Download Horizon Pine v6Research package (.zip)
//@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
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.

One setup. One attempt.
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.
A gold dot marks preparation. The base qualifies; its release level stops moving.
A teal triangle marks a close above the level with enough volume and a controlled extension.
Follow the 10/20-day ribbon. A thin coral line holds the breakout day’s low.
A coral cross marks a later risk touch or close below your chosen average.
A tightening base qualifies at the close. The release level is sealed at 91.96725 for the next five bars.
The next close reaches 92 with 1.5× prior average volume. A single BREAK appears; the initial risk reference is 91.
The campaign stays active above its initial reference and the selected moving average. No repeated entry triangles.
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
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.
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
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.
//@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
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
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
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
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.
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.
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
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 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
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
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
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.
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.