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.
Why use zones?
Section titled “Why use zones?”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, andBROKENstates 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.
How it works
Section titled “How it works”A zone definition specifies three things:
- When to create a zone – Formation conditions evaluated per bar (e.g., “this bar’s body is 2x the previous bar’s body”).
- Where the zone is – Bound expressions that compute the upper and lower boundaries from the triggering bar.
- When to remove it – Invalidation conditions evaluated on subsequent bars (e.g., “price closed above the zone’s upper boundary”).
Zone types
Section titled “Zone types”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.
Full configuration
Section titled “Full configuration”| 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).
Zone lifecycle
Section titled “Zone lifecycle”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,
touchCountincrements andstrengthdecays. - 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.
Referencing zones in rules
Section titled “Referencing zones in rules”Once defined, zones are accessible in rule conditions and expressions through zone query functions. The basic pattern is:
- Retrieve zones with
ZONES({id: 'your-zone-id'}) - Query them with a zone function
// Check if current price is inside any active supply/demand zoneZONE_CONTAINS(ZONES({id: 'sd-zones'}), price)
// Get the lower boundary of the nearest zone above priceZONE_NEAREST_ABOVE(ZONES({id: 'sd-zones'}), price).lower
// Check if the nearest zone above is within half an ATRZONE_DISTANCE(ZONES({id: 'sd-zones'}), price, 'above') < ATR({period: 14}) * 0.5
// Only act when at least 3 zones are activeZONE_ACTIVE_COUNT(ZONES({id: 'sd-zones'})) >= 3
// Combine zone proximity with a momentum filterZONE_DISTANCE(ZONES({id: 'demand-zones'}), price, 'below') < ATR({period: 14}) * 0.3 && RSI({period: 14}) < 35Zone query functions
Section titled “Zone query functions”| 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') |
Filtering zones
Section titled “Filtering zones”Functions that accept an optional filter parameter let you narrow results by lifecycle state:
// Only count zones that are still untestedZONE_ACTIVE_COUNT(ZONES({id: 'sd-zones'}), {state: 'active'})
// Check if price is in a tested (but not broken) zoneZONE_CONTAINS(ZONES({id: 'sd-zones'}), price, {state: 'tested'})Price Zone properties
Section titled “Price Zone properties”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) |
Built-in presets
Section titled “Built-in presets”Nine presets are available out of the box. Select them from the Add Zone Definition dropdown in the strategy editor.
Supply and demand
Section titled “Supply and demand”| 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) |
Gap zones
Section titled “Gap zones”| 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 |
Structure zones
Section titled “Structure zones”| 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 |
Strategy examples with zones
Section titled “Strategy examples with zones”Demand zone bounce with RSI confirmation
Section titled “Demand zone bounce with RSI confirmation”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:
ZONE_CONTAINS(ZONES({id: 'sd-zones'}), price, {state: 'active'})– Price is in a fresh, untested demand zone- AND
RSI({period: 14}) < 35– RSI confirms oversold
Exit rule conditions:
position.positionReturnPct > 3– Take profit at 3%- OR
ZONE_DISTANCE(ZONES({id: 'sd-zones'}), price, 'above') < ATR({period: 14}) * 0.2– Exit near the next supply zone above
Gap fill strategy
Section titled “Gap fill strategy”Trade unfilled gaps as mean-reversion targets:
Zone definition: Use the “True Gap Up” preset.
Entry rule conditions (short):
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- AND
RSI({period: 14}) > 65– Momentum is losing steam
Exit rule conditions:
ZONE_CONTAINS(ZONES({id: 'true-gap-up'}), price)– Price has entered the gap zone (gap filled)
Multi-zone confluence
Section titled “Multi-zone confluence”Enter when multiple independent zone types agree on a level:
Zone definitions: Add both “Supply & Demand Zones” and “Consolidation Zones” presets.
Entry rule conditions:
ZONE_DISTANCE(ZONES({id: 'supply-demand'}), price, 'below') < ATR({period: 14}) * 0.3– Near a supply/demand zone- AND
ZONE_DISTANCE(ZONES({id: 'consolidation'}), price, 'below') < ATR({period: 14}) * 0.5– Also near a consolidation zone - AND
volume > SMA({length: 20, source: 'volume'}) * 1.5– Volume confirms the move
Zone strength filter
Section titled “Zone strength filter”Use zone strength to prioritize fresh zones over heavily-tested ones:
// Only enter at zones that haven't been tested more than onceZONE_NEAREST_BELOW(ZONES({id: 'demand-zones'}), price).strength > 0.7A 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.
Choosing lookback and interval
Section titled “Choosing lookback and interval”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.
Getting started
Section titled “Getting started”- Open a strategy in the strategy editor.
- Scroll to the Zone Definitions section and click Configure Zone Definitions.
- Click Add Zone Definition and select a preset or create a custom zone.
- Configure formation conditions, bound expressions, and invalidation criteria.
- Reference the zone in your rules using
ZONES({id: 'your-id'}). - Run a backtest to verify zone detection matches your expectations.
