ENT Entry Gate Systems
The filter that separates good signals from expensive noise
Learning Objectives
- •Understand the purpose and mechanics of entry filtering
- •Learn what features make effective entry filters
- •See how over-filtering destroys profitability
- •Design entry rules that improve signal quality without killing trade frequency
Explain Like I'm 5
Your signal model says "there might be an opportunity here." The entry gate says "sure, but is RIGHT NOW a good time to act on it?" It's like having a scout who says "the restaurant is great" vs. a friend who adds "yeah but there's a two-hour wait and the kitchen closes in 30 minutes — let's come back tomorrow."
Think of It This Way
Think of airport security. The ticket counter (L1) says "you're allowed to fly." Security (L2) says "but can you fly RIGHT NOW based on current conditions?" Most people pass both. But the ones who get stopped at security were going to cause problems. L2 catches the signals that look good on paper but would perform badly in current conditions.
1Why Entry Filtering Matters
Win Rate by Entry Condition
2The Over-Filtering Problem
Trade Frequency vs Win Rate: The Filtering Tradeoff
3Effective Entry Filter Features
4ML-Based vs Rule-Based Filtering
Key Formulas
Entry Filter Expected Value
Expected return per trade that passes the filter. WR = win rate of filtered trades, W̄ = average win, L̄ = average loss. The filter should increase this relative to unfiltered trades.
Spread-Adjusted Edge
Edge after accounting for round-trip spread cost. When spread widens, your effective edge shrinks. If E_adj goes negative, don't trade.
Hands-On Code
Entry Filter Implementation
class EntryFilter:
"""L2 entry gate — filters L1 signals by market conditions."""
def __init__(self, max_spread_ratio=2.0, min_confidence=0.55):
self.max_spread_ratio = max_spread_ratio
self.min_confidence = min_confidence
def evaluate(self, l1_signal, market_context):
"""Decide whether to enter on an L1 signal."""
reasons = []
# Spread check
spread_ratio = market_context['current_spread'] / market_context['avg_spread']
if spread_ratio > self.max_spread_ratio:
reasons.append(f"Spread too wide: {spread_ratio:.1f}x normal")
# Confidence check
if l1_signal['probability'] < self.min_confidence:
reasons.append(f"Low confidence: {l1_signal['probability']:.2f}")
# Regime check
regime = market_context.get('regime', 'unknown')
signal_type = l1_signal.get('type', 'trend')
if regime == 'choppy' and signal_type == 'trend':
reasons.append("Regime mismatch: trend signal in choppy market")
# Drawdown check
dd_zone = market_context.get('dd_zone', 'NORMAL')
if dd_zone in ('DANGER', 'CRITICAL'):
if l1_signal['probability'] < 0.60:
reasons.append(f"DD zone {dd_zone}: need higher confidence")
if reasons:
return {'decision': 'SKIP', 'reasons': reasons}
return {'decision': 'ENTER', 'reasons': ['All checks passed']}Each check has a clear purpose: spread protects against transaction costs, confidence filters weak signals, regime prevents style mismatch, and drawdown protects capital during adverse periods. Any single failure = skip.
Knowledge Check
Q1.Your entry filter has a 68% win rate but only takes 8 trades per month. Is this good?
Assignment
Build a simple entry filter with three rules: spread check, regime check, and confidence threshold. Backtest your L1 signals with and without the filter. Compare: win rate, trade count, total R, and monthly Sharpe ratio. The filter should improve win rate but watch for over-filtering.