Debugging Strategies
Even a well-designed strategy will occasionally behave in ways you don’t expect — a rule doesn’t fire when you thought it would, a variable holds the wrong value, or a stop triggers at the wrong time. Debugging a strategy in Stratifyre is less about hunting bugs in code and more about inspecting state: looking at what each rule saw, what each variable held, and why each condition evaluated the way it did.
This guide covers the tools Stratifyre gives you for exactly that.
The debugging mindset
Section titled “The debugging mindset”Before reaching for tools, it helps to think about strategies the same way a programmer thinks about programs: state flows through rules, and unexpected behavior almost always means unexpected state. When something is wrong, the question to ask is:
“Which variable held a value I didn’t expect, and which rule put it there?”
Because Stratifyre’s rule model is built around named variables (see Why variables exist), answering that question is usually a matter of reading values, not tracing control flow. This is a deliberate design advantage over rule engines that rely on deeply nested expressions — you get to see inside the logic, not just its final output.
Inspecting rule evaluation in the backtester
Section titled “Inspecting rule evaluation in the backtester”After running a backtest, the results view includes a Rule Evaluation panel for each bar where any rule fired (or could have fired). For each rule, it shows:
- Whether the rule fired on that bar
- The evaluated value of each condition’s LHS and RHS — so you can see the actual numbers being compared, not just the expression
- Which action(s) ran and their parameters (order side, quantity, prices)
- The current values of all variables the rule read or wrote
This per-bar snapshot is the single most useful debugging tool. If your entry rule didn’t fire on a bar where you thought it should, open that bar in the Rule Evaluation panel and compare the shown LHS/RHS values against your mental model. Nine times out of ten, the answer is that an indicator was one bar behind what you expected, or a variable was never set in the first place.
Using ALERT actions for print-debugging
Section titled “Using ALERT actions for print-debugging”For situations where the built-in inspection tools aren’t enough — or when you’re debugging a strategy running in live or paper trading where there’s no backtest timeline to inspect — the Send Alert action is an excellent “print statement” substitute.
The alert message field accepts expressions in curly braces, so you can emit the current state of anything you care about:
Entry fired. RSI={RSI(14)} price={price} entryPrice={vars.entryPrice} streak={vars.streak}Add an alert-only rule that fires under the same conditions as the rule you’re suspicious of. It won’t affect trading logic at all, but every alert it emits is a timestamped snapshot of exactly what the engine saw at that moment. Common uses:
- Boundary debugging — “Does this rule even see the condition I think it should?” Add a broad alert rule that fires on a superset of your conditions, so you can confirm the engine is reaching that point at all.
- Variable tracing — Emit a variable’s value on every bar to track when it changed. Pair with a concrete event in the message so you can grep alerts later.
- Decision logging — “Why did the exit rule fire instead of the trailing-stop rule?” Have both rules emit distinct alerts so you can see which triggered first.
Reading the strategy trace
Section titled “Reading the strategy trace”Live and paper-trading strategies maintain a trace log accessible from the strategy detail view. It records every rule evaluation with:
- Timestamp
- Rule name
- Fired / did not fire
- The per-condition LHS and RHS values (same format as the backtester)
- Any actions taken and their parameters
- Any errors or validation failures
The trace is your primary debugging tool for live strategies. When a live strategy behaves unexpectedly, find the bar where the surprise happened in the trace and read through the evaluations — the numbers don’t lie.
Common issues and where to look
Section titled “Common issues and where to look”A rule didn’t fire when you expected
Section titled “A rule didn’t fire when you expected”- Check the Rule Evaluation panel for that bar. If the rule is listed but marked “not fired,” look at the condition values — one of them wasn’t what you thought.
- Check the rule’s frequency setting. A rule with
Oncewill never fire twice in a run. A rule withOnce per periodmay be on cooldown. See Execution frequency. - Check whether an earlier rule consumed the trigger. If you have a risk rule at the top of the strategy that flattens first, a later entry rule may see
position.open_qty > 0becomes== 0on the same bar and skip its own logic.
A variable holds the wrong value
Section titled “A variable holds the wrong value”- Check the Variable Chart to find the exact bar where it changed.
- Look at which rule wrote it. In the Rule Evaluation panel for that bar, every rule that performed a
Set Variableaction is listed with the new value. - Watch for missing resets. Counters and flags that should reset each day often don’t because the reset rule was placed in the wrong section or used the wrong frequency.
A rule fires more often than expected
Section titled “A rule fires more often than expected”- Frequency is almost always the cause. Entry rules should usually be
OnceorOnce per period, notNo limit. The default changes this behavior significantly. - Check for stuck latches. If your entry depends on
vars.armed == 1, and you never set it back to 0 after firing, the rule will re-trigger every bar that the other conditions are met.
A trailing stop doesn’t trail
Section titled “A trailing stop doesn’t trail”- Make sure the update rule has the “one-way” condition. A rule that unconditionally sets
vars.trailingStop = price - ATR(14) * 1.5will move the stop down when price pulls back. The right pattern is to only update when the new value is higher than the current one — see the ATR trailing-stop example.
A global variable isn’t shared
Section titled “A global variable isn’t shared”- Confirm the scope is actually Global, not Local.
vars.xxxandglobals.xxxlook similar but are completely separate. Check theSet Variableaction’s Scope setting. - Global variables persist across instruments, but not across strategy runs. They reset at the start of every backtest or when a live strategy restarts.
When to reach for a minimal repro
Section titled “When to reach for a minimal repro”If a bug is stubborn and you can’t find the cause from the tools above, the next step is to reduce the strategy to its smallest version that still reproduces the problem. Duplicate the strategy, then:
- Delete any rule that isn’t involved in the specific misbehavior.
- Simplify expressions — replace
RSI(14) < 30 && price > SMA(200)with justRSI(14) < 30if the SMA filter isn’t relevant. - Reduce the instrument universe to one symbol.
- Shrink the backtest date range to a single day that contains the issue.
A three-rule, one-symbol, one-day repro is almost always enough to pinpoint what’s going wrong — and if you need to ask for help in Stratifyre’s community channels, a minimal repro makes it dramatically easier for others to help you.
Best practices
Section titled “Best practices”- Name variables descriptively.
vars.bullishSetupArmedis self-documenting;vars.v1is not. When something breaks, descriptive names save you from guessing what you meant. - Name rules descriptively. “Arm on daily trend + hourly pullback” beats “Rule 3.” Rule names are the first thing you see in the evaluation panel.
- Add ALERT rules early. When you’re building a complex strategy, add debug alerts from the start, not after things break. You can disable them once the strategy is working.
- Test patterns in isolation. When you add a new pattern (a counter, a latch, a state machine), build it in a minimal test strategy first to confirm it behaves correctly, then copy it into your real strategy.
- Read the first bar carefully. Many bugs show up on the very first bar of a strategy because variables are uninitialized (they default to 0). If a condition reads
bar_index - vars.lastExitBar > 10, the first bar will be true becausevars.lastExitBarstarts at 0.
Next steps
Section titled “Next steps”- Browse the Strategy Cookbook for the building blocks that make strategies debuggable by design.
- Review Expressions for the philosophy behind Stratifyre’s variable-first model.
- Check Rules — common mistakes for a quick reference of the most frequent pitfalls.
