Skip to content

Zone Definitions

Zone definitions let you detect and track persistent horizontal price regions — supply/demand areas, consolidation ranges, gap zones, pivot levels — and reference them directly in your strategy rules. Unlike point-in-time indicators that produce a single value per bar, a zone definition scans historical bars, creates zones wherever its formation criteria are met, and manages each zone’s lifecycle as price interacts with it over time.

Each Zone Definition is evaluated over historical bars, producing live-updating Price Zones that flow into the rule engine alongside indicators, price data, and everything else in the expression language.

Most strategies rely on indicators that tell you what the market is doing now. Zones tell you where the market has been and which of those levels still matter:

  • Structure-aware trading. Automate decisions around price areas where supply and demand have historically shifted — levels that would otherwise require manual chart annotation.
  • Full lifecycle tracking. Zones transition through ACTIVE, TESTED, and BROKEN states as price interacts with them, giving your rules nuanced context about whether a level is fresh, battle-tested, or already invalidated.
  • Composable with everything else. Zone query functions work inside the same expression language you already use for indicators, account data, and custom variables. Combine zones with RSI, ATR, volume filters, or anything else in a single condition.

A zone definition specifies three things:

  1. When to create a zone — Formation conditions evaluated per bar (e.g., “this bar’s body is 2x the previous bar’s body”).
  2. Where the zone is — Bound expressions that compute the upper and lower boundaries from the triggering bar.
  3. When to remove it — Optional invalidation conditions evaluated on subsequent bars (e.g., “price closed above the zone’s upper boundary”). Leave them empty for a persistent zone with no automatic invalidation.

Zone Type is a classification label, not a detection mode. Every zone created by a definition inherits its selected type. The type describes the zone’s intended trading purpose. It does not change when a zone forms, where its boundaries are, when it breaks, or how it appears on a chart.

TypeUse it for
SupplyPrice areas where selling pressure is expected to emerge
DemandPrice areas where buying pressure is expected to emerge
SupportLower price levels or narrow regions expected to hold when approached from above
ResistanceUpper price levels or narrow regions expected to hold when approached from below
CustomPatterns that do not fit those directional labels, such as consolidation ranges

Choose the type that matches the formation logic you configured. For example, label a zone demand only when its formation and bound expressions define an area where you expect buyers to respond. Selecting demand by itself does not add demand-zone detection logic. Built-in presets configure detection rules and select an appropriate label together; changing the label later does not rewrite those rules.

FieldDescription
NameDescriptive label shown in the UI and logs
TypeClassification inherited by every detected zone. See Zone types
IntervalBar interval to scan (e.g., D, 1h, 15m)
LookbackNumber of historical bars to evaluate (default: 200)
Formation ConditionsConditions evaluated per bar to detect zone creation. Uses the same operators and expressions as rule conditions
Formation OperandsAND/OR connectors between formation conditions
Upper Bound ExpressionExpression computing the zone’s upper price boundary
Lower Bound ExpressionExpression computing the zone’s lower price boundary
Invalidation ConditionsOptional conditions evaluated on bars after zone creation to determine when the zone breaks; empty means persistent
Invalidation OperandsAND/OR connectors between invalidation conditions
Max Active ZonesCap on simultaneous active zones (default: 20, range: 1—100)

Formation conditions are evaluated for each historical candidate bar. A condition can use the candidate bar, earlier bars, indicators, and calculations. Use indexed bar fields to describe multi-bar patterns:

bar.close > bar.open
bar.close > bar.high[1]
bar.volume > AVG(bars['D'].volume, 20)
bar.close - bar.open > ATR({period: 14}) * 1.5

Connect conditions with AND or OR in the editor. A three-bar pattern can be expressed by referencing two earlier bars, for example:

bar.close[2] < bar.open[2]
bar.close[1] < bar.open[1]
bar.close > bar.open
bar.close > bar.high[1]

This detects a bullish bar that follows two bearish bars and closes above the prior bar’s high. The final bar is the formation bar that creates the zone. The detector does not create a separate multi-bar object; the pattern must identify the final bar.

Zone boundaries can still span the whole pattern. For example:

