Most EA pages are about win rate. This page is about what the system refuses to do — the gates, the rejections, the audit trail. If you break a gate, we don't fudge the numbers; we send the alert.
These are the per-client gates inside client_router_agent.py. Every signal walks all six before it touches your account. Click a gate to see the actual rule, an anonymized excerpt of the Python, and what happens when it rejects vs. passes.
Reject path: trade blocked, decision logged to ea_trades, Telegram alert fires. Pass path: position sized for THIS account, order routed to MT5.
The EA can't do these even if a future commit accidentally tries. They're not policies — they're missing code paths.
Martingale
We never average into a losing position. Each entry is independent and risk-sized at 1%. A 7-trade winning streak followed by 1 loss is +6 units, not -32.
Grid trading
No stacked opposing orders waiting for the market to come back. Every trade has a single defined entry, stop, and target — and a hard timeout.
News-time scalps
Prop firms with required news filters get them on by default. Signals firing within ±5 minutes of high-impact news are blocked, logged, never sent.
Hidden risk parameters
Every cap is a number you can see in the dashboard. Daily loss, drawdown, max trades, lot size — visible and editable per client. No 'magic' multipliers.
Untracked trades
No trades fire that aren't in ea_trades. No copies that aren't logged. If you can't see it on /performance, it didn't happen.
Profit share
We don't take a cut of your gains. Subscription pricing only. The system either works for you at $99/mo or it doesn't — there's no incentive on our side to push more trades.
What actually happens, end-to-end, when the system meets the edge cases people worry about.
Account hits 3% daily loss
11:42 GMT. Three losing trades in a row on XAUUSD. Cumulative daily loss reaches 3% of starting balance.
Decision flow
- 01Trade Manager closes the third loser — confirmed fill, P&L written to ea_trades.
- 02Router refreshes today_stats → cumulative loss = -3.04% of starting balance.
- 0312:08 GMT signal arrives. Daily P&L gate evaluates: -3.04% ≥ 3% loss cap → REJECT.
- 04Trade NOT routed to MT5. Decision logged with reason='daily_loss_lock'.
- 05Telegram alert fires to client + admin: '⚠️ Account paused — daily loss cap hit.'
- 06Dashboard flips account state to 'Paused — resumes 00:00 GMT.'
All subsequent signals for this client are blocked until session reset. The funded slot survives the bad day.
8 agents agree, but Risk vetoes
Strong signal: 8/8 agents long on XAUUSD, grade A+. But the client's drawdown is 41% below their HWM — past the 40% disconnect threshold.
Decision flow
- 01Scanner emits consensus_long(XAUUSD, 8/8, A+) at 14:22 GMT.
- 02Router queries this client's MT5 equity → equity 41% below HWM.
- 03Drawdown gate evaluates: 0.41 ≥ 0.40 → REJECT.
- 04Trade NOT routed. EA marked 'disconnected' for this client.
- 05Telegram alert: '🛑 EA disconnected — drawdown 41% from peak. Manual review required.'
- 06Other clients with healthier drawdown still receive the signal.
Per-client risk overrides per-signal conviction. A great signal doesn't save a hurting account.
Signal coincides with NFP release
Signal fires at 13:28 GMT, two minutes before US Non-Farm Payrolls. Client is on FundedNext (requires news filter).
Decision flow
- 01Scanner emits consensus_long(XAUUSD, 7/8, A) at 13:28 GMT.
- 02Router runs prop_firm_ok() — checks FundedNext profile.
- 03Profile.requires_news_filter = true. is_news_window() returns true (NFP at 13:30, ±5min).
- 04Prop Firm gate evaluates: news_filter active → REJECT.
- 05Trade NOT routed. Decision logged with reason='news_filter'.
- 06Dashboard shows: 'Filtered: news (NFP @ 13:30 GMT).'
- 07After 13:35 GMT, news window clears. Next consensus evaluates normally.
Prop firm news rules enforced. The client's funded slot doesn't get nuked by a news spike on a 7/8 consensus.
What the dispatch loop actually looks like. What the trade log actually stores. The names match production; the bodies are simplified.
def dispatch(signal: Signal) -> DispatchResult: """Per-client fan-out. Each client gates independently.""" decisions = [] for client in active_clients(): result = evaluate_gates(signal, client) if not result.ok: log_rejected(signal, client, result.reason) telegram_alert(client, signal, rejected=True) continue lots = position_size(client, signal) if lots <= 0: log_rejected(signal, client, "size_too_small") continue send_to_mt5(client, signal, lots) decisions.append((client.id, "executed", lots)) return DispatchResult(decisions)# Every trade — executed OR rejected — writes here.# Read by /performance, /dashboard/trades, and the audit-trail export.schema_ea_trades = { "id": "INTEGER PRIMARY KEY", "client_id": "INTEGER NOT NULL", "signal_id": "TEXT NOT NULL", # links to agent_activity row "symbol": "TEXT", "direction": "TEXT", "lots": "REAL", "entry_price": "REAL", "exit_price": "REAL", "pnl_usd": "REAL", "executed": "INTEGER NOT NULL", # 0 = rejected "reject_reason": "TEXT", # null when executed=1 "opened_at": "INTEGER NOT NULL", "closed_at": "INTEGER",}Every trade — executed or rejected — is written to three places before you see it in the dashboard.
Turso (live DB)
ea_trades row written immediately on signal evaluation. Includes reject reason if blocked. Visible in the dashboard within seconds.
Telegram (alert)
Real-time message to client + admin Telegram. Filled trades show entry/SL/TP + lot size. Rejected trades show the gate that fired.
MT5 (the broker)
If executed, the order lives in your MT5 account history forever. We can't delete it; you can verify it against the dashboard.
Three independent records. Tampering with one is detectable from the others. The system is designed so that we can't cherry-pick which trades to show — they're all there, all the time.
Cancel anytime · 8 agents · 6 gates · 3-place audit trail