Idea in brief
A filter does not buy anything by itself. It answers "yes" or "no" to the question of whether to let today's signal from an existing entry through. The card presents two approaches to such filters from two systematic traders who build intraday breakout systems.
Brendan (Trivium System Trading) generates strategies in StrategyQuant X and hardly lets indicators with settings into the builder. Instead of RSI and MACD, Brendan has coded about a hundred custom conditions with nothing to optimize. Two examples: yesterday closed above its open; the daily close is above a 200-period average, where 200 is fixed for good.
Andrea Unger, a four-time World Cup Trading Championship winner, starts with a simple skeleton: a breakout of the prior session high, an end-of-day exit, a mandatory stop. If the equity curve rises but the average trade does not cover costs, Unger runs the skeleton through a library of conditions about the previous day: a quiet day, a big move, three up closes in a row, Monday or Friday. Each condition is tested separately.
Why it might work
Brendan's argument is about degrees of freedom. The RSI period can vary from two to almost infinity, plus a threshold. MACD has three settings. The more such knobs, the easier it is to fit the curve to history. The condition "yesterday closed up" is either met or not, and there is nothing to fit in it.
Unger's argument is about market behavior. The same entry works differently depending on what the market did the day before. A filter cuts out days where the breakout is worse on average and lifts the average trade to a level where it covers commission and slippage.
Neither argument removes selection. A zero-parameter condition becomes a parameter as soon as it is chosen from a hundred others. Unger guards against this as follows: no search for the best variant, only a check of whether there is a difference; a condition stays only if it can be explained; a curve that looks too good is not trusted. About reworking a weakened system with new filters Unger says it plainly: it is a road to overfitting, and it is better to rebuild the system from its original principle.
Rules
Brendan's blocks (author)
// conditions are calculated on closed daily bars and applied on an intraday chart
// the author uses 30-minute charts, CFDs on US indices, DAX, Nikkei, bitcoin, gold, oil
UpDay = Close[D1, 1] > Open[D1, 1] // author: yesterday's session closed up
Above200 = Close[D1, 1] > MA(Close[D1], 200)[1] // author: period 200 is not optimized
// Finetiq: average type not named, we use SMA
// Finetiq: mirrored for shorts, Close[D1, 1] < Open[D1, 1] and a close below the average
// an example of a condition the genetic builder found (the author gives it from memory)
SessHighVs3W = today's session window high > Open[D1, 15]
// Finetiq: three weeks = 15 trading days, window = first 2 hours, checked after the window closes
// this condition already has two hidden parameters: the window and the lookback depth
Library of prior-day conditions (Unger, Finetiq thresholds)
Unger describes the conditions in words. The interview has no numeric definitions, so below are starting values for testing.
// [1] = yesterday's closed daily bar
// author: a daily series built from 1440-minute bars, so the close is the last trade, not the settlement
Range1 = High[1] - Low[1]
ATR20 = ATR(20)[1] // Finetiq
Quiet = Range1 < 0.7 * ATR20 // author: "yesterday was a quiet day"; Finetiq: threshold
Choppy = |Close[1] - Open[1]| < 0.25 * Range1 // author: "choppy day"; Finetiq: body smaller than a quarter of the range
HugeMove = Range1 > 1.5 * ATR20 // author: "big move"; Finetiq: threshold
DownClose = Close[1] < Close[2] // author: a down close "to a level", the level is not named
ThreeUp = Close[1] > Close[2] AND Close[2] > Close[3] AND Close[3] > Close[4] // author
Monday = today is Monday // author
Friday = today is Friday // author
HighVol = ATR(5)[1] > ATR(50)[1] // author: "high volatility"; Finetiq: the comparison
LowADX = ADX(5)[1] < 35 // author: "below 40 or 35"
Selection procedure (author, Finetiq limits)
// skeleton (author): BUY STOP at PrevHigh, EXIT at end of day (or after 2 days, or at a target), always a stop
// the skeleton is suitable if the curve rises while the average trade is smaller than costs (author)
FOR EACH P IN Library
On = skeleton trades on days where P = TRUE
Off = skeleton trades on days where P = FALSE
compare the average trade, number of trades and curve shape
KEEP P IF the difference is noticeable AND there is an explanation why // author: common sense versus coincidence
// Finetiq: at least 100 trades in On, no more than two conditions in a system
// Finetiq: conditions are chosen only in-sample, the decision is checked out-of-sample
// Validator (author): shift yesterday's High and Open by ±10 ticks, recalculate the system many times
// and see which percentile the original curve falls in. The tail of the distribution means instability
Unger does not apply walk-forward to the library: a pattern list is not a continuous parameter, and choosing "the best pattern of the week" makes no sense.
Plugging into cards in the base (Finetiq)
// example 1: prev-high-breakout-adx. There the filter has two parameters: ADX(5) < 35
Allowed_A = ADX(5)[1] < 35 // as in the card
Allowed_B = UpDay // zero-parameter filter instead of ADX
Allowed_C = ADX(5)[1] < 35 AND Above200
IF Allowed_x AND TradesToday = 0
BUY STOP at PrevHigh + 1 tick // the card's entry and exit unchanged
// example 2: intraday-breakout-eod-exit. There UpDay AND Above200 are already in place
Filter = UpDay AND Above200 // as in the card
Filter_Test = Filter AND NOT HugeMove // one new condition at a time, per Unger's procedure
Parameters
| Parameter | Value | Source |
|---|---|---|
| Average period in Above200 | 200, not optimized | author (Brendan) |
| Average type | SMA | Finetiq |
| Session window in SessHighVs3W | first 2 hours | Finetiq |
| "Three weeks ago" | 15 trading days | Finetiq |
| Daily bar | 1440 minutes, not settlement | author (Unger) |
| Quiet | yesterday's range < 0.7 ATR(20) | Finetiq |
| Choppy | body < 25% of the range | Finetiq |
| HugeMove | yesterday's range > 1.5 ATR(20) | Finetiq |
| HighVol | ATR(5) > ATR(50) | Finetiq |
| LowADX | ADX(5) < 35 | author (Unger: 40 or 35) |
| Price shift in Validator | ±10 ticks | author (Unger) |
| Minimum trades in On | 100 | Finetiq |
| Conditions per system | no more than 2 | Finetiq |
What to test
- On versus off difference for each condition. On one skeleton and one market, split the trades by each condition and compare the average trade after costs. A summary table across all conditions at once shows how many of them produce a difference by chance.
- A random filter with the same frequency. If a condition is true on, say, 40% of days, label days at random with the same share 200 times and run the skeleton. If the real filter does not land in the top 5% of the random ones, its improvement is explained by selection.
- Unger's price shift. Shift yesterday's High and Open by ±10 ticks (for CFDs by 5% of ATR) and count how often the condition flips and how much the result drops.
- Neighborhood of hidden parameters. 200 versus 150 and 250, thresholds of 0.7 and 1.5 ATR versus 0.5–0.9 and 1.2–2.0. Brendan treats 200 as a constant, but it is worth making sure the result does not rest on it alone.
- Out-of-sample in time. Choose conditions on the first half of the history and evaluate them on the second. The set of conditions may only be changed before this step.
- Other markets. Brendan expects logic found on the NASDAQ to be at least profitable on the Dow and the S&P 500. Unger does not test this way. Run the filter on neighboring indices.
- Costs and number of trades. Check whether the average trade after the filter stays above double the costs and whether there are enough trades to draw conclusions.
Platform notes
TradingView (Pine Script)
- Daily values on an intraday chart:
request.security(syminfo.tickerid, "D", close[1] > open[1], lookahead = barmerge.lookahead_on). Without[1]today's close leaks into the backtest. - The daily bar is built on the symbol's session. For CME futures the day starts at 18:00 ET the day before, and CFDs trade almost around the clock. "Yesterday" differs between a cash index and a futures contract, and UpDay triggers on different days.
- Set the session window for SessHighVs3W with the exchange time zone:
time(timeframe.period, "0930-1130", "America/New_York"). Otherwise the window will shift when daylight saving time changes.
MultiCharts and TradeStation (EasyLanguage)
- Unger uses 1440-minute bars instead of daily ones. For a futures contract in TradeStation the daily bar closes at settlement, the 1440-minute bar at the last trade. UpDay and ThreeUp will fall on different days on the two bar types.
- The daily series is added as a second data stream (
Close of Data2). Check on the chart that during the session Data2 returns yesterday's closed bar, not the current one. - Day of the week:
DayOfWeek(Date), Sunday 0, Monday 1, Friday 5. AvgTrueRangeis a simple average of TR, while Pine's ATR is Wilder. Quiet and HugeMove thresholds as fractions of ATR will pick different days on the two platforms.
MetaTrader 5 (MQL5)
- Yesterday's bar:
iOpen(_Symbol, PERIOD_D1, 1)andiClose(_Symbol, PERIOD_D1, 1). The daily bar is built on the broker's server time, not exchange time. - Brokers whose server is not on GMT+2/+3 may show short Sunday bars. On Monday "yesterday" will be a Sunday fragment, and UpDay, Quiet and ThreeUp will be calculated from it.
iADXdoes not use Wilder smoothing. To match LowADX with Pine and EasyLanguage, useiADXWilder.iATRis a simple average of TR, as in EasyLanguage.- Calculate the ±10 tick shift for the Validator via
SYMBOL_TRADE_TICK_SIZE. On an index CFD the tick can be 0.01 or 0.1, and 10 futures ticks correspond to a different shift there.
Where the idea can break
- Brendan's interview was published on the StrategyQuant channel, a software vendor, and at the end the author promotes their course. There are no figures for individual blocks, only the general conclusion that they pass checks more often.
- Zero parameters inside a condition does not mean zero degrees of freedom. The genetic builder searches through a hundred blocks and their combinations. Brendan cites a condition that looks random, and trades such conditions if they pass the checks.
- Unger gives no numeric definitions. With the thresholds from this card the library is no longer parameter-free, and it needs a neighborhood test.
- Unger describes the Validator as a recommendation to students, while personally relying on experience. Unger's common sense cannot be reproduced mechanically.
- Every filter shrinks the sample, and Unger also builds a separate system for each market without testing it on others. Both circumstances raise the risk of overfitting.
- "Yesterday" depends on where the day ends: the cash session, the futures trading day, the broker's server. The same filter on three platforms can pick different days.