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.

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 Optional conditions evaluated on bars after zone creation to determine when the zone breaks; empty means persistent
Invalidation Operands AND/OR connectors between invalidation conditions
Max Active Zones Cap 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
Function Returns Description
ZONES({id}) PriceZone[] All active Price Zones from the named zone definition
ZONE_CONTAINS(ZONES({id}), price) 1 or 0 1 if price is within any active zone’s upper/lower range
ZONE_NEAREST_ABOVE(ZONES({id}), price) number or 0 Lower boundary of the closest active zone above price
ZONE_NEAREST_BELOW(ZONES({id}), price) number or 0 Upper boundary of the closest active zone below price
ZONE_ACTIVE_COUNT(ZONES({id})) number Count of currently active (non-broken) zones
ZONE_DISTANCE(ZONES({id}), price[, dir]) number Distance 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:

Property Description
.upper Upper boundary price
.lower Lower boundary price
.midpoint Midpoint: (upper + lower) / 2
.width Zone width: upper - lower
.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 Three-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 opens 1825 bars (5 years daily)
Expansion Supply Zones Mirrored 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 opens 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) – 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 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.
  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.