Back to articles
DocumentationSource: backend/agents/notes/trade_discipline_rules_20260425.md

AUTHOR: Tony Mudau

Trade Discipline Rules — System Hardening (April 25 2026)

Purpose: This document explains the filter rules added to technical_agent and orchestration_agent on April 25 2026. It is written for someone who understands basic trading concepts but is still learning how algorithmic systems translate market intuition into code.


The Core Problem We Solved

Before this change, the system worked like a competition with no entry requirements.

Every symbol was scored, and the symbol with the highest score was automatically selected — even if all scores were terrible. There was always a "winner", even when the market offered nothing worth trading.

The analogy: imagine a tennis tournament where the rules say someone must win even if all players are injured. The result is a bad match. Our system was doing the same thing with trades.

Before (broken)

Loop symbols → Score each one → Pick highest score → Execute

After (fixed)

Loop symbols → FILTER (must pass hard rules) → Score survivors → Pick highest → Execute
         ↑
     Most cycles end here with "no_trade" when conditions are poor

This is the difference between a ranking system and a filter-then-rank system.


Financial Concepts You Need to Know First

Volatility

Volatility is how much a price moves over a period. High volatility = big swings. Low volatility = price barely moving.

  • Why it matters for trading: Trend strategies need movement to work. If a market is frozen, there is no trend to follow. Entering a trend trade in a frozen market is like trying to surf when there are no waves.

ATR (Average True Range)

A technical indicator that measures average price movement per candle. It is the most common way to measure short-term volatility.

  • Low ATR → market is quiet → low volatility
  • High ATR → market is active → high volatility

Bollinger Band Width

Bollinger Bands are two lines drawn around price, spaced by standard deviation. When the bands are narrow (squeezed), the market is in compression — consolidating with no clear direction. When they expand, the market is breaking out.

Volatility Regime

The system classifies the market into three regimes based on ATR percentile and Bollinger Band Width percentile:

Regime Meaning Trade implication
expansion Market is moving strongly, above the 82nd percentile Good for trend trades
normal Market moving at average pace Neutral
compression Market very quiet, below the 25th percentile Avoid trend trades; only mean-reversion is suitable

Strategy Modes

The system chooses one of three strategies per symbol per cycle:

Mode What it does Best in
trend Follows the existing direction — buys in an uptrend, sells in a downtrend Expansion / normal volatility
htf_pullback Enters a short-term counter-move within a larger trend (buy a dip in an uptrend) Normal volatility, when higher TF trend is clear
mean_reversion Fades extremes — buys when oversold, sells when overbought Compression / quiet markets

EMA Trend Strength

EMA 50 and EMA 200 are moving averages over 50 and 200 candles respectively. When they are far apart, it confirms a strong trend. When they are very close together, the market has no direction.

trend_strength = |EMA_50 - EMA_200| / price

A value below 0.0008 (0.08% of price) means the two averages are almost touching — no meaningful trend exists.

RSI (Relative Strength Index)

RSI measures whether a market is overbought (above 70) or oversold (below 30). These are "exhaustion zones" — the market has moved so far that a reversal is likely.

  • Buying into an overbought market is chasing the top of a move that is about to reverse — a classic mistake.
  • Selling into an oversold market is shorting the bottom of a move that is about to bounce.

Correlation Pressure

If the portfolio already holds multiple trades in the same underlying currency, adding another one increases concentration risk. All those positions will win or lose together. This is measured as same_side_pressure — the combined lot exposure on the same side across correlated pairs.

Trading Sessions

Forex markets have three main sessions: Asia, London, and New York (NY). Each has different liquidity and volatility characteristics.

  • The most volatile and highest-volume time is London/NY overlap (afternoon European time).
  • Late New York (especially Fridays after 3 PM NY time) is a "dead zone" — traders are closing positions for the week, volume collapses, and false moves are common. Entering trends in this window is a recurring cause of losses.

Confidence Score

