v2.1.0 · Focus / Audit · optional Strategy Lab
Fractal Model Desk
Fractal, explained
How Fractal reads a price move.
First, see what happened. Then decide what deserves a closer look.

New in v2.1: Seeing a sweep is only the start. Audit keeps the observations; Focus shows records that passed extra review checks. You still assess the wider chart. The original feedback ↗
Explore the chartRead the video explanation
- Small candles build one hour. Each small candle covers five minutes. Twelve make a completed hour, keeping the first open, highest price, lowest price and final close.
- Price dips below the previous low. The move below 100 is a raw sweep. It is recorded at 09:10; the hour is still forming. This observation alone does not qualify a review.
- A small candle closes back above the line. At 09:15, a close at 112 crosses the downward series’ opening price of 111. This early change does not confirm the hour.
- The completed hour gets a separate check. The hour ends at 10:00 with its close back inside the previous range. The tool records that check at 10:05, after the next five-minute candle closes.
- Even a failed setup stays in the record. At 10:10, a later wick breaks the fixed low of 96. The setup fails; its earlier marks and observation times stay available to review.
This film shows raw observations in Audit, not a qualified review or entry. The two checks are independent. This short example has too little earlier price history to measure the first change against v2.1’s volatility baseline. Later candles cannot repair that missing evidence.
Trader feedback is paraphrased from the linked public discussion. V2.1 adds review filters and separate child observations; better trading returns have not been established. Failed records remain within the history limit. Read the release checks.
What is Fractal Model Desk?
Fractal Model Desk v2.1 is a free TradingView indicator that records smaller-candle changes and larger-candle closures. Focus shows two recent delivery reviews after additional strength and context checks; Audit reveals retained raw events. Independent IC records track smaller continuation attempts. Reviews can end or be withdrawn without erasing their earlier evidence. Strategy Lab tests separate orders; observations are not trades.
How does a raw event differ from a delivery review?
Sweeps, CISD breaks and larger-candle closures are raw observations. Review also checks matching closure and CISD, first-CISD V reversal or displacement, intact structure, remaining objective and overlapping ranges. These are disclosed approximations; point of interest and higher-timeframe bias remain manual. Separate observations from review
Does Focus hide failed setups from the history?
Focus selects the two latest review times, including later withdrawals and completions. Audit reveals raw records. History defaults to 24, adjustable 8–40; capacity removes oldest completed records while protecting live setups. IC has separate retention. Changed data or settings can change recalculation. Read the history limits
Does every review become a Strategy Lab trade?
No. The Lab needs an active parent review, available position and usable fixed stop/target. It does not trade IC children. Optional FVG retest requires review already known when the retest bar opens; an earlier first retest is skipped. Default 10% equity allocation is not stop-loss risk. Check the separate Lab rules
One large candle. Many small steps.
Start with price. Review needs more evidence.
Audit · raw observations. Learn how candles connect. This short example does not earn a v2.1 delivery review.
- Hour high / low
- 114 / 107
- Latest close
- 108
One candle covers five minutes.
Its thin line shows the high and low. The solid body joins the opening and closing prices.
Fictional prices. C1’s completed range is supplied; C2 uses only revealed candles. No orders or participants are inferred.
How can this help a trade?
Focus shortlists records to study. You check the price area, larger direction and remaining objective. Review is not a buy or sell instruction.
Where did the idea come from?
Built from public TTrades material, trader feedback and research into three open-source projects. The numerical filters are disclosed choices; matching a paid indicator or improving returns has not been established.
An early change is not the final close.
Explore raw observations in Audit.
Audit · raw observations. These marks show what happened. Focus shows delivery reviews; none is earned by this short replay.
- CISD observed
- Pending
- Closure observed
- Pending
C1 supplies the reference.
The completed 08:00–09:00 hour ranged from 100 to 136. C2’s first small candle stays inside. No sweep candidate exists yet.
This replay teaches raw observations. Its supplied C1 hour is not the earlier five-minute history needed for v2.1 volatility checks.
Steps skip quiet candles, not calculations. Later hours and decisions appear only when their inputs are known.
See the dated raw record
Record shown: the upward C2 from 09:00. Other detected setups remain independent. No record yet.
No v2.1 Lab submission is demonstrated here. Its default experiment requires a delivery review, not just a raw closure and CISD. A retest experiment additionally needs review before the retest bar; an earlier or same-bar first retest cannot qualify.
How v2.1 selects a delivery review
These configurable checks select records for manual assessment. They do not validate a complete trading model.
- Pair the observations. C2 closure needs CISD in C2; delayed C3 closure needs CISD in C3.
- Measure the first CISD. Use 14 earlier chart bars for volatility. The close must pass the line by at least 0.05 of prior ATR14. Then either a V reversal takes at most 3 bars with path efficiency ≥0.70, or a directional candle body reaches ≥0.80 ATR and ≥0.60 of its range. Later candles cannot improve this saved measurement.
- Check current blockers. No ended structure, failed CISD, dual sweep or already-reached C1 opposite extreme. Congestion blocks review when both adjacent overlaps of the last 3 closed hours reach ≥0.70 and those hours contain at least 3 raw sweeps. A later blocker can withdraw an earlier review.
- Keep context manual. You still assess the higher-timeframe area of interest and directional bias. Focus shows the latest two reviews, including later outcomes; Audit restores raw observations.
Path efficiency compares net travel from the wick extreme with the total path through closing prices; the extreme bar counts toward the 3-bar limit. Overlap is divided by the smaller hourly range. These are product heuristics, not a reference author’s exact rules.
What this short replay assumes
Fictional continuous five-minute candles, automatic one-hour pairing, supplied completed C1 at 08:00–09:00. Later hourly prices use revealed candles. The unchanged raw core uses wick-origin projections and wick invalidation. C1’s hourly packet supplies no earlier five-minute ATR history. Delivery quality, congestion, IC children and Lab execution are not simulated. The raw lesson follows strict sweeps, body-series changes, newly known gaps, later overlap, opposing setups and retained records. It does not reproduce market data requests, session gaps, alerts or broker fills. Missing chart bars can delay observation of an hourly close.
What do CISD and FVG mean?
CISD (change in state of delivery): a small candle closes beyond an opposing candle series’ opening price. FVG (fair value gap): space between the first and third candles’ ranges. Neither proves trader intent or a fill.
Focus narrows the list. You assess the context.
Start with Focus; inspect the sequence in Audit.