Upper bound: MAX(bar.high, MAX(bar.high[1], bar.high[2]))
Lower bound: MIN(bar.low, MIN(bar.low[1], bar.low[2]))

For example, bar.close[1] refers to the previous bar’s close and bar.high[2] refers to the high from two bars earlier. prevBar.close is also available for the immediately preceding bar. Standard indicators, aggregation functions, arithmetic, and comparisons use the same expression language described in the expression reference.

Invalidation conditions can compare the current close, price, with zone properties such as zone.upper, zone.lower, zone.midpoint, zone.width, zone.touchCount, and zone.strength.

Zones progress through a state machine as price interacts with them:

Formation Price enters zone Invalidation
detected boundary conditions met
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ ACTIVE │────────▶│ TESTED │─────────────▶│ BROKEN │
│ │ │ │ │ │
└────────┘ └────────┘ └────────┘
│ │
│ │ Price enters again
│ └──────────┐
│ │
│ ┌──────────────┘
│ ▼
│ touch_count++
│ strength decays
└──────────────────────────────────────────▶ BROKEN
(invalidation can happen from ACTIVE too)
  • ACTIVE — Zone has been detected and price has not yet entered it.
  • TESTED — Price has entered the zone boundary. Each time price re-enters, touchCount increments and strength decays.
  • BROKEN — Invalidation conditions were met. The zone is removed from the active set.

Strength decay: A zone starts with strength 1.0. Each touch reduces it by 0.15, down to a floor of 0.1. A zone touched 3 times has strength 0.55. This lets your rules distinguish fresh, untested zones from heavily-tested ones that may be weakening.

Once defined, zones are accessible in rule conditions and expressions through zone query functions. The basic pattern is:

  1. Retrieve zones with ZONES({id: 'your-zone-id'})
  2. Query them with a zone function
// Check if current price is inside any active supply/demand zone
ZONE_CONTAINS(ZONES({id: 'sd-zones'}), price)
// Get the lower boundary of the nearest zone above price
ZONE_NEAREST_ABOVE(ZONES({id: 'sd-zones'}), price)
// Check if the nearest zone above is within half an ATR
ZONE_DISTANCE(ZONES({id: 'sd-zones'}), price, 'above') < ATR({period: 14}) * 0.5
// Only act when at least 3 zones are active
ZONE_ACTIVE_COUNT(ZONES({id: 'sd-zones'})) >= 3
// Combine zone proximity with a momentum filter
ZONE_DISTANCE(ZONES({id: 'demand-zones'}), price, 'below') < ATR({period: 14}) * 0.3
&& RSI({period: 14}) < 35
FunctionReturnsDescription
ZONES({id})PriceZone[]All active Price Zones from the named zone definition
ZONE_CONTAINS(ZONES({id}), price)1 or 01 if price is within any active zone’s upper/lower range
ZONE_NEAREST_ABOVE(ZONES({id}), price)number or 0Lower boundary of the closest active zone above price
ZONE_NEAREST_BELOW(ZONES({id}), price)number or 0Upper boundary of the closest active zone below price
ZONE_ACTIVE_COUNT(ZONES({id}))numberCount of currently active (non-broken) zones
ZONE_DISTANCE(ZONES({id}), price[, dir])numberDistance in price units to the nearest zone edge; optional dir restricts the search to 'above' or 'below'. Returns Infinity when no zone exists in the specified direction.

Rules can access retained-zone properties with indexed ZONES lookup:

PropertyDescription
.upperUpper boundary price
.lowerLower boundary price
.midpointMidpoint: (upper + lower) / 2
.widthZone width: upper - lower
.touchCountNumber of times price has entered the zone
.strengthStrength score from 0.1 to 1.0 (decays with touches)

Nine presets are available out of the box. Select them from the Add Zone Definition dropdown in the strategy editor.

