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 – Invalidation conditions evaluated on subsequent bars (e.g., “price closed above the zone’s upper boundary”).

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.

Type Use it for
Supply Price areas where selling pressure is expected to emerge
Demand Price areas where buying pressure is expected to emerge
Support Lower price levels or narrow regions expected to hold when approached from above
Resistance Upper price levels or narrow regions expected to hold when approached from below
Custom Patterns 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.

Field Description
Name Descriptive label shown in the UI and logs
Type Classification inherited by every detected zone. See Zone types
Interval Bar interval to scan (e.g., D, 1h, 15m)
Lookback Number of historical bars to evaluate (default: 200)
Formation Conditions Conditions evaluated per bar to detect zone creation. Uses the same operators and expressions as rule conditions
Formation Operands AND/OR connectors between formation conditions
Upper Bound Expression Expression computing the zone’s upper price boundary
Lower Bound Expression Expression computing the zone’s lower price boundary
Invalidation Conditions Conditions evaluated on bars after zone creation to determine when the zone breaks
Invalidation Operands AND/OR connectors between invalidation conditions
Max Active Zones Cap on simultaneous active zones (default: 20, range: 1–100)

Formation conditions have access to bar.* (current bar) and prevBar.* (previous bar) fields. Invalidation conditions have access to price (current close) and zone.* properties (zone.upper, zone.lower, zone.midpoint, zone.width, zone.touchCount, 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).lower
// 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
Function Returns Description
ZONES({id}) PriceZone[] All active Price Zones from the named zone definition
ZONE_CONTAINS(zones, price, filter?) 1 or 0 1 if price is within any zone’s upper/lower range
ZONE_NEAREST_ABOVE(zones, price, filter?) PriceZone or null The closest zone whose lower boundary is above the current price
ZONE_NEAREST_BELOW(zones, price, filter?) PriceZone or null The closest zone whose upper boundary is below the current price
ZONE_ACTIVE_COUNT(zones, filter?) number Count of currently active (non-broken) zones
ZONE_DISTANCE(zones, price, dir) number Distance in price units to the nearest zone in the specified direction ('above' or 'below')

Functions that accept an optional filter parameter let you narrow results by lifecycle state:

// Only count zones that are still untested
ZONE_ACTIVE_COUNT(ZONES({id: 'sd-zones'}), {state: 'active'})
// Check if price is in a tested (but not broken) zone
ZONE_CONTAINS(ZONES({id: 'sd-zones'}), price, {state: 'tested'})

When a zone function returns a PriceZone (like ZONE_NEAREST_ABOVE), you can access its properties:

Property Description
.upper Upper boundary price
.lower Lower boundary price
.midpoint Midpoint: (upper + lower) / 2
.width Zone width: upper - lower
.state Zone state – active, tested, or broken
.touchCount Number of times price has entered the zone
.strength Strength 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.

Preset What it detects Lookback
Supply & Demand Zones Large displacement candles where the body exceeds 2x the previous bar’s body. These mark areas where aggressive buying or selling overwhelmed the other side 200 bars
Expansion Demand Zones Bullish reversal bars that gap above the prior bar with minimal lower wick, indicating strong institutional demand. Multi-condition detection with impulse validation 1825 bars (5 years daily)
Expansion Supply Zones Bearish reversal bars that gap below the prior bar with minimal upper wick, indicating institutional distribution 1825 bars (5 years daily)
Preset What it detects Lookback
True Gap Up Bullish gaps with no wick overlap between bars – the gap region acts as demand until filled 200 bars
True Gap Down Bearish gaps with no wick overlap – the gap acts as supply until filled 200 bars
Body Gap Up Wicks overlap but candle bodies leave an unfilled bullish gap zone 200 bars
Body Gap Down Wicks overlap but candle bodies leave an unfilled bearish gap zone 200 bars
Preset What it detects Lookback
Consolidation Zones Clusters of small-range bars where range is less than 0.5x the previous bar’s body – potential breakout areas 100 bars
Previous Session High/Low Thin zones around the prior session’s high price, acting as intraday support/resistance levels 20 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, {state: 'active'}) – Price is in a fresh, untested 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_NEAREST_BELOW(ZONES({id: 'true-gap-up'}), price).upper - price < 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
ZONE_NEAREST_BELOW(ZONES({id: 'demand-zones'}), price).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 case Interval Lookback Why
Intraday scalping 5m or 15m 50–100 Focuses on recent session structure
Swing trading 1h or 4h 100–200 Captures multi-day structure without noise
Position trading D 200–500 Several months to a year of daily structure
Long-term institutional levels D 1000–1825 Multi-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 and click Configure Zone Definitions.
  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.