Appearance

Personalize your experience

Mode
Accent Color
Direction
Debugging Strategies

Debugging Strategies

Coding

A method for diagnosing strategies that do nothing, do too much, or do the wrong thing — plus the specific symptoms that have specific causes.

Bisection beats inspection

bugs resist reading. A can be completely legible and still not do what it says, because the failure is usually an interaction — between a and a warm-up , between a trigger and a cooldown, between a timezone and a . Staring at it does not interactions.

Bisection does. Delete conditions until the behaviour changes, and the last thing you removed is implicated. It takes minutes and it is reliable in a way that inspection is not.

  1. 1
    Reduce to the trigger alone
    Delete every . Keep the single or comparison meant to fire the entry. Run the backtest.
  2. 2
    Confirm the trigger fires at all
    If there are still no , the problem is the , the range, or the warm-up — not the filters. Stop here and fix that.
  3. 3
    Add filters back one at a time
    Run after each. The that takes the to zero is the culprit, and now you know it exactly rather than suspecting it.
  4. 4
    Inspect that filter’s values
    the indicator on the chart and read the range it actually occupies. Most guilty filters have a threshold the series never reaches.
  5. 5
    Widen, do not delete
    A that never passes is usually in spirit and wrong in threshold. ADX > 40 becoming ADX > 20 often restores the and keeps the intent.
★
Pro tip:Do this on a short — a month is plenty. Fast iterations matter more than significance while you are still finding out why nothing happens.

No trades at all

The commonest complaint, and it has six causes worth checking in this order because that is roughly their frequency.

CauseTypeDescription
Two triggers ANDedlogicTwo crossovers in one AND group must make their transitions on the same bar. Keep one as the ; rewrite the other as a comparison.
Warm-up swallowed the rangedataSMA(200) has no until bar 200. On a three-month daily test almost every bar is warm-up, and comparisons against an are false.
A threshold never reachedlogicADX > 40 on a range-bound , or a 3× volume on something illiquid. the indicator and read its actual range.
Mutually exclusive filterslogic > 70 AND RSI < 30 is the obvious form. A strong-trend combined with a volatility-squeeze filter is the same error wearing a disguise.
Timezone mismatchconfigTime resolve in the exchange’s timezone. HourOfDay >= 10 means IST on NSE and UTC on , and nothing warns you.
Cooldown or position capconfigA cooldown longer than the signal’s spacing, or maxPositions already occupied. Silent, but recorded in the trade log.

Far too many trades

  • A condition used as a . RSI(14) < 30 is true on every bar stays oversold, so it fires on every one of them. The fix is crossesAbove 30, which fires once.
  • No cooldown on a fast timeframe. A 1-minute with a legitimate can still trade more than its costs support. Add cooldownBars.
  • An empty AND group. A group with no children evaluates true. If a tree was built programmatically and a branch came out empty, that branch fires on every bar.
  • Re-entry left enabled. Cooldown re-entry with a low bar count converts one signal into a stream of additions to the same position.
!
Caution:Check the cost line before assuming a high-frequency result is viable. At 0.03% per side, 600 trips a year is 36% of capital in commission — enough to turn a genuinely profitable signal into a losing , and the reason a zero-cost backtest is not evidence of anything.

Results that look too good

Treat an exceptional backtest as a bug report. A above 3, a above 80%, or an with no meaningful is far more likely to be a leak than an edge.

SuspectDescription
Look-ahead in a formulaA referencing a that settles after the bar closes. Run the prefix-stability test: identical inputs must produce identical outputs regardless of how much follows.
Zero costsCommission and slippage left at zero. The first thing to fix, and often the entire explanation.
Too few tradesTwelve can produce any statistic you like. Below about 30 the are noise with decimal places.
One outsized tradeSort the trade by P&L. If the top trade is most of the result, the has not been validated — one lucky trade has.
A single regimeA trend-follower tested only across a sustained bull market has never met the conditions it fails in.

Backtest and live disagree

The is the same code in both, so a divergence is never the logic disagreeing with itself. It is one of four things, and they are distinguishable.

SourceTypeDescription
Intra-bar movementstructuralSignals evaluate at , so the backtest never sees what happened inside the bar. A stop that at its exact level in the test can fill materially worse .
SlippagecostMeasurable: your average fill against the the signal fired on. Feed that number back into the backtest and re-run.
Latency and feed gapstimingA feed arrives late and occasionally drops. A stored series has neither property, so signal timing shifts by a bar in the cases where it matters most.
Partial fillsexecutionThe engine assumes complete . Limit orders in a thin book do not oblige, so position sizes from the simulated ones.
★
Pro tip:Quantify before you theorise. Take twenty , each fill against the it should have executed at, and take the mean. That single number, entered as slippage, usually reconciles most of the gap — and if it does not, the residue is timing, which is a different fix.

Instrumenting a strategy

See also

Related articles

  • Strategy DSL — Conditions That Can Never Be True

    Coding / Strategy DSL

    Which conditions the engine and the editors reject because no bar could ever satisfy them, how the check works, what it deliberately does not reject, and…

  • Building Custom Indicators

    Coding / Custom Indicators

    Composing new indicators from existing ones, the recursive shapes that need care, warm-up handling, and how to check a custom indicator is actually correct.

  • Built-in Indicators & Overlays

    Coding / IDE / Reference

    Quatick ships with a full library of pre-built indicators and overlays. You can use these out of the box, or study their implementations as reference for…

  • Coding for Trading — Fundamentals

    Coding / Fundamentals

    The concepts every strategy rests on, whether you write it in the condition builder or in code: series, bar indexing, the difference between a value and a…

  • Condition Recipes

    Coding / Recipes

    Patterns worth knowing by heart: regime detection, confirmation, divergence, volatility gating, session control, and the idioms that express each one cleanly.

  • Evaluation Performance

    Coding / Performance

    Why a backtest is slow and what actually fixes it: condition ordering, indicator reuse, incremental computation, and the difference between an O(n) and an…

  • Expressions & Operators

    Coding / Expressions

    How an expression is parsed and evaluated: precedence, operand types, boolean folding, the operators available, and the mistakes that produce a silently…

  • IDE Keyboard Shortcuts

    Coding / IDE / Keyboard

    Quatick IDE supports extensive keyboard shortcuts inspired by VS Code. Shortcuts are organized by area. On macOS, replace Ctrl with ⌘ and Alt with ⌥ where…

  • Python Scripting with Pyodide

    Coding / IDE / Python

    Quatick strategies run in Python 3.11 via Pyodide — a full CPython compiled to WebAssembly. This means your strategies execute entirely in the browser, with…

Browse all knowledge base articles →