1 · Focus: the short list
Two latest reviews, including later outcomes. Empty can be correct: read the reason.
2 · Audit: the evidence
Inspect sweeps, small-candle changes, hourly closes and failures. Hidden does not mean deleted.
3 · Your context: still essential
Check the price area and larger direction. A delivery review is not complete model validation.
Start with Setup Atlas.
Complete Pine v6 source, under the MIT licence.
- 1
Open Pine Editor
Start a new indicator on a standard five-minute TradingView chart.
- 2
Paste, save, then Add to chart
Use the full Setup Atlas source. Automatic pairing selects one hour.
- 3
Start in Focus
Hover a review for evidence. Inspect raw facts in Audit. Save Lab separately.
Setup Atlas · indicator
Focus/Audit, delivery reviews and independent IC history.
Copy the code as it is. No coding needed.
View the full code
//@version=6
// FRACTAL MODEL DESK 2.1.0 | MIT | Copyright 2026 ProEA Lab
// Original implementation informed by public rules and separately documented source audits.
// Raw sweeps are observations. Automated delivery review is not full model validation.
// Closed-bar V/displacement proxies, overlap caution, and immutable review histories.
// HTF closure known at first chart-bar close in next HTF period. POI and higher bias require assessment.
indicator("Fractal Model Desk · Setup Atlas", "FM Atlas", overlay = true, max_labels_count = 500, max_lines_count = 500, max_boxes_count = 100)
// Setup engine: no orders, positions, grade, session, or daily-trade gates.
// C1 = reference HTF candle; C2 = sweep candle. All timestamps are observation times.
type Setup
string key = ""
int dir = 0
int frameNo = 0
int c1Time = na
int c2Time = na
int seenTime = na
float c1High = na
float c1Low = na
float seedExt = na
float ext = na
float bodyExt = na
float anchor = na
int anchorTime = na
int extremeTime = na
int cisdTime = na
int cisdFrame = na
float cisd = na
float projOrigin = na
float unit = na
bool cisdFailed = false
int cisdFailTime = na
float c2Open = na
float c2High = na
float c2Low = na
float c2Close = na
int c2KnownTime = na
bool c2Valid = false
int closure = 0
int closureTime = na
int c3Time = na
int c3Frame = na
int c4Time = na
int c4Frame = na
bool c3Held = true
bool c3Strong = false
float c3Eq = na
int stage = 2
float invalidLevel = na
bool done = false
string status = "C2 developing"
int endTime = na
float endPrice = na
int fvgTime = na
float fvgTop = na
float fvgBot = na
int touchTime = na
int liqTime = na
bool dualSweep = false
type FmEvent
string key = ""
string kind = ""
int stamp = na
float price = na
int dir = 0
string detail = ""
type Engine
int frameTime = na
int frameNo = 0
bool ready = false
float frameOpen = na
float frameHigh = na
float frameLow = na
float frameClose = na
bool seededBull = false
bool seededBear = false
int runDir = 0
float runOpen = na
int runTime = na
float runBody = na
float lastBullOpen = na
int lastBullTime = na
float lastBullBody = na
float lastBearOpen = na
int lastBearTime = na
float lastBearBody = na
float prevHigh = na
float prevLow = na
float prev2High = na
float prev2Low = na
int prevTime = na
int prev2Time = na
int total = 0
int confirmed = 0
int invalidated = 0
int evicted = 0
f_event(array<FmEvent> events, Setup s, string kind, int stamp, float price, string detail) =>
array.push(events, FmEvent.new(s.key, kind, stamp, price, s.dir, detail))
f_level(Setup s, float multiple) =>
s.projOrigin + s.dir * multiple * s.unit
type FmBar
float o = na
float h = na
float l = na
float c = na
int stamp = na
int closedAt = na
int frame = na
float po = na
float ph = na
float pl = na
float pc = na
int pt = na
f_finish(Setup s, array<FmEvent> events, string reason, int stamp, float price) =>
s.done := true
s.status := reason
s.endTime := stamp
s.endPrice := price
f_event(events, s, "END", stamp, price, reason)
// Called ONCE per confirmed chart bar. Synthetic probes call this identical function.
// HTF closes are observed at the first confirmed chart bar of the following frame.
f_step(Engine e, array<Setup> book, array<FmEvent> events, FmBar b, int keep, bool useBody, bool wickInvalid) =>
array.clear(events)
bool newFrame = na(e.frameTime) or b.frame != e.frameTime
if newFrame
e.ready := not na(e.frameTime) or b.stamp == b.frame
e.frameTime := b.frame
e.frameNo += 1
e.frameOpen := b.o
e.frameHigh := b.h
e.frameLow := b.l
e.seededBull := false
e.seededBear := false
else
e.frameHigh := math.max(e.frameHigh, b.h)
e.frameLow := math.min(e.frameLow, b.l)
e.frameClose := b.c
// Consecutive candle bodies; a doji ends the run. Keep the extreme-producing run.
int bodyDir = b.c > b.o ? 1 : b.c < b.o ? -1 : 0
if bodyDir != 0
if bodyDir != e.runDir
e.runOpen := b.o
e.runTime := b.stamp
e.runBody := b.c
else
e.runBody := bodyDir == 1 ? math.max(e.runBody, b.c) : math.min(e.runBody, b.c)
if bodyDir == 1
e.lastBullOpen := e.runOpen
e.lastBullTime := e.runTime
e.lastBullBody := e.runBody
else
e.lastBearOpen := e.runOpen
e.lastBearTime := e.runTime
e.lastBearBody := e.runBody
e.runDir := bodyDir
// Advance every existing setup independently, including opposing setups.
if array.size(book) > 0
for s in book
if not s.done
int age = e.frameNo - s.frameNo + 2
s.stage := math.min(age, 4)
if newFrame
bool priorMatches = age == 3 ? b.pt == s.c2Time : age == 4 ? b.pt == s.c3Frame : age == 5 ? b.pt == s.c4Frame : true
if not priorMatches
f_finish(s, events, "HTF data gap", b.closedAt, b.c)
else if age == 3
s.c2Open := b.po
s.c2High := b.ph
s.c2Low := b.pl
s.c2Close := b.pc
s.c2KnownTime := b.closedAt
s.c2Valid := b.pc > s.c1Low and b.pc < s.c1High
s.c3Time := b.closedAt
s.c3Frame := b.frame
if s.c2Valid
s.closure := 2
s.closureTime := b.closedAt
s.invalidLevel := s.dir == 1 ? b.pl : b.ph
s.status := "C2 closed / C3 watch"
e.confirmed += 1
f_event(events, s, "C2_CLOSED", b.closedAt, b.pc, "C2 closed strictly inside C1")
else
s.status := "C2 failed / C3 reversal watch"
f_event(events, s, "C2_FAILED", b.closedAt, b.pc, "C2 did not close inside C1; retained")
f_event(events, s, "C3", b.closedAt, b.o, "Third HTF candle; no new trade required")
else if age == 4
if s.closure == 0
bool bodyBreak = s.dir == 1 ? b.pc > math.max(s.c2Open, s.c2Close) : b.pc < math.min(s.c2Open, s.c2Close)
bool held = s.c3Held and (s.dir == 1 ? b.pl >= s.c2Low : b.ph <= s.c2High)
if bodyBreak and held
s.closure := 3
s.closureTime := b.closedAt
s.invalidLevel := s.dir == 1 ? s.c2Low : s.c2High
e.confirmed += 1
f_event(events, s, "C3_CLOSED", b.closedAt, b.pc, "C3 body-break reversal; C2 extreme held")
else
f_finish(s, events, "No C2/C3 closure", b.closedAt, b.pc)
if not s.done
s.c3Strong := s.dir == 1 ? b.pc > s.c2High and b.pc > b.po : b.pc < s.c2Low and b.pc < b.po
s.c3Eq := (b.ph + b.pl) / 2
s.c4Time := b.closedAt
s.c4Frame := b.frame
s.status := s.c3Strong ? "C4 expansion watch" : "C4 / weak C3 close"
f_event(events, s, "C4", b.closedAt, b.o, s.status)
else if age >= 5
f_finish(s, events, s.closure > 0 ? "C4 window complete" : "Unconfirmed", b.closedAt, b.pc)
if not s.done
if age == 3 and s.closure == 0
s.c3Held := s.c3Held and (s.dir == 1 ? b.l >= s.c2Low : b.h <= s.c2High)
// Only a developing C2 can move the candidate extreme and its series.
bool extends = age == 2 and (s.dir == 1 ? b.l < s.ext : b.h > s.ext)
if na(s.cisdTime) and extends
s.ext := s.dir == 1 ? b.l : b.h
s.extremeTime := b.stamp
s.anchor := s.dir == 1 ? e.lastBearOpen : e.lastBullOpen
s.anchorTime := s.dir == 1 ? e.lastBearTime : e.lastBullTime
s.bodyExt := s.dir == 1 ? e.lastBearBody : e.lastBullBody
// A failed early trigger remains a fact; HTF closure is evaluated separately.
if not na(s.cisdTime) and not s.cisdFailed and b.closedAt > s.cisdTime
bool earlyBroken = s.dir == 1 ? (wickInvalid ? b.l < s.ext : b.c < s.ext) : (wickInvalid ? b.h > s.ext : b.c > s.ext)
if earlyBroken
s.cisdFailed := true
s.cisdFailTime := b.closedAt
f_event(events, s, "CISD_FAILED", b.closedAt, b.c, "Frozen CISD extreme violated; earlier signal retained")
// No new C4 CISD. C2 early and C3 confirmations remain visibly distinct from HTF closure.
if na(s.cisdTime) and age <= 3 and not na(s.anchor)
float origin = useBody ? s.bodyExt : s.ext
float signedLeg = (s.anchor - origin) * s.dir
bool crossed = s.dir == 1 ? b.c > s.anchor : b.c < s.anchor
if crossed and not na(origin) and signedLeg > 0
s.cisd := s.anchor
s.cisdTime := b.closedAt
s.cisdFrame := b.frame
s.projOrigin := origin
s.unit := signedLeg
f_event(events, s, "CISD", b.closedAt, s.cisd, age == 2 ? "Early C2 CISD; HTF closure still pending" : "C3 CISD")
if s.closure > 0
bool broken = s.dir == 1 ? (wickInvalid ? b.l < s.invalidLevel : b.c < s.invalidLevel) : (wickInvalid ? b.h > s.invalidLevel : b.c > s.invalidLevel)
if broken
e.invalidated += 1
f_finish(s, events, "Invalidated", b.closedAt, b.c)
if not s.done
// FVG is optional context: only a gap observed since this extreme, never a backfilled entry.
if not na(s.cisdTime) and not s.cisdFailed and na(s.fvgTime) and not na(e.prev2Time) and e.prev2Time >= s.extremeTime
bool gap = s.dir == 1 ? b.l > e.prev2High : b.h < e.prev2Low
if gap
s.fvgTime := b.closedAt
s.fvgTop := s.dir == 1 ? b.l : e.prev2Low
s.fvgBot := s.dir == 1 ? e.prev2High : b.h
f_event(events, s, "FVG", b.closedAt, (s.fvgTop + s.fvgBot) / 2, "First newly observed FVG after CISD")
if not na(s.fvgTime) and na(s.touchTime) and b.closedAt > s.fvgTime and b.h >= s.fvgBot and b.l <= s.fvgTop
s.touchTime := b.closedAt
f_event(events, s, "RETEST", b.closedAt, b.c, "Range overlaps known FVG; context, not an entry")
float liquidity = s.dir == 1 ? s.c1High : s.c1Low
if s.closure > 0 and na(s.liqTime) and (s.dir == 1 ? b.h >= liquidity : b.l <= liquidity)
s.liqTime := b.closedAt
f_event(events, s, "LIQUIDITY", b.closedAt, liquidity, "C1 opposite extreme reached after closure; setup retained")
// A wick beyond C1 starts a candidate even if the LTF bar is still outside C1.
// Deduplicate by HTF time + side, not by trade state. Both sides of an outside bar survive.
if e.ready and not na(b.ph) and not na(b.pl)
bool bullSweep = b.l < b.pl
bool bearSweep = b.h > b.ph
for direction = -1 to 1 by 2
bool detected = direction == 1 ? bullSweep and not e.seededBull : bearSweep and not e.seededBear
if detected
Setup s = Setup.new()
s.key := str.tostring(b.frame) + (direction == 1 ? "-L" : "-S")
s.dir := direction
s.frameNo := e.frameNo
s.c1Time := b.pt
s.c2Time := b.frame
s.seenTime := b.closedAt
s.c1High := b.ph
s.c1Low := b.pl
s.seedExt := direction == 1 ? b.l : b.h
s.ext := s.seedExt
s.extremeTime := b.stamp
s.anchor := direction == 1 ? e.lastBearOpen : e.lastBullOpen
s.anchorTime := direction == 1 ? e.lastBearTime : e.lastBullTime
s.bodyExt := direction == 1 ? e.lastBearBody : e.lastBullBody
s.dualSweep := bullSweep and bearSweep
array.push(book, s)
if e.seededBull or e.seededBear
for sibling in book
if sibling.c2Time == b.frame
sibling.dualSweep := true
e.total += 1
if direction == 1
e.seededBull := true
else
e.seededBear := true
f_event(events, s, "SWEEP", b.closedAt, direction == 1 ? b.pl : b.ph, s.dualSweep ? "Both C1 extremes swept; order unknown" : "C2 candidate; awaiting HTF close")
// Same-bar CISD is knowable at this close; never infer intrabar sweep/close fills.
float origin = useBody ? s.bodyExt : s.ext
float signedLeg = (s.anchor - origin) * direction
bool crossed = direction == 1 ? b.c > s.anchor : b.c < s.anchor
if not na(s.anchor) and not na(origin) and signedLeg > 0 and crossed
s.cisd := s.anchor
s.cisdTime := b.closedAt
s.cisdFrame := b.frame
s.projOrigin := origin
s.unit := signedLeg
f_event(events, s, "CISD", b.closedAt, s.cisd, "Early C2 CISD; HTF closure still pending")
// Capacity is the ONLY removal path. Preserve live records; evict oldest finished first.
while array.size(book) > keep
int removeAt = na
for i = 0 to array.size(book) - 1
if na(removeAt) and array.get(book, i).done
removeAt := i
if na(removeAt)
break
array.remove(book, removeAt)
e.evicted += 1
e.prev2High := e.prevHigh
e.prev2Low := e.prevLow
e.prev2Time := e.prevTime
e.prevHigh := b.h
e.prevLow := b.l
e.prevTime := b.stamp
// Delivery review is a separate, causal assessment of raw structural observations.
// Thresholds below are disclosed implementation heuristics, not TTrades' exact rules.
type ContextRules
int maxVBars = 3
float minEfficiency = 0.7
float minBodyAtr = 0.8
float minBodyFraction = 0.6
float minBreakAtr = 0.05
float chopOverlap = 0.7
int repeatedSweeps = 3
bool useChop = true
int minSamples = 14
type LocalSweep
int dir = 0
int stamp = na
float level = na
int pivotTime = na
int knownTime = na
type ContextState
array<FmBar> tape = na
array<FmBar> frames = na
array<LocalSweep> localSweeps = na
float atr = na
float atrBefore = na
int samples = 0
int frame = na
float previousClose = na
float pivotLow = na
float pivotHigh = na
int pivotLowTime = na
int pivotHighTime = na
int pivotLowKnown = na
int pivotHighKnown = na
bool lowTaken = false
bool highTaken = false
int reviews = 0
int withdrawals = 0
type ContextRecord
string key = ""
int dir = 0
int trackedExtreme = na
int legBars = 0
float legPath = 0
float legClose = na
float efficiency = na
float bodyAtr = na
float bodyFraction = na
float breakAtr = na
bool measured = false
bool vShape = false
bool displacement = false
bool chop = false
int reviewTime = na
float reviewPrice = na
string reviewModel = ""
string evidence = ""
string reason = "Waiting for closure and delivery"
string lifecycle = "Assessing"
int endedAt = na
float endPrice = na
int internalSweepTime = na
float internalLevel = na
f_newContextState() =>
ContextState.new(tape = array.new<FmBar>(), frames = array.new<FmBar>(), localSweeps = array.new<LocalSweep>())
f_contextFind(array<ContextRecord> records, string key) =>
ContextRecord found = na
for item in records
if item.key == key
found := item
break
found
f_contextActive(ContextRecord item) =>
not na(item) and not na(item.reviewTime) and na(item.endedAt)
f_contextNumber(float value) => na(value) ? "unknown" : str.tostring(value, "0.00")
f_contextOverlap(FmBar a, FmBar b) =>
float denominator = math.min(a.h - a.l, b.h - b.l)
denominator > 0 ? math.max(0, math.min(a.h, b.h) - math.max(a.l, b.l)) / denominator : 1.0
f_contextChop(ContextState state, array<Setup> book, ContextRules rules) =>
bool result = false
if rules.useChop and array.size(state.frames) >= 3
int n = array.size(state.frames)
FmBar older = array.get(state.frames, n - 3)
FmBar middle = array.get(state.frames, n - 2)
FmBar latest = array.get(state.frames, n - 1)
int count = 0
for s in book
if s.c2Time >= older.stamp and s.c2Time <= latest.stamp
count += 1
result := count >= rules.repeatedSweeps and f_contextOverlap(older, middle) >= rules.chopOverlap and f_contextOverlap(middle, latest) >= rules.chopOverlap
result
// No labels are backdated: local pivots become known after their right-hand bar closes.
// This is a local swing sweep / possible inducement sequence, not inferred intent,
// and not TTrades' 'internal liquidity', which refers to FVGs.
f_contextLocalSwings(ContextState state, FmBar b) =>
if not na(state.pivotLow) and not state.lowTaken and state.pivotLowKnown <= b.stamp and b.l < state.pivotLow
state.lowTaken := true
if b.c > state.pivotLow
array.push(state.localSweeps, LocalSweep.new(1, b.closedAt, state.pivotLow, state.pivotLowTime, state.pivotLowKnown))
if not na(state.pivotHigh) and not state.highTaken and state.pivotHighKnown <= b.stamp and b.h > state.pivotHigh
state.highTaken := true
if b.c < state.pivotHigh
array.push(state.localSweeps, LocalSweep.new(-1, b.closedAt, state.pivotHigh, state.pivotHighTime, state.pivotHighKnown))
if array.size(state.tape) >= 2
FmBar left = array.get(state.tape, array.size(state.tape) - 2)
FmBar middle = array.get(state.tape, array.size(state.tape) - 1)
if middle.l < left.l and middle.l < b.l
state.pivotLow := middle.l
state.pivotLowTime := middle.stamp
state.pivotLowKnown := b.closedAt
state.lowTaken := false
if middle.h > left.h and middle.h > b.h
state.pivotHigh := middle.h
state.pivotHighTime := middle.stamp
state.pivotHighKnown := b.closedAt
state.highTaken := false
while array.size(state.localSweeps) > 32
array.shift(state.localSweeps)
f_contextStep(ContextState state, array<ContextRecord> records, array<FmEvent> reviewEvents, array<Setup> book, ContextRules rules, FmBar b) =>
array.clear(reviewEvents)
state.atrBefore := state.atr
bool warm = state.samples >= rules.minSamples and not na(state.atrBefore) and state.atrBefore > 0
if na(state.frame) or state.frame != b.frame
state.frame := b.frame
if not na(b.pt)
FmBar closedFrame = FmBar.new(b.po, b.ph, b.pl, b.pc, b.pt, b.frame, b.pt)
array.push(state.frames, closedFrame)
if array.size(state.frames) > 3
array.shift(state.frames)
f_contextLocalSwings(state, b)
bool rangeChop = f_contextChop(state, book, rules)
map<string, ContextRecord> byKey = map.new<string, ContextRecord>()
map<string, bool> retainedKeys = map.new<string, bool>()
for item in records
map.put(byKey, item.key, item)
for s in book
map.put(retainedKeys, s.key, true)
ContextRecord q = map.contains(byKey, s.key) ? map.get(byKey, s.key) : na
if na(q)
q := ContextRecord.new(key = s.key, dir = s.dir)
// Only a separately observed, earlier local sweep can supply this note.
// Same-bar outer and local sweeps have unknown ordering and do not qualify.
for local in state.localSweeps
if local.dir == s.dir and local.stamp <= b.stamp and local.stamp < s.seenTime and local.stamp >= s.c1Time and local.level > s.c1Low and local.level < s.c1High
q.internalSweepTime := local.stamp
q.internalLevel := local.level
array.push(records, q)
map.put(byKey, s.key, q)
if not q.measured and not s.done
if na(q.trackedExtreme) or q.trackedExtreme != s.extremeTime
q.trackedExtreme := s.extremeTime
q.legBars := 1
q.legPath := math.abs(b.c - s.ext)
else
q.legBars += 1
q.legPath += math.abs(b.c - q.legClose)
q.legClose := b.c
if not na(s.cisdTime)
q.measured := true
q.efficiency := q.legPath > 0 ? math.min(1, math.max(0, (b.c - s.ext) * s.dir) / q.legPath) : 0
q.bodyAtr := warm ? math.abs(b.c - b.o) / state.atrBefore : na
q.bodyFraction := b.h > b.l ? math.abs(b.c - b.o) / (b.h - b.l) : 0
q.breakAtr := warm ? (b.c - s.cisd) * s.dir / state.atrBefore : na
bool decisive = warm and q.breakAtr >= rules.minBreakAtr
q.vShape := decisive and q.legBars <= rules.maxVBars and q.efficiency >= rules.minEfficiency
q.displacement := decisive and (b.c - b.o) * s.dir > 0 and q.bodyAtr >= rules.minBodyAtr and q.bodyFraction >= rules.minBodyFraction
q.reviewModel := q.vShape and q.displacement ? "V + displacement" : q.vShape ? "V reversal" : q.displacement ? "Displacement" : "Marginal / slow CISD"
q.evidence := "CISD quality observed at " + str.tostring(b.closedAt) + "; bars from extreme=" + str.tostring(q.legBars) + "; path efficiency=" + f_contextNumber(q.efficiency) + "; body/prior ATR=" + f_contextNumber(q.bodyAtr) + "; body/range=" + f_contextNumber(q.bodyFraction) + "; close beyond CISD/prior ATR=" + f_contextNumber(q.breakAtr) + ". Configurable proxies; POI and higher bias remain manual."
q.evidence += na(q.internalSweepTime) ? " No separately known prior local swing sweep; inducement intent unknown." : " Earlier local swing sweep/reclaim observed at " + str.tostring(q.internalSweepTime) + "; level=" + str.tostring(q.internalLevel, "0.################") + ". Possible inducement context, not proof of intent."
q.chop := rangeChop
bool paired = s.closure == 2 ? s.cisdFrame == s.c2Time : s.closure == 3 ? s.cisdFrame == s.c3Frame : false
string blocker = s.done ? s.status : s.cisdFailed ? "Frozen CISD failed" : s.dualSweep ? "Two-sided sweep / direction unresolved" : not na(s.liqTime) ? "C1 objective already reached" : rangeChop ? "Repeated sweeps in overlapping HTF ranges" : not q.measured ? "Waiting for CISD" : na(q.bodyAtr) ? "Insufficient prior volatility history at first CISD; no later reassessment" : not (q.vShape or q.displacement) ? "Marginal or slow CISD; delivery unclear" : s.closure == 0 ? "Waiting for HTF closure" : not paired ? "CISD and closure belong to different stages" : ""
if na(q.reviewTime)
q.reason := blocker == "" ? "Delivery checks met; POI / bias manual" : blocker
q.lifecycle := s.done or s.cisdFailed or s.dualSweep or (q.measured and (na(q.bodyAtr) or not (q.vShape or q.displacement))) ? "Excluded" : "Assessing"
if blocker == ""
q.reviewTime := b.closedAt
q.reviewPrice := b.c
q.lifecycle := "Review"
state.reviews += 1
f_event(reviewEvents, s, "DELIVERY_REVIEW", b.closedAt, b.c, q.reviewModel + "; " + q.evidence + " Chop check clear when reviewed. This is not full model validation or an entry.")
else if na(q.endedAt) and blocker != ""
q.endedAt := b.closedAt
q.endPrice := b.c
q.reason := blocker
q.lifecycle := s.done and s.status == "C4 window complete" ? "Complete" : "Withdrawn"
state.withdrawals += 1
f_event(reviewEvents, s, "REVIEW_END", b.closedAt, b.c, blocker + "; original review retained")
// Retention follows the raw ledger only; a noisy or failed record is not erased.
int i = array.size(records) - 1
while i >= 0
if not map.contains(retainedKeys, array.get(records, i).key)
array.remove(records, i)
i -= 1
float tr = na(state.previousClose) ? b.h - b.l : math.max(b.h - b.l, math.max(math.abs(b.h - state.previousClose), math.abs(b.l - state.previousClose)))
state.samples += 1
state.atr := na(state.atr) ? tr : state.samples <= 14 ? (state.atr * (state.samples - 1) + tr) / state.samples : (state.atr * 13 + tr) / 14
state.previousClose := b.c
array.push(state.tape, b)
if array.size(state.tape) > 3
array.shift(state.tape)
// Independent IC continuation observations. No orders and no parent-state mutation.
// One attempt per reviewed parent per C3/C4 frame. Parent review must precede the
// opposing leg. Anatomy and the stricter FVG/delivery review are separate facts.
// The HTF-open wick, parent-FVG and ATR rules are disclosed product heuristics.
type IcRecord
string key = ""
string parentKey = ""
int dir = 0
int frame = na
int stage = 0
int parentReviewTime = na
int legTime = na
int seenTime = na
float level = na
float frameOpen = na
float legExtreme = na
bool openSwept = false
float leftHigh = na
float leftLow = na
float middleHigh = na
float middleLow = na
int middleTime = na
float swingLevel = na
int swingTime = na
int swingKnownTime = na
int formedTime = na
float formedPrice = na
float protectedLevel = na
int protectedTime = na
int protectedKnownTime = na
int poiTime = na
int poiKnownTime = na
float poiTop = na
float poiBot = na
int reviewTime = na
float bodyAtr = na
float bodyFraction = na
float breakAtr = na
int takenTime = na
int endedAt = na
string formationReason = ""
string endReason = ""
string reason = "Opposing leg; waiting for swing and close-through"
f_icFind(array<IcRecord> records, string key) =>
IcRecord found = na
for item in records
if item.key == key
found := item
break
found
f_icParent(array<Setup> setups, string key) =>
Setup found = na
for item in setups
if item.key == key
found := item
break
found
f_icActive(IcRecord item) =>
not na(item) and not na(item.reviewTime) and na(item.takenTime) and na(item.endedAt)
f_icEmit(array<FmEvent> events, IcRecord item, string kind, int stamp, float price, string detail) =>
// Copy scalar values into a new event; later lifecycle changes cannot rewrite it.
array.push(events, FmEvent.new(item.key, kind, stamp, price, item.dir, "ic_reviewed=" + str.tostring(not na(item.reviewTime)) + "; parent=" + item.parentKey + "; " + detail))
f_icEnd(IcRecord item, array<FmEvent> events, FmBar b, string reason) =>
if na(item.endedAt)
item.endedAt := b.closedAt
item.endReason := reason
item.reason := reason
f_icEmit(events, item, "IC_END", b.closedAt, b.c, reason + "; original observations retained")
f_icObservePoi(IcRecord item, Setup parent, FmBar b) =>
// A gap first created by this bar is not available at its open. Equality
// permits a gap known at the preceding close, before the current bar begins.
if na(item.poiTime) and not na(parent.fvgTime) and parent.fvgTime <= b.stamp and parent.fvgTop > parent.fvgBot and b.h >= parent.fvgBot and b.l <= parent.fvgTop
item.poiTime := b.closedAt
item.poiKnownTime := parent.fvgTime
item.poiTop := parent.fvgTop
item.poiBot := parent.fvgBot
f_icProgress(IcRecord item, Setup parent, array<FmEvent> events, ContextState state, ContextRules rules, FmBar b) =>
f_icObservePoi(item, parent, b)
item.legExtreme := item.dir == 1 ? math.min(item.legExtreme, b.l) : math.max(item.legExtreme, b.h)
item.openSwept := item.openSwept or (item.dir == 1 ? b.l < item.frameOpen : b.h > item.frameOpen)
// Current bar is the right neighbor. No swing is known at its middle bar.
bool strictSwing = item.dir == 1 ? item.middleLow < item.leftLow and item.middleLow < b.l : item.middleHigh > item.leftHigh and item.middleHigh > b.h
float candidate = item.dir == 1 ? item.middleLow : item.middleHigh
if item.middleTime >= item.legTime and strictSwing
if na(item.swingLevel) or (item.dir == 1 ? candidate < item.swingLevel : candidate > item.swingLevel)
item.swingLevel := candidate
item.swingTime := item.middleTime
item.swingKnownTime := b.closedAt
bool crossed = (b.c - item.level) * item.dir > 0
// The stored swing must still be the actual leg extreme; a lower/higher
// unconfirmed current wick cannot leave an already-broken protected swing.
bool holds = not na(item.swingLevel) and item.swingLevel == item.legExtreme
if b.stamp > item.legTime and item.openSwept and crossed and holds
item.formedTime := b.closedAt
item.formedPrice := b.c
item.protectedLevel := item.swingLevel
item.protectedTime := item.swingTime
item.protectedKnownTime := item.swingKnownTime
bool warm = state.samples > rules.minSamples and not na(state.atrBefore) and state.atrBefore > 0
item.bodyAtr := warm ? math.abs(b.c - b.o) / state.atrBefore : na
item.bodyFraction := b.h > b.l ? math.abs(b.c - b.o) / (b.h - b.l) : 0
item.breakAtr := warm ? (b.c - item.level) * item.dir / state.atrBefore : na
bool decisive = warm and (b.c - b.o) * item.dir > 0 and item.bodyAtr >= rules.minBodyAtr and item.bodyFraction >= rules.minBodyFraction and item.breakAtr >= rules.minBreakAtr
item.formationReason := na(item.poiTime) ? "IC anatomy; parent FVG POI not observed" : not warm ? "IC anatomy; insufficient prior volatility history" : not decisive ? "IC anatomy; decisive delivery check not met" : "IC FVG and delivery checks met; higher bias / full POI context manual"
item.reason := item.formationReason
f_icEmit(events, item, "IC_ANATOMY", b.closedAt, b.c, "Fixed opposing open crossed after a strict local swing; protected price=" + str.tostring(item.protectedLevel, "0.################") + "; swing time=" + str.tostring(item.protectedTime) + "; " + item.formationReason)
if not na(item.poiTime) and decisive
item.reviewTime := b.closedAt
f_icEmit(events, item, "IC_REVIEW", b.closedAt, b.c, "Previously known parent FVG overlapped; decisive close met configurable ATR/body checks; nested continuation observation, not an entry")
item.leftHigh := item.middleHigh
item.leftLow := item.middleLow
item.middleHigh := b.h
item.middleLow := b.l
item.middleTime := b.stamp
// Call after f_step and f_contextStep, once per confirmed chart bar. Keep the IC
// event buffer separate from raw/context events; callers may concatenate copies.
f_icStep(array<IcRecord> icBook, array<FmEvent> icEvents, array<Setup> setups, array<ContextRecord> contexts, Engine engine, ContextState state, ContextRules rules, FmBar b, int keep) =>
array.clear(icEvents)
for item in icBook
// Continue marking taken swings even after their frame/parent has ended,
// while the completed record remains inside the disclosed retention cap.
if not na(item.formedTime) and na(item.takenTime) and b.stamp >= item.formedTime
bool taken = item.dir == 1 ? b.l < item.protectedLevel : b.h > item.protectedLevel
if taken
item.takenTime := b.closedAt
item.reason := "Protected IC swing taken; parent record unchanged"
f_icEmit(icEvents, item, "IC_TAKEN", b.closedAt, item.protectedLevel, item.reason)
if na(item.endedAt)
item.endedAt := b.closedAt
item.endReason := "Protected IC swing taken"
if na(item.endedAt)
Setup parent = f_icParent(setups, item.parentKey)
ContextRecord context = f_contextFind(contexts, item.parentKey)
if b.frame != item.frame
f_icEnd(item, icEvents, b, "IC frame ended")
else if na(parent)
f_icEnd(item, icEvents, b, "Parent left retained history")
else if not f_contextActive(context) or parent.done or parent.stage < 3 or parent.stage > 4
f_icEnd(item, icEvents, b, "Parent review ended or withdrawn")
else if na(item.formedTime)
f_icProgress(item, parent, icEvents, state, rules, b)
for parent in setups
ContextRecord context = f_contextFind(contexts, parent.key)
bool eligible = f_contextActive(context) and not parent.done and (parent.stage == 3 or parent.stage == 4)
if eligible
// The raw engine has just consumed this bar. A run that started before
// parent review cannot acquire a parent retroactively partway through.
bool firstOppositeBody = engine.runDir == -parent.dir and engine.runTime == b.stamp
bool parentKnown = context.reviewTime <= b.stamp
string key = parent.key + ":IC:" + str.tostring(b.frame) + ":1"
if firstOppositeBody and parentKnown and na(f_icFind(icBook, key))
IcRecord item = IcRecord.new()
item.key := key
item.parentKey := parent.key
item.dir := parent.dir
item.frame := b.frame
item.stage := parent.stage
item.parentReviewTime := context.reviewTime
item.legTime := b.stamp
item.seenTime := b.closedAt
item.level := b.o
item.frameOpen := engine.frameOpen
item.legExtreme := parent.dir == 1 ? b.l : b.h
item.openSwept := parent.dir == 1 ? b.l < engine.frameOpen : b.h > engine.frameOpen
// f_step updates prev2 to the bar immediately preceding b.
item.leftHigh := engine.prev2High
item.leftLow := engine.prev2Low
item.middleHigh := b.h
item.middleLow := b.l
item.middleTime := b.stamp
f_icObservePoi(item, parent, b)
array.push(icBook, item)
// Keep current-frame completed records as deduplication tombstones. The cap
// may temporarily exceed keep when all records are active/current-frame.
while array.size(icBook) > keep
int removeAt = na
for i = 0 to array.size(icBook) - 1
IcRecord item = array.get(icBook, i)
if na(removeAt) and not na(item.endedAt) and item.frame != b.frame
removeAt := i
if na(removeAt)
break
array.remove(icBook, removeAt)
array.size(icBook)
gModel = "Structure"
inAuto = input.bool(true, "Automatic HTF pairing", group = gModel)
inTf = input.timeframe("60", "Manual higher timeframe", group = gModel)
inBody = input.bool(false, "Projection anchor: body (default wick)", group = gModel, tooltip = "Origin and leg use the same anchor. 1x equals frozen CISD. These are leg multiples, not statistical standard deviations.")
inWickInvalid = input.bool(true, "Invalidate on wick through extreme (off = close)", group = gModel, tooltip = "Strictly beyond; equal touches do not invalidate. Failed early CISD is retained separately from HTF closure.")
inHistory = input.int(24, "Setups retained", minval = 8, maxval = 40, group = gModel, tooltip = "Developing, confirmed, failed and completed setups. Only oldest completed records are evicted at capacity. Live setups are preserved.")
inJson = input.bool(true, "JSON event batch alerts", group = "Alerts", tooltip = "One payload per confirmed bar contains all events and original setup IDs. Create an Any alert() function call alert.")
inAlertScope = input.string("Delivery review", "Event stream", options = ["Delivery review", "All observations"], group = "Alerts", tooltip = "Default: automated delivery reviews, IC reviews and their later outcomes. All observations also includes neutral raw sweeps, CISD and stage facts. Neither stream is a buy/sell instruction.")
gContext = "Delivery review · disclosed proxies"
inVMax = input.int(3, "V reversal: maximum bars from extreme", minval = 1, maxval = 12, group = gContext)
inEfficiency = input.float(0.7, "V reversal: minimum path efficiency", minval = 0.1, maxval = 1, step = 0.05, group = gContext, tooltip = "Net directional travel from the wick divided by total close-to-close travel, including the extreme-to-close segment. A configurable approximation of a clean reversal.")
inBodyAtr = input.float(0.8, "Displacement: minimum body / prior ATR14", minval = 0.1, maxval = 3, step = 0.1, group = gContext)
inBodyFraction = input.float(0.6, "Displacement: minimum body / range", minval = 0.1, maxval = 1, step = 0.05, group = gContext)
inBreakAtr = input.float(0.05, "Decisive close: distance past CISD / prior ATR14", minval = 0.01, maxval = 1, step = 0.01, group = gContext)
inChop = input.bool(true, "Pause reviews in repeated overlapping ranges", group = gContext)
inOverlap = input.float(0.7, "Chop: minimum pairwise overlap of 3 closed HTFs", minval = 0.1, maxval = 1, step = 0.05, group = gContext)
inRepeat = input.int(3, "Chop: sweep observations across those 3 HTFs", minval = 2, maxval = 6, group = gContext, tooltip = "Both adjacent overlaps must pass and the sweep count must reach this threshold. This is a declared noise heuristic, not a universal regime detector.")
int chartSeconds = timeframe.in_seconds()
string autoTf = chartSeconds <= 60 ? "15" : chartSeconds <= 300 ? "60" : chartSeconds <= 900 ? "240" : chartSeconds <= 3600 ? "1D" : chartSeconds <= 14400 ? "1W" : "1M"
string htfTf = inAuto ? autoTf : inTf
if barstate.isfirst and (not chart.is_standard or na(chartSeconds) or timeframe.in_seconds(htfTf) <= chartSeconds)
runtime.error("Use standard time-based bars and an HTF strictly greater than the chart timeframe.")
[pO1, pH1, pL1, pC1, pT1, pO2, pH2, pL2, pC2, pT2, pO3, pH3, pL3, pC3, pT3] = request.security(syminfo.tickerid, htfTf, [open[1], high[1], low[1], close[1], time[1], open[2], high[2], low[2], close[2], time[2], open[3], high[3], low[3], close[3], time[3]], lookahead = barmerge.lookahead_on)
int htfTime = time(htfTf)
int htfEnd = time_close(htfTf)
if not na(htfEnd) and time_close > htfEnd
runtime.error("Chart bars must fit entirely inside the selected HTF. Choose an aligned pair such as 5m / 1H.")
var array<Setup> setups = array.new<Setup>()
var Engine engine = Engine.new()
var array<FmEvent> events = array.new<FmEvent>()
var array<ContextRecord> contexts = array.new<ContextRecord>()
var ContextState contextState = f_newContextState()
var ContextRules contextRules = ContextRules.new(maxVBars = inVMax, minEfficiency = inEfficiency, minBodyAtr = inBodyAtr, minBodyFraction = inBodyFraction, minBreakAtr = inBreakAtr, chopOverlap = inOverlap, repeatedSweeps = inRepeat, useChop = inChop)
var array<FmEvent> reviewEvents = array.new<FmEvent>()
var array<IcRecord> icBook = array.new<IcRecord>()
var array<FmEvent> icEvents = array.new<FmEvent>()
if barstate.isconfirmed
FmBar b = FmBar.new(open, high, low, close, time, time_close, htfTime, pO1, pH1, pL1, pC1, pT1)
f_step(engine, setups, events, b, inHistory, inBody, inWickInvalid)
f_contextStep(contextState, contexts, reviewEvents, setups, contextRules, b)
f_icStep(icBook, icEvents, setups, contexts, engine, contextState, contextRules, b, 24)
plot(engine.total, "Audit / candidates observed", display = display.data_window)
plot(engine.confirmed, "Audit / HTF closures", display = display.data_window)
plot(engine.invalidated, "Audit / structural invalidations", display = display.data_window)
plot(engine.evicted, "Audit / capacity evictions", display = display.data_window)
plot(contextState.reviews, "Review / automated delivery checks met", display = display.data_window)
plot(contextState.withdrawals, "Review / ended or withdrawn", display = display.data_window)
// Display owns its handles; the engine owns setup facts. A finished setup is never
// removed by the renderer. Handles disappear only after its key leaves retention.
string fmVisualGroup = "Display · setup history"
string fmView = input.string("Focus", "View", options=["Focus", "Audit"], group=fmVisualGroup, tooltip="Focus shows the two most recently observed delivery reviews, including later withdrawn or completed reviews. Audit reveals retained raw observations. Display filtering never deletes engine history.")
string fmPalette = input.string("Colorblind", "Direction palette", options=["Colorblind", "Classic"], group=fmVisualGroup)
string fmProjectionScope = input.string("Latest review", "Projection visibility", options=["Latest review", "Selected reviews", "All retained", "Off"], group=fmVisualGroup, tooltip="Latest review uses observation time, never later success. Focus always limits projections to its two selected reviews; All retained is available in Audit. Prices remain frozen CISD-leg multiples, not statistical standard deviations.")
bool fmShowProjections = fmProjectionScope != "Off"
bool fmShowFvg = input.bool(true, "Show first FVG after CISD", group=fmVisualGroup, tooltip="Context only. Audit starts the box at gap observation; Focus starts no earlier than delivery review. A touch requires a later bar.")
bool fmShowStages = input.bool(false, "Audit: show C3 / C4 stage markers", group=fmVisualGroup)
bool fmShowDeveloping = input.bool(false, "Audit: preview developing CISD level", group=fmVisualGroup, tooltip="A neutral dashed rail for a pending observation. This level may move before confirmation; it is not a delivery review.")
bool fmShowHtf = input.bool(true, "Show higher-timeframe candles", group=fmVisualGroup)
bool fmShowDesk = input.bool(true, "Show recent setup ledger", group=fmVisualGroup)
bool fmShowIc = input.bool(true, "Show latest two IC observations", group=fmVisualGroup, tooltip="Focus shows the latest two IC delivery reviews, including later failures. Audit also shows neutral IC anatomy without known FVG or decisive delivery. IC history is independent of parent structure; 24 records retained with active/current-frame overflow protected.")
string fmDeskPosition = input.string("Bottom right", "Ledger position", options=["Bottom right", "Bottom left", "Top right", "Top left"], group=fmVisualGroup)
string fmDeskText = input.string("Small", "Ledger text size", options=["Small", "Normal", "Tiny"], group=fmVisualGroup)
string fmTablePosition = fmDeskPosition == "Bottom left" ? position.bottom_left : fmDeskPosition == "Top right" ? position.top_right : fmDeskPosition == "Top left" ? position.top_left : position.bottom_right
string fmTableSize = fmDeskText == "Normal" ? size.normal : fmDeskText == "Tiny" ? size.tiny : size.small
color fmBull = fmPalette == "Colorblind" ? color.rgb(0, 158, 173) : color.rgb(34, 171, 148)
color fmBear = fmPalette == "Colorblind" ? color.rgb(218, 139, 36) : color.rgb(239, 83, 80)
color fmInk = chart.fg_color
color fmPaper = chart.bg_color
color fmMuted = color.new(fmInk, 48)
color fmReview = color.rgb(56, 189, 248)
bool fmAudit = fmView == "Audit"
int fmDeskRows = fmAudit ? 9 : 6
type SetupInk
string key = ""
line reference = na
line delivery = na
line level1 = na
line level2 = na
line level25 = na
line level4 = na
line invalidation = na
box gap = na
label seed = na
label cisd = na
label c2 = na
label c3 = na
label c4 = na
label earlyFailure = na
label finish = na
label gapSeen = na
label gapTouch = na
label liquidity = na
label review = na
label reviewOutcome = na
type CandleInk
box body = na
line wick = na
label caption = na
var array<SetupInk> fmInks = array.new<SetupInk>()
var map<string, SetupInk> fmInkByKey = map.new<string, SetupInk>()
var array<CandleInk> fmCandles = array.new<CandleInk>()
var line fmPrevHigh = na
var line fmPrevLow = na
var line fmPrevEq = na
var line fmDeveloping = na
var label fmDevelopingCaption = na
var table fmDesk = table.new(fmTablePosition, 5, fmDeskRows, bgcolor=color.new(fmPaper, 8), border_color=color.new(fmInk, 88), border_width=1, frame_color=color.new(fmInk, 75), frame_width=1)
f_fmLine(line handle, int left, int right, float price, color ink, string style, int width) =>
line result = handle
if na(result)
result := line.new(left, price, right, price, xloc=xloc.bar_time, extend=extend.none, color=ink, style=style, width=width)
else
line.set_xy1(result, left, price)
line.set_xy2(result, right, price)
line.set_color(result, ink)
result
f_fmLabel(int stamp, float price, string captionText, color ink, string labelStyle, string tip) =>
label.new(stamp, price, captionText, xloc=xloc.bar_time, yloc=yloc.price, color=color.new(fmPaper, 12), style=labelStyle, textcolor=ink, size=size.tiny, tooltip=tip)
f_fmTime(int stamp) =>
na(stamp) ? "—" : str.format_time(stamp, "MM-dd HH:mm", syminfo.timezone)
f_fmPrice(float price) =>
str.tostring(price, format.mintick)
f_fmLabelVisibility(label handle, bool visible, color ink) =>
if not na(handle)
label.set_textcolor(handle, visible ? ink : color.new(ink, 100))
label.set_color(handle, color.new(fmPaper, visible ? 12 : 100))
f_fmShort(string value) =>
str.length(value) > 38 ? str.substring(value, 0, 35) + "…" : value
f_fmErase(SetupInk ink) =>
line.delete(ink.reference)
line.delete(ink.delivery)
line.delete(ink.level1)
line.delete(ink.level2)
line.delete(ink.level25)
line.delete(ink.level4)
line.delete(ink.invalidation)
box.delete(ink.gap)
label.delete(ink.seed)
label.delete(ink.cisd)
label.delete(ink.c2)
label.delete(ink.c3)
label.delete(ink.c4)
label.delete(ink.earlyFailure)
label.delete(ink.finish)
label.delete(ink.gapSeen)
label.delete(ink.gapTouch)
label.delete(ink.liquidity)
label.delete(ink.review)
label.delete(ink.reviewOutcome)
f_fmPaint(Setup s, ContextRecord q, SetupInk ink, bool selected, string projectionKey) =>
bool reviewed = not na(q) and not na(q.reviewTime)
bool reviewVisible = reviewed and (fmAudit or selected)
bool railsVisible = fmAudit or reviewVisible
bool ended = reviewed and not na(q.endedAt)
int observedEnd = na(s.endTime) ? time_close : s.endTime
int right = not fmAudit and ended ? q.endedAt : observedEnd
int reviewStart = reviewed ? q.reviewTime : s.seenTime
color railInk = fmAudit ? fmMuted : color.new(fmReview, ended ? 78 : 35)
string identity = "Observation " + s.key + "\nFirst seen " + f_fmTime(s.seenTime) + " (" + syminfo.timezone + ")"
string seedStyle = s.dir == 1 ? label.style_label_up : label.style_label_down
float reference = s.dir == 1 ? s.c1Low : s.c1High
int referenceStart = fmAudit ? s.c1Time : math.max(s.c1Time, reviewStart)
ink.reference := f_fmLine(ink.reference, referenceStart, right, reference, color.new(fmInk, railsVisible ? 78 : 100), line.style_dotted, 1)
// Raw observations are permanently neutral. A later review gets a NEW marker.
if na(ink.seed)
ink.seed := f_fmLabel(s.seenTime, s.seedExt, s.dir == 1 ? "C2 · low swept" : "C2 · high swept", fmMuted, seedStyle, identity + "\nRaw C1 " + (s.dir == 1 ? "low " : "high ") + f_fmPrice(reference) + " sweep.\nThis is not a directional recommendation or a context-qualified model.\nAudit references the original candle; Focus begins its rails only when delivery review is observed.")
if not na(s.cisdTime)
int cisdStart = fmAudit ? s.cisdTime : math.max(s.cisdTime, reviewStart)
bool deliveryVisible = railsVisible and (fmAudit or s.cisdTime <= right)
ink.delivery := f_fmLine(ink.delivery, cisdStart, right, s.cisd, deliveryVisible ? railInk : color.new(fmInk, 100), line.style_solid, 1)
if na(ink.cisd)
string levels = "\nFrozen leg multiples, not statistical σ:\n1 = " + f_fmPrice(f_level(s, 1)) + " · 2 = " + f_fmPrice(f_level(s, 2)) + "\n2.5 = " + f_fmPrice(f_level(s, 2.5)) + " · 4 = " + f_fmPrice(f_level(s, 4))
ink.cisd := f_fmLabel(s.cisdTime, s.cisd, "CISD observed", fmMuted, seedStyle, identity + "\nCandle-series open crossed at " + f_fmTime(s.cisdTime) + ".\nThis price observation alone does not pass delivery or POI checks." + levels)
if fmShowProjections and not na(s.unit)
bool projectionVisible = deliveryVisible and (fmProjectionScope == "All retained" or (fmProjectionScope == "Selected reviews" and selected) or s.key == projectionKey)
color projectionInk = fmAudit ? fmInk : fmReview
ink.level1 := f_fmLine(ink.level1, cisdStart, right, f_level(s, 1), color.new(projectionInk, projectionVisible ? (ended ? 85 : 72) : 100), line.style_dotted, 1)
ink.level2 := f_fmLine(ink.level2, cisdStart, right, f_level(s, 2), color.new(projectionInk, projectionVisible ? (ended ? 82 : 54) : 100), line.style_dashed, 1)
ink.level25 := f_fmLine(ink.level25, cisdStart, right, f_level(s, 2.5), color.new(projectionInk, projectionVisible ? (ended ? 85 : 66) : 100), line.style_dotted, 1)
ink.level4 := f_fmLine(ink.level4, cisdStart, right, f_level(s, 4), color.new(projectionInk, projectionVisible ? (ended ? 85 : 72) : 100), line.style_dashed, 1)
if not na(s.c2KnownTime)
if na(ink.c2)
string fact = s.c2Valid ? "C2 · inside C1" : "C2 · outside C1"
ink.c2 := f_fmLabel(s.c2KnownTime, s.c2Close, fact, fmMuted, label.style_label_left, identity + "\nC2 closed " + (s.c2Valid ? "strictly inside" : "outside or on the boundary of") + " the entire C1 range.\nKnown " + f_fmTime(s.c2KnownTime) + ".\nThis is a closure fact, not delivery-context approval.")
if not na(s.invalidLevel) and not na(s.closureTime)
int invalidStart = fmAudit ? s.closureTime : math.max(s.closureTime, reviewStart)
bool invalidVisible = railsVisible and (fmAudit or s.closureTime <= right)
ink.invalidation := f_fmLine(ink.invalidation, invalidStart, right, s.invalidLevel, color.new(fmInk, invalidVisible ? 80 : 100), line.style_dashed, 1)
if fmShowStages and not na(s.c3Time) and na(ink.c3)
ink.c3 := f_fmLabel(s.c3Time, reference, "C3 stage", fmMuted, label.style_label_left, identity + "\nC3 stage observed " + f_fmTime(s.c3Time) + ". No context approval implied.")
if (fmShowStages or s.closure == 3) and not na(s.c4Time) and na(ink.c4)
ink.c4 := f_fmLabel(s.c4Time, reference, s.closure == 3 ? "C3 closure observed" : "C4 stage", fmMuted, label.style_label_left, identity + "\nObserved " + f_fmTime(s.c4Time) + ".\nC3 directional close beyond the C2 range: " + (s.c3Strong ? "yes" : "no") + ". Delivery review remains separate.")
if s.cisdFailed and not na(s.cisdFailTime) and na(ink.earlyFailure)
ink.earlyFailure := f_fmLabel(s.cisdFailTime, s.ext, "· CISD failed", fmMuted, label.style_label_left, identity + "\nFrozen sweep extreme violated " + f_fmTime(s.cisdFailTime) + ".\nOriginal observations stay recorded; structural invalidation is separate.")
if fmShowFvg and not na(s.fvgTime)
int gapStart = fmAudit ? s.fvgTime : math.max(s.fvgTime, reviewStart)
bool gapVisible = railsVisible and (fmAudit or s.fvgTime <= right)
if na(ink.gap)
ink.gap := box.new(gapStart, s.fvgTop, right, s.fvgBot, xloc=xloc.bar_time, border_color=color.new(fmInk, 100), bgcolor=color.new(fmInk, 100))
ink.gapSeen := f_fmLabel(s.fvgTime, s.dir == 1 ? s.fvgBot : s.fvgTop, "FVG observed", fmMuted, seedStyle, identity + "\nGap observed " + f_fmTime(s.fvgTime) + ".\n" + f_fmPrice(s.fvgBot) + " – " + f_fmPrice(s.fvgTop) + ".\nA gap alone does not pass delivery checks.")
box.set_left(ink.gap, gapStart)
box.set_right(ink.gap, right)
box.set_border_color(ink.gap, color.new(fmAudit ? fmInk : fmReview, gapVisible ? 80 : 100))
box.set_bgcolor(ink.gap, color.new(fmAudit ? fmInk : fmReview, gapVisible ? 96 : 100))
if not na(s.touchTime) and na(ink.gapTouch)
ink.gapTouch := f_fmLabel(s.touchTime, (s.fvgTop + s.fvgBot) / 2, "· FVG touch", fmMuted, label.style_label_left, identity + "\nLater bar overlap observed " + f_fmTime(s.touchTime) + ". Context only.")
if not na(s.liqTime) and na(ink.liquidity)
ink.liquidity := f_fmLabel(s.liqTime, s.dir == 1 ? s.c1High : s.c1Low, "C1 opposite level reached", fmMuted, label.style_label_left, identity + "\nOpposite C1 extreme reached " + f_fmTime(s.liqTime) + ".\nThis is not a trade result or setup-quality verdict.")
if s.done and not na(s.endTime) and na(ink.finish)
ink.finish := f_fmLabel(s.endTime, s.endPrice, "· " + s.status, fmMuted, label.style_label_left, identity + "\n" + s.status + " at " + f_fmTime(s.endTime) + ".\nRaw observation history remains available in Audit.")
if reviewed and na(ink.review)
string levels = "\nFrozen CISD " + f_fmPrice(s.cisd) + "\nLeg multiples: 1 = " + f_fmPrice(f_level(s, 1)) + " · 2 = " + f_fmPrice(f_level(s, 2)) + "\n2.5 = " + f_fmPrice(f_level(s, 2.5)) + " · 4 = " + f_fmPrice(f_level(s, 4))
ink.review := f_fmLabel(q.reviewTime, q.reviewPrice, "Delivery review\nPOI / bias manual", fmReview, label.style_label_left, "Delivery checks observed " + f_fmTime(q.reviewTime) + " (" + syminfo.timezone + ")\nObservation " + s.key + "\nModel: " + q.reviewModel + "\nFrozen evidence: " + q.evidence + levels + "\nAutomated evidence for review; POI, bias and a trading decision remain manual.\nThis marker is never backdated to the original sweep.")
if ended and na(ink.reviewOutcome)
ink.reviewOutcome := f_fmLabel(q.endedAt, q.endPrice, q.lifecycle == "Withdrawn" ? "· Review withdrawn" : "· Review complete", fmMuted, label.style_label_left, "Delivery review " + s.key + "\n" + q.lifecycle + " at " + f_fmTime(q.endedAt) + " (" + syminfo.timezone + ")\n" + q.reason + "\nThe original review and its evidence remain unchanged.")
// Opacity is a view choice. No event label is removed, moved, or recast as success.
f_fmLabelVisibility(ink.seed, fmAudit, fmMuted)
f_fmLabelVisibility(ink.cisd, fmAudit, fmMuted)
f_fmLabelVisibility(ink.c2, fmAudit, fmMuted)
f_fmLabelVisibility(ink.c3, fmAudit, fmMuted)
f_fmLabelVisibility(ink.c4, fmAudit, fmMuted)
f_fmLabelVisibility(ink.earlyFailure, fmAudit, color.new(fmInk, 65))
f_fmLabelVisibility(ink.finish, fmAudit, color.new(fmInk, 65))
f_fmLabelVisibility(ink.gapSeen, fmAudit, fmMuted)
f_fmLabelVisibility(ink.gapTouch, fmAudit, fmMuted)
f_fmLabelVisibility(ink.liquidity, fmAudit, fmMuted)
f_fmLabelVisibility(ink.review, reviewVisible, fmReview)
f_fmLabelVisibility(ink.reviewOutcome, reviewVisible, fmMuted)
ink
// Reconcile by key, not array position; concurrent directions and adjacent models
// retain independent histories. Updating on EVERY close freezes completed rails
// identically in historical calculation, replay and subsequent real-time bars.
// Selection depends ONLY on immutable reviewTime. A later withdrawal/completion
// cannot remove an otherwise selected review or turn Focus into a winners filter.
int fmFirst = -1
int fmSecond = -1
int fmFirstTime = na
int fmSecondTime = na
int fmReviewCount = 0
int fmExcludedCount = 0
int fmAssessingCount = 0
int fmPendingIndex = -1
string fmProjectionKey = ""
string fmLatestReason = "Waiting for a raw observation"
if array.size(setups) > 0
for i = 0 to array.size(setups) - 1
Setup s = array.get(setups, i)
ContextRecord q = f_contextFind(contexts, s.key)
if not na(q)
fmLatestReason := q.reason
fmExcludedCount += q.lifecycle == "Excluded" ? 1 : 0
fmAssessingCount += q.lifecycle == "Assessing" ? 1 : 0
if not na(q.reviewTime)
fmReviewCount += 1
if na(fmFirstTime) or q.reviewTime >= fmFirstTime
fmSecond := fmFirst
fmSecondTime := fmFirstTime
fmFirst := i
fmFirstTime := q.reviewTime
fmProjectionKey := s.key
else if na(fmSecondTime) or q.reviewTime >= fmSecondTime
fmSecond := i
fmSecondTime := q.reviewTime
if not s.done and s.stage <= 3 and na(s.cisdTime) and not na(s.anchor)
fmPendingIndex := i
if barstate.isconfirmed
map<string, bool> retainedKeys = map.new<string, bool>()
if array.size(setups) > 0
for i = 0 to array.size(setups) - 1
Setup s = array.get(setups, i)
map.put(retainedKeys, s.key, true)
int fmOld = array.size(fmInks) - 1
while fmOld >= 0
SetupInk stale = array.get(fmInks, fmOld)
if not map.contains(retainedKeys, stale.key)
f_fmErase(stale)
map.remove(fmInkByKey, stale.key)
array.remove(fmInks, fmOld)
fmOld -= 1
if array.size(setups) > 0
for i = 0 to array.size(setups) - 1
Setup s = array.get(setups, i)
if not map.contains(fmInkByKey, s.key)
SetupInk fresh = SetupInk.new(key=s.key)
array.push(fmInks, fresh)
map.put(fmInkByKey, s.key, fresh)
SetupInk ink = map.get(fmInkByKey, s.key)
ContextRecord q = f_contextFind(contexts, s.key)
f_fmPaint(s, q, ink, i == fmFirst or i == fmSecond, fmProjectionKey)
if fmAudit and fmShowDeveloping and fmPendingIndex >= 0
Setup pending = array.get(setups, fmPendingIndex)
fmDeveloping := f_fmLine(fmDeveloping, pending.seenTime, time_close, pending.anchor, color.new(fmInk, 70), line.style_dashed, 1)
string tip = "Developing level for observation " + pending.key + ".\nCurrent candle-series open: " + f_fmPrice(pending.anchor) + ".\nThis level may move before confirmation. No delivery review or directional recommendation is implied."
if na(fmDevelopingCaption)
fmDevelopingCaption := f_fmLabel(time_close, pending.anchor, "Developing CISD · unconfirmed", fmMuted, label.style_label_left, tip)
else
label.set_xy(fmDevelopingCaption, time_close, pending.anchor)
label.set_text(fmDevelopingCaption, "Developing CISD · unconfirmed")
label.set_textcolor(fmDevelopingCaption, fmMuted)
label.set_color(fmDevelopingCaption, color.new(fmPaper, 12))
label.set_tooltip(fmDevelopingCaption, tip)
else
if not na(fmDeveloping)
line.set_color(fmDeveloping, color.new(fmInk, 100))
label.set_text(fmDevelopingCaption, "")
label.set_color(fmDevelopingCaption, color.new(fmPaper, 100))
f_fmCandle(CandleInk ink, int center, int halfWidth, float o, float h, float l, float c, string caption, string tip, bool forming) =>
color direction = c >= o ? fmBull : fmBear
float top = math.max(o, c)
float bottom = math.min(o, c)
if not na(o)
if na(ink.body)
ink.wick := line.new(center, l, center, h, xloc=xloc.bar_time, color=direction, width=1)
ink.body := box.new(center - halfWidth, top, center + halfWidth, bottom, xloc=xloc.bar_time, border_color=direction, bgcolor=color.new(direction, forming ? 87 : 64))
ink.caption := f_fmLabel(center, l, caption, forming ? fmInk : fmMuted, label.style_label_up, tip)
else
line.set_xy1(ink.wick, center, l)
line.set_xy2(ink.wick, center, h)
line.set_color(ink.wick, direction)
box.set_lefttop(ink.body, center - halfWidth, top)
box.set_rightbottom(ink.body, center + halfWidth, bottom)
box.set_border_color(ink.body, direction)
box.set_bgcolor(ink.body, color.new(direction, forming ? 87 : 64))
label.set_xy(ink.caption, center, l)
label.set_text(ink.caption, caption)
label.set_tooltip(ink.caption, tip)
ink
if barstate.isfirst
for i = 0 to 3
array.push(fmCandles, CandleInk.new())
table.merge_cells(fmDesk, 0, 0, 4, 0)
table.merge_cells(fmDesk, 0, 1, 4, 1)
table.merge_cells(fmDesk, 0, fmDeskRows - 1, 4, fmDeskRows - 1)
if barstate.islast
// Preview is display-only: the engine never consumes this forming OHLC.
int fmStep = int(timeframe.in_seconds() * 1000)
if fmShowHtf and fmStep > 0
bool frameIsCurrent = engine.frameTime == htfTime
float previewOpen = frameIsCurrent ? engine.frameOpen : open
float previewHigh = frameIsCurrent ? math.max(engine.frameHigh, high) : high
float previewLow = frameIsCurrent ? math.min(engine.frameLow, low) : low
float previewClose = close
int secondsLeft = int(math.max(0, (htfEnd - (barstate.isrealtime ? timenow : time_close)) / 1000))
string clockText = str.tostring(int(secondsLeft / 60)) + ":" + str.tostring(secondsLeft % 60, "00")
array<float> os = array.from(pO3, pO2, pO1, previewOpen)
array<float> hs = array.from(pH3, pH2, pH1, previewHigh)
array<float> ls = array.from(pL3, pL2, pL1, previewLow)
array<float> cs = array.from(pC3, pC2, pC1, previewClose)
array<int> ts = array.from(pT3, pT2, pT1, htfTime)
for i = 0 to 3
int center = time + fmStep * (8 + i * 5)
bool forming = i == 3
string caption = forming ? htfTf + " · FORMING\n" + clockText : str.format_time(array.get(ts, i), "HH:mm", syminfo.timezone)
string tip = (forming ? "LIVE / FORMING — not setup confirmation" : "Closed HTF candle") + "\n" + htfTf + " · " + f_fmTime(array.get(ts, i)) + " (" + syminfo.timezone + ")\nO " + f_fmPrice(array.get(os, i)) + " H " + f_fmPrice(array.get(hs, i)) + "\nL " + f_fmPrice(array.get(ls, i)) + " C " + f_fmPrice(array.get(cs, i)) + "\nEQ " + f_fmPrice((array.get(hs, i) + array.get(ls, i)) / 2)
f_fmCandle(array.get(fmCandles, i), center, fmStep, array.get(os, i), array.get(hs, i), array.get(ls, i), array.get(cs, i), caption, tip, forming)
if not na(pH1)
fmPrevHigh := f_fmLine(fmPrevHigh, time + fmStep * 16, time + fmStep * 25, pH1, color.new(fmInk, 65), line.style_dotted, 1)
fmPrevLow := f_fmLine(fmPrevLow, time + fmStep * 16, time + fmStep * 25, pL1, color.new(fmInk, 65), line.style_dotted, 1)
fmPrevEq := f_fmLine(fmPrevEq, time + fmStep * 16, time + fmStep * 25, (pH1 + pL1) / 2, color.new(fmInk, 80), line.style_dashed, 1)
if fmShowDesk
table.cell(fmDesk, 0, 0, "FRACTAL MODEL DESK · " + str.upper(fmView) + " / " + htfTf, text_color=fmInk, text_size=fmTableSize, text_halign=text.align_left, bgcolor=color.new(fmInk, 91))
table.cell(fmDesk, 0, 1, "Observed " + str.tostring(engine.total) + " · Retained " + str.tostring(array.size(setups)) + "/" + str.tostring(inHistory) + "\nIn retained: reviews " + str.tostring(fmReviewCount) + " · excluded " + str.tostring(fmExcludedCount) + " · assessing " + str.tostring(fmAssessingCount), text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left, tooltip="Raw observation count is separate from delivery reviews. Retention evictions: " + str.tostring(engine.evicted) + ". Exclusion means current automated delivery checks were not met; it does not delete the observation.")
array<string> headers = fmAudit ? array.from("C2 OPEN", "SWEEP", "OBSERVATIONS", "CONTEXT", "REASON") : array.from("REVIEWED", "SWEEP", "EVIDENCE", "STATE", "CURRENT REASON")
for col = 0 to 4
table.cell(fmDesk, col, 2, array.get(headers, col), text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left, tooltip="Times: " + syminfo.timezone + ". Review time is when automated delivery evidence became observable. A review still needs manual POI and bias assessment.")
for row = 0 to (fmAudit ? 4 : 1)
int idx = fmAudit ? array.size(setups) - 1 - row : row == 0 ? fmFirst : fmSecond
if idx >= 0
Setup s = array.get(setups, idx)
ContextRecord q = f_contextFind(contexts, s.key)
string closureText = s.closure > 0 ? "C" + str.tostring(s.closure) + " close seen" : not na(s.c2KnownTime) ? "C2 outside" : "C2 pending"
string cisdText = s.cisdFailed ? "CISD failed" : not na(s.cisdTime) ? "CISD observed" : "CISD pending"
string reason = na(q) ? "Awaiting context assessment" : q.reason
string lifecycle = na(q) ? "Assessing" : q.lifecycle
string identity = "Observation " + s.key + "\nC2 open: " + f_fmTime(s.c2Time) + "\nSweep observed: " + f_fmTime(s.seenTime) + "\nCISD: " + f_fmTime(s.cisdTime) + "\nClosure known: " + f_fmTime(s.closureTime) + "\nTimes: " + syminfo.timezone
identity += not na(q) and not na(q.internalSweepTime) ? "\nEarlier local sweep/reclaim " + f_fmTime(q.internalSweepTime) + " at " + f_fmPrice(q.internalLevel) + "; possible inducement context, intent unknown." : "\nNo separately known prior local swing sweep; inducement intent unknown."
string evidence = fmAudit ? closureText + "\n" + cisdText : q.reviewModel
string evidenceTip = fmAudit ? identity + "\n" + s.status : "Review observed " + f_fmTime(q.reviewTime) + ".\nFrozen evidence: " + q.evidence + "\nManual POI and bias assessment required."
table.cell(fmDesk, 0, row + 3, f_fmTime(fmAudit ? s.c2Time : q.reviewTime), text_color=fmInk, text_size=fmTableSize, text_halign=text.align_left, tooltip=identity)
table.cell(fmDesk, 1, row + 3, (s.dir == 1 ? "Low swept" : "High swept") + (s.dualSweep ? "\nBoth extremes" : ""), text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left)
table.cell(fmDesk, 2, row + 3, evidence, text_color=fmAudit ? fmMuted : fmReview, text_size=fmTableSize, text_halign=text.align_left, tooltip=evidenceTip)
table.cell(fmDesk, 3, row + 3, lifecycle, text_color=fmMuted, text_size=fmTableSize)
table.cell(fmDesk, 4, row + 3, f_fmShort(reason), text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left, tooltip=reason)
else
for col = 0 to 4
string emptyText = row == 0 and col == 0 ? (fmAudit ? "No observations" : "No delivery review") : row == 0 and col == 4 ? f_fmShort(fmLatestReason) : ""
table.cell(fmDesk, col, row + 3, emptyText, text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left, tooltip=row == 0 ? "Latest observation: " + fmLatestReason : "")
table.cell(fmDesk, 0, fmDeskRows - 1, "Automated delivery checks · POI / bias manual\nTimes: " + syminfo.timezone + " · " + (fmAudit ? "Raw audit history" : "Latest 2 reviews · outcomes preserved"), text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left, tooltip="Focus selects two records by immutable review observation time, including later withdrawn and completed reviews. It never selects by trade outcome. Audit exposes retained raw facts. Hidden drawings remain recorded. Ledger times use the exchange timezone, which can differ from chart display time. Automated delivery evidence is not manual POI or directional-bias approval.")
// Two reusable display slots bound IC drawings independently of retained history.
// Rails start when anatomy is observed, never at the historical swing location.
var array<line> fmIcRails = array.new<line>()
var array<label> fmIcLabels = array.new<label>()
if barstate.isconfirmed and fmShowIc
for rail in fmIcRails
line.delete(rail)
for caption in fmIcLabels
label.delete(caption)
array.clear(fmIcRails)
array.clear(fmIcLabels)
array<IcRecord> selectedIc = array.new<IcRecord>()
if array.size(icBook) > 0
for item in icBook
if not na(item.formedTime) and (fmAudit or not na(item.reviewTime))
int slot = array.size(selectedIc)
if array.size(selectedIc) > 0
for n = 0 to array.size(selectedIc) - 1
if item.formedTime >= array.get(selectedIc, n).formedTime
slot := n
break
array.insert(selectedIc, slot, item)
if array.size(selectedIc) > 2
array.pop(selectedIc)
for item in selectedIc
bool reviewed = not na(item.reviewTime)
int endTime = na(item.endedAt) ? time_close : item.endedAt
string tip = "IC " + item.key + "\nParent " + item.parentKey + "\nAnatomy observed " + f_fmTime(item.formedTime) + "\nProtected swing " + f_fmPrice(item.protectedLevel) + " at " + f_fmTime(item.protectedTime) + "\nSwing known " + f_fmTime(item.protectedKnownTime) + "\n" + item.formationReason + "\nCurrent: " + item.reason + "\nChild swing taken does not itself invalidate the parent."
array.push(fmIcRails, line.new(item.formedTime, item.protectedLevel, endTime, item.protectedLevel, xloc=xloc.bar_time, color=reviewed ? color.new(fmReview, 45) : fmMuted, style=line.style_dotted))
array.push(fmIcLabels, f_fmLabel(item.formedTime, item.formedPrice, reviewed ? "IC delivery review\nPOI / bias assessment" : "IC anatomy\nContext incomplete", reviewed ? fmReview : fmMuted, label.style_label_left, tip))
if not na(item.endedAt)
array.push(fmIcLabels, f_fmLabel(item.endedAt, item.protectedLevel, "IC ended", fmMuted, label.style_label_left, "Observed " + f_fmTime(item.endedAt) + "\n" + item.endReason + (not na(item.takenTime) ? "\nProtected swing taken " + f_fmTime(item.takenTime) : "") + "\nOriginal IC anatomy retained; parent assessed independently."))
f_jsonNumber(float value) => na(value) ? "null" : str.tostring(value, "0.################")
f_escape(string value) => str.replace_all(str.replace_all(value, "\\", "\\\\"), "\"", "\\\"")
f_eventClass(string kind) => kind == "DELIVERY_REVIEW" ? "delivery_review" : kind == "REVIEW_END" ? "review_outcome" : kind == "IC_REVIEW" ? "ic_review" : kind == "IC_ANATOMY" ? "ic_anatomy" : kind == "IC_TAKEN" or kind == "IC_END" ? "ic_outcome" : "raw_observation"
bool alertSweep = false
bool alertCisd = false
bool alertClosure = false
bool alertFailure = false
bool alertRetest = false
bool alertReview = false
bool alertReviewEnd = false
bool alertIc = false
bool alertIcEnd = false
array<FmEvent> outboundEvents = array.new<FmEvent>()
if barstate.isconfirmed
for event in reviewEvents
array.push(outboundEvents, event)
for event in icEvents
if inAlertScope == "All observations" or event.kind == "IC_REVIEW" or ((event.kind == "IC_TAKEN" or event.kind == "IC_END") and str.contains(event.detail, "ic_reviewed=true;"))
array.push(outboundEvents, event)
if inAlertScope == "All observations"
for event in events
array.push(outboundEvents, event)
if barstate.isconfirmed and array.size(outboundEvents) > 0
string payload = ""
for event in outboundEvents
alertSweep := alertSweep or event.kind == "SWEEP"
alertCisd := alertCisd or event.kind == "CISD"
alertClosure := alertClosure or event.kind == "C2_CLOSED" or event.kind == "C3_CLOSED"
alertFailure := alertFailure or event.kind == "C2_FAILED" or event.kind == "CISD_FAILED" or (event.kind == "END" and event.detail != "C4 window complete")
alertRetest := alertRetest or event.kind == "RETEST"
alertReview := alertReview or event.kind == "DELIVERY_REVIEW"
alertReviewEnd := alertReviewEnd or event.kind == "REVIEW_END"
alertIc := alertIc or event.kind == "IC_REVIEW"
alertIcEnd := alertIcEnd or event.kind == "IC_TAKEN" or event.kind == "IC_END"
payload += (payload == "" ? "" : ",") + "{\"id\":\"" + event.key + "\",\"event\":\"" + event.kind + "\",\"classification\":\"" + f_eventClass(event.kind) + "\",\"observed_at\":" + str.tostring(event.stamp) + ",\"direction\":" + str.tostring(event.dir) + ",\"price\":" + f_jsonNumber(event.price) + ",\"detail\":\"" + f_escape(event.detail) + "\"}"
if inJson
alert("{\"schema\":\"fm-desk-v2.1\",\"rules_version\":\"2.1.0\",\"scope\":\"" + inAlertScope + "\",\"full_model_validation\":false,\"symbol\":\"" + f_escape(syminfo.tickerid) + "\",\"chart_tf\":\"" + timeframe.period + "\",\"htf\":\"" + htfTf + "\",\"events\":[" + payload + "]}", alert.freq_once_per_bar_close)
alertcondition(alertReview, "Delivery checks met — review context", "Fractal Model Desk: automated delivery checks met; POI and higher bias still need assessment. Not an entry instruction.")
alertcondition(alertReviewEnd, "Reviewed context ended", "Fractal Model Desk: previously reviewed context ended or was withdrawn; original observation retained.")
alertcondition(alertIc, "IC delivery checks met — review context", "Fractal Model Desk: nested IC FVG and delivery checks met; higher bias and full POI context remain manual.")
alertcondition(alertIcEnd, "IC context ended or swing taken", "Fractal Model Desk: retained IC observation ended or its protected swing was taken. Parent structure is assessed independently.")
alertcondition(alertSweep, "Audit only · raw sweep", "Fractal Model Desk: raw sweep observed; no model validation.")
alertcondition(alertCisd, "Audit only · raw CISD", "Fractal Model Desk: raw close through series open; no quality or context validation.")
alertcondition(alertClosure, "Audit only · HTF closure", "Fractal Model Desk: structural closure observed; no complete model validation.")
alertcondition(alertFailure, "Audit only · failure", "Fractal Model Desk: failed observation retained in audit history.")
alertcondition(alertRetest, "Audit only · FVG overlap", "Fractal Model Desk: range overlaps a previously known FVG; context only.")
Strategy Lab · optional
Tests review entries or later FVG retests. No IC trades.
Copy the code as it is. No coding needed.
View the full code
//@version=6
// FRACTAL MODEL DESK 2.1.0 | MIT | Copyright 2026 ProEA Lab
// Original implementation informed by public rules and separately documented source audits.
// Raw sweeps are observations. Automated delivery review is not full model validation.
// Closed-bar V/displacement proxies, overlap caution, and immutable review histories.
// HTF closure known at first chart-bar close in next HTF period. POI and higher bias require assessment.
// Strategy Lab is an optional execution experiment, not the definition of a setup.
strategy("Fractal Model Desk · Strategy Lab v2", "FM Lab v2", overlay = true, max_labels_count = 500, max_lines_count = 500, max_boxes_count = 100, initial_capital = 100000, default_qty_type = strategy.percent_of_equity, default_qty_value = 10, pyramiding = 0, commission_type = strategy.commission.percent, commission_value = 0.02, slippage = 1, process_orders_on_close = true, calc_on_every_tick = false, calc_on_order_fills = false)
// Setup engine: no orders, positions, grade, session, or daily-trade gates.
// C1 = reference HTF candle; C2 = sweep candle. All timestamps are observation times.
type Setup
string key = ""
int dir = 0
int frameNo = 0
int c1Time = na
int c2Time = na
int seenTime = na
float c1High = na
float c1Low = na
float seedExt = na
float ext = na
float bodyExt = na
float anchor = na
int anchorTime = na
int extremeTime = na
int cisdTime = na
int cisdFrame = na
float cisd = na
float projOrigin = na
float unit = na
bool cisdFailed = false
int cisdFailTime = na
float c2Open = na
float c2High = na
float c2Low = na
float c2Close = na
int c2KnownTime = na
bool c2Valid = false
int closure = 0
int closureTime = na
int c3Time = na
int c3Frame = na
int c4Time = na
int c4Frame = na
bool c3Held = true
bool c3Strong = false
float c3Eq = na
int stage = 2
float invalidLevel = na
bool done = false
string status = "C2 developing"
int endTime = na
float endPrice = na
int fvgTime = na
float fvgTop = na
float fvgBot = na
int touchTime = na
int liqTime = na
bool dualSweep = false
type FmEvent
string key = ""
string kind = ""
int stamp = na
float price = na
int dir = 0
string detail = ""
type Engine
int frameTime = na
int frameNo = 0
bool ready = false
float frameOpen = na
float frameHigh = na
float frameLow = na
float frameClose = na
bool seededBull = false
bool seededBear = false
int runDir = 0
float runOpen = na
int runTime = na
float runBody = na
float lastBullOpen = na
int lastBullTime = na
float lastBullBody = na
float lastBearOpen = na
int lastBearTime = na
float lastBearBody = na
float prevHigh = na
float prevLow = na
float prev2High = na
float prev2Low = na
int prevTime = na
int prev2Time = na
int total = 0
int confirmed = 0
int invalidated = 0
int evicted = 0
f_event(array<FmEvent> events, Setup s, string kind, int stamp, float price, string detail) =>
array.push(events, FmEvent.new(s.key, kind, stamp, price, s.dir, detail))
f_level(Setup s, float multiple) =>
s.projOrigin + s.dir * multiple * s.unit
type FmBar
float o = na
float h = na
float l = na
float c = na
int stamp = na
int closedAt = na
int frame = na
float po = na
float ph = na
float pl = na
float pc = na
int pt = na
f_finish(Setup s, array<FmEvent> events, string reason, int stamp, float price) =>
s.done := true
s.status := reason
s.endTime := stamp
s.endPrice := price
f_event(events, s, "END", stamp, price, reason)
// Called ONCE per confirmed chart bar. Synthetic probes call this identical function.
// HTF closes are observed at the first confirmed chart bar of the following frame.
f_step(Engine e, array<Setup> book, array<FmEvent> events, FmBar b, int keep, bool useBody, bool wickInvalid) =>
array.clear(events)
bool newFrame = na(e.frameTime) or b.frame != e.frameTime
if newFrame
e.ready := not na(e.frameTime) or b.stamp == b.frame
e.frameTime := b.frame
e.frameNo += 1
e.frameOpen := b.o
e.frameHigh := b.h
e.frameLow := b.l
e.seededBull := false
e.seededBear := false
else
e.frameHigh := math.max(e.frameHigh, b.h)
e.frameLow := math.min(e.frameLow, b.l)
e.frameClose := b.c
// Consecutive candle bodies; a doji ends the run. Keep the extreme-producing run.
int bodyDir = b.c > b.o ? 1 : b.c < b.o ? -1 : 0
if bodyDir != 0
if bodyDir != e.runDir
e.runOpen := b.o
e.runTime := b.stamp
e.runBody := b.c
else
e.runBody := bodyDir == 1 ? math.max(e.runBody, b.c) : math.min(e.runBody, b.c)
if bodyDir == 1
e.lastBullOpen := e.runOpen
e.lastBullTime := e.runTime
e.lastBullBody := e.runBody
else
e.lastBearOpen := e.runOpen
e.lastBearTime := e.runTime
e.lastBearBody := e.runBody
e.runDir := bodyDir
// Advance every existing setup independently, including opposing setups.
if array.size(book) > 0
for s in book
if not s.done
int age = e.frameNo - s.frameNo + 2
s.stage := math.min(age, 4)
if newFrame
bool priorMatches = age == 3 ? b.pt == s.c2Time : age == 4 ? b.pt == s.c3Frame : age == 5 ? b.pt == s.c4Frame : true
if not priorMatches
f_finish(s, events, "HTF data gap", b.closedAt, b.c)
else if age == 3
s.c2Open := b.po
s.c2High := b.ph
s.c2Low := b.pl
s.c2Close := b.pc
s.c2KnownTime := b.closedAt
s.c2Valid := b.pc > s.c1Low and b.pc < s.c1High
s.c3Time := b.closedAt
s.c3Frame := b.frame
if s.c2Valid
s.closure := 2
s.closureTime := b.closedAt
s.invalidLevel := s.dir == 1 ? b.pl : b.ph
s.status := "C2 closed / C3 watch"
e.confirmed += 1
f_event(events, s, "C2_CLOSED", b.closedAt, b.pc, "C2 closed strictly inside C1")
else
s.status := "C2 failed / C3 reversal watch"
f_event(events, s, "C2_FAILED", b.closedAt, b.pc, "C2 did not close inside C1; retained")
f_event(events, s, "C3", b.closedAt, b.o, "Third HTF candle; no new trade required")
else if age == 4
if s.closure == 0
bool bodyBreak = s.dir == 1 ? b.pc > math.max(s.c2Open, s.c2Close) : b.pc < math.min(s.c2Open, s.c2Close)
bool held = s.c3Held and (s.dir == 1 ? b.pl >= s.c2Low : b.ph <= s.c2High)
if bodyBreak and held
s.closure := 3
s.closureTime := b.closedAt
s.invalidLevel := s.dir == 1 ? s.c2Low : s.c2High
e.confirmed += 1
f_event(events, s, "C3_CLOSED", b.closedAt, b.pc, "C3 body-break reversal; C2 extreme held")
else
f_finish(s, events, "No C2/C3 closure", b.closedAt, b.pc)
if not s.done
s.c3Strong := s.dir == 1 ? b.pc > s.c2High and b.pc > b.po : b.pc < s.c2Low and b.pc < b.po
s.c3Eq := (b.ph + b.pl) / 2
s.c4Time := b.closedAt
s.c4Frame := b.frame
s.status := s.c3Strong ? "C4 expansion watch" : "C4 / weak C3 close"
f_event(events, s, "C4", b.closedAt, b.o, s.status)
else if age >= 5
f_finish(s, events, s.closure > 0 ? "C4 window complete" : "Unconfirmed", b.closedAt, b.pc)
if not s.done
if age == 3 and s.closure == 0
s.c3Held := s.c3Held and (s.dir == 1 ? b.l >= s.c2Low : b.h <= s.c2High)
// Only a developing C2 can move the candidate extreme and its series.
bool extends = age == 2 and (s.dir == 1 ? b.l < s.ext : b.h > s.ext)
if na(s.cisdTime) and extends
s.ext := s.dir == 1 ? b.l : b.h
s.extremeTime := b.stamp
s.anchor := s.dir == 1 ? e.lastBearOpen : e.lastBullOpen
s.anchorTime := s.dir == 1 ? e.lastBearTime : e.lastBullTime
s.bodyExt := s.dir == 1 ? e.lastBearBody : e.lastBullBody
// A failed early trigger remains a fact; HTF closure is evaluated separately.
if not na(s.cisdTime) and not s.cisdFailed and b.closedAt > s.cisdTime
bool earlyBroken = s.dir == 1 ? (wickInvalid ? b.l < s.ext : b.c < s.ext) : (wickInvalid ? b.h > s.ext : b.c > s.ext)
if earlyBroken
s.cisdFailed := true
s.cisdFailTime := b.closedAt
f_event(events, s, "CISD_FAILED", b.closedAt, b.c, "Frozen CISD extreme violated; earlier signal retained")
// No new C4 CISD. C2 early and C3 confirmations remain visibly distinct from HTF closure.
if na(s.cisdTime) and age <= 3 and not na(s.anchor)
float origin = useBody ? s.bodyExt : s.ext
float signedLeg = (s.anchor - origin) * s.dir
bool crossed = s.dir == 1 ? b.c > s.anchor : b.c < s.anchor
if crossed and not na(origin) and signedLeg > 0
s.cisd := s.anchor
s.cisdTime := b.closedAt
s.cisdFrame := b.frame
s.projOrigin := origin
s.unit := signedLeg
f_event(events, s, "CISD", b.closedAt, s.cisd, age == 2 ? "Early C2 CISD; HTF closure still pending" : "C3 CISD")
if s.closure > 0
bool broken = s.dir == 1 ? (wickInvalid ? b.l < s.invalidLevel : b.c < s.invalidLevel) : (wickInvalid ? b.h > s.invalidLevel : b.c > s.invalidLevel)
if broken
e.invalidated += 1
f_finish(s, events, "Invalidated", b.closedAt, b.c)
if not s.done
// FVG is optional context: only a gap observed since this extreme, never a backfilled entry.
if not na(s.cisdTime) and not s.cisdFailed and na(s.fvgTime) and not na(e.prev2Time) and e.prev2Time >= s.extremeTime
bool gap = s.dir == 1 ? b.l > e.prev2High : b.h < e.prev2Low
if gap
s.fvgTime := b.closedAt
s.fvgTop := s.dir == 1 ? b.l : e.prev2Low
s.fvgBot := s.dir == 1 ? e.prev2High : b.h
f_event(events, s, "FVG", b.closedAt, (s.fvgTop + s.fvgBot) / 2, "First newly observed FVG after CISD")
if not na(s.fvgTime) and na(s.touchTime) and b.closedAt > s.fvgTime and b.h >= s.fvgBot and b.l <= s.fvgTop
s.touchTime := b.closedAt
f_event(events, s, "RETEST", b.closedAt, b.c, "Range overlaps known FVG; context, not an entry")
float liquidity = s.dir == 1 ? s.c1High : s.c1Low
if s.closure > 0 and na(s.liqTime) and (s.dir == 1 ? b.h >= liquidity : b.l <= liquidity)
s.liqTime := b.closedAt
f_event(events, s, "LIQUIDITY", b.closedAt, liquidity, "C1 opposite extreme reached after closure; setup retained")
// A wick beyond C1 starts a candidate even if the LTF bar is still outside C1.
// Deduplicate by HTF time + side, not by trade state. Both sides of an outside bar survive.
if e.ready and not na(b.ph) and not na(b.pl)
bool bullSweep = b.l < b.pl
bool bearSweep = b.h > b.ph
for direction = -1 to 1 by 2
bool detected = direction == 1 ? bullSweep and not e.seededBull : bearSweep and not e.seededBear
if detected
Setup s = Setup.new()
s.key := str.tostring(b.frame) + (direction == 1 ? "-L" : "-S")
s.dir := direction
s.frameNo := e.frameNo
s.c1Time := b.pt
s.c2Time := b.frame
s.seenTime := b.closedAt
s.c1High := b.ph
s.c1Low := b.pl
s.seedExt := direction == 1 ? b.l : b.h
s.ext := s.seedExt
s.extremeTime := b.stamp
s.anchor := direction == 1 ? e.lastBearOpen : e.lastBullOpen
s.anchorTime := direction == 1 ? e.lastBearTime : e.lastBullTime
s.bodyExt := direction == 1 ? e.lastBearBody : e.lastBullBody
s.dualSweep := bullSweep and bearSweep
array.push(book, s)
if e.seededBull or e.seededBear
for sibling in book
if sibling.c2Time == b.frame
sibling.dualSweep := true
e.total += 1
if direction == 1
e.seededBull := true
else
e.seededBear := true
f_event(events, s, "SWEEP", b.closedAt, direction == 1 ? b.pl : b.ph, s.dualSweep ? "Both C1 extremes swept; order unknown" : "C2 candidate; awaiting HTF close")
// Same-bar CISD is knowable at this close; never infer intrabar sweep/close fills.
float origin = useBody ? s.bodyExt : s.ext
float signedLeg = (s.anchor - origin) * direction
bool crossed = direction == 1 ? b.c > s.anchor : b.c < s.anchor
if not na(s.anchor) and not na(origin) and signedLeg > 0 and crossed
s.cisd := s.anchor
s.cisdTime := b.closedAt
s.cisdFrame := b.frame
s.projOrigin := origin
s.unit := signedLeg
f_event(events, s, "CISD", b.closedAt, s.cisd, "Early C2 CISD; HTF closure still pending")
// Capacity is the ONLY removal path. Preserve live records; evict oldest finished first.
while array.size(book) > keep
int removeAt = na
for i = 0 to array.size(book) - 1
if na(removeAt) and array.get(book, i).done
removeAt := i
if na(removeAt)
break
array.remove(book, removeAt)
e.evicted += 1
e.prev2High := e.prevHigh
e.prev2Low := e.prevLow
e.prev2Time := e.prevTime
e.prevHigh := b.h
e.prevLow := b.l
e.prevTime := b.stamp
// Delivery review is a separate, causal assessment of raw structural observations.
// Thresholds below are disclosed implementation heuristics, not TTrades' exact rules.
type ContextRules
int maxVBars = 3
float minEfficiency = 0.7
float minBodyAtr = 0.8
float minBodyFraction = 0.6
float minBreakAtr = 0.05
float chopOverlap = 0.7
int repeatedSweeps = 3
bool useChop = true
int minSamples = 14
type LocalSweep
int dir = 0
int stamp = na
float level = na
int pivotTime = na
int knownTime = na
type ContextState
array<FmBar> tape = na
array<FmBar> frames = na
array<LocalSweep> localSweeps = na
float atr = na
float atrBefore = na
int samples = 0
int frame = na
float previousClose = na
float pivotLow = na
float pivotHigh = na
int pivotLowTime = na
int pivotHighTime = na
int pivotLowKnown = na
int pivotHighKnown = na
bool lowTaken = false
bool highTaken = false
int reviews = 0
int withdrawals = 0
type ContextRecord
string key = ""
int dir = 0
int trackedExtreme = na
int legBars = 0
float legPath = 0
float legClose = na
float efficiency = na
float bodyAtr = na
float bodyFraction = na
float breakAtr = na
bool measured = false
bool vShape = false
bool displacement = false
bool chop = false
int reviewTime = na
float reviewPrice = na
string reviewModel = ""
string evidence = ""
string reason = "Waiting for closure and delivery"
string lifecycle = "Assessing"
int endedAt = na
float endPrice = na
int internalSweepTime = na
float internalLevel = na
f_newContextState() =>
ContextState.new(tape = array.new<FmBar>(), frames = array.new<FmBar>(), localSweeps = array.new<LocalSweep>())
f_contextFind(array<ContextRecord> records, string key) =>
ContextRecord found = na
for item in records
if item.key == key
found := item
break
found
f_contextActive(ContextRecord item) =>
not na(item) and not na(item.reviewTime) and na(item.endedAt)
f_contextNumber(float value) => na(value) ? "unknown" : str.tostring(value, "0.00")
f_contextOverlap(FmBar a, FmBar b) =>
float denominator = math.min(a.h - a.l, b.h - b.l)
denominator > 0 ? math.max(0, math.min(a.h, b.h) - math.max(a.l, b.l)) / denominator : 1.0
f_contextChop(ContextState state, array<Setup> book, ContextRules rules) =>
bool result = false
if rules.useChop and array.size(state.frames) >= 3
int n = array.size(state.frames)
FmBar older = array.get(state.frames, n - 3)
FmBar middle = array.get(state.frames, n - 2)
FmBar latest = array.get(state.frames, n - 1)
int count = 0
for s in book
if s.c2Time >= older.stamp and s.c2Time <= latest.stamp
count += 1
result := count >= rules.repeatedSweeps and f_contextOverlap(older, middle) >= rules.chopOverlap and f_contextOverlap(middle, latest) >= rules.chopOverlap
result
// No labels are backdated: local pivots become known after their right-hand bar closes.
// This is a local swing sweep / possible inducement sequence, not inferred intent,
// and not TTrades' 'internal liquidity', which refers to FVGs.
f_contextLocalSwings(ContextState state, FmBar b) =>
if not na(state.pivotLow) and not state.lowTaken and state.pivotLowKnown <= b.stamp and b.l < state.pivotLow
state.lowTaken := true
if b.c > state.pivotLow
array.push(state.localSweeps, LocalSweep.new(1, b.closedAt, state.pivotLow, state.pivotLowTime, state.pivotLowKnown))
if not na(state.pivotHigh) and not state.highTaken and state.pivotHighKnown <= b.stamp and b.h > state.pivotHigh
state.highTaken := true
if b.c < state.pivotHigh
array.push(state.localSweeps, LocalSweep.new(-1, b.closedAt, state.pivotHigh, state.pivotHighTime, state.pivotHighKnown))
if array.size(state.tape) >= 2
FmBar left = array.get(state.tape, array.size(state.tape) - 2)
FmBar middle = array.get(state.tape, array.size(state.tape) - 1)
if middle.l < left.l and middle.l < b.l
state.pivotLow := middle.l
state.pivotLowTime := middle.stamp
state.pivotLowKnown := b.closedAt
state.lowTaken := false
if middle.h > left.h and middle.h > b.h
state.pivotHigh := middle.h
state.pivotHighTime := middle.stamp
state.pivotHighKnown := b.closedAt
state.highTaken := false
while array.size(state.localSweeps) > 32
array.shift(state.localSweeps)
f_contextStep(ContextState state, array<ContextRecord> records, array<FmEvent> reviewEvents, array<Setup> book, ContextRules rules, FmBar b) =>
array.clear(reviewEvents)
state.atrBefore := state.atr
bool warm = state.samples >= rules.minSamples and not na(state.atrBefore) and state.atrBefore > 0
if na(state.frame) or state.frame != b.frame
state.frame := b.frame
if not na(b.pt)
FmBar closedFrame = FmBar.new(b.po, b.ph, b.pl, b.pc, b.pt, b.frame, b.pt)
array.push(state.frames, closedFrame)
if array.size(state.frames) > 3
array.shift(state.frames)
f_contextLocalSwings(state, b)
bool rangeChop = f_contextChop(state, book, rules)
map<string, ContextRecord> byKey = map.new<string, ContextRecord>()
map<string, bool> retainedKeys = map.new<string, bool>()
for item in records
map.put(byKey, item.key, item)
for s in book
map.put(retainedKeys, s.key, true)
ContextRecord q = map.contains(byKey, s.key) ? map.get(byKey, s.key) : na
if na(q)
q := ContextRecord.new(key = s.key, dir = s.dir)
// Only a separately observed, earlier local sweep can supply this note.
// Same-bar outer and local sweeps have unknown ordering and do not qualify.
for local in state.localSweeps
if local.dir == s.dir and local.stamp <= b.stamp and local.stamp < s.seenTime and local.stamp >= s.c1Time and local.level > s.c1Low and local.level < s.c1High
q.internalSweepTime := local.stamp
q.internalLevel := local.level
array.push(records, q)
map.put(byKey, s.key, q)
if not q.measured and not s.done
if na(q.trackedExtreme) or q.trackedExtreme != s.extremeTime
q.trackedExtreme := s.extremeTime
q.legBars := 1
q.legPath := math.abs(b.c - s.ext)
else
q.legBars += 1
q.legPath += math.abs(b.c - q.legClose)
q.legClose := b.c
if not na(s.cisdTime)
q.measured := true
q.efficiency := q.legPath > 0 ? math.min(1, math.max(0, (b.c - s.ext) * s.dir) / q.legPath) : 0
q.bodyAtr := warm ? math.abs(b.c - b.o) / state.atrBefore : na
q.bodyFraction := b.h > b.l ? math.abs(b.c - b.o) / (b.h - b.l) : 0
q.breakAtr := warm ? (b.c - s.cisd) * s.dir / state.atrBefore : na
bool decisive = warm and q.breakAtr >= rules.minBreakAtr
q.vShape := decisive and q.legBars <= rules.maxVBars and q.efficiency >= rules.minEfficiency
q.displacement := decisive and (b.c - b.o) * s.dir > 0 and q.bodyAtr >= rules.minBodyAtr and q.bodyFraction >= rules.minBodyFraction
q.reviewModel := q.vShape and q.displacement ? "V + displacement" : q.vShape ? "V reversal" : q.displacement ? "Displacement" : "Marginal / slow CISD"
q.evidence := "CISD quality observed at " + str.tostring(b.closedAt) + "; bars from extreme=" + str.tostring(q.legBars) + "; path efficiency=" + f_contextNumber(q.efficiency) + "; body/prior ATR=" + f_contextNumber(q.bodyAtr) + "; body/range=" + f_contextNumber(q.bodyFraction) + "; close beyond CISD/prior ATR=" + f_contextNumber(q.breakAtr) + ". Configurable proxies; POI and higher bias remain manual."
q.evidence += na(q.internalSweepTime) ? " No separately known prior local swing sweep; inducement intent unknown." : " Earlier local swing sweep/reclaim observed at " + str.tostring(q.internalSweepTime) + "; level=" + str.tostring(q.internalLevel, "0.################") + ". Possible inducement context, not proof of intent."
q.chop := rangeChop
bool paired = s.closure == 2 ? s.cisdFrame == s.c2Time : s.closure == 3 ? s.cisdFrame == s.c3Frame : false
string blocker = s.done ? s.status : s.cisdFailed ? "Frozen CISD failed" : s.dualSweep ? "Two-sided sweep / direction unresolved" : not na(s.liqTime) ? "C1 objective already reached" : rangeChop ? "Repeated sweeps in overlapping HTF ranges" : not q.measured ? "Waiting for CISD" : na(q.bodyAtr) ? "Insufficient prior volatility history at first CISD; no later reassessment" : not (q.vShape or q.displacement) ? "Marginal or slow CISD; delivery unclear" : s.closure == 0 ? "Waiting for HTF closure" : not paired ? "CISD and closure belong to different stages" : ""
if na(q.reviewTime)
q.reason := blocker == "" ? "Delivery checks met; POI / bias manual" : blocker
q.lifecycle := s.done or s.cisdFailed or s.dualSweep or (q.measured and (na(q.bodyAtr) or not (q.vShape or q.displacement))) ? "Excluded" : "Assessing"
if blocker == ""
q.reviewTime := b.closedAt
q.reviewPrice := b.c
q.lifecycle := "Review"
state.reviews += 1
f_event(reviewEvents, s, "DELIVERY_REVIEW", b.closedAt, b.c, q.reviewModel + "; " + q.evidence + " Chop check clear when reviewed. This is not full model validation or an entry.")
else if na(q.endedAt) and blocker != ""
q.endedAt := b.closedAt
q.endPrice := b.c
q.reason := blocker
q.lifecycle := s.done and s.status == "C4 window complete" ? "Complete" : "Withdrawn"
state.withdrawals += 1
f_event(reviewEvents, s, "REVIEW_END", b.closedAt, b.c, blocker + "; original review retained")
// Retention follows the raw ledger only; a noisy or failed record is not erased.
int i = array.size(records) - 1
while i >= 0
if not map.contains(retainedKeys, array.get(records, i).key)
array.remove(records, i)
i -= 1
float tr = na(state.previousClose) ? b.h - b.l : math.max(b.h - b.l, math.max(math.abs(b.h - state.previousClose), math.abs(b.l - state.previousClose)))
state.samples += 1
state.atr := na(state.atr) ? tr : state.samples <= 14 ? (state.atr * (state.samples - 1) + tr) / state.samples : (state.atr * 13 + tr) / 14
state.previousClose := b.c
array.push(state.tape, b)
if array.size(state.tape) > 3
array.shift(state.tape)
// Independent IC continuation observations. No orders and no parent-state mutation.
// One attempt per reviewed parent per C3/C4 frame. Parent review must precede the
// opposing leg. Anatomy and the stricter FVG/delivery review are separate facts.
// The HTF-open wick, parent-FVG and ATR rules are disclosed product heuristics.
type IcRecord
string key = ""
string parentKey = ""
int dir = 0
int frame = na
int stage = 0
int parentReviewTime = na
int legTime = na
int seenTime = na
float level = na
float frameOpen = na
float legExtreme = na
bool openSwept = false
float leftHigh = na
float leftLow = na
float middleHigh = na
float middleLow = na
int middleTime = na
float swingLevel = na
int swingTime = na
int swingKnownTime = na
int formedTime = na
float formedPrice = na
float protectedLevel = na
int protectedTime = na
int protectedKnownTime = na
int poiTime = na
int poiKnownTime = na
float poiTop = na
float poiBot = na
int reviewTime = na
float bodyAtr = na
float bodyFraction = na
float breakAtr = na
int takenTime = na
int endedAt = na
string formationReason = ""
string endReason = ""
string reason = "Opposing leg; waiting for swing and close-through"
f_icFind(array<IcRecord> records, string key) =>
IcRecord found = na
for item in records
if item.key == key
found := item
break
found
f_icParent(array<Setup> setups, string key) =>
Setup found = na
for item in setups
if item.key == key
found := item
break
found
f_icActive(IcRecord item) =>
not na(item) and not na(item.reviewTime) and na(item.takenTime) and na(item.endedAt)
f_icEmit(array<FmEvent> events, IcRecord item, string kind, int stamp, float price, string detail) =>
// Copy scalar values into a new event; later lifecycle changes cannot rewrite it.
array.push(events, FmEvent.new(item.key, kind, stamp, price, item.dir, "ic_reviewed=" + str.tostring(not na(item.reviewTime)) + "; parent=" + item.parentKey + "; " + detail))
f_icEnd(IcRecord item, array<FmEvent> events, FmBar b, string reason) =>
if na(item.endedAt)
item.endedAt := b.closedAt
item.endReason := reason
item.reason := reason
f_icEmit(events, item, "IC_END", b.closedAt, b.c, reason + "; original observations retained")
f_icObservePoi(IcRecord item, Setup parent, FmBar b) =>
// A gap first created by this bar is not available at its open. Equality
// permits a gap known at the preceding close, before the current bar begins.
if na(item.poiTime) and not na(parent.fvgTime) and parent.fvgTime <= b.stamp and parent.fvgTop > parent.fvgBot and b.h >= parent.fvgBot and b.l <= parent.fvgTop
item.poiTime := b.closedAt
item.poiKnownTime := parent.fvgTime
item.poiTop := parent.fvgTop
item.poiBot := parent.fvgBot
f_icProgress(IcRecord item, Setup parent, array<FmEvent> events, ContextState state, ContextRules rules, FmBar b) =>
f_icObservePoi(item, parent, b)
item.legExtreme := item.dir == 1 ? math.min(item.legExtreme, b.l) : math.max(item.legExtreme, b.h)
item.openSwept := item.openSwept or (item.dir == 1 ? b.l < item.frameOpen : b.h > item.frameOpen)
// Current bar is the right neighbor. No swing is known at its middle bar.
bool strictSwing = item.dir == 1 ? item.middleLow < item.leftLow and item.middleLow < b.l : item.middleHigh > item.leftHigh and item.middleHigh > b.h
float candidate = item.dir == 1 ? item.middleLow : item.middleHigh
if item.middleTime >= item.legTime and strictSwing
if na(item.swingLevel) or (item.dir == 1 ? candidate < item.swingLevel : candidate > item.swingLevel)
item.swingLevel := candidate
item.swingTime := item.middleTime
item.swingKnownTime := b.closedAt
bool crossed = (b.c - item.level) * item.dir > 0
// The stored swing must still be the actual leg extreme; a lower/higher
// unconfirmed current wick cannot leave an already-broken protected swing.
bool holds = not na(item.swingLevel) and item.swingLevel == item.legExtreme
if b.stamp > item.legTime and item.openSwept and crossed and holds
item.formedTime := b.closedAt
item.formedPrice := b.c
item.protectedLevel := item.swingLevel
item.protectedTime := item.swingTime
item.protectedKnownTime := item.swingKnownTime
bool warm = state.samples > rules.minSamples and not na(state.atrBefore) and state.atrBefore > 0
item.bodyAtr := warm ? math.abs(b.c - b.o) / state.atrBefore : na
item.bodyFraction := b.h > b.l ? math.abs(b.c - b.o) / (b.h - b.l) : 0
item.breakAtr := warm ? (b.c - item.level) * item.dir / state.atrBefore : na
bool decisive = warm and (b.c - b.o) * item.dir > 0 and item.bodyAtr >= rules.minBodyAtr and item.bodyFraction >= rules.minBodyFraction and item.breakAtr >= rules.minBreakAtr
item.formationReason := na(item.poiTime) ? "IC anatomy; parent FVG POI not observed" : not warm ? "IC anatomy; insufficient prior volatility history" : not decisive ? "IC anatomy; decisive delivery check not met" : "IC FVG and delivery checks met; higher bias / full POI context manual"
item.reason := item.formationReason
f_icEmit(events, item, "IC_ANATOMY", b.closedAt, b.c, "Fixed opposing open crossed after a strict local swing; protected price=" + str.tostring(item.protectedLevel, "0.################") + "; swing time=" + str.tostring(item.protectedTime) + "; " + item.formationReason)
if not na(item.poiTime) and decisive
item.reviewTime := b.closedAt
f_icEmit(events, item, "IC_REVIEW", b.closedAt, b.c, "Previously known parent FVG overlapped; decisive close met configurable ATR/body checks; nested continuation observation, not an entry")
item.leftHigh := item.middleHigh
item.leftLow := item.middleLow
item.middleHigh := b.h
item.middleLow := b.l
item.middleTime := b.stamp
// Call after f_step and f_contextStep, once per confirmed chart bar. Keep the IC
// event buffer separate from raw/context events; callers may concatenate copies.
f_icStep(array<IcRecord> icBook, array<FmEvent> icEvents, array<Setup> setups, array<ContextRecord> contexts, Engine engine, ContextState state, ContextRules rules, FmBar b, int keep) =>
array.clear(icEvents)
for item in icBook
// Continue marking taken swings even after their frame/parent has ended,
// while the completed record remains inside the disclosed retention cap.
if not na(item.formedTime) and na(item.takenTime) and b.stamp >= item.formedTime
bool taken = item.dir == 1 ? b.l < item.protectedLevel : b.h > item.protectedLevel
if taken
item.takenTime := b.closedAt
item.reason := "Protected IC swing taken; parent record unchanged"
f_icEmit(icEvents, item, "IC_TAKEN", b.closedAt, item.protectedLevel, item.reason)
if na(item.endedAt)
item.endedAt := b.closedAt
item.endReason := "Protected IC swing taken"
if na(item.endedAt)
Setup parent = f_icParent(setups, item.parentKey)
ContextRecord context = f_contextFind(contexts, item.parentKey)
if b.frame != item.frame
f_icEnd(item, icEvents, b, "IC frame ended")
else if na(parent)
f_icEnd(item, icEvents, b, "Parent left retained history")
else if not f_contextActive(context) or parent.done or parent.stage < 3 or parent.stage > 4
f_icEnd(item, icEvents, b, "Parent review ended or withdrawn")
else if na(item.formedTime)
f_icProgress(item, parent, icEvents, state, rules, b)
for parent in setups
ContextRecord context = f_contextFind(contexts, parent.key)
bool eligible = f_contextActive(context) and not parent.done and (parent.stage == 3 or parent.stage == 4)
if eligible
// The raw engine has just consumed this bar. A run that started before
// parent review cannot acquire a parent retroactively partway through.
bool firstOppositeBody = engine.runDir == -parent.dir and engine.runTime == b.stamp
bool parentKnown = context.reviewTime <= b.stamp
string key = parent.key + ":IC:" + str.tostring(b.frame) + ":1"
if firstOppositeBody and parentKnown and na(f_icFind(icBook, key))
IcRecord item = IcRecord.new()
item.key := key
item.parentKey := parent.key
item.dir := parent.dir
item.frame := b.frame
item.stage := parent.stage
item.parentReviewTime := context.reviewTime
item.legTime := b.stamp
item.seenTime := b.closedAt
item.level := b.o
item.frameOpen := engine.frameOpen
item.legExtreme := parent.dir == 1 ? b.l : b.h
item.openSwept := parent.dir == 1 ? b.l < engine.frameOpen : b.h > engine.frameOpen
// f_step updates prev2 to the bar immediately preceding b.
item.leftHigh := engine.prev2High
item.leftLow := engine.prev2Low
item.middleHigh := b.h
item.middleLow := b.l
item.middleTime := b.stamp
f_icObservePoi(item, parent, b)
array.push(icBook, item)
// Keep current-frame completed records as deduplication tombstones. The cap
// may temporarily exceed keep when all records are active/current-frame.
while array.size(icBook) > keep
int removeAt = na
for i = 0 to array.size(icBook) - 1
IcRecord item = array.get(icBook, i)
if na(removeAt) and not na(item.endedAt) and item.frame != b.frame
removeAt := i
if na(removeAt)
break
array.remove(icBook, removeAt)
array.size(icBook)
gModel = "Structure"
inAuto = input.bool(true, "Automatic HTF pairing", group = gModel)
inTf = input.timeframe("60", "Manual higher timeframe", group = gModel)
inBody = input.bool(false, "Projection anchor: body (default wick)", group = gModel, tooltip = "Origin and leg use the same anchor. 1x equals frozen CISD. These are leg multiples, not statistical standard deviations.")
inWickInvalid = input.bool(true, "Invalidate on wick through extreme (off = close)", group = gModel, tooltip = "Strictly beyond; equal touches do not invalidate. Failed early CISD is retained separately from HTF closure.")
inHistory = input.int(24, "Setups retained", minval = 8, maxval = 40, group = gModel, tooltip = "Developing, confirmed, failed and completed setups. Only oldest completed records are evicted at capacity. Live setups are preserved.")
inJson = input.bool(true, "JSON event batch alerts", group = "Alerts", tooltip = "One payload per confirmed bar contains all events and original setup IDs. Create an Any alert() function call alert.")
inAlertScope = input.string("Delivery review", "Event stream", options = ["Delivery review", "All observations"], group = "Alerts", tooltip = "Default: automated delivery reviews, IC reviews and their later outcomes. All observations also includes neutral raw sweeps, CISD and stage facts. Neither stream is a buy/sell instruction.")
gContext = "Delivery review · disclosed proxies"
inVMax = input.int(3, "V reversal: maximum bars from extreme", minval = 1, maxval = 12, group = gContext)
inEfficiency = input.float(0.7, "V reversal: minimum path efficiency", minval = 0.1, maxval = 1, step = 0.05, group = gContext, tooltip = "Net directional travel from the wick divided by total close-to-close travel, including the extreme-to-close segment. A configurable approximation of a clean reversal.")
inBodyAtr = input.float(0.8, "Displacement: minimum body / prior ATR14", minval = 0.1, maxval = 3, step = 0.1, group = gContext)
inBodyFraction = input.float(0.6, "Displacement: minimum body / range", minval = 0.1, maxval = 1, step = 0.05, group = gContext)
inBreakAtr = input.float(0.05, "Decisive close: distance past CISD / prior ATR14", minval = 0.01, maxval = 1, step = 0.01, group = gContext)
inChop = input.bool(true, "Pause reviews in repeated overlapping ranges", group = gContext)
inOverlap = input.float(0.7, "Chop: minimum pairwise overlap of 3 closed HTFs", minval = 0.1, maxval = 1, step = 0.05, group = gContext)
inRepeat = input.int(3, "Chop: sweep observations across those 3 HTFs", minval = 2, maxval = 6, group = gContext, tooltip = "Both adjacent overlaps must pass and the sweep count must reach this threshold. This is a declared noise heuristic, not a universal regime detector.")
int chartSeconds = timeframe.in_seconds()
string autoTf = chartSeconds <= 60 ? "15" : chartSeconds <= 300 ? "60" : chartSeconds <= 900 ? "240" : chartSeconds <= 3600 ? "1D" : chartSeconds <= 14400 ? "1W" : "1M"
string htfTf = inAuto ? autoTf : inTf
if barstate.isfirst and (not chart.is_standard or na(chartSeconds) or timeframe.in_seconds(htfTf) <= chartSeconds)
runtime.error("Use standard time-based bars and an HTF strictly greater than the chart timeframe.")
[pO1, pH1, pL1, pC1, pT1, pO2, pH2, pL2, pC2, pT2, pO3, pH3, pL3, pC3, pT3] = request.security(syminfo.tickerid, htfTf, [open[1], high[1], low[1], close[1], time[1], open[2], high[2], low[2], close[2], time[2], open[3], high[3], low[3], close[3], time[3]], lookahead = barmerge.lookahead_on)
int htfTime = time(htfTf)
int htfEnd = time_close(htfTf)
if not na(htfEnd) and time_close > htfEnd
runtime.error("Chart bars must fit entirely inside the selected HTF. Choose an aligned pair such as 5m / 1H.")
var array<Setup> setups = array.new<Setup>()
var Engine engine = Engine.new()
var array<FmEvent> events = array.new<FmEvent>()
var array<ContextRecord> contexts = array.new<ContextRecord>()
var ContextState contextState = f_newContextState()
var ContextRules contextRules = ContextRules.new(maxVBars = inVMax, minEfficiency = inEfficiency, minBodyAtr = inBodyAtr, minBodyFraction = inBodyFraction, minBreakAtr = inBreakAtr, chopOverlap = inOverlap, repeatedSweeps = inRepeat, useChop = inChop)
var array<FmEvent> reviewEvents = array.new<FmEvent>()
var array<IcRecord> icBook = array.new<IcRecord>()
var array<FmEvent> icEvents = array.new<FmEvent>()
if barstate.isconfirmed
FmBar b = FmBar.new(open, high, low, close, time, time_close, htfTime, pO1, pH1, pL1, pC1, pT1)
f_step(engine, setups, events, b, inHistory, inBody, inWickInvalid)
f_contextStep(contextState, contexts, reviewEvents, setups, contextRules, b)
f_icStep(icBook, icEvents, setups, contexts, engine, contextState, contextRules, b, 24)
plot(engine.total, "Audit / candidates observed", display = display.data_window)
plot(engine.confirmed, "Audit / HTF closures", display = display.data_window)
plot(engine.invalidated, "Audit / structural invalidations", display = display.data_window)
plot(engine.evicted, "Audit / capacity evictions", display = display.data_window)
plot(contextState.reviews, "Review / automated delivery checks met", display = display.data_window)
plot(contextState.withdrawals, "Review / ended or withdrawn", display = display.data_window)
// Optional experiment. Broker positions never feed back into f_step or its records.
labMode = input.string("Delivery review", "Entry experiment", options = ["Delivery review", "FVG retest after review"], group = "Strategy Lab", tooltip = "Experimental execution of automated delivery checks; manual higher bias and POI are not verified. Indicator reviews are not trade instructions.")
labBuffer = input.int(1, "Stop buffer in ticks", minval = 0, group = "Strategy Lab")
var float labStop = na
var float labTarget = na
var string labKey = ""
if barstate.isconfirmed
bool submitted = false
if strategy.position_size == 0 and nz(strategy.position_size[1]) != 0
labStop := na
labTarget := na
labKey := ""
array<FmEvent> labEvents = labMode == "Delivery review" ? reviewEvents : events
if strategy.position_size == 0 and array.size(labEvents) > 0
for event in labEvents
bool trigger = labMode == "Delivery review" ? event.kind == "DELIVERY_REVIEW" : event.kind == "RETEST"
if trigger and not submitted
for s in setups
bool pairedCisd = s.closure == 2 ? s.cisdFrame == s.c2Time : s.cisdFrame == s.c3Frame
ContextRecord q = f_contextFind(contexts, s.key)
bool reviewPrecedesRetest = labMode == "Delivery review" or (f_contextActive(q) and q.reviewTime <= time and event.stamp > q.reviewTime)
if s.key == event.key and f_contextActive(q) and reviewPrecedesRetest and s.closure > 0 and not s.done and not s.dualSweep and pairedCisd and not s.cisdFailed and not na(s.cisdTime) and na(s.liqTime)
float stop = s.invalidLevel - s.dir * labBuffer * syminfo.mintick
float target = s.dir == 1 ? s.c1High : s.c1Low
// Spent/behind targets are skipped; never replaced with arbitrary 2R.
if (close - stop) * s.dir > syminfo.mintick and (target - close) * s.dir > syminfo.mintick
labStop := stop
labTarget := target
labKey := s.key
strategy.entry("FM", s.dir == 1 ? strategy.long : strategy.short, comment = s.key)
// Bracket on ENTRY calculation, before position_size updates.
strategy.exit("FM-X", "FM", stop = labStop, limit = labTarget)
submitted := true
if strategy.position_size != 0 and not na(labStop)
strategy.exit("FM-X", "FM", stop = labStop, limit = labTarget)
// Display owns its handles; the engine owns setup facts. A finished setup is never
// removed by the renderer. Handles disappear only after its key leaves retention.
string fmVisualGroup = "Display · setup history"
string fmView = input.string("Focus", "View", options=["Focus", "Audit"], group=fmVisualGroup, tooltip="Focus shows the two most recently observed delivery reviews, including later withdrawn or completed reviews. Audit reveals retained raw observations. Display filtering never deletes engine history.")
string fmPalette = input.string("Colorblind", "Direction palette", options=["Colorblind", "Classic"], group=fmVisualGroup)
string fmProjectionScope = input.string("Latest review", "Projection visibility", options=["Latest review", "Selected reviews", "All retained", "Off"], group=fmVisualGroup, tooltip="Latest review uses observation time, never later success. Focus always limits projections to its two selected reviews; All retained is available in Audit. Prices remain frozen CISD-leg multiples, not statistical standard deviations.")
bool fmShowProjections = fmProjectionScope != "Off"
bool fmShowFvg = input.bool(true, "Show first FVG after CISD", group=fmVisualGroup, tooltip="Context only. Audit starts the box at gap observation; Focus starts no earlier than delivery review. A touch requires a later bar.")
bool fmShowStages = input.bool(false, "Audit: show C3 / C4 stage markers", group=fmVisualGroup)
bool fmShowDeveloping = input.bool(false, "Audit: preview developing CISD level", group=fmVisualGroup, tooltip="A neutral dashed rail for a pending observation. This level may move before confirmation; it is not a delivery review.")
bool fmShowHtf = input.bool(true, "Show higher-timeframe candles", group=fmVisualGroup)
bool fmShowDesk = input.bool(true, "Show recent setup ledger", group=fmVisualGroup)
bool fmShowIc = input.bool(true, "Show latest two IC observations", group=fmVisualGroup, tooltip="Focus shows the latest two IC delivery reviews, including later failures. Audit also shows neutral IC anatomy without known FVG or decisive delivery. IC history is independent of parent structure; 24 records retained with active/current-frame overflow protected.")
string fmDeskPosition = input.string("Bottom right", "Ledger position", options=["Bottom right", "Bottom left", "Top right", "Top left"], group=fmVisualGroup)
string fmDeskText = input.string("Small", "Ledger text size", options=["Small", "Normal", "Tiny"], group=fmVisualGroup)
string fmTablePosition = fmDeskPosition == "Bottom left" ? position.bottom_left : fmDeskPosition == "Top right" ? position.top_right : fmDeskPosition == "Top left" ? position.top_left : position.bottom_right
string fmTableSize = fmDeskText == "Normal" ? size.normal : fmDeskText == "Tiny" ? size.tiny : size.small
color fmBull = fmPalette == "Colorblind" ? color.rgb(0, 158, 173) : color.rgb(34, 171, 148)
color fmBear = fmPalette == "Colorblind" ? color.rgb(218, 139, 36) : color.rgb(239, 83, 80)
color fmInk = chart.fg_color
color fmPaper = chart.bg_color
color fmMuted = color.new(fmInk, 48)
color fmReview = color.rgb(56, 189, 248)
bool fmAudit = fmView == "Audit"
int fmDeskRows = fmAudit ? 9 : 6
type SetupInk
string key = ""
line reference = na
line delivery = na
line level1 = na
line level2 = na
line level25 = na
line level4 = na
line invalidation = na
box gap = na
label seed = na
label cisd = na
label c2 = na
label c3 = na
label c4 = na
label earlyFailure = na
label finish = na
label gapSeen = na
label gapTouch = na
label liquidity = na
label review = na
label reviewOutcome = na
type CandleInk
box body = na
line wick = na
label caption = na
var array<SetupInk> fmInks = array.new<SetupInk>()
var map<string, SetupInk> fmInkByKey = map.new<string, SetupInk>()
var array<CandleInk> fmCandles = array.new<CandleInk>()
var line fmPrevHigh = na
var line fmPrevLow = na
var line fmPrevEq = na
var line fmDeveloping = na
var label fmDevelopingCaption = na
var table fmDesk = table.new(fmTablePosition, 5, fmDeskRows, bgcolor=color.new(fmPaper, 8), border_color=color.new(fmInk, 88), border_width=1, frame_color=color.new(fmInk, 75), frame_width=1)
f_fmLine(line handle, int left, int right, float price, color ink, string style, int width) =>
line result = handle
if na(result)
result := line.new(left, price, right, price, xloc=xloc.bar_time, extend=extend.none, color=ink, style=style, width=width)
else
line.set_xy1(result, left, price)
line.set_xy2(result, right, price)
line.set_color(result, ink)
result
f_fmLabel(int stamp, float price, string captionText, color ink, string labelStyle, string tip) =>
label.new(stamp, price, captionText, xloc=xloc.bar_time, yloc=yloc.price, color=color.new(fmPaper, 12), style=labelStyle, textcolor=ink, size=size.tiny, tooltip=tip)
f_fmTime(int stamp) =>
na(stamp) ? "—" : str.format_time(stamp, "MM-dd HH:mm", syminfo.timezone)
f_fmPrice(float price) =>
str.tostring(price, format.mintick)
f_fmLabelVisibility(label handle, bool visible, color ink) =>
if not na(handle)
label.set_textcolor(handle, visible ? ink : color.new(ink, 100))
label.set_color(handle, color.new(fmPaper, visible ? 12 : 100))
f_fmShort(string value) =>
str.length(value) > 38 ? str.substring(value, 0, 35) + "…" : value
f_fmErase(SetupInk ink) =>
line.delete(ink.reference)
line.delete(ink.delivery)
line.delete(ink.level1)
line.delete(ink.level2)
line.delete(ink.level25)
line.delete(ink.level4)
line.delete(ink.invalidation)
box.delete(ink.gap)
label.delete(ink.seed)
label.delete(ink.cisd)
label.delete(ink.c2)
label.delete(ink.c3)
label.delete(ink.c4)
label.delete(ink.earlyFailure)
label.delete(ink.finish)
label.delete(ink.gapSeen)
label.delete(ink.gapTouch)
label.delete(ink.liquidity)
label.delete(ink.review)
label.delete(ink.reviewOutcome)
f_fmPaint(Setup s, ContextRecord q, SetupInk ink, bool selected, string projectionKey) =>
bool reviewed = not na(q) and not na(q.reviewTime)
bool reviewVisible = reviewed and (fmAudit or selected)
bool railsVisible = fmAudit or reviewVisible
bool ended = reviewed and not na(q.endedAt)
int observedEnd = na(s.endTime) ? time_close : s.endTime
int right = not fmAudit and ended ? q.endedAt : observedEnd
int reviewStart = reviewed ? q.reviewTime : s.seenTime
color railInk = fmAudit ? fmMuted : color.new(fmReview, ended ? 78 : 35)
string identity = "Observation " + s.key + "\nFirst seen " + f_fmTime(s.seenTime) + " (" + syminfo.timezone + ")"
string seedStyle = s.dir == 1 ? label.style_label_up : label.style_label_down
float reference = s.dir == 1 ? s.c1Low : s.c1High
int referenceStart = fmAudit ? s.c1Time : math.max(s.c1Time, reviewStart)
ink.reference := f_fmLine(ink.reference, referenceStart, right, reference, color.new(fmInk, railsVisible ? 78 : 100), line.style_dotted, 1)
// Raw observations are permanently neutral. A later review gets a NEW marker.
if na(ink.seed)
ink.seed := f_fmLabel(s.seenTime, s.seedExt, s.dir == 1 ? "C2 · low swept" : "C2 · high swept", fmMuted, seedStyle, identity + "\nRaw C1 " + (s.dir == 1 ? "low " : "high ") + f_fmPrice(reference) + " sweep.\nThis is not a directional recommendation or a context-qualified model.\nAudit references the original candle; Focus begins its rails only when delivery review is observed.")
if not na(s.cisdTime)
int cisdStart = fmAudit ? s.cisdTime : math.max(s.cisdTime, reviewStart)
bool deliveryVisible = railsVisible and (fmAudit or s.cisdTime <= right)
ink.delivery := f_fmLine(ink.delivery, cisdStart, right, s.cisd, deliveryVisible ? railInk : color.new(fmInk, 100), line.style_solid, 1)
if na(ink.cisd)
string levels = "\nFrozen leg multiples, not statistical σ:\n1 = " + f_fmPrice(f_level(s, 1)) + " · 2 = " + f_fmPrice(f_level(s, 2)) + "\n2.5 = " + f_fmPrice(f_level(s, 2.5)) + " · 4 = " + f_fmPrice(f_level(s, 4))
ink.cisd := f_fmLabel(s.cisdTime, s.cisd, "CISD observed", fmMuted, seedStyle, identity + "\nCandle-series open crossed at " + f_fmTime(s.cisdTime) + ".\nThis price observation alone does not pass delivery or POI checks." + levels)
if fmShowProjections and not na(s.unit)
bool projectionVisible = deliveryVisible and (fmProjectionScope == "All retained" or (fmProjectionScope == "Selected reviews" and selected) or s.key == projectionKey)
color projectionInk = fmAudit ? fmInk : fmReview
ink.level1 := f_fmLine(ink.level1, cisdStart, right, f_level(s, 1), color.new(projectionInk, projectionVisible ? (ended ? 85 : 72) : 100), line.style_dotted, 1)
ink.level2 := f_fmLine(ink.level2, cisdStart, right, f_level(s, 2), color.new(projectionInk, projectionVisible ? (ended ? 82 : 54) : 100), line.style_dashed, 1)
ink.level25 := f_fmLine(ink.level25, cisdStart, right, f_level(s, 2.5), color.new(projectionInk, projectionVisible ? (ended ? 85 : 66) : 100), line.style_dotted, 1)
ink.level4 := f_fmLine(ink.level4, cisdStart, right, f_level(s, 4), color.new(projectionInk, projectionVisible ? (ended ? 85 : 72) : 100), line.style_dashed, 1)
if not na(s.c2KnownTime)
if na(ink.c2)
string fact = s.c2Valid ? "C2 · inside C1" : "C2 · outside C1"
ink.c2 := f_fmLabel(s.c2KnownTime, s.c2Close, fact, fmMuted, label.style_label_left, identity + "\nC2 closed " + (s.c2Valid ? "strictly inside" : "outside or on the boundary of") + " the entire C1 range.\nKnown " + f_fmTime(s.c2KnownTime) + ".\nThis is a closure fact, not delivery-context approval.")
if not na(s.invalidLevel) and not na(s.closureTime)
int invalidStart = fmAudit ? s.closureTime : math.max(s.closureTime, reviewStart)
bool invalidVisible = railsVisible and (fmAudit or s.closureTime <= right)
ink.invalidation := f_fmLine(ink.invalidation, invalidStart, right, s.invalidLevel, color.new(fmInk, invalidVisible ? 80 : 100), line.style_dashed, 1)
if fmShowStages and not na(s.c3Time) and na(ink.c3)
ink.c3 := f_fmLabel(s.c3Time, reference, "C3 stage", fmMuted, label.style_label_left, identity + "\nC3 stage observed " + f_fmTime(s.c3Time) + ". No context approval implied.")
if (fmShowStages or s.closure == 3) and not na(s.c4Time) and na(ink.c4)
ink.c4 := f_fmLabel(s.c4Time, reference, s.closure == 3 ? "C3 closure observed" : "C4 stage", fmMuted, label.style_label_left, identity + "\nObserved " + f_fmTime(s.c4Time) + ".\nC3 directional close beyond the C2 range: " + (s.c3Strong ? "yes" : "no") + ". Delivery review remains separate.")
if s.cisdFailed and not na(s.cisdFailTime) and na(ink.earlyFailure)
ink.earlyFailure := f_fmLabel(s.cisdFailTime, s.ext, "· CISD failed", fmMuted, label.style_label_left, identity + "\nFrozen sweep extreme violated " + f_fmTime(s.cisdFailTime) + ".\nOriginal observations stay recorded; structural invalidation is separate.")
if fmShowFvg and not na(s.fvgTime)
int gapStart = fmAudit ? s.fvgTime : math.max(s.fvgTime, reviewStart)
bool gapVisible = railsVisible and (fmAudit or s.fvgTime <= right)
if na(ink.gap)
ink.gap := box.new(gapStart, s.fvgTop, right, s.fvgBot, xloc=xloc.bar_time, border_color=color.new(fmInk, 100), bgcolor=color.new(fmInk, 100))
ink.gapSeen := f_fmLabel(s.fvgTime, s.dir == 1 ? s.fvgBot : s.fvgTop, "FVG observed", fmMuted, seedStyle, identity + "\nGap observed " + f_fmTime(s.fvgTime) + ".\n" + f_fmPrice(s.fvgBot) + " – " + f_fmPrice(s.fvgTop) + ".\nA gap alone does not pass delivery checks.")
box.set_left(ink.gap, gapStart)
box.set_right(ink.gap, right)
box.set_border_color(ink.gap, color.new(fmAudit ? fmInk : fmReview, gapVisible ? 80 : 100))
box.set_bgcolor(ink.gap, color.new(fmAudit ? fmInk : fmReview, gapVisible ? 96 : 100))
if not na(s.touchTime) and na(ink.gapTouch)
ink.gapTouch := f_fmLabel(s.touchTime, (s.fvgTop + s.fvgBot) / 2, "· FVG touch", fmMuted, label.style_label_left, identity + "\nLater bar overlap observed " + f_fmTime(s.touchTime) + ". Context only.")
if not na(s.liqTime) and na(ink.liquidity)
ink.liquidity := f_fmLabel(s.liqTime, s.dir == 1 ? s.c1High : s.c1Low, "C1 opposite level reached", fmMuted, label.style_label_left, identity + "\nOpposite C1 extreme reached " + f_fmTime(s.liqTime) + ".\nThis is not a trade result or setup-quality verdict.")
if s.done and not na(s.endTime) and na(ink.finish)
ink.finish := f_fmLabel(s.endTime, s.endPrice, "· " + s.status, fmMuted, label.style_label_left, identity + "\n" + s.status + " at " + f_fmTime(s.endTime) + ".\nRaw observation history remains available in Audit.")
if reviewed and na(ink.review)
string levels = "\nFrozen CISD " + f_fmPrice(s.cisd) + "\nLeg multiples: 1 = " + f_fmPrice(f_level(s, 1)) + " · 2 = " + f_fmPrice(f_level(s, 2)) + "\n2.5 = " + f_fmPrice(f_level(s, 2.5)) + " · 4 = " + f_fmPrice(f_level(s, 4))
ink.review := f_fmLabel(q.reviewTime, q.reviewPrice, "Delivery review\nPOI / bias manual", fmReview, label.style_label_left, "Delivery checks observed " + f_fmTime(q.reviewTime) + " (" + syminfo.timezone + ")\nObservation " + s.key + "\nModel: " + q.reviewModel + "\nFrozen evidence: " + q.evidence + levels + "\nAutomated evidence for review; POI, bias and a trading decision remain manual.\nThis marker is never backdated to the original sweep.")
if ended and na(ink.reviewOutcome)
ink.reviewOutcome := f_fmLabel(q.endedAt, q.endPrice, q.lifecycle == "Withdrawn" ? "· Review withdrawn" : "· Review complete", fmMuted, label.style_label_left, "Delivery review " + s.key + "\n" + q.lifecycle + " at " + f_fmTime(q.endedAt) + " (" + syminfo.timezone + ")\n" + q.reason + "\nThe original review and its evidence remain unchanged.")
// Opacity is a view choice. No event label is removed, moved, or recast as success.
f_fmLabelVisibility(ink.seed, fmAudit, fmMuted)
f_fmLabelVisibility(ink.cisd, fmAudit, fmMuted)
f_fmLabelVisibility(ink.c2, fmAudit, fmMuted)
f_fmLabelVisibility(ink.c3, fmAudit, fmMuted)
f_fmLabelVisibility(ink.c4, fmAudit, fmMuted)
f_fmLabelVisibility(ink.earlyFailure, fmAudit, color.new(fmInk, 65))
f_fmLabelVisibility(ink.finish, fmAudit, color.new(fmInk, 65))
f_fmLabelVisibility(ink.gapSeen, fmAudit, fmMuted)
f_fmLabelVisibility(ink.gapTouch, fmAudit, fmMuted)
f_fmLabelVisibility(ink.liquidity, fmAudit, fmMuted)
f_fmLabelVisibility(ink.review, reviewVisible, fmReview)
f_fmLabelVisibility(ink.reviewOutcome, reviewVisible, fmMuted)
ink
// Reconcile by key, not array position; concurrent directions and adjacent models
// retain independent histories. Updating on EVERY close freezes completed rails
// identically in historical calculation, replay and subsequent real-time bars.
// Selection depends ONLY on immutable reviewTime. A later withdrawal/completion
// cannot remove an otherwise selected review or turn Focus into a winners filter.
int fmFirst = -1
int fmSecond = -1
int fmFirstTime = na
int fmSecondTime = na
int fmReviewCount = 0
int fmExcludedCount = 0
int fmAssessingCount = 0
int fmPendingIndex = -1
string fmProjectionKey = ""
string fmLatestReason = "Waiting for a raw observation"
if array.size(setups) > 0
for i = 0 to array.size(setups) - 1
Setup s = array.get(setups, i)
ContextRecord q = f_contextFind(contexts, s.key)
if not na(q)
fmLatestReason := q.reason
fmExcludedCount += q.lifecycle == "Excluded" ? 1 : 0
fmAssessingCount += q.lifecycle == "Assessing" ? 1 : 0
if not na(q.reviewTime)
fmReviewCount += 1
if na(fmFirstTime) or q.reviewTime >= fmFirstTime
fmSecond := fmFirst
fmSecondTime := fmFirstTime
fmFirst := i
fmFirstTime := q.reviewTime
fmProjectionKey := s.key
else if na(fmSecondTime) or q.reviewTime >= fmSecondTime
fmSecond := i
fmSecondTime := q.reviewTime
if not s.done and s.stage <= 3 and na(s.cisdTime) and not na(s.anchor)
fmPendingIndex := i
if barstate.isconfirmed
map<string, bool> retainedKeys = map.new<string, bool>()
if array.size(setups) > 0
for i = 0 to array.size(setups) - 1
Setup s = array.get(setups, i)
map.put(retainedKeys, s.key, true)
int fmOld = array.size(fmInks) - 1
while fmOld >= 0
SetupInk stale = array.get(fmInks, fmOld)
if not map.contains(retainedKeys, stale.key)
f_fmErase(stale)
map.remove(fmInkByKey, stale.key)
array.remove(fmInks, fmOld)
fmOld -= 1
if array.size(setups) > 0
for i = 0 to array.size(setups) - 1
Setup s = array.get(setups, i)
if not map.contains(fmInkByKey, s.key)
SetupInk fresh = SetupInk.new(key=s.key)
array.push(fmInks, fresh)
map.put(fmInkByKey, s.key, fresh)
SetupInk ink = map.get(fmInkByKey, s.key)
ContextRecord q = f_contextFind(contexts, s.key)
f_fmPaint(s, q, ink, i == fmFirst or i == fmSecond, fmProjectionKey)
if fmAudit and fmShowDeveloping and fmPendingIndex >= 0
Setup pending = array.get(setups, fmPendingIndex)
fmDeveloping := f_fmLine(fmDeveloping, pending.seenTime, time_close, pending.anchor, color.new(fmInk, 70), line.style_dashed, 1)
string tip = "Developing level for observation " + pending.key + ".\nCurrent candle-series open: " + f_fmPrice(pending.anchor) + ".\nThis level may move before confirmation. No delivery review or directional recommendation is implied."
if na(fmDevelopingCaption)
fmDevelopingCaption := f_fmLabel(time_close, pending.anchor, "Developing CISD · unconfirmed", fmMuted, label.style_label_left, tip)
else
label.set_xy(fmDevelopingCaption, time_close, pending.anchor)
label.set_text(fmDevelopingCaption, "Developing CISD · unconfirmed")
label.set_textcolor(fmDevelopingCaption, fmMuted)
label.set_color(fmDevelopingCaption, color.new(fmPaper, 12))
label.set_tooltip(fmDevelopingCaption, tip)
else
if not na(fmDeveloping)
line.set_color(fmDeveloping, color.new(fmInk, 100))
label.set_text(fmDevelopingCaption, "")
label.set_color(fmDevelopingCaption, color.new(fmPaper, 100))
f_fmCandle(CandleInk ink, int center, int halfWidth, float o, float h, float l, float c, string caption, string tip, bool forming) =>
color direction = c >= o ? fmBull : fmBear
float top = math.max(o, c)
float bottom = math.min(o, c)
if not na(o)
if na(ink.body)
ink.wick := line.new(center, l, center, h, xloc=xloc.bar_time, color=direction, width=1)
ink.body := box.new(center - halfWidth, top, center + halfWidth, bottom, xloc=xloc.bar_time, border_color=direction, bgcolor=color.new(direction, forming ? 87 : 64))
ink.caption := f_fmLabel(center, l, caption, forming ? fmInk : fmMuted, label.style_label_up, tip)
else
line.set_xy1(ink.wick, center, l)
line.set_xy2(ink.wick, center, h)
line.set_color(ink.wick, direction)
box.set_lefttop(ink.body, center - halfWidth, top)
box.set_rightbottom(ink.body, center + halfWidth, bottom)
box.set_border_color(ink.body, direction)
box.set_bgcolor(ink.body, color.new(direction, forming ? 87 : 64))
label.set_xy(ink.caption, center, l)
label.set_text(ink.caption, caption)
label.set_tooltip(ink.caption, tip)
ink
if barstate.isfirst
for i = 0 to 3
array.push(fmCandles, CandleInk.new())
table.merge_cells(fmDesk, 0, 0, 4, 0)
table.merge_cells(fmDesk, 0, 1, 4, 1)
table.merge_cells(fmDesk, 0, fmDeskRows - 1, 4, fmDeskRows - 1)
if barstate.islastconfirmedhistory or barstate.isrealtime
// Preview is display-only: the engine never consumes this forming OHLC.
int fmStep = int(timeframe.in_seconds() * 1000)
if fmShowHtf and fmStep > 0
bool frameIsCurrent = engine.frameTime == htfTime
float previewOpen = frameIsCurrent ? engine.frameOpen : open
float previewHigh = frameIsCurrent ? math.max(engine.frameHigh, high) : high
float previewLow = frameIsCurrent ? math.min(engine.frameLow, low) : low
float previewClose = close
int secondsLeft = int(math.max(0, (htfEnd - (barstate.isrealtime ? timenow : time_close)) / 1000))
string clockText = str.tostring(int(secondsLeft / 60)) + ":" + str.tostring(secondsLeft % 60, "00")
array<float> os = array.from(pO3, pO2, pO1, previewOpen)
array<float> hs = array.from(pH3, pH2, pH1, previewHigh)
array<float> ls = array.from(pL3, pL2, pL1, previewLow)
array<float> cs = array.from(pC3, pC2, pC1, previewClose)
array<int> ts = array.from(pT3, pT2, pT1, htfTime)
for i = 0 to 3
int center = time + fmStep * (8 + i * 5)
bool forming = i == 3
string caption = forming ? htfTf + " · FORMING /\nbar-close snapshot" : str.format_time(array.get(ts, i), "HH:mm", syminfo.timezone)
string tip = (forming ? "FORMING / bar-close snapshot — refreshed on confirmed chart closes; not setup confirmation" : "Closed HTF candle") + "\n" + htfTf + " · " + f_fmTime(array.get(ts, i)) + " (" + syminfo.timezone + ")\nO " + f_fmPrice(array.get(os, i)) + " H " + f_fmPrice(array.get(hs, i)) + "\nL " + f_fmPrice(array.get(ls, i)) + " C " + f_fmPrice(array.get(cs, i)) + "\nEQ " + f_fmPrice((array.get(hs, i) + array.get(ls, i)) / 2)
f_fmCandle(array.get(fmCandles, i), center, fmStep, array.get(os, i), array.get(hs, i), array.get(ls, i), array.get(cs, i), caption, tip, forming)
if not na(pH1)
fmPrevHigh := f_fmLine(fmPrevHigh, time + fmStep * 16, time + fmStep * 25, pH1, color.new(fmInk, 65), line.style_dotted, 1)
fmPrevLow := f_fmLine(fmPrevLow, time + fmStep * 16, time + fmStep * 25, pL1, color.new(fmInk, 65), line.style_dotted, 1)
fmPrevEq := f_fmLine(fmPrevEq, time + fmStep * 16, time + fmStep * 25, (pH1 + pL1) / 2, color.new(fmInk, 80), line.style_dashed, 1)
if fmShowDesk
table.cell(fmDesk, 0, 0, "FRACTAL MODEL DESK · " + str.upper(fmView) + " / " + htfTf, text_color=fmInk, text_size=fmTableSize, text_halign=text.align_left, bgcolor=color.new(fmInk, 91))
table.cell(fmDesk, 0, 1, "Observed " + str.tostring(engine.total) + " · Retained " + str.tostring(array.size(setups)) + "/" + str.tostring(inHistory) + "\nIn retained: reviews " + str.tostring(fmReviewCount) + " · excluded " + str.tostring(fmExcludedCount) + " · assessing " + str.tostring(fmAssessingCount), text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left, tooltip="Raw observation count is separate from delivery reviews. Retention evictions: " + str.tostring(engine.evicted) + ". Exclusion means current automated delivery checks were not met; it does not delete the observation.")
array<string> headers = fmAudit ? array.from("C2 OPEN", "SWEEP", "OBSERVATIONS", "CONTEXT", "REASON") : array.from("REVIEWED", "SWEEP", "EVIDENCE", "STATE", "CURRENT REASON")
for col = 0 to 4
table.cell(fmDesk, col, 2, array.get(headers, col), text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left, tooltip="Times: " + syminfo.timezone + ". Review time is when automated delivery evidence became observable. A review still needs manual POI and bias assessment.")
for row = 0 to (fmAudit ? 4 : 1)
int idx = fmAudit ? array.size(setups) - 1 - row : row == 0 ? fmFirst : fmSecond
if idx >= 0
Setup s = array.get(setups, idx)
ContextRecord q = f_contextFind(contexts, s.key)
string closureText = s.closure > 0 ? "C" + str.tostring(s.closure) + " close seen" : not na(s.c2KnownTime) ? "C2 outside" : "C2 pending"
string cisdText = s.cisdFailed ? "CISD failed" : not na(s.cisdTime) ? "CISD observed" : "CISD pending"
string reason = na(q) ? "Awaiting context assessment" : q.reason
string lifecycle = na(q) ? "Assessing" : q.lifecycle
string identity = "Observation " + s.key + "\nC2 open: " + f_fmTime(s.c2Time) + "\nSweep observed: " + f_fmTime(s.seenTime) + "\nCISD: " + f_fmTime(s.cisdTime) + "\nClosure known: " + f_fmTime(s.closureTime) + "\nTimes: " + syminfo.timezone
identity += not na(q) and not na(q.internalSweepTime) ? "\nEarlier local sweep/reclaim " + f_fmTime(q.internalSweepTime) + " at " + f_fmPrice(q.internalLevel) + "; possible inducement context, intent unknown." : "\nNo separately known prior local swing sweep; inducement intent unknown."
string evidence = fmAudit ? closureText + "\n" + cisdText : q.reviewModel
string evidenceTip = fmAudit ? identity + "\n" + s.status : "Review observed " + f_fmTime(q.reviewTime) + ".\nFrozen evidence: " + q.evidence + "\nManual POI and bias assessment required."
table.cell(fmDesk, 0, row + 3, f_fmTime(fmAudit ? s.c2Time : q.reviewTime), text_color=fmInk, text_size=fmTableSize, text_halign=text.align_left, tooltip=identity)
table.cell(fmDesk, 1, row + 3, (s.dir == 1 ? "Low swept" : "High swept") + (s.dualSweep ? "\nBoth extremes" : ""), text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left)
table.cell(fmDesk, 2, row + 3, evidence, text_color=fmAudit ? fmMuted : fmReview, text_size=fmTableSize, text_halign=text.align_left, tooltip=evidenceTip)
table.cell(fmDesk, 3, row + 3, lifecycle, text_color=fmMuted, text_size=fmTableSize)
table.cell(fmDesk, 4, row + 3, f_fmShort(reason), text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left, tooltip=reason)
else
for col = 0 to 4
string emptyText = row == 0 and col == 0 ? (fmAudit ? "No observations" : "No delivery review") : row == 0 and col == 4 ? f_fmShort(fmLatestReason) : ""
table.cell(fmDesk, col, row + 3, emptyText, text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left, tooltip=row == 0 ? "Latest observation: " + fmLatestReason : "")
table.cell(fmDesk, 0, fmDeskRows - 1, "Automated delivery checks · POI / bias manual\nTimes: " + syminfo.timezone + " · " + (fmAudit ? "Raw audit history" : "Latest 2 reviews · outcomes preserved"), text_color=fmMuted, text_size=fmTableSize, text_halign=text.align_left, tooltip="Focus selects two records by immutable review observation time, including later withdrawn and completed reviews. It never selects by trade outcome. Audit exposes retained raw facts. Hidden drawings remain recorded. Ledger times use the exchange timezone, which can differ from chart display time. Automated delivery evidence is not manual POI or directional-bias approval.")
// Two reusable display slots bound IC drawings independently of retained history.
// Rails start when anatomy is observed, never at the historical swing location.
var array<line> fmIcRails = array.new<line>()
var array<label> fmIcLabels = array.new<label>()
if barstate.isconfirmed and fmShowIc
for rail in fmIcRails
line.delete(rail)
for caption in fmIcLabels
label.delete(caption)
array.clear(fmIcRails)
array.clear(fmIcLabels)
array<IcRecord> selectedIc = array.new<IcRecord>()
if array.size(icBook) > 0
for item in icBook
if not na(item.formedTime) and (fmAudit or not na(item.reviewTime))
int slot = array.size(selectedIc)
if array.size(selectedIc) > 0
for n = 0 to array.size(selectedIc) - 1
if item.formedTime >= array.get(selectedIc, n).formedTime
slot := n
break
array.insert(selectedIc, slot, item)
if array.size(selectedIc) > 2
array.pop(selectedIc)
for item in selectedIc
bool reviewed = not na(item.reviewTime)
int endTime = na(item.endedAt) ? time_close : item.endedAt
string tip = "IC " + item.key + "\nParent " + item.parentKey + "\nAnatomy observed " + f_fmTime(item.formedTime) + "\nProtected swing " + f_fmPrice(item.protectedLevel) + " at " + f_fmTime(item.protectedTime) + "\nSwing known " + f_fmTime(item.protectedKnownTime) + "\n" + item.formationReason + "\nCurrent: " + item.reason + "\nChild swing taken does not itself invalidate the parent."
array.push(fmIcRails, line.new(item.formedTime, item.protectedLevel, endTime, item.protectedLevel, xloc=xloc.bar_time, color=reviewed ? color.new(fmReview, 45) : fmMuted, style=line.style_dotted))
array.push(fmIcLabels, f_fmLabel(item.formedTime, item.formedPrice, reviewed ? "IC delivery review\nPOI / bias assessment" : "IC anatomy\nContext incomplete", reviewed ? fmReview : fmMuted, label.style_label_left, tip))
if not na(item.endedAt)
array.push(fmIcLabels, f_fmLabel(item.endedAt, item.protectedLevel, "IC ended", fmMuted, label.style_label_left, "Observed " + f_fmTime(item.endedAt) + "\n" + item.endReason + (not na(item.takenTime) ? "\nProtected swing taken " + f_fmTime(item.takenTime) : "") + "\nOriginal IC anatomy retained; parent assessed independently."))
f_jsonNumber(float value) => na(value) ? "null" : str.tostring(value, "0.################")
f_escape(string value) => str.replace_all(str.replace_all(value, "\\", "\\\\"), "\"", "\\\"")
f_eventClass(string kind) => kind == "DELIVERY_REVIEW" ? "delivery_review" : kind == "REVIEW_END" ? "review_outcome" : kind == "IC_REVIEW" ? "ic_review" : kind == "IC_ANATOMY" ? "ic_anatomy" : kind == "IC_TAKEN" or kind == "IC_END" ? "ic_outcome" : "raw_observation"
bool alertSweep = false
bool alertCisd = false
bool alertClosure = false
bool alertFailure = false
bool alertRetest = false
bool alertReview = false
bool alertReviewEnd = false
bool alertIc = false
bool alertIcEnd = false
array<FmEvent> outboundEvents = array.new<FmEvent>()
if barstate.isconfirmed
for event in reviewEvents
array.push(outboundEvents, event)
for event in icEvents
if inAlertScope == "All observations" or event.kind == "IC_REVIEW" or ((event.kind == "IC_TAKEN" or event.kind == "IC_END") and str.contains(event.detail, "ic_reviewed=true;"))
array.push(outboundEvents, event)
if inAlertScope == "All observations"
for event in events
array.push(outboundEvents, event)
if barstate.isconfirmed and array.size(outboundEvents) > 0
string payload = ""
for event in outboundEvents
alertSweep := alertSweep or event.kind == "SWEEP"
alertCisd := alertCisd or event.kind == "CISD"
alertClosure := alertClosure or event.kind == "C2_CLOSED" or event.kind == "C3_CLOSED"
alertFailure := alertFailure or event.kind == "C2_FAILED" or event.kind == "CISD_FAILED" or (event.kind == "END" and event.detail != "C4 window complete")
alertRetest := alertRetest or event.kind == "RETEST"
alertReview := alertReview or event.kind == "DELIVERY_REVIEW"
alertReviewEnd := alertReviewEnd or event.kind == "REVIEW_END"
alertIc := alertIc or event.kind == "IC_REVIEW"
alertIcEnd := alertIcEnd or event.kind == "IC_TAKEN" or event.kind == "IC_END"
payload += (payload == "" ? "" : ",") + "{\"id\":\"" + event.key + "\",\"event\":\"" + event.kind + "\",\"classification\":\"" + f_eventClass(event.kind) + "\",\"observed_at\":" + str.tostring(event.stamp) + ",\"direction\":" + str.tostring(event.dir) + ",\"price\":" + f_jsonNumber(event.price) + ",\"detail\":\"" + f_escape(event.detail) + "\"}"
if inJson
alert("{\"schema\":\"fm-desk-v2.1\",\"rules_version\":\"2.1.0\",\"scope\":\"" + inAlertScope + "\",\"full_model_validation\":false,\"symbol\":\"" + f_escape(syminfo.tickerid) + "\",\"chart_tf\":\"" + timeframe.period + "\",\"htf\":\"" + htfTf + "\",\"events\":[" + payload + "]}", alert.freq_once_per_bar_close)
Updating from an earlier version?
Replace the whole script and recreate alerts. Defaults now use Focus, Latest review projections and Delivery review alerts. Webhook receivers must accept fm-desk-v2.1 and full_model_validation: false. Compare saved Lab settings with the new defaults; same-bar review/retest no longer qualifies. V1 grades and trade results remain historical.
The rules, one question at a time.
Open the detail you need.
Which timeframes work together?
Five-minute charts automatically use one hour. All pairings appear in Settings below.
Use standard time-based candles. The larger interval must exceed the chart interval, with each chart candle wholly inside it. Boundary-crossing candles raise an error. The first partial larger interval is skipped.
What starts a raw setup?
C1 is the previous closed larger candle. A wick strictly beyond its low or high records a raw C2 candidate. No reclaim is required. One candidate per direction per larger candle is retained, including both on a dual sweep.
C2 closure needs its final close strictly inside the whole C1 range. Equality fails. Closure and CISD remain separate raw facts; neither alone qualifies for delivery review.
When was a larger close actually known?
A larger close becomes known at the next interval’s first confirmed small-candle close. On 5m / 1h, C2 ending at 10:00 is recorded at 10:05, or later after a session gap.
Markers use observation time. In Audit, the dotted C1 reference points back to its source candle; Focus starts it no earlier than review. Atlas FORMING is a live preview, not confirmation; the Lab preview updates at chart-bar close.
What fixes the CISD line?
The opposing candle series behind the extreme supplies its opening-price line. A confirmed small candle closes strictly beyond it. Dojis end a series; only a new strict C2 extreme can update a pending anchor.
First CISD freezes the line, extreme and projection leg. Its delivery quality is also measured then using prior volatility. A later strong candle cannot improve weak or missing evidence. A strict extreme breach records failure without erasing the earlier observation.
What do C3 and C4 mean?
After failed C2 closure, bullish C3 can still hold C2's wick low and close above both C2 body prices; bearish mirrors it. This fallback is a declared implementation rule.
C3/C4 name stages, not entries. Strong C3 closes directionally beyond C2's range. C4 completion ends the observation window, not a broker position. New parent CISD cannot first confirm in C4.
What stays after failure?
After closure, a strict breach of fixed C2 extreme invalidates structure: wick by default, close optionally. Equality survives. Confirmed lines and observation times remain in the record.
A review can become Withdrawn or Complete without losing its original evidence. These are review states, not losses or wins. Focus selects two by original review time; Audit shows five recent raw records.
Raw history defaults to 24, adjustable 8–40. Oldest completed records are evicted first; developing records are protected. IC has a separate 24-record history, with active/current-frame overflow protected.
Is a gap automatically an entry?
No. A raw FVG is the first new three-candle gap after known, unfailed CISD, starting no earlier than the tracked extreme. Audit begins its box at detection; Focus starts it no earlier than review. A retest needs a later overlapping candle.
Parent delivery review does not require an FVG. The limited IC-review route does. A gap or overlap proves neither trader intent nor a limit fill.
What earns a delivery review?
Closure and CISD must belong to the matching larger candle, with enough prior volatility history. The first CISD needs a decisive close and either a quick, direct V reversal or a strong directional candle.
Repeated overlapping ranges, dual sweeps, failed structure or a spent objective can block review. Thresholds are our adjustable heuristics. A first CISD without 14 prior chart bars is Excluded; later data cannot repair that frozen measurement. A review still leaves POI and bias to you.
What is the smaller IC pattern?
IC follows a pullback inside C3/C4 of an already-reviewed parent. It has its own ID, opposing-leg opening level and protected three-candle swing. It never moves the parent anchor.
IC anatomy needs an opposing leg through the current larger-candle open, a later close back through that leg’s open, and a swing already confirmed by its right neighbor. IC review also needs a parent FVG already known when the touch bar opens, plus displacement. A same-bar new gap or later touch cannot improve earlier anatomy.
One IC attempt starts per parent per C3/C4 frame, even without an FVG; its confirmed swing must remain the pullback extreme. Attempts end with the frame or parent review. This does not cover every POI model. A protected-swing breach records a child outcome separately. Focus shows two IC reviews; Audit can show two formed anatomies. The Lab does not trade them.
Does a local sweep prove inducement?
No. It records a sweep/reclaim of an already-known local pivot before the outer candidate. It neither establishes anyone’s intent nor gates delivery review. IC protected-swing failure is also separate from parent invalidation.
What do the projections measure?
Origin + direction × multiple × frozen leg. The 1 / 2 / 2.5 / 4 labels are price-leg multiples, not standard deviations or promised targets. The 1× line equals CISD.
For CISD 110, wick 100 and opposing-series extreme close 104: Wick gives 110 / 120 / 125 / 140; Body gives 110 / 116 / 119 / 128. Invalid geometry is not rescued with an absolute value.
Latest review can select an ended record. Focus never starts its displayed levels before review became known. An old projection is not a current entry.
When does the optional Lab enter?
Delivery review acts on a new qualifying review. FVG retest after review requires a later retest event with the review already known when that retest bar opens. A first retest before or in the review bar is skipped; no replacement retest is invented.
Both need active review, paired intact structure, unfailed CISD and an available C1 opposite-extreme target. IC children are not traded. One position and one submission per calculation are allowed. These restrictions never hide Atlas observations.
Stop uses fixed C2 extreme plus tick padding. Target uses the opposite C1 extreme; there is no 2R fallback. Spent or behind-entry targets and invalid distances are skipped. The entry submits its stop/limit bracket immediately. C4 completion does not itself close a broker position.
How do I inspect the raw record?
Audit shows neutral raw observations. Match IDs and observation times across the ledger and chart; Times uses the exchange timezone. FORMING is a live preview, never a review.
Focus instead selects the latest two reviews, including withdrawn/completed ones. Display filtering preserves engine history. Latest review projections can therefore belong to an ended review.

