Twelve discretionary judgments, mechanized and measured

Mean reversion · 3 min

I have completed a new library, btengine/judgment.py, designed to automate the "discretionary conditions" often cited by traders.

Connors RSI2 entry example (USDJPY daily, real data): buy the dip when price is above the 200-day SMA and RSI(2) falls below 10.

Connors RSI2 entry example (USDJPY daily, real data): buy the dip when price is above the 200-day SMA and RSI(2) falls below 10.

I have completed a new library, btengine/judgment.py, designed to automate the “discretionary conditions” often cited by traders. By turning subjective concepts like “strong trend” or “range-bound” into rigid, OHLCV-based programmatic rules, I can finally test whether these common filters actually improve a trading strategy or just add unnecessary friction.

The mechanics of the test

The library consists of three main components:

  • A library of 12 judgment proxies: I have codified common trader jargon into testable code. This includes conditions like adx_strong (for trends), er_high (Kaufman efficiency ratio), vol_squeeze (market consolidation), and ema_stack (perfect order).
  • The FilteredStrategy wrapper: This acts as a gatekeeper. It wraps any existing strategy and suppresses entries when the conditions aren’t met, while allowing exits and state machine updates to pass through undisturbed. This mimics a human trader watching a setup but choosing to “sit on their hands.”
  • Automated scoring with FDR correction: To ensure I don’t mistake random noise for a genuine edge, I use a two-stage flow. I label trades, measure performance (PF and monthly return), and calculate statistical significance. Specifically, I apply the Benjamini-Hochberg False Discovery Rate (FDR) correction. This adjusts for the fact that testing many conditions increases the likelihood of finding a “lucky” result by pure chance.

Testing the “Discretionary” hypothesis

I applied this framework to two distinct cases using data from 2015-01 to 2026-04.

Strategy TypeBase Performance (PF / Monthly Return)Result
Case A: EmaCrossPullbackPF 0.78 / -0.03%All 12 conditions failed significance.
Case B: Connors CorePF 1.44 / +0.22%All 12 conditions failed significance.
In Case A, the common wisdom that “you just need to trade during a strong trend” failed to save the strategy. Even the best-performing condition (adx_strong) resulted in a PF of 1.09, which fell far short of the required statistical significance threshold (κ* = 0.25). It seems these verbalized discretionary rules do not provide the edge they claim to.
In Case B, adding filters to a strategy that already has an edge proved counterproductive. While some conditions made the PF look better (such as vol_squeeze hitting a PF of 1.69), the monthly returns were consistently lower than the base strategy. In other words, the “filter” was simply reducing the trade frequency while increasing costs, effectively paying a penalty for no real gain.

Verdict

I am adopting this library as a standard part of my research workflow. Moving forward, any trading method I encounter will be put through a rigorous pipeline: extracting the rules, calculating the required significance threshold, measuring the actual edge with this new battery of tests, and applying FDR correction. If a strategy cannot survive this process, I can confidently reject the claim that discretionary filters are the missing ingredient. The “magic” often attributed to discretionary trading appears to be more elusive or even nonexistent than the common jargon suggests.

How this connects

This verification builds on earlier ones (what failed before and what I tried this time, comparisons between approaches).

Code to reproduce

You can reproduce this with the following scripts (see repo).

  • btengine/judgment.py