Skip to main content

Strategy Design Patterns

This guide shows how to model common real-world trading scenarios using FORJ's states, transitions, conditions, actions, and indicators. Each pattern includes a description of the trading logic, the states and transitions to create, and step-by-step instructions.

For anti-patterns to avoid, see Strategy Anti-Patterns.

How entries and exits actually work in FORJ

Before reading the patterns, understand the division of responsibility:

  • Entries are state-machine decisions. A guard on an entry transition decides whether to take a trade. When the transition fires, an Execute Trade action references a Trade Plan, which logs the entry signal with resolved SL/TP/size and (optionally) starts position tracking.
  • Exits are owned by the Trade Plan, not by guards on exit transitions. When you enable Track Open Position on a trade plan, the trade plan engine watches each open position on every event and decides when to close it — based on SL/TP breach, an exit condition you define on the plan, a timer, or a combination. When closure criteria fire, the plan logs a trade exit event and emits a Trade Plan Exit (SL/TP) or Trade Plan Timer lifecycle event.
  • The state machine cannot currently observe trade-plan closure. Trade-plan lifecycle events (Trade Executed, Trade Plan Exit, Trade Plan Timer) are surfaced for alerts but are not available as transition triggers or guard inputs today. The state machine and the trade plan run side by side, not as a single pipeline.
  • What "In Position" is for, in practice. Since the state machine has no signal that the trade plan has closed the position, an In Position state should be treated as a re-entry cooldown gate — it prevents the strategy from firing another Execute Trade until enough time/events have passed for the trade plan to have plausibly closed the previous position. Use an After trigger (timer) or a context counter to leave In Position, not a guard on price action that pretends to detect closure.

Every pattern below assumes that the actual SL/TP/condition/timer exit logic is configured on the trade plan's Track Open Position settings — see Trade Plans → Track Open Position.


Pattern 1 — Simple entry/exit (the foundation)

The most fundamental strategy pattern: wait for an entry signal, take a position, then wait for the trade plan to close it. Almost every trading strategy is built on this skeleton.

The trading logic

  1. Monitor the market and wait for conditions that suggest a trade
  2. When conditions are met, enter a position (Execute Trade fires against a trade plan with Track Open Position enabled)
  3. While the position is open, the trade plan watches for its configured exit criteria (SL/TP, condition, timer)
  4. After the trade plan closes the position, return to monitoring for the next entry

States

StatePurpose
ScanningMonitoring the market, waiting for entry conditions
In PositionRe-entry cooldown — the trade plan is managing an open position; the strategy will not fire another entry until the cooldown expires

Set Scanning as the initial state.

Transitions

FromToTriggerConditionsActions
ScanningIn PositionNew CandleEntry conditions (e.g., RSI < 30)Execute Trade
In PositionScanningAfter (e.g., 30 min)(none)(none)

The entry transition fires Execute Trade, which logs a trade event using the parameters from your Trade Plan and — if Track Open Position is enabled — starts tracking the position. The exit transition is not what closes the trade. The trade plan's Track Open Position settings close it. The After trigger on In Position simply lets the state machine cycle back to Scanning after enough time has elapsed for the trade plan to have plausibly closed the position, so the strategy is free to look for the next entry signal.

Step by step

  1. Trade Plan: From the Strategy Toolbar, open Manage Trade Plans and create a plan (see Trade Plans). Set the instrument type (e.g., forex), direction (long), and configure stop loss and take profit (e.g., SL: 20 pips distance, TP: 40 pips distance).
  2. Enable Track Open Position on the trade plan and pick an exit mode:
    • SL/TP — close when price breaches your SL or TP
    • Condition — close when a guard you define on the plan evaluates true (e.g., RSI > 70)
    • Timer — close after a fixed duration
    • SL/TP + Condition or SL/TP + Timer — whichever triggers first This is where exit logic lives, not on the state-machine transition.
  3. Indicators: Subscribe to an RSI indicator (period 14) assigned to your data stream.
  4. States: Create Scanning and In Position. Set Scanning as initial.
  5. Guard — Entry: Create RSI_Oversold with condition RSI < 30.
  6. Action — Enter: Create Enter_Long with a Predefined Action → Execute Trade step, selecting your trade plan.
  7. Transition — Enter: On Scanning, add a New Candle transition to In Position with guard RSI_Oversold and action Enter_Long.
  8. Transition — Cooldown release: On In Position, add an After transition to Scanning with a duration long enough to cover the trade plan's expected hold time (for example, the timer mode's duration, or a conservative upper bound on how long SL/TP usually take to resolve). When this timer fires, the strategy is free to scan for the next entry.
  9. Data stream: Subscribe to a candle series data stream.