What does every setting change?
All 26 Setup Atlas inputs are also in Strategy Lab, with identical defaults. The Lab adds two controls. Exact TradingView names appear below each explanation.
Larger candle and model rules
Use standard time-based candles. Each chart candle must fit completely inside one strictly larger candle.
- Choose the larger timeframe automatically
- Default: On
- Five minutes pairs with one hour. Full table below.
- TradingView: Automatic HTF pairing
- Choose the larger timeframe yourself
- Default: 60 minutes
- Used with automatic pairing Off. Incompatible or overlapping intervals raise an error.
- TradingView: Manual higher timeframe
- Measure from body or wick
- Default: Off · wick anchor
- On uses the opposing series’ extreme close. Origin and positive leg freeze at CISD; 1× remains CISD.
- TradingView: Projection anchor: body (default wick)
- What counts as breaking the extreme
- Default: On · wick breach
- Off requires a close beyond it. Equal touches survive. C3 fallback still has to hold the C2 wick extreme in either mode.
- TradingView: Invalidate on wick through extreme (off = close)
- How many records to keep
- Default: 24 records · range 8–40
- Capacity removes oldest completed records. Live setups are protected; a failure remains a recorded event.
- TradingView: Setups retained
Choose which events reach you
Recreate alerts for fm-desk-v2.1. Both formats follow Event stream; full_model_validation remains false.
- Batch a closed candle’s events
- Default: On
- Select Any alert() function call. Keeps IDs and observation times. Off disables JSON, not the indicator’s separate alert conditions.
- TradingView: JSON event batch alerts
- Send reviews or raw observations too
- Default: Delivery review
- Parent and IC reviews plus later outcomes. All observations adds raw facts. Neither stream is an entry instruction.
- TradingView: Event stream
How strong must the first change be?
Declared heuristics, not a commercial formula. Parent review accepts V reversal OR displacement. Evidence freezes at the first CISD; later candles cannot improve weak evidence. Prior ATR14 needs 14 earlier samples; missing history excludes that observation.
- Limit how long a quick reversal takes
- Default: 3 chart bars
- Includes the extreme candle. The reversal must also pass path efficiency and decisive-close checks.
- TradingView: V reversal: maximum bars from extreme
- Limit back-and-forth movement
- Default: 0.70
- Net directional travel from the wick divided by total close-to-close travel, including the wick-to-first-close segment.
- TradingView: V reversal: minimum path efficiency
- Require a large enough candle body
- Default: 0.80 of prior ATR14
- Body must point in the reviewed direction. Also applies to IC review.
- TradingView: Displacement: minimum body / prior ATR14
- Keep the body large relative to wicks
- Default: 60% of candle range
- Body divided by high–low range. Also applies to IC review.
- TradingView: Displacement: minimum body / range
- Close far enough beyond the line
- Default: 0.05 of prior ATR14
- Required for both parent review routes; IC uses its own fixed opposing-body opening price.
- TradingView: Decisive close: distance past CISD / prior ATR14
- Pause when ranges keep repeating
- Default: On
- The two overlap checks and sweep count must all pass. Can withhold or withdraw a review; raw history stays.
- TradingView: Pause reviews in repeated overlapping ranges
- Measure overlap between larger candles
- Default: 70%
- Uses three completed larger candles. Both adjacent pairs must overlap this much, divided by each pair’s smaller range.
- TradingView: Chop: minimum pairwise overlap of 3 closed HTFs
- Count sweeps inside the repeated ranges
- Default: 3 raw sweep records
- Counts observations across those same three larger candles. A configurable noise check, not a market-regime guarantee.
- TradingView: Chop: sweep observations across those 3 HTFs
Strategy Lab experiments only
Orders use active parent reviews, never IC children. Raw records stay unchanged. One position maximum; point of interest and higher-timeframe bias remain manual.
- Choose an order trigger
- Default: Delivery review
- Default: new review event. FVG retest after review requires review already known when the retest bar opens. An earlier or same-bar first retest is skipped, never replaced.
- TradingView: Entry experiment
- Space beyond the fixed C2 extreme
- Default: 1 tick
- Zero allowed. Target: opposite C1 extreme, without fallback. Signal-close-to-stop and signal-close-to-target distances must exceed one tick, before broker fill/slippage.
- TradingView: Stop buffer in ticks
Choose what stays visible
Display controls preserve records. Forming previews move; confirmed anchors stay fixed for unchanged input data.
- Read two reviews or the raw history
- Default: Focus
- Focus selects the two latest review times, including later withdrawals/completions. Audit reveals retained raw observations; hiding does not delete.
- TradingView: View
- Choose colors for the two directions
- Default: Colorblind
- Colorblind uses teal/orange; Classic uses green/red. Direction labels remain available.
- TradingView: Direction palette
- Choose frozen projections to draw
- Default: Latest review
- Latest may be ended. Selected reviews follows Focus’s two; All retained expands only in Audit. Off hides lines. Leg multiples, not statistical deviations.
- TradingView: Projection visibility
- Show the first new three-candle gap
- Default: On
- Audit starts at detection; Focus starts no earlier than review. Retest needs a later candle. Hiding does not stop tracking.
- TradingView: Show first FVG after CISD
- Label later candle stages in Audit
- Default: Off
- Stage labels are neither entries nor completed candles.
- TradingView: Audit: show C3 / C4 stage markers
- Show the unconfirmed level in Audit
- Default: Off
- A neutral dashed line can move before confirmation; it is not a delivery review.
- TradingView: Audit: preview developing CISD level
- Show the larger candles
- Default: On
- Three completed candles plus FORMING. Indicator preview moves live; Lab preview updates at chart close. Neither confirms a review.
- TradingView: Show higher-timeframe candles
- Show reasons beside the records
- Default: On
- Focus lists two reviews; Audit lists five raw records. Counts distinguish raw observations, reviews and exclusions, not trading results.
- TradingView: Show recent setup ledger
- Show the smaller continuation attempts
- Default: On
- Focus: two latest IC reviews. Audit: two latest formed anatomies. Independent history keeps 24; active/current-frame records can temporarily exceed capacity.
- TradingView: Show latest two IC observations
- Place the setup list
- Default: Bottom right
- Bottom right/left or Top right/left. Ledger and tooltip clocks use the symbol’s exchange timezone.
- TradingView: Ledger position
- Make the list easier to read
- Default: Small
- Small, Normal or Tiny; Normal is larger.
- TradingView: Ledger text size
Automatic timeframe pairings
The tool skips a partial first larger candle. It observes each completed larger candle at the first confirmed chart close of the following interval.
- 1 minute or less
- 15 minutes
- Above 1 through 5 minutes
- 1 hour
- Above 5 through 15 minutes
- 4 hours
- Above 15 minutes through 1 hour
- 1 day
- Above 1 through 4 hours
- 1 week
- Above 4 hours
- 1 month
Strategy Lab Properties
These are the shipped broker-experiment defaults, separate from script Inputs. Saved Properties can override them. A setup ending does not close a Lab position; its fixed broker bracket continues independently.
- Initial capital
- Default: 100,000
- Starting balance; check Properties for account currency.
- Position sizing method
- Default: Percent of equity
- Allocation follows current equity, not fixed cash risk.
- Position size
- Default: 10% of equity
- Ten percent controls position allocation, not the percentage lost if the stop is hit.
- Stack entries in the same direction
- Default: 0
- No additions to existing positions; at most one entry per calculation.
- Commission method
- Default: Percentage
- Commission depends on order value.
- Commission
- Default: 0.02% per side
- Adjust this test cost for your instrument.
- Slippage
- Default: 1 tick
- Emulator adjustment where applicable; fills can differ from chart references.
- Process orders on the closing tick
- Default: On
- Allows close-tick processing. Entry and native stop/limit bracket submit together.
- Recalculate on every tick
- Default: Off
- The experiment calculates on confirmed chart candles.
- Extra calculation after a fill
- Default: Off
- No immediate extra calculation after fills.
What changed in v2.1 alerts?
Delivery review is the default stream: delivery/IC reviews and later outcomes. All observations also includes neutral raw facts and IC anatomy. Enable JSON batches and create Any alert() function call.
The schema is fm-desk-v2.1 with scope and full_model_validation: false. IDs, observation times and evidence travel with each event. Update old receivers and recreate alerts. Named alert conditions are indicator-only; Lab broker notifications are separate.
What has actually been checked?
The producer's release evidence records 228/228 native Pine assertions across 50 fixtures: core 91, delivery/context 86 and IC 51. Its build/package tests passed 18/18. Both scripts and three probes compiled without errors or warnings.
Market smoke checks, prefix tests and same-history reconstruction have documented limits. No native five-minute Bar Replay or paid-indicator parity pass is claimed. These checks establish neither trading accuracy nor profit. Read the evidence and its limits.
The Lab's 10% equity position size is allocation, not stop-loss risk. Review costs, Properties and emulator fills separately.
What remains your decision?
POI means the price area you choose to study; bias means your larger directional view. Both remain manual, alongside the objective and trade risk. Review is not full model validation.
Published research informed these disclosed heuristics, not a recovered proprietary formula. Settings, feeds and loaded history can change recalculation. No blanket repaint or paid-indicator parity promise is made. Read the three-project source audit.
How should I start?
Use standard 5m / 1h and default Focus. Read one review's evidence, then compare its raw sequence in Audit. An empty Focus view can be correct. Learn the separate entry and cost rules before experimenting with the Lab.
Every change, dated
What shipped and what changed in Fractal Model Desk, newest first. Ask for the next change below; what gets built lands here with a date, and on the lab queue on the front page.
Focused reviews, with the full audit history v2.1.0 Updated Fractal Model Desk
Focus highlights two delivery reviews; Audit keeps raw observations visible. Separate quality and IC checks explain what passed, waited or ended.
- First-CISD evidence freezes; configurable V/displacement and overlapping-range checks separate raw events from review
- Independent IC anatomy, known-parent-FVG review and protected-swing history; local sweeps do not prove inducement intent
- New review-only alert default; Strategy Lab requires review already known when the retest candle opens
- Producer reports 228/228 native assertions across 50 fixtures and 18/18 package tests; these checks do not establish trading returns
Historical v2.0.0 setup history and separate Lab Lab note Fractal Model Desk
V2.0 introduced separate closure, CISD and retained failures. Its original Lab rules and verification remain historical; current downloads serve v2.1.
- Bounded C2/C3/C4 history preserves failures and protects developing setups
- Frozen price-leg projections and event batches with setup IDs
- Separate Lab orders, position sizing and costs; v1 measurements remain historical
- Actual EURUSD and XAUUSD charts, complete source and beginner replay
Fractal Model Desk v1.1.0 Shipped
Connects a larger candle's reversal to an entry on smaller candles.
Historical v1.1.0 model and strategy twin Lab note Fractal Model Desk
The original v1.1.0 shipped CISD, formation tags, grades and projections. Its rules and measurements are historical; current downloads serve v2.1.
- Higher-timeframe silhouettes and the next frame's previous high and low
- C2, C3 and C4 formations, a CISD trigger and an entry zone
- Time rule, formation-liquidity target and A/B grades in the shipped defaults
- Indicator and strategy twin, both MIT and ungated
Something to change in Fractal Model Desk?
A small friction or a big idea. Tell us what would make this tool work better for you.
Requests for a new tool go on the same queue: see what is asked for and what is being built.