Strategy Anti-Patterns
This page covers common design mistakes that make strategies brittle, confusing, or hard to trust in production.
Infinite transition loops
A classic failure mode is an unbroken cycle of always transitions:
State A --always--> State B --always--> State A
Because always transitions fire immediately on state entry, the machine can spin internally without waiting for a new event.
How to avoid it:
- replace at least one leg of the cycle with an event-triggered transition, or
- add a guard that genuinely breaks the loop
Dead-end states
A dead-end state has outgoing transitions on paper, but none of them can realistically fire.
Typical causes:
- every guard on the outgoing transitions is too strict or contradictory
- the trigger never matches the events the strategy actually receives
How to avoid it:
- make sure at least one outgoing path is realistically reachable
- verify stream subscriptions and trigger types match the incoming event flow
Unreachable states
An unreachable state has no path into it from the rest of the machine, unless it is the initial state.
How to avoid it:
- inspect incoming paths on the canvas
- use Manage Transitions grouped by target state
- delete unused states instead of leaving dead structure in the graph
Guards that never pass
This happens when a reusable guard contains impossible or contradictory conditions, such as:
RSI > 200- two conflicting equality checks in the same required group
- conditions built on context variables that never change
How to avoid it:
- start with the simplest version of the rule
- verify each condition independently
- then combine them into the final guard
Guards that always pass
The opposite failure mode is a guard that is effectively always true.
Examples:
RSI > 0- comparing a value to itself
- adding a broad
ORbranch that matches almost everything
How to avoid it:
- tighten the threshold
- remove trivial comparisons
- make sure the rule describes a real market scenario rather than a near-constant state
Missing stream subscriptions
An event-triggered strategy can look structurally valid and still do nothing if it is not subscribed to the stream its transitions expect.
How to avoid it:
- configure the correct stream in the Strategy Toolbar
- verify the transition trigger references the same event source the strategy actually receives
Treating conditions as if they were direct transition assets
In FORJ, transitions attach guards, and guards contain conditions. Trying to reason about transitions as if each transition owns its own standalone condition set leads to confusion when you start reusing guards across multiple transitions.
How to avoid it:
- think of conditions as building blocks
- think of guards as the reusable transition-level asset
Editing shared guards or actions without checking reuse
Guards and actions are shared definitions. If you edit one, every transition that references it changes too.
How to avoid it:
- review where a shared guard or action is used before editing it
- duplicate the asset when you need similar but not identical behavior
Overloading a single state
Too many overlapping transitions from one state make runtime behavior hard to predict.
How to avoid it:
- split broad states into smaller, more focused states
- use routing states intentionally
- keep each state's outgoing transitions as distinct as possible