A number from 0.0 to 1.0 that summarises how aligned all the signals are. Higher is better. The formula blends pattern win-rate, trend alignment, MTF alignment, trend strength, and volatility adjustments.


The Rules — What They Do and Why


RULE 1 — Block Trend/HTF-Pullback in Volatility Compression

In code (technical_agent):

if vol_regime == "compression" and strategy_mode != "mean_reversion":
    return None

What it means:

When the market is in compression (Bollinger Bands squeezed, ATR at its lowest percentile), the system used to still attempt trend trades. This is fundamentally wrong.

A trend trade requires directional price movement. In compression, price is coiling — it moves sideways with no conviction. Entering a trend trade here is like pressing the accelerator when the engine is off.

The only valid strategy during compression is mean-reversion — fading the small oscillations within the tight range. Everything else gets rejected.

Learner analogy: Imagine trying to ride a wave on a lake with no wind. The wave is not there. You sit down and wait.


RULE 2 — Block Trend Trades with No EMA Momentum

In code (technical_agent):

if strategy_mode == "trend" and trend_strength < 0.0008:
    return None

What it means:

Even if volatility looks normal, the EMA spread could be tiny — meaning the short-term and long-term moving averages are almost identical. This means the market has no confirmed directional bias at the macro level.

Trend following works because the market has inertia — a ball rolling downhill keeps rolling. If the ball is barely moving, there is no inertia to follow.

0.0008 means EMA 50 and EMA 200 are less than 0.08% apart. At this level, the "trend" signal is statistical noise, not a real trend.


RULE 4 — Hard Minimum Confidence (0.62 Floor)

In code (technical_agent):

if confidence < 0.62:
    return None

What it means:

Before this fix, the confidence formula could produce values like 0.58 or 0.60, and those signals would still be passed to the orchestration agent. The orchestration agent would score them and potentially execute them because they were the "least bad" option.

The problem: 0.60 confidence does not mean "a good trade". It means "slightly better than a coin flip".

By adding a hard floor at 0.62 inside the technical agent itself, we prevent mediocre signals from even reaching the scoring stage. The signal is killed at the source.

Why 0.62? The confidence formula starts at a base of 0.42, then gets lifted by pattern win-rate, alignment, trend strength, and MTF alignment. Getting above 0.62 requires meaningful confirmation from multiple indicators simultaneously — not just one or two.

Learner analogy: You don't just need green lights — you need all the important ones to be green at the same time.


RULE 5 — Block Trend Trades in Low Volatility Markets

In code (technical_agent):

if context_volatility == "low" and strategy_mode == "trend":
    return None

What it means:

context_volatility comes from the ATR ratio (ATR divided by price). When this ratio is in the lowest bucket ("low"), the market's daily range is tiny. There is not enough movement to:

  1. Justify the spread cost of entering a trade
  2. Reach a meaningful take-profit target before reversing
  3. Show a clean trend signal

This rule is complementary to RULE 1. RULE 1 catches compression at the percentile/regime level. RULE 5 catches low ATR ratio at the absolute level. Together they provide two independent checks for the same underlying problem: the market is not moving.


RULE 6 — Block New York Session Dead Zone Trades

In code (technical_agent):

if session_label == "new_york" and vol_regime != "expansion" and trend_strength < 0.001:
    return None

What it means:

The New York session is not uniformly active. The first half (9:30 AM – 1 PM NY time) overlaps with London and is the highest-volume period. The second half, and especially Friday afternoons, is the worst time to enter new trend trades:

  • Institutional desks are closing positions for the week
  • Volume drops sharply
  • Price moves are choppy and random — fake breakouts are common
  • Spreads can widen

This rule only fires when all three conditions align:

  1. We are in the New York session
  2. The regime is not showing true expansion (no strong directional move)
  3. EMA trend strength is below 0.001 (barely any trend)

When all three are true, the system refuses to enter. It waits.