Why this works

The state machine emits the entry signal when the entry guard passes; the trade plan owns the entire lifecycle of the resulting open position via Track Open Position. The state machine has no visibility into when the position actually closes — so In Position is a cooldown gate, not a true mirror of position state. The After trigger releases the cooldown after a safe interval. If the cooldown is too short, the strategy may try to enter again before the previous trade has resolved; if it is too long, you miss valid follow-on entries. Tune it to match your trade plan's typical hold time.

For signal-only setups (no Track Open Position), trade lifecycle events, and alert integration with trade signals, see Trade Plans.


Pattern 2 — Confirmation with a staging state

In real trading, you often want to wait for a signal and then confirm it on the next bar before entering. A staging state handles this naturally.

The trading logic

  1. Monitor for an initial signal (e.g., price crosses above EMA)
  2. When the signal appears, move to a confirmation phase
  3. If the next bar confirms the move (e.g., close is still above EMA), enter the trade
  4. If confirmation fails, go back to monitoring

States

StatePurpose
ScanningWaiting for initial signal
ConfirmingSignal detected, waiting one more bar to confirm
In PositionTrade entered, watching for exit

Transitions

FromToTriggerConditionsActions
ScanningConfirmingNew CandleEMA_Cross_Up == true(none)
ConfirmingIn PositionNew CandleClose > EMA_50Execute Trade
ConfirmingScanningNew CandleClose <= EMA_50(none — signal failed)
In PositionScanningAfter (e.g., 30 min)(none)(none)

Step by step

  1. Trade Plan: Configure your trade plan with Track Open Position and choose an exit mode (SL/TP, Condition, Timer, or a combination) — this is what actually closes the trade.
  2. Context: Subscribe to an EMA indicator (period 50). Add an indicator event called EMA_Cross_Up (type: Cross, left: Close, right: EMA_50, polarity: Cross Up).
  3. States: Create Scanning, Confirming, In Position. Set Scanning as initial.
  4. Guard — Signal: Create EMA_Crossed_Up with condition EMA_Cross_Up == true (indicator event source).
  5. Guard — Confirmed: Create Price_Above_EMA with condition Close > EMA_50 (event source vs indicator source).
  6. Guard — Not Confirmed: Create Price_Below_EMA with condition Close <= EMA_50.
  7. Transition — Signal: ScanningConfirming on New Candle, guard EMA_Crossed_Up.
  8. Transition — Confirm: ConfirmingIn Position on New Candle, guard Price_Above_EMA, action Execute Trade (referencing your trade plan).
  9. Transition — Reject: ConfirmingScanning on New Candle, guard Price_Below_EMA.
  10. Transition — Cooldown release: In PositionScanning via an After trigger sized to cover the trade plan's expected hold time.

Why this works

The Confirming state acts as a buffer. The strategy only enters a position if two consecutive bars agree on the direction — the first with a crossover, the second with follow-through. This reduces false signals from single-bar noise.

tip

This confirmation pattern scales. You can chain multiple confirming states for stronger conviction signals (e.g., require 3 consecutive bars above EMA). Just add more intermediate states, each advancing on one New Candle with the confirmation condition.


Pattern 3 — Decision routing with always transitions

Use always transitions to create decision trees — the strategy lands in a routing state and immediately moves to the correct destination based on conditions, without waiting for a new event.

The trading logic

After the trade plan closes a position, decide whether to re-enter immediately (if conditions are still hot) or go fully idle until the market resets.

States

StatePurpose
In PositionHolding a trade
EvaluateRouting state — check conditions instantly
Re-entryConditions still favorable, ready to trade again
CooldownConditions exhausted, wait for the market to reset

Transitions

FromToTriggerConditionsActions
In PositionEvaluateAfter (e.g., 30 min)(none)(none)
EvaluateRe-entryAlwaysRSI < 50(none)
EvaluateCooldownAlwaysRSI >= 50(none)
Re-entryIn PositionNew CandleEntry conditionsExecute Trade
CooldownRe-entryNew CandleRSI < 40(none)

