Rules
Rules are the building blocks of every strategy in Stratifyre. Each rule defines a set of conditions to watch for and an action to take when those conditions are met. By combining multiple rules, you build a complete trading system that handles entries, exits, risk management, and more.
Anatomy of a rule
Section titled “Anatomy of a rule”Every rule has three parts:
- Conditions – One or more “if this, then that” checks that must all be true at the same time for the rule to fire.
- Action – What happens when all conditions are met (place an order, close a position, send an alert, etc.).
- Execution frequency – How often the rule is allowed to fire (every time, once, or once per period).
Conditions
Section titled “Conditions”A condition compares two values using an operator. You build conditions visually by selecting values from dropdown menus:
- Left-hand side (LHS) – The value you are measuring. This can be a technical indicator (like RSI or SMA), a price field (like Close or High), a volume value, an account metric, a position metric, a time value, or a custom expression.
- Operator – How the two sides are compared. Stratifyre offers 15 operators that go beyond simple greater-than / less-than comparisons.
- Right-hand side (RHS) – The value you are comparing against. This can be a number, another indicator, a price field, or any expression.
When a rule has multiple conditions, you connect them with AND or OR operands. Each pair of adjacent conditions has an operand between them, so a rule with 3 conditions has 2 operands, a rule with 4 conditions has 3 operands, and so on.
Evaluation context and rule scope
Section titled “Evaluation context and rule scope”Rule conditions are evaluated in a current-instrument context on each bar or tick. The strategy’s selected instruments and baskets define that default universe, and a backtest can override it for one run.
allscope – Evaluate the rule once for every instrument in the active universe.specificscope – Evaluate the rule only for the instruments listed on that rule.nonescope – Evaluate the rule at the portfolio level with no implicit current instrument.
Inside all or specific scope, values like price, close, position.*, vars.*, and bars['5m'] all refer to the instrument currently being evaluated. Inside none scope, there is no implicit instrument, so cross-instrument bar access must use an explicit reference such as bars['EQ:NASDAQ:QQQ~5m'].close.
Fibonacci rule examples
Section titled “Fibonacci rule examples”Fibonacci indicators are rule-usable like any other numeric indicator output. For example:
close > FIB_LEVELS({lookback: 120, anchorProvider: 'swing'}).extension1618FIB_PATTERN({pattern: 'gartley', lookback: 200}).matched == 1FIB_PATTERN({pattern: 'bat', lookback: 200}).completionPrice > closeFIB_LEVELS exposes named retracement and extension fields from the selected swing anchor. FIB_PATTERN exposes scalar pattern fields such as matched, confidence, completionPrice, and target1, so you can compare them directly in a condition without unpacking nested objects.
How mixed AND/OR is evaluated
Section titled “How mixed AND/OR is evaluated”Operands are evaluated strictly left-to-right with no precedence — AND does not bind tighter than OR the way it does in most programming languages. This is deliberate, because it keeps rule evaluation transparent and predictable. Here is what that means concretely:
| Conditions and operands | Evaluated as |
|---|---|
A AND B AND C |
A AND B AND C (all three must be true) |
A OR B OR C |
A OR B OR C (any one must be true) |
A AND B OR C |
(A AND B) OR C |
A OR B AND C |
(A OR B) AND C — not A OR (B AND C) |
A AND B OR C AND D |
((A AND B) OR C) AND D — not (A AND B) OR (C AND D) |
The last row is the one that surprises people: because there is no precedence, you cannot express (A AND B) OR (C AND D) as a single rule. If you need that shape, see Expressing complex boolean logic below for two clean ways to write it.
Condition operators
Section titled “Condition operators”Stratifyre provides 15 operators that let you express a wide range of market situations. Here is what each one does, with a practical example.
Equals
Section titled “Equals”What it checks: The left side is exactly equal to the right side.
Example: Position Quantity equals 0 – True when you have no open position.
Not Equals
Section titled “Not Equals”What it checks: The left side is not equal to the right side.
Example: Position Quantity not equals 0 – True when you have an open position (long or short).
Greater Than
Section titled “Greater Than”What it checks: The left side is strictly larger than the right side.
Example: RSI(14) greater than 70 – True when RSI is above the overbought threshold.
Less Than
Section titled “Less Than”What it checks: The left side is strictly smaller than the right side.
Example: RSI(14) less than 30 – True when RSI is below the oversold threshold.
Greater Than or Equal
Section titled “Greater Than or Equal”What it checks: The left side is greater than or equal to the right side.
Example: Volume greater than or equal to 1000000 – True when volume reaches at least one million.
Less Than or Equal
Section titled “Less Than or Equal”What it checks: The left side is less than or equal to the right side.
Example: ATR(14) less than or equal to 0.50 – True when volatility is at or below a certain level.
Crossing
Section titled “Crossing”What it checks: The left side crossed the right side in either direction on the most recent bar. On the previous bar they were on one side; now they are on the other.
Example: MACD Line crossing Signal Line – True the moment MACD crosses the signal line, whether from above or below.
Crossing Up
Section titled “Crossing Up”What it checks: The left side was below (or equal to) the right side on the previous bar and is now above it. This detects upward crossovers.
Example: SMA(20) crossing up SMA(50) – The classic “golden cross” signal for trend-following entries.
Crossing Down
Section titled “Crossing Down”What it checks: The left side was above (or equal to) the right side on the previous bar and is now below it. This detects downward crossovers.
Example: SMA(20) crossing down SMA(50) – The “death cross” signal, often used for exits or short entries.
Entering Channel
Section titled “Entering Channel”What it checks: The left-side value has moved inside a range defined by the right side (a channel with an upper and lower boundary). On the previous bar it was outside; now it is inside.
Example: Price entering channel Bollinger Bands(20, 2) – True when price re-enters the Bollinger Band envelope after being outside it.
Exiting Channel
Section titled “Exiting Channel”What it checks: The left-side value has moved outside the channel defined by the right side. On the previous bar it was inside; now it is outside.
Example: Price exiting channel Keltner Channel(20, 1.5) – True when price breaks out of the Keltner Channel, signaling a potential volatility expansion.
Moving Up
Section titled “Moving Up”What it checks: The left-side value increased from the previous bar to the current bar by at least the absolute amount specified on the right side.
Example: Price moving up 2.00 – True when price has risen by at least $2.00 since the last bar.
Moving Down
Section titled “Moving Down”What it checks: The left-side value decreased from the previous bar to the current bar by at least the absolute amount specified on the right side.
Example: Price moving down 1.50 – True when price has fallen by at least $1.50 since the last bar.
Moving Up %
Section titled “Moving Up %”What it checks: The left-side value increased by at least the percentage specified on the right side, relative to its previous-bar value.
Example: Volume moving up % 50 – True when volume has jumped by 50% or more compared to the prior bar. Useful for detecting volume spikes.
Moving Down %
Section titled “Moving Down %”What it checks: The left-side value decreased by at least the percentage specified on the right side, relative to its previous-bar value.
Example: Price moving down % 3 – True when price has dropped by 3% or more in a single bar. Useful for detecting sharp selloffs.
Actions
Section titled “Actions”When all of a rule’s conditions are met, the rule executes its action. Stratifyre supports the following action types:
Place Order
Section titled “Place Order”Opens a new position or adds to an existing one. When you select this action, you configure:
- Side – Buy or Sell
- Order type – Market, Limit, Stop, Stop-Limit, Bracket, TWAP, VWAP, or Iceberg (see Order Types)
- Quantity – A fixed number or a dynamic expression (for example,
account.equity * 0.02 / ATR(14)for risk-based sizing) - Price (for Limit and Stop orders) – A fixed price or an expression
- Time-in-force – DAY, GTC, IOC, or FOK
Cancel Orders
Section titled “Cancel Orders”Removes pending orders that have not yet been filled. Options include:
- Cancel – Cancel pending orders for the current instrument
- Cancel All – Cancel all pending orders across all instruments
Close Position
Section titled “Close Position”Exits an open position. Options include:
- Flatten – Close the position for the current instrument
- Flatten All – Close all positions across all instruments
- Cancel and Flatten All – Cancel all pending orders and close all positions at once
Send Alert
Section titled “Send Alert”Sends a notification (email, push notification, or webhook) without placing any trade. This is useful for monitoring conditions or for strategies that you want to execute manually.
You can include dynamic text in the alert message that references current market data, indicator values, or account information.
Set Variable
Section titled “Set Variable”Stores a value in a custom variable that other rules can reference later. This is how you implement logic that spans multiple rules – for example, recording the entry price in one rule and referencing it in a trailing-stop rule.
- Variable name – A descriptive name like “entryPrice” or “trailingStopLevel”
- Value – A number, price, indicator value, or expression
- Scope – Local (per instrument) or Global (shared across all instruments in the strategy)
See Expressions for details on using variables, and Multi-Instrument Strategies for how local and global variables differ.
Execution frequency
Section titled “Execution frequency”The frequency setting controls how often a rule is allowed to fire:
No limit
Section titled “No limit”The rule fires every time its conditions are met, on every bar evaluation. This is appropriate for:
- Trailing-stop updates that need to adjust continuously
- Alert rules that should notify you whenever a condition recurs
- Variable updates that need to track changing values
The rule fires at most one time for the entire strategy run. After it triggers once, it will not trigger again even if the conditions become true again later. This is appropriate for:
- One-time entry signals where you only want a single position
- Initial setup actions at the start of a strategy
Once per period
Section titled “Once per period”The rule fires at most one time within each time interval you specify (for example, once per hour, once per day, once per week). After firing, it becomes eligible again when the next period begins. This is appropriate for:
- Daily breakout entries that should only trigger once per day
- Periodic rebalancing actions
- Rate-limited alert notifications
Expressing complex boolean logic
Section titled “Expressing complex boolean logic”Rules support mixed AND/OR operands, but they are evaluated left-to-right with no precedence and no parenthesized grouping. That means shapes like (A AND B) OR (C AND D) cannot be expressed as a single rule’s condition list. Fortunately, anything a nested boolean tree can express can be written more readably using either custom variables, the expression language itself, or state machines across multiple rules. All three approaches produce strategies that are easier to debug than nested logic would be.
Approach 1: Decompose with variables
Section titled “Approach 1: Decompose with variables”The canonical shape that doesn’t fit into flat operands is (A AND B) OR (C AND D). You can flatten it into helper rules that set flags, followed by a consumer rule that checks them. For example, suppose you want:
Buy when (trend is up AND RSI is oversold) OR (price broke resistance AND volume confirmed).
You can express this as three rules:
- Helper rule — “Pullback setup”: conditions check
SMA(50) > SMA(200)ANDRSI(14) < 30. Action:Set VariablepullbackSignal = 1(local scope). - Helper rule — “Breakout setup”: conditions check
close > MAX(high, 20)[1]ANDvolume > AVG(volume, 20) * 2. Action:Set VariablebreakoutSignal = 1(local scope). - Entry rule: condition
vars.pullbackSignal == 1 || vars.breakoutSignal == 1. Action: place order.
The helper rules have names (pullbackSignal, breakoutSignal) that document your intent, and you can inspect each flag independently when a strategy misfires. A deeply nested expression cannot be inspected mid-evaluation.
Approach 2: Count signals inline
Section titled “Approach 2: Count signals inline”For “any N of M” confirmation patterns — “enter if at least 2 of these 4 signals agree” — you do not need helper rules at all. Boolean comparisons in expressions evaluate to 1 (true) or 0 (false), so you can add them:
(RSI(14) < 30) + (MACD().histogram > 0) + (close > SMA(50)) + (volume > AVG(volume, 20)) >= 2This is a single condition that fires when at least 2 of the 4 signals are true. See Counting signals with expressions in the Expressions guide for the full pattern, including weighted voting.
Approach 3: Stateful sequences
Section titled “Approach 3: Stateful sequences”Some logic is not boolean at all — it is sequential. “RSI crossed 30 while price was above VWAP, then later broke the prior high” cannot be expressed with nested AND/OR because it involves ordering. Use Set Variable to latch state when the first event occurs, then check the latched flag in a second rule that watches for the follow-up event:
- Rule 1 — Condition:
RSI(14) crossing up 30 && price > VWAP(). Action:Set Variablearmed = 1(local). - Rule 2 — Condition:
vars.armed == 1 && high > MAX(high, 10)[1]. Action: place order, thenSet Variablearmed = 0.
This state-machine pattern handles arbitrarily complex sequential conditions that boolean trees simply cannot express.
Rule priority and organization
Section titled “Rule priority and organization”Rules are evaluated in the order they appear in the strategy builder, from top to bottom. This order matters because:
- Higher-priority rules execute first. If a risk-management rule and an entry rule both trigger on the same bar, the one listed first takes priority.
- You can drag and drop rules to reorder them in the strategy builder.
A recommended ordering approach:
- Risk-management rules at the top (stop-losses, maximum drawdown checks, daily loss limits)
- Exit rules in the middle (profit targets, indicator-based exits)
- Entry rules toward the bottom (new position signals)
This way, your protective rules always get evaluated before any new positions are opened.
Common mistakes
Section titled “Common mistakes”These are the most frequent rule-authoring pitfalls we see — especially from users coming from other rule engines or from LLM-generated strategies. Each one has a quick fix.
The entry rule fires every single bar
Section titled “The entry rule fires every single bar”Cause: Trigger frequency is set to No limit. Entry rules should almost never use this setting.
Fix: Change it to Once (fire at most once for the whole run) or Once per period (fire at most once per interval, such as daily). No limit is appropriate for continuous-update rules like trailing-stop adjustments, not for entries.
A flag stays “sticky” and re-fires forever
Section titled “A flag stays “sticky” and re-fires forever”Cause: You set vars.signal = 1 on a condition, but nothing ever sets it back to 0. Every subsequent bar sees vars.signal == 1 and triggers the consumer rule again.
Fix: Add a reset rule. Either explicitly clear the flag at the start of each period (time.min_since_rth_open == 0 → vars.signal = 0), or have the consumer rule reset it as part of its action (place order; vars.signal = 0).
Mixed AND/OR produces a surprising result
Section titled “Mixed AND/OR produces a surprising result”Cause: You wrote A AND B OR C AND D expecting (A AND B) OR (C AND D). Because operands evaluate left-to-right with no precedence, it actually computes ((A AND B) OR C) AND D.
Fix: For grouped shapes like (A AND B) OR (C AND D), use two helper rules with variables (see Expressing complex boolean logic). For simple cases, reorder the conditions so left-to-right evaluation matches your intent.
The first bar of a strategy behaves wrong
Section titled “The first bar of a strategy behaves wrong”Cause: Variables default to 0, so conditions like bar_index - vars.lastExitBar > 10 will be true on bar 0 (because 0 - 0 > 10 is false — but 100 - 0 > 10 is true, and the second bar onward will fail for a different reason). Unset anchored levels read as 0, which may accidentally satisfy comparisons.
Fix: Either initialize variables explicitly in an early portfolio rule, or gate your conditions on whether the variable has been set (for example, vars.lastExitBar > 0 && bar_index - vars.lastExitBar > 10).
A risk rule “disappears” the entry signal on the same bar
Section titled “A risk rule “disappears” the entry signal on the same bar”Cause: Rules execute in order. If a risk-management rule at the top of your strategy closes a position on the same bar that an entry rule wants to open one, the position state changes mid-evaluation and the entry rule sees the new state.
Fix: Confirm the rule order is intentional (risk rules at the top is usually correct). If you don’t want entry rules to see the effect of same-bar exits, add a condition like vars.flatThisBar == 0 to the entry rule, and have the exit rule set that flag.
Global and local variables are confused
Section titled “Global and local variables are confused”Cause: vars.x (local) and globals.x (global) look similar, but they are completely separate. Writing to one does not affect the other. In a multi-instrument strategy, local variables are unique per instrument, so “the same vars.x” in two different instruments are actually two different values.
Fix: Double-check the Set Variable action’s Scope setting. Use global scope for anything that needs to coordinate across instruments (position caps, daily loss limits, leader signals). Use local scope for anything that should be unique per instrument (entry prices, trailing stops, per-symbol counters).
A portfolio rule reads instrument data unexpectedly
Section titled “A portfolio rule reads instrument data unexpectedly”Cause: none-scoped rules do not have a current instrument. Expressions like bars['5m'].close, price, or position.open_qty only make sense when a rule is evaluating a specific instrument.
Fix: Move the rule to all or specific scope if it should run per instrument. Keep none scope for portfolio logic, and use explicit instrument references such as bars['EQ:NASDAQ:QQQ~5m'].close when the rule needs instrument bar data.
A rule runs on the wrong side of the market
Section titled “A rule runs on the wrong side of the market”Cause: The condition is symmetric — for example, RSI(14) crossing 30 matches both crossing down (into oversold) and crossing up (out of oversold), depending on which Crossing operator you picked.
Fix: Use the directional operators Crossing Up or Crossing Down when you care which direction. The plain Crossing operator fires on either direction.
The “Once per period” rule fires at the wrong time of day
Section titled “The “Once per period” rule fires at the wrong time of day”Cause: Once per period with period 1D means “once per calendar day,” but strategies running during regular trading hours may interpret this differently when there’s no bar at midnight.
Fix: If you want “once at the session open,” combine the condition with time.min_since_rth_open == 0 (or < 15 for “within the first 15 minutes”). If you want “once anywhere in the day,” the standard Once per period with 1D is correct — just make sure your strategy’s primary timeframe emits bars during that period.
Best practices
Section titled “Best practices”- Keep conditions focused. A rule with one or two clear conditions is easier to understand and debug than one with five or six.
- Use descriptive rule names. Names like “RSI Oversold Long Entry” or “ATR Trailing Stop Exit” make strategies much easier to maintain.
- Group related rules together. Keep all your entry rules in one section, exit rules in another, and risk rules at the top.
- Test rule interactions. When you have many rules, backtest to make sure they interact the way you expect – especially when multiple rules can trigger on the same bar.
- Start with fewer rules and add more over time. It is much easier to diagnose issues in a strategy with three rules than one with fifteen.