This directly addresses a real observation: the previous system was noted to be losing money on Friday NY session trades in quiet markets. This rule stops that pattern.


RULE 7 — No Market Orders in Dead Markets

In code (technical_agent):

if execution_type == "market" and context_volatility == "low":
    return None

What it means:

There are two ways to enter a trade:

  • Market order: Enter immediately at the current price (whatever it is)
  • Pending order (limit/stop): Place an order at a specific price level, and only enter if price reaches that level

In a low volatility market, a market order is dangerous because:

  1. The spread (difference between buy and sell price) is a proportionally larger cost relative to the tiny expected move
  2. There is no "thrust" to carry the trade toward profit quickly
  3. Price may drift back immediately, stopping you out at a loss

Pending orders are safer in quiet markets because they require price to move intentionally to your entry level — which filters out dead, drifting markets naturally.

This rule says: never submit an immediate market order when the market is quiet. If the entry type is a limit or stop order, that is fine — the pending order will simply not be filled if the market doesn't move.


RULE 8 — Stricter Correlation Block (2.0 instead of 3.4)

In code (technical_agentcorrelation_overlay):

# Changed from:
if same_side_pressure >= 3.4:
    block = True
# To:
if same_side_pressure >= 2.0:
    block = True

What it means:

In forex, many currency pairs share the same base or quote currency. For example:

  • EURUSD and GBPUSD both use USD as the quote. If USD weakens, both pairs go up.
  • Holding 1 lot BUY on EURUSD and 1 lot BUY on GBPUSD is not diversification — it is a doubled USD bet.

same_side_pressure is the measure of how much total correlated exposure the portfolio already has on the intended side.

Previously the block only triggered at 3.4 — meaning the system was allowing very concentrated correlated positions before refusing. This was a significant risk concentration issue.

Lowering the block to 2.0 means: once the combined exposure on the same side exceeds 2 lots of equivalent pressure, no new correlated trades are added. This enforces proper position diversification.


Orchestration Agent Changes

The orchestration agent is the "conductor" — it loops through candidate symbols, collects signals from the technical agent, scores each one, and selects a trade.

The problem was: it always selected something.


Hard Quality Gate — Before Scoring

if not technical_signal or direction not in {"buy", "sell"}:
    rejection = "no_direction"
elif confidence < 0.65:
    rejection = "low_confidence"
elif strategy_mode == "mean_reversion":
    rejection = "mean_reversion_blocked"
elif vol_regime == "compression":
    rejection = "low_volatility_compression"

What it means:

Before any scoring happens, each symbol must pass four checks:

  1. Has a direction — the signal must say BUY or SELL. No direction = no trade.
  2. Confidence ≥ 0.65 — higher bar than the technical agent's 0.62, because even a signal that squeaked past the technical filter must prove itself again at the orchestration level. This is double-verification.
  3. Not mean-reversion — the orchestration agent makes a blanket decision: no mean-reversion trades. Mean-reversion is a legitimate strategy but it requires different risk sizing and timing than what this system's execution pipeline is calibrated for. Rather than execute it incorrectly, it is blocked entirely.
  4. Not compression regime — catches any compression-regime signals that somehow passed the technical agent filter (defensive programming).

Rejected symbols are stored in filtered_out_symbols with their rejection reason, visible in every cycle trace and the Telegram notification.


Minimum Confidence Raised: 0.52 → 0.65

Even after passing the quality gate, a proposal is only allowed into the selection competition if its effective confidence (after MTF and reversal adjustments) is at or above 0.65.

Previously the bar was 0.52 — this was dangerously low. A 52% confidence trade is barely better than random. The new 65% threshold means the combined signal alignment must be genuinely meaningful.


Minimum Combined Score Gate: 0.6

After all signals are combined into a weighted score (technical confidence, market sentiment, precheck conditions, MTF alignment), if the combined score is below 0.6, the symbol is filtered out even if it has a proposal.

combined_score = (tech_conf × 0.46) + (market_conf × 0.2) + (precheck × 0.14) + (mtf × 0.2) + alignment_bonus