How to build it

  1. Create all four states. Set your initial state to whatever precedes In Position in your strategy.
  2. Create the Evaluate state as a routing node — it has no event transitions, only always transitions outward.
  3. On Evaluate, add an always transition to Re-entry with condition RSI < 50.
  4. On Evaluate, add an always transition to Cooldown with condition RSI >= 50.
  5. The conditions on the always transitions out of Evaluate should be mutually exclusive (one checks < 50, the other >= 50). This guarantees exactly one fires.
warning

Always transitions resolve instantly. If you have two always transitions out of a state, make sure one always fires (otherwise the strategy is stuck) and they are mutually exclusive (otherwise behavior depends on evaluation order). When in doubt, use complementary conditions like value < X and value >= X.

Why this works

Evaluate is a zero-cost routing node. The strategy passes through it instantly on export — the user never sees the strategy "sitting" in Evaluate, because it immediately resolves to either Re-entry or Cooldown. This keeps the state diagram clean while modeling a branching decision.


Pattern 4 — Trade counting with context variables

Track how many trades have been executed and stop entering new positions after a limit is reached.

The trading logic

  1. Enter trades when conditions are met
  2. Count each trade
  3. After N trades, stop entering — go to a "done" state
  4. Wait for a manual reset or specific condition to restart

Context variables

VariableTypeDefaultPurpose
tradeCountnumber0Tracks number of trades taken

States

StatePurpose
ScanningWaiting for entry
In PositionHolding a trade
Max TradesLimit reached, no more entries

Transitions

FromToTriggerConditionsActions
ScanningIn PositionNew CandleEntry conditions AND tradeCount < 3Execute Trade, Increment tradeCount
ScanningMax TradesNew CandletradeCount >= 3(none)
In PositionScanningAfter (e.g., 30 min)(none)(none)

How to build it

  1. Context: Add a custom variable tradeCount with type number and default 0.
  2. Guard — Under Limit: Create Under_Trade_Limit with conditions: your entry conditions AND tradeCount < 3 (context source, static source).
  3. Guard — At Limit: Create At_Trade_Limit with condition tradeCount >= 3.
  4. Action — Enter and Count: Create Enter_And_Count with two steps: a Predefined Action → Execute Trade step (selecting your trade plan), then an Operation step that increments tradeCount by 1.
  5. Wire the transitions as described above.

Why this works

The tradeCount variable persists across events. Each time the Entry action fires, the counter increments. Once it hits 3, the scanning state routes to Max Trades instead of entering a position. To reset, you would disable and re-enable the strategy (which resets context to defaults).


Pattern 5 — Multi-indicator confluence with indicator events

Require multiple indicators to align before entering a trade — a common approach for trend-following strategies. This pattern demonstrates how indicator events eliminate manual bookkeeping for detecting indicator movement.

The trading logic

Enter long when the trend is confirmed across multiple indicators:

  • Price is above its 50-period EMA (trend direction)
  • RSI is between 40-60 (not overbought/oversold — room to run)
  • Bollinger Bands are widening (volatility expanding)

The third condition — "Bollinger Bands are widening" — requires comparing the current BB upper value to its previous value. There is no built-in "previous bar" accessor in guard conditions. However, FORJ's indicator events handle this automatically.

Context setup

Subscribe to three indicators:

  • EMA with period 50
  • RSI with period 14
  • Bollinger Bands with period 20, deviation 2

Create one indicator event:

  • BB_Upper_Trending_Up — type: TREND, source: BB_upper (indicator source), polarity: Up

The TREND indicator event automatically compares the current bar's BB upper value to the previous bar's BB upper value. If the current value is higher, BB_Upper_Trending_Up resolves to true. This happens inside the event pipeline before the machine even sees the event — no custom context variables or action steps needed.

Guards

Guard nameConditions (all AND)
Confluence_EntryClose > EMA_50 AND RSI > 40 AND RSI < 60 AND BB_Upper_Trending_Up == true

The exit (closing the position) is configured on the trade plan via Track Open Position with a Condition exit mode, for example Close < EMA_50 OR RSI > 75. The state machine does not need a separate exit guard.

