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.
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)orTrade Plan Timerlifecycle 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 Positionstate 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 anAftertrigger (timer) or a context counter to leaveIn 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
- Monitor the market and wait for conditions that suggest a trade
- When conditions are met, enter a position (Execute Trade fires against a trade plan with Track Open Position enabled)
- While the position is open, the trade plan watches for its configured exit criteria (SL/TP, condition, timer)
- After the trade plan closes the position, return to monitoring for the next entry
States
| State | Purpose |
|---|---|
| Scanning | Monitoring the market, waiting for entry conditions |
| In Position | Re-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
| From | To | Trigger | Conditions | Actions |
|---|---|---|---|---|
| Scanning | In Position | New Candle | Entry conditions (e.g., RSI < 30) | Execute Trade |
| In Position | Scanning | After (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
- 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).
- 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.
- Indicators: Subscribe to an RSI indicator (period 14) assigned to your data stream.
- States: Create
ScanningandIn Position. SetScanningas initial. - Guard — Entry: Create
RSI_Oversoldwith conditionRSI < 30. - Action — Enter: Create
Enter_Longwith a Predefined Action → Execute Trade step, selecting your trade plan. - Transition — Enter: On
Scanning, add a New Candle transition toIn Positionwith guardRSI_Oversoldand actionEnter_Long. - Transition — Cooldown release: On
In Position, add anAftertransition toScanningwith 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. - 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
- Monitor for an initial signal (e.g., price crosses above EMA)
- When the signal appears, move to a confirmation phase
- If the next bar confirms the move (e.g., close is still above EMA), enter the trade
- If confirmation fails, go back to monitoring
States
| State | Purpose |
|---|---|
| Scanning | Waiting for initial signal |
| Confirming | Signal detected, waiting one more bar to confirm |
| In Position | Trade entered, watching for exit |
Transitions
| From | To | Trigger | Conditions | Actions |
|---|---|---|---|---|
| Scanning | Confirming | New Candle | EMA_Cross_Up == true | (none) |
| Confirming | In Position | New Candle | Close > EMA_50 | Execute Trade |
| Confirming | Scanning | New Candle | Close <= EMA_50 | (none — signal failed) |
| In Position | Scanning | After (e.g., 30 min) | (none) | (none) |
Step by step
- 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.
- 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). - States: Create
Scanning,Confirming,In Position. SetScanningas initial. - Guard — Signal: Create
EMA_Crossed_Upwith conditionEMA_Cross_Up == true(indicator event source). - Guard — Confirmed: Create
Price_Above_EMAwith conditionClose > EMA_50(event source vs indicator source). - Guard — Not Confirmed: Create
Price_Below_EMAwith conditionClose <= EMA_50. - Transition — Signal:
Scanning→Confirmingon New Candle, guardEMA_Crossed_Up. - Transition — Confirm:
Confirming→In Positionon New Candle, guardPrice_Above_EMA, action Execute Trade (referencing your trade plan). - Transition — Reject:
Confirming→Scanningon New Candle, guardPrice_Below_EMA. - Transition — Cooldown release:
In Position→Scanningvia anAftertrigger 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.
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
| State | Purpose |
|---|---|
| In Position | Holding a trade |
| Evaluate | Routing state — check conditions instantly |
| Re-entry | Conditions still favorable, ready to trade again |
| Cooldown | Conditions exhausted, wait for the market to reset |
Transitions
| From | To | Trigger | Conditions | Actions |
|---|---|---|---|---|
| In Position | Evaluate | After (e.g., 30 min) | (none) | (none) |
| Evaluate | Re-entry | Always | RSI < 50 | (none) |
| Evaluate | Cooldown | Always | RSI >= 50 | (none) |
| Re-entry | In Position | New Candle | Entry conditions | Execute Trade |
| Cooldown | Re-entry | New Candle | RSI < 40 | (none) |
How to build it
- Create all four states. Set your initial state to whatever precedes
In Positionin your strategy. - Create the
Evaluatestate as a routing node — it has no event transitions, onlyalwaystransitions outward. - On
Evaluate, add analwaystransition toRe-entrywith conditionRSI < 50. - On
Evaluate, add analwaystransition toCooldownwith conditionRSI >= 50. - The conditions on the
alwaystransitions out ofEvaluateshould be mutually exclusive (one checks< 50, the other>= 50). This guarantees exactly one fires.
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
- Enter trades when conditions are met
- Count each trade
- After N trades, stop entering — go to a "done" state
- Wait for a manual reset or specific condition to restart
Context variables
| Variable | Type | Default | Purpose |
|---|---|---|---|
tradeCount | number | 0 | Tracks number of trades taken |
States
| State | Purpose |
|---|---|
| Scanning | Waiting for entry |
| In Position | Holding a trade |
| Max Trades | Limit reached, no more entries |
Transitions
| From | To | Trigger | Conditions | Actions |
|---|---|---|---|---|
| Scanning | In Position | New Candle | Entry conditions AND tradeCount < 3 | Execute Trade, Increment tradeCount |
| Scanning | Max Trades | New Candle | tradeCount >= 3 | (none) |
| In Position | Scanning | After (e.g., 30 min) | (none) | (none) |
How to build it
- Context: Add a custom variable
tradeCountwith type number and default0. - Guard — Under Limit: Create
Under_Trade_Limitwith conditions: your entry conditions ANDtradeCount < 3(context source, static source). - Guard — At Limit: Create
At_Trade_Limitwith conditiontradeCount >= 3. - Action — Enter and Count: Create
Enter_And_Countwith two steps: a Predefined Action → Execute Trade step (selecting your trade plan), then an Operation step that incrementstradeCountby1. - 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 name | Conditions (all AND) |
|---|---|
Confluence_Entry | Close > 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 sourceRSI— indicator source40,60— static sourceBB_Upper_Trending_Up— indicator event source (boolean)
How to build it
- Indicators: Ensure EMA (50), RSI (14), and Bollinger Bands (20, 2) are assigned to your data stream.
- Indicator event: In the Strategy Toolbar under Assets → Manage Indicator Events, create
BB_Upper_Trending_Upwith type TREND, source set to the BB_upper output (indicator source), polarity Up. - Entry guard: Create
Confluence_Entrywith four conditions in the All (AND) group:Close > EMA_50,RSI > 40,RSI < 60,BB_Upper_Trending_Up == true. - 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. - States and transitions: Create a two-state strategy (Scanning → In Position) where the entry transition uses
Confluence_Entryand the cycle-back transition uses anAftertrigger sized to cover the trade plan's expected hold time. - Data stream: Subscribe to a candle series.
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:
| State | Purpose |
|---|---|
| Emergency | Safe 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
| Target | Trigger | Conditions | Actions |
|---|---|---|---|
| Emergency | New Candle | Close < EMA_50 AND RSI < 15 | (optional: alert, log) |
Operand sources:
Close— event source (current candle's close price)EMA_50— indicator sourceRSI— indicator source15— static source
How to build it
- Indicators: Ensure EMA (50) and RSI (14) are assigned to your data stream.
- State: Create an
Emergencystate with a New Candle transition back to your main scanning state (with or without conditions — maybe wait for volatility to subside before resuming). - Global handler: In the Strategy Toolbar, select Add Global Event Handler. Set the target to
Emergency, trigger to New Candle. - Guard: Create
Crash_Detectedwith two conditions in the All (AND) group:Close < EMA_50(event source vs indicator source) andRSI < 15(indicator source vs static source). - 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
- Subscribe to two EMA indicators: EMA with period 10 (fast) and EMA with period 50 (slow).
- Add two indicator events:
Fast_Crosses_Above_Slow— Cross event, left:EMA_10, right:EMA_50, polarity: Cross UpFast_Crosses_Below_Slow— Cross event, left:EMA_10, right:EMA_50, polarity: Cross Down
States and transitions
| State | Purpose |
|---|---|
| Waiting | No position, waiting for crossover |
| Long | In a long position |
| From | To | Trigger | Conditions | Actions |
|---|---|---|---|---|
| Waiting | Long | New Candle | Fast_Crosses_Above_Slow == true | Execute Trade |
| Long | Waiting | New Candle | Fast_Crosses_Below_Slow == true | (none — see signal-only note below) |
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
- 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).
- Indicators: Ensure both EMAs are assigned to your data stream and create both indicator events in the Strategy Toolbar under Assets → Manage Indicator Events.
- Guards: Create
Golden_Cross(condition:Fast_Crosses_Above_Slow == true) andDeath_Cross(condition:Fast_Crosses_Below_Slow == true). - Action: Create
Enter_Longwith a Predefined Action → Execute Trade step, selecting your trade plan. - States and transitions: Wire as above. Set
Waitingas initial. - 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
- Wait for price to touch or break below the lower Bollinger Band
- Confirm that RSI is also oversold (avoids entering during a strong downtrend)
- Enter a long position expecting price to revert toward the mean
- 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, andBB_upperoutputs - RSI with period 14 — provides a single
RSIvalue
States
| State | Purpose |
|---|---|
| Watching | Waiting for price to reach lower band |
| In Position | Holding, waiting for reversion to mean |
Set Watching as the initial state.
Guards
| Guard name | Conditions | Operand sources |
|---|---|---|
Band_Touch | Close <= BB_lower AND RSI < 30 | event vs indicator (BB_lower), indicator vs static (30) |
Mean_Reached | Close >= BB_middle | event vs indicator (BB_middle) |
Mean_Reached does not close the position by itself — see the signal-only note below.
Transitions
| From | To | Trigger | Guard | Actions |
|---|---|---|---|---|
| Watching | In Position | New Candle | Band_Touch | Execute Trade |
| In Position | Watching | New Candle | Mean_Reached | (none — see signal-only note below) |
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
- 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).
- Indicators: Ensure Bollinger Bands (20, 2) and RSI (14) are assigned to your data stream.
- Guard — Entry: Create
Band_Touchwith two conditions in the All (AND) group:Close <= BB_lower(event source vs indicator source) andRSI < 30(indicator source vs static source). - Guard — Reset: Create
Mean_Reachedwith 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. - Action: Create
Enter_Longwith a Predefined Action → Execute Trade step, selecting your trade plan. - States: Create
WatchingandIn Position. SetWatchingas initial. - Transitions: Wire
Watching→In Position(New Candle, guardBand_Touch, actionEnter_Long) andIn Position→Watching(New Candle, guardMean_Reached). - 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
| State | Purpose |
|---|---|
| Scanning | Waiting for alignment |
| In Position | Holding a trade |
Set Scanning as the initial state.
Guards
| Guard name | Conditions | Operand sources |
|---|---|---|
Trend_Aligned | Close > [1h] EMA_50 AND [5m] RSI < 35 | event vs cross-stream indicator, triggering indicator vs static |
Momentum_Reset | [5m] RSI > 65 | triggering 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
| From | To | Trigger | Guard | Actions |
|---|---|---|---|---|
| Scanning | In Position | New Candle (5min stream) | Trend_Aligned | Execute Trade |
| In Position | Scanning | New Candle (5min stream) | Momentum_Reset | (none — see signal-only note below) |
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
- Data sources: Create two data sources — one providing 1-hour candles, one providing 5-minute candles.
- Data subscriptions: Subscribe the strategy to both streams in the Data Subscriptions panel.
- Indicators: Assign EMA (50) to the 1-hour stream and RSI (14) to the 5-minute stream.
- Guard — Entry: Create
Trend_Alignedwith 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)
- Left:
- Guard — Reset: Create
Momentum_Resetwith conditionRSI > 65(triggering stream indicator vs static). This releases the state-machine cooldown; it does not close the position. - Action: Create
Enter_Longwith a Predefined Action → Execute Trade step, selecting your trade plan. - 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.
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.
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
- Open the Strategy Toolbar and select Manage Strategy Activation from the Runtime section.
- Set the activation mode to Only When Rules Match.
- Add a Session Window rule and select NYSE Regular Hours.
- 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
| Goal | Mode | Rule |
|---|---|---|
| Only trade during market hours | Only When Rules Match | Session Window → NYSE Regular Hours, Entire session |
| Skip after-hours data | Inactive When Rules Match | Session Window → NYSE After-Hours, Entire session |
| Only trade the opening range | Only When Rules Match | Session Window → NYSE Regular Hours, First 30 minutes |
| Blackout the close | Inactive When Rules Match | Session Window → NYSE Regular Hours, Last 15 minutes |
| Weekday mornings only (London time) | Only When Rules Match | Local 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:
| Need | Tool | Effort |
|---|---|---|
| Detect if an indicator is trending up or down | TREND indicator event — source: indicator, polarity: up/down | Zero maintenance — computed automatically each bar |
| Detect if two values crossed each other | CROSS indicator event — left/right sources, polarity: up/down/any | Zero maintenance — computed automatically each bar |
| Detect if an event payload field changed direction | TREND indicator event — source: event, path: field name | Zero maintenance — engine uses previous event payload automatically |
| Compare indicators across streams or timeframes | Cross-stream indicator reference in condition operand | Zero maintenance — select the target stream in the operand source selector |
| Store a value for use across events (not covered by indicators) | Context variable + action assignment | Manual — requires a context variable and an action step on relevant transitions |
| Track a running count or flag across events | Context variable + action increment/assign | Manual — 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— notS1,S2,S3 - Guards:
RSI_Oversold,Price_Above_EMA,Under_Trade_Limit— notGuard1,Guard2 - Actions:
Enter_Long_With_Tight_SL,Increment_Counter— notAction1
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.