This prevents a symbol with a strong technical signal but terrible precheck conditions (wide spread, no liquidity, poor timing) from being selected.

The combined score ensures holistic quality — not just technical quality.


Execution Guard — Last Line of Defence

if proposal.confidence < 0.65:
    proposal = None  # block execution

Even after the portfolio agent and risk agent have shaped the proposal, if the confidence has been adjusted down below 0.65, the trade is not executed. This prevents cases where portfolio or risk adjustments reduce confidence but the system still proceeds.

Think of this as the bouncer at the door — even if you got through all the previous checks, you still need to show your credentials at the entrance.


No-Trade Return — Explicit System Decision

When no symbol survives all the filters, the system now explicitly returns:

{
  "status": "no_trade",
  "reason": "No valid high-quality setups found",
  "filtered_out_symbols": [
    {"symbol": "EURUSD", "rejected": true, "reason": "low_confidence"},
    {"symbol": "GBPUSD", "rejected": true, "reason": "low_volatility_compression"},
    {"symbol": "USDJPY", "rejected": true, "reason": "mean_reversion_blocked"}
  ],
  "final_candidate_count": 0
}

This is a first-class decision, not an error or a fallback. The system chose not to trade, which is the correct choice when conditions are poor.

The Telegram notification also reports which symbols were filtered and why, so a human can verify the system's reasoning without reading logs.


The Big Picture — Why This Matters Financially

Before: Slow Bleed

The old system made many low-quality trades. Each one had a small edge at best, or a random outcome at worst. Over time:

  • Win rate hovers around 50%
  • Losses and wins roughly cancel
  • Spreads and swap costs create a net negative drift
  • Capital slowly erodes

After: Asymmetric Discipline

The new system trades rarely, but only when multiple conditions align. The expected outcome:

Metric Before After (expected)
Trade frequency High Low
Average confidence ~0.60 ≥0.65
Win rate ~48–52% Higher
Drawdown per cycle Variable (often negative) Reduced
Capital preservation Poor Improved

The Professional Analogy

A professional trader does not trade every hour. They sit at their desk, watch the market, and wait for the one setup that checks every box. Then they size in with conviction.

The old system was like a trader who places a bet every five minutes out of boredom. The new system waits. It skips. And when it trades, it trades with a reason.

The edge is not in finding the best of what is available. The edge is in refusing to trade when nothing good is available.


Summary Table

Change Location What changed Financial principle
Compression blocks trend technical_agent Returns None when regime = compression + strategy = trend No trend trade without momentum
EMA spread minimum technical_agent Blocks trend when trend_strength < 0.0008 Trend requires confirmed directional bias
Confidence floor 0.62 technical_agent Kills signals below 0.62 at source Only high-conviction setups enter pipeline
Low volatility blocks trend technical_agent Blocks trend when context_volatility = low Dead markets cannot sustain directional trades
NY dead zone filter technical_agent Blocks NY + non-expansion + weak trend Avoid end-of-week/session liquidity traps
No market orders in low vol technical_agent Blocks market-type entry when volatility = low Spread cost disproportionate; no thrust to profit
Correlation block 3.4→2.0 technical_agent Stricter FX exposure concentration cap Prevents correlated position doubling risk
Quality gate in orchestration orchestration_agent Filters before scoring, logs rejections Only competitive setups enter ranking
Confidence raised 0.52→0.65 orchestration_agent Higher bar for proposal acceptance Removes "slightly above coin flip" trades
Combined score gate 0.6 orchestration_agent Filters low holistic quality proposals Holistic quality, not just technical signal
Execution guard 0.65 orchestration_agent Last confidence check before live execution Prevents degraded proposals from executing
No-trade as explicit decision orchestration_agent Returns status + reason + filtered symbols Transparency; no-trade is a valid outcome

Written: 2026-04-25 | Applies to: TFA Agents v1 backend