Operand sources for the entry guard:

  • Close — event source (current candle's close price)
  • EMA_50 — indicator source
  • RSI — indicator source
  • 40, 60 — static source
  • BB_Upper_Trending_Up — indicator event source (boolean)

How to build it

  1. Indicators: Ensure EMA (50), RSI (14), and Bollinger Bands (20, 2) are assigned to your data stream.
  2. Indicator event: In the Strategy Toolbar under AssetsManage Indicator Events, create BB_Upper_Trending_Up with type TREND, source set to the BB_upper output (indicator source), polarity Up.
  3. Entry guard: Create Confluence_Entry with four conditions in the All (AND) group: Close > EMA_50, RSI > 40, RSI < 60, BB_Upper_Trending_Up == true.
  4. Exit logic: On your trade plan, enable Track Open Position with mode Condition (or SL/TP + Condition), and configure the exit condition Close < EMA_50 OR RSI > 75. The trade plan engine evaluates this on every event and closes the position when it passes.
  5. States and transitions: Create a two-state strategy (Scanning → In Position) where the entry transition uses Confluence_Entry and the cycle-back transition uses an After trigger sized to cover the trade plan's expected hold time.
  6. Data stream: Subscribe to a candle series.
tip

Indicator events are the FORJ-idiomatic way to detect indicator movement (trending up/down) and crossovers between any two values. They compute automatically on each bar with no manual bookkeeping. Before reaching for a custom context variable, check whether a TREND or CROSS indicator event can express what you need.


Pattern 6 — Global event handler for emergency exits

Use a global event handler for logic that should fire from any state — like an emergency stop or a circuit breaker.

The trading logic

If a catastrophic market move occurs, immediately return to a safe state regardless of what the strategy is doing. Detect extreme conditions using indicator-based signals: a combination of how far price has deviated from its moving average and how severe the momentum reading is.

States

Your regular strategy states, plus:

StatePurpose
EmergencySafe state after an extreme move

Context setup

Subscribe to:

  • EMA with period 50 (measures trend deviation)
  • RSI with period 14 (measures momentum exhaustion)

Global event handler

TargetTriggerConditionsActions
EmergencyNew CandleClose < EMA_50 AND RSI < 15(optional: alert, log)

Operand sources:

  • Close — event source (current candle's close price)
  • EMA_50 — indicator source
  • RSI — indicator source
  • 15 — static source

How to build it

  1. Indicators: Ensure EMA (50) and RSI (14) are assigned to your data stream.
  2. State: Create an Emergency state with a New Candle transition back to your main scanning state (with or without conditions — maybe wait for volatility to subside before resuming).
  3. Global handler: In the Strategy Toolbar, select Add Global Event Handler. Set the target to Emergency, trigger to New Candle.
  4. Guard: Create Crash_Detected with two conditions in the All (AND) group: Close < EMA_50 (event source vs indicator source) and RSI < 15 (indicator source vs static source).
  5. This transition can fire from any state — it doesn't matter whether you're in Scanning, In Position, or any other state.

Why this works

Global handlers are evaluated alongside every state's own transitions but with lower priority. They provide a catch-all safety net without requiring you to add the same emergency transition to every state manually.


Pattern 7 — Trend following with EMA crossover

A classic strategy: enter when a fast EMA crosses above a slow EMA, exit when it crosses back below.

Context setup

  1. Subscribe to two EMA indicators: EMA with period 10 (fast) and EMA with period 50 (slow).
  2. Add two indicator events:
    • Fast_Crosses_Above_Slow — Cross event, left: EMA_10, right: EMA_50, polarity: Cross Up
    • Fast_Crosses_Below_Slow — Cross event, left: EMA_10, right: EMA_50, polarity: Cross Down

States and transitions

StatePurpose
WaitingNo position, waiting for crossover
LongIn a long position
FromToTriggerConditionsActions
WaitingLongNew CandleFast_Crosses_Above_Slow == trueExecute Trade
LongWaitingNew CandleFast_Crosses_Below_Slow == true(none — see signal-only note below)
Signal vs. position closure

The Long → Waiting transition cycles the state machine back to scanning when the bearish crossover fires, but it does not close any open position — the trade plan owns that. If you want a hard closure when the death cross fires, configure your trade plan's Track Open Position with a Condition exit mode that matches the same crossover (or pair this transition with a Do Nothing + alert action to emit a manual exit signal). See Trade Plans → Signal-only pattern.

Step by step

  1. Trade Plan: From the Strategy Toolbar, open Manage Trade Plans and create a plan with direction long and your SL/TP preferences (e.g., distance-based stop loss and take profit in pips).
  2. Indicators: Ensure both EMAs are assigned to your data stream and create both indicator events in the Strategy Toolbar under AssetsManage Indicator Events.
  3. Guards: Create Golden_Cross (condition: Fast_Crosses_Above_Slow == true) and Death_Cross (condition: Fast_Crosses_Below_Slow == true).
  4. Action: Create Enter_Long with a Predefined Action → Execute Trade step, selecting your trade plan.
  5. States and transitions: Wire as above. Set Waiting as initial.
  6. Data stream: Subscribe to a candle series.

This pattern demonstrates indicator events eliminating the need for manual crossover detection logic.


Pattern 8 — Mean reversion with Bollinger Bands

Buy when price touches the lower Bollinger Band, sell when it reaches the middle band. A classic mean reversion approach.

The trading logic

  1. Wait for price to touch or break below the lower Bollinger Band
  2. Confirm that RSI is also oversold (avoids entering during a strong downtrend)
  3. Enter a long position expecting price to revert toward the mean
  4. Exit when price reaches the middle band (the 20-period SMA)

Context setup

Ensure two indicators are assigned to your data stream:

  • Bollinger Bands with period 20, deviation 2 — provides BB_lower, BB_middle, and BB_upper outputs
  • RSI with period 14 — provides a single RSI value

States

StatePurpose
WatchingWaiting for price to reach lower band
In PositionHolding, waiting for reversion to mean

Set Watching as the initial state.

Guards

Guard nameConditionsOperand sources
Band_TouchClose <= BB_lower AND RSI < 30event vs indicator (BB_lower), indicator vs static (30)
Mean_ReachedClose >= BB_middleevent vs indicator (BB_middle)

Mean_Reached does not close the position by itself — see the signal-only note below.

Transitions

FromToTriggerGuardActions
WatchingIn PositionNew CandleBand_TouchExecute Trade
In PositionWatchingNew CandleMean_Reached(none — see signal-only note below)
Signal vs. position closure

The In Position → Watching transition cycles the state machine back when price reaches the middle band, but it does not close any open position. Configure your trade plan's Track Open Position with a Condition exit mode using Close >= BB_middle if you want the trade plan to actually close on this condition. See Trade Plans → Track Open Position.

How to build it

  1. Trade Plan: From the Strategy Toolbar, open Manage Trade Plans and create a plan with direction long and your risk parameters (e.g., forex instrument type, SL and TP as pip distances).
  2. Indicators: Ensure Bollinger Bands (20, 2) and RSI (14) are assigned to your data stream.
  3. Guard — Entry: Create Band_Touch with two conditions in the All (AND) group: Close <= BB_lower (event source vs indicator source) and RSI < 30 (indicator source vs static source).
  4. Guard — Reset: Create Mean_Reached with one condition: Close >= BB_middle (event source vs indicator source). This guards the state-machine cycle back — closure of the position itself is handled by the trade plan.
  5. Action: Create Enter_Long with a Predefined Action → Execute Trade step, selecting your trade plan.
  6. States: Create Watching and In Position. Set Watching as initial.
  7. Transitions: Wire WatchingIn Position (New Candle, guard Band_Touch, action Enter_Long) and In PositionWatching (New Candle, guard Mean_Reached).
  8. Data stream: Subscribe to a candle series that matches your context symbol and timeframe.

Why this works

The entry combines a Bollinger Band touch with RSI confirmation — requiring both prevents entering during a strong downtrend where price can ride the lower band for many bars. The middle-band target (the 20-period SMA) is the exit logic, configured on the trade plan as a Condition exit mode. The In Position → Watching state-machine transition cycles the strategy back so it can scan for the next setup.


Pattern 9 — Multi-stream strategies with cross-stream conditions

Strategies can subscribe to multiple data streams and react to different instruments or timeframes. This pattern demonstrates how to use cross-stream indicator references for multi-timeframe analysis.

The trading logic

Monitor a 1-hour chart for trend direction. Use a 5-minute chart for entry timing. Only enter on the 5-minute chart when the 1-hour trend agrees.

How cross-stream indicators work

When your strategy subscribes to multiple streams, each stream has its own indicator pipeline. When building conditions, you can reference indicators from any subscribed stream — not just the stream that triggered the current event. The condition operand selector lets you pick a specific stream for each side of a comparison.

This means you can directly compare a 5-minute EMA against a 1-hour EMA in a single condition — no context variables needed. FORJ resolves each stream's latest indicator snapshot automatically.

Setup

Subscribe to two data streams:

  • EURUSD 1h — 1-hour candles for trend direction
  • EURUSD 5m — 5-minute candles for entry timing

Subscribe to indicators on both streams:

  • EMA (period 50) assigned to the 1-hour stream
  • RSI (period 14) assigned to the 5-minute stream

States

StatePurpose
ScanningWaiting for alignment
In PositionHolding a trade

Set Scanning as the initial state.

Guards

Guard nameConditionsOperand sources
Trend_AlignedClose > [1h] EMA_50 AND [5m] RSI < 35event vs cross-stream indicator, triggering indicator vs static
Momentum_Reset[5m] RSI > 65triggering indicator vs static

The entry guard compares the current 5-minute close (event source) against the 1-hour stream's EMA 50 (cross-stream indicator reference), and checks that the 5-minute RSI is oversold. Momentum_Reset cycles the state machine back but does not close the position — see the signal-only note below.

Transitions

FromToTriggerGuardActions
ScanningIn PositionNew Candle (5min stream)Trend_AlignedExecute Trade
In PositionScanningNew Candle (5min stream)Momentum_Reset(none — see signal-only note below)
Signal vs. position closure

Momentum_Reset releases the state-machine cooldown when 5-minute RSI is overbought, freeing the strategy to look for the next aligned entry. It does not close the position. Configure the trade plan's Track Open Position with a Condition exit mode (e.g., RSI > 65) or an SL/TP if you want the trade plan to actually close.

How to build it

  1. Data sources: Create two data sources — one providing 1-hour candles, one providing 5-minute candles.
  2. Data subscriptions: Subscribe the strategy to both streams in the Data Subscriptions panel.
  3. Indicators: Assign EMA (50) to the 1-hour stream and RSI (14) to the 5-minute stream.
  4. Guard — Entry: Create Trend_Aligned with two conditions in the All (AND) group:
    • Left: Close (event source) > Right: [EURUSD 1h]EMA_50 (indicator source, selecting the 1-hour stream explicitly)
    • Left: RSI (indicator source, triggering stream) < Right: 35 (static)
  5. Guard — Reset: Create Momentum_Reset with condition RSI > 65 (triggering stream indicator vs static). This releases the state-machine cooldown; it does not close the position.
  6. Action: Create Enter_Long with a Predefined Action → Execute Trade step, selecting your trade plan.
  7. Transitions: Wire both transitions as described, with the 5-minute stream as the trigger for both.

Why this works

Cross-stream indicator references let each condition operand resolve against a specific stream's indicator snapshot. The 1-hour EMA stays current as 1-hour candles arrive and update the indicator pipeline. When a 5-minute candle triggers evaluation, the guard reads the latest 1-hour EMA value directly — no manual caching needed.

Since both transitions trigger only on the 5-minute stream, you don't need self-transitions to maintain the higher-timeframe data. FORJ handles indicator snapshot resolution per stream automatically.

tip

This pattern extends to any cross-stream scenario: comparing instruments ([EURUSD 5m] EMA_50 > [GBPUSD 5m] EMA_50), checking a volatility index against an equity indicator, or requiring confluence across timeframes. Each operand selects its own stream independently.

When to still use context variables

Cross-stream indicator references cover most multi-stream needs. Use context variables for values that indicators can't express — like storing a specific price level at entry, counting events across streams, or tracking custom flags that persist across transitions.


Pattern 10 — Session-gated trading with activation

Use Strategy Activation to restrict your strategy to specific market hours without adding time-based conditions to every transition.

The trading logic

Only process events during NYSE Regular Hours. Outside that window, the strategy stays in its current state and ignores all incoming candles — no transitions fire, no actions execute.

Setup

  1. Open the Strategy Toolbar and select Manage Strategy Activation from the Runtime section.
  2. Set the activation mode to Only When Rules Match.
  3. Add a Session Window rule and select NYSE Regular Hours.
  4. Leave the sub-window preset as Entire session.

Your strategy now only processes events between 9:30 AM and 4:00 PM Eastern. Events arriving outside this window are silently dropped.

Variations

GoalModeRule
Only trade during market hoursOnly When Rules MatchSession Window → NYSE Regular Hours, Entire session
Skip after-hours dataInactive When Rules MatchSession Window → NYSE After-Hours, Entire session
Only trade the opening rangeOnly When Rules MatchSession Window → NYSE Regular Hours, First 30 minutes
Blackout the closeInactive When Rules MatchSession Window → NYSE Regular Hours, Last 15 minutes
Weekday mornings only (London time)Only When Rules MatchLocal Time Range → Europe/London, 08:00–12:00, Mon–Fri

Combining activation with time conditions

Activation and time conditions are complementary:

  • Activation decides whether the event is processed at all — use it for broad scheduling.
  • Time conditions on transitions decide whether a specific transition fires — use them for fine-grained timing within your strategy logic.

Example: Activation restricts to NYSE Regular Hours. Inside the strategy, a time condition on the entry transition further restricts entries to after 10:00 AM (avoiding the volatile opening 30 minutes) while still processing all candles for indicator computation.


General design principles

Choosing the right tool for value comparisons

Many strategy patterns involve comparing a current value to a previous value. FORJ provides a hierarchy of tools for this, from most automated to most manual:

NeedToolEffort
Detect if an indicator is trending up or downTREND indicator event — source: indicator, polarity: up/downZero maintenance — computed automatically each bar
Detect if two values crossed each otherCROSS indicator event — left/right sources, polarity: up/down/anyZero maintenance — computed automatically each bar
Detect if an event payload field changed directionTREND indicator event — source: event, path: field nameZero maintenance — engine uses previous event payload automatically
Compare indicators across streams or timeframesCross-stream indicator reference in condition operandZero maintenance — select the target stream in the operand source selector
Store a value for use across events (not covered by indicators)Context variable + action assignmentManual — requires a context variable and an action step on relevant transitions
Track a running count or flag across eventsContext variable + action increment/assignManual — same pattern as Pattern 4

Indicator events and cross-stream references should be your first choices. They compute at the pipeline level before the strategy evaluates guards, require no custom context variables, and need no action steps on transitions. Reserve context variables for counters (Pattern 4), flags, and values that indicators can't express.

Start simple, iterate

Begin with the minimal two-state pattern (Pattern 1). Verify it works end-to-end — events arrive, conditions fire, trade events log, the strategy cycles back. Then add complexity one piece at a time: an extra state, an additional condition, a counter variable.

Name things clearly

Give states, guards, actions, and variables descriptive names that reflect their purpose:

  • States: Scanning, Awaiting_Confirmation, In_Long, Cooldown — not S1, S2, S3
  • Guards: RSI_Oversold, Price_Above_EMA, Under_Trade_Limit — not Guard1, Guard2
  • Actions: Enter_Long_With_Tight_SL, Increment_Counter — not Action1

Good names make the canvas readable and debugging much easier.

Use reusable guards and actions

Create guards and actions as named, reusable entities. Attach them to transitions by reference. When you change a guard's conditions, every transition using that guard updates automatically. The same applies to actions.

Test with CRUCIBLE before going live

Use CRUCIBLE to backtest your strategy against historical data before enabling it. This lets you verify that transitions fire at the right moments and trade entries/exits make sense, without risking real performance.

Use Performance Charts for debugging

When a strategy isn't behaving as expected, Performance Charts show you exactly what happened on each candle — which conditions passed, which transitions fired, and which actions executed. This is the first place to look when debugging.

Keep always transitions acyclic

Always transitions are useful for decision routing, but they must not form loops (see Anti-Patterns). The safe pattern is a one-way fan-out: one routing state with multiple always transitions going to different target states, where the targets do NOT have always transitions back to the routing state.

        ┌──always (cond A)──▶ State X
Routing─┤
└──always (cond B)──▶ State Y

Both State X and State Y should use event-triggered transitions for their onward flow.