PresetWhat it detectsLookback
Supply & Demand ZonesLarge displacement candles where the body exceeds 2x the previous bar’s body. These mark areas where aggressive buying or selling overwhelmed the other side200 bars
Expansion Demand ZonesThree-bar bullish expansion: a bullish impulse, bearish consolidation inside its range, and a stronger bullish continuation that breaks the prior high. Bounds use the two origin opens1825 bars (5 years daily)
Expansion Supply ZonesMirrored three-bar bearish expansion: a bearish impulse, bullish consolidation inside its range, and a stronger bearish continuation that breaks the prior low. Bounds use the two origin opens1825 bars (5 years daily)
PresetWhat it detectsLookback
True Gap UpBullish gaps with no wick overlap between bars — the gap region acts as demand until filled200 bars
True Gap DownBearish gaps with no wick overlap — the gap acts as supply until filled200 bars
Body Gap UpWicks overlap but candle bodies leave an unfilled bullish gap zone200 bars
Body Gap DownWicks overlap but candle bodies leave an unfilled bearish gap zone200 bars
PresetWhat it detectsLookback
Consolidation ZonesClusters of small-range bars where range is less than 0.5x the previous bar’s body — potential breakout areas100 bars
Previous Session High/LowThin zones around the prior session’s high price, acting as intraday support/resistance levels20 bars

Enter long when price pulls back to an untested demand zone and RSI confirms oversold conditions:

Zone definition: Use the “Supply & Demand Zones” preset with type set to demand.

Entry rule conditions:

  1. ZONE_CONTAINS(ZONES({id: 'sd-zones'}), price) — Price is in a demand zone
  2. AND RSI({period: 14}) < 35 — RSI confirms oversold

Exit rule conditions:

  1. position.positionReturnPct > 3 — Take profit at 3%
  2. OR ZONE_DISTANCE(ZONES({id: 'sd-zones'}), price, 'above') < ATR({period: 14}) * 0.2 — Exit near the next supply zone above

Trade unfilled gaps as mean-reversion targets:

Zone definition: Use the “True Gap Up” preset.

Entry rule conditions (short):

  1. ZONE_DISTANCE(ZONES({id: 'true-gap-up'}), price, 'above') < ATR({period: 14}) * 0.5 — Price is within half an ATR of a gap zone above
  2. AND RSI({period: 14}) > 65 — Momentum is losing steam

Exit rule conditions:

  1. ZONE_CONTAINS(ZONES({id: 'true-gap-up'}), price) — Price has entered the gap zone (gap filled)

Enter when multiple independent zone types agree on a level:

Zone definitions: Add both “Supply & Demand Zones” and “Consolidation Zones” presets.

Entry rule conditions:

  1. ZONE_DISTANCE(ZONES({id: 'supply-demand'}), price, 'below') < ATR({period: 14}) * 0.3 — Near a supply/demand zone
  2. AND ZONE_DISTANCE(ZONES({id: 'consolidation'}), price, 'below') < ATR({period: 14}) * 0.5 — Also near a consolidation zone
  3. AND volume > SMA({length: 20, source: 'volume'}) * 1.5 — Volume confirms the move

Use zone strength to prioritize fresh zones over heavily-tested ones:

// Only enter at zones that haven't been tested more than once
ZONES({id: 'demand-zones'})[0].strength > 0.7

A strength above 0.7 means the zone has been touched at most once. Combine this with distance checks and momentum indicators for higher-quality entries.

The lookback and interval settings control the historical window the zone detector scans:

Use caseIntervalLookbackWhy
Intraday scalping5m or 15m50—100Focuses on recent session structure
Swing trading1h or 4h100—200Captures multi-day structure without noise
Position tradingD200—500Several months to a year of daily structure
Long-term institutional levelsD1000—1825Multi-year supply/demand zones from major moves

Larger lookbacks find more zones, but zones from the distant past are more likely to have been tested and weakened. The maxActiveZones cap (default 20) keeps the active set manageable by retaining the zones closest to the current price.

  1. Open a strategy in the strategy editor.
  2. Scroll to the Zone Definitions section.
  3. Click Add Zone Definition and select a preset or create a custom zone.
  4. Configure formation conditions, bound expressions, and invalidation criteria.
  5. Reference the zone in your rules using ZONES({id: 'your-id'}).
  6. Run a backtest to verify zone detection matches your expectations.