How the system manages risk, exits, and portfolio-level decisions across 9 live EAs on a $100k FundedNext Stellar Lite demo account. Written 2026-05-22 for external review.
The Position Manager (PM) is a Python process that runs continuously alongside MT5 on the same machine. It does not open positions — that's the EA's (Expert Advisor's) job. The PM's role is everything that happens after entry: stop-loss adjustments, partial profit- taking, breakeven moves, drawdown kill switches, portfolio-level exposure caps, news-event blackouts, and friday flatten.
The architectural decision to split entry (in MQL5 .ex5 EAs) from management (in Python PM) was deliberate: it lets us iterate on risk rules without rewriting every EA, and it gives us a single point of truth for portfolio-level decisions that no individual EA can see.
The PM has evolved through 20 sequential rule versions (V1 → V20),
each added in response to a specific live-trading incident. Most rules
fire on every tick check (~once per minute). Some rules are
cap-style (block new entries), some are
action-style (close positions, adjust SL), and a few are
alert-only (Telegram warning, no automated action).
The system has three layers communicating via files:
EAs (.mq5 binaries) ← place new entries, manage trailing
│
│ attach to charts in MT5 Terminal
▼
MT5 Terminal (IC Markets demo) ← maintains positions, executes orders
│
│ Python MetaTrader5 module reads positions + history
▼
Position Manager (Python) ← evaluates rules every minute,
│ adjusts SL/TP, closes positions
│
│ writes disabled_magics.txt + pm_heartbeat.txt
▼
pm_guard.mqh (shared include) ← read by every EA before placing orders
so disabled magics never even fire
The PM reads MT5 state via the official MetaTrader5 Python API
(positions, history, account info) and writes back via the same
API (modify SL/TP, close position). It also writes plain-text "state"
files into MQL5/Common/Files/ that the EAs read on each
tick — that's how the EAs know which magics are blocked.
Every tick, in order:
disabled_magics.txt, disabled_pairs.txt, and pm_heartbeat.txt to MQL5/Common/Files/The PM is intentionally conservative: it never opens positions, only manages them. If the PM crashes, the EAs continue running but with stop-losses already in place at entry — meaning each open position has a defined max loss. Phase 2a adds a heartbeat so the EAs detect a dead PM and switch to fail-safe behavior (use last cached state).
Rules are tagged ACTION if they close/adjust positions, CAP if they block new entries, or ALERT if they only emit Telegram warnings without changing trade state.
If floating drawdown breaches the configured account DD limit (currently -2.5% daily / -8% total), flatten every position immediately and freeze new entries until the next day. Hard stop, no recovery within session.
Once the account hits +1.5% daily PnL, close all winning positions and stop opening new ones for the day. Locks the win, prevents giveback. Underwater positions are explicitly skipped (bug fixed 2026-05-21).
Close everything before the weekend gap. Standard prop-firm convention. Combined with V19 for gradient unwinding earlier in the week.
Hard limit of 8 simultaneous open positions across all EAs. If exceeded (race condition), the most recently opened are auto-closed back down to 8.
Wed 16:00 UTC tightens stops; Thu 8:00 UTC takes partial 50%; Fri 19:00 UTC full flat (V10). Smooths the Friday close instead of one abrupt event.
After V12 partial+BE fires at +0.5R and price crosses +1.0R, strip the EA's take-profit and chandelier-trail the runner half at 1.0×initial_risk behind the running peak (long) / trough (short). Plugs the "capped winner" leak where positions reached ~+1R peak but exited at only +0.47R historically (56% capture ratio). Per-magic allowlist for staged rollout — initially enabled on 8 magics including FibChandelier H1, FibStructRetrace M15, and all 3 Multimind GOLDs.
Progressive partial + SL ratchet: at +1R close 50% + SL to entry; at +1.5R SL to +0.5R; at +2R SL to +1R; etc. Standard "scale out + trail" framework.
Once total realized R across all open winners hits +5R, partial 50% of every winner and move SL to BE. Account-level lock-in, complementary to V9.
More aggressive variant of V1. At +0.5R, close 50% + move SL to entry. Captures more frequent wins; the remaining 50% can run to V1's higher levels.
If a single position's unrealized PnL drops below -1.0% of account, close it. Catches oversized SL-distance trades before they damage the account.
Risk multiplier on new entries: 1.00x normal, 0.75x at -1% DD, 0.50x at -2%, 0.25x at -3%. Smaller bets when the account is bleeding, full size when recovered.
Telegram warning when more than N positions are open in the same currency family and direction. Observation only, no auto-action. Pending upgrade to active sizing (round-2 council unanimous recommendation).
Aggregates positions by macro thesis (USD-long, JPY-short, etc.). Originally alert-only — upgraded 2026-05-22 to active sizing (P2): new entries are scaled down based on existing stacked exposure (1.0× solo / 0.6× one correlated / 0.4× two / 0.25× three+). Published to size_overrides.txt via the same atomic-write mechanism as the disabled-magic firewall.
Max 2 entries per (magic, symbol) per 240-minute window. Prevents revenge-trading by any single EA.
When H1 ATR ≥ 90th percentile (extreme volatility), cut new position sizes by 50%. Defensive sizing during chaos.
Tier-1 events (NFP, FOMC, CPI): 60-min entry blackout + flatten existing positions at -0.5R. Tier-2 (PMI, retail sales): 30-min blackout + flatten at +0.3R.
Tags every entry with the prevailing regime: trending (ADX ≥ 25 + ATR ≥ p90) vs ranging. Currently used for post-hoc analysis only. Pending upgrade to size by regime.
Logs every entry's bid/ask spread + execution slippage to exec_breadcrumbs.jsonl. Used to detect when execution costs are eating the edge.
A maintained set of magic numbers (currently 5) that should never be allowed to hold positions. If any of those fires, PM closes immediately. Combined with the EA-level firewall (see §5), the EAs now know not to even fire.
Even within a "good" magic, some pair combinations bleed consistently. Set of (magic, symbol) tuples auto-closed on entry.
If a (magic, symbol) hits 4 closing losses in a row, pause that combination for 240 minutes. Prevents the EA from grinding into a hole.
Of the most recent 72 trades, 32 were closed by the DISABLED_MAGICS rule (44% of all activity). Each cost ~1.5 pips of spread. The EAs were dumb — they kept firing trades that PM immediately closed, paying spread tax on each round trip with zero edge captured.
The fix moves the gate upstream: the PM publishes the kill list to a file the EAs read before placing orders. EAs literally don't fire if their magic is disabled.
Phase 2a hardening addresses four failure modes the multi-model review identified:
disabled_magics.txt must be a positive integer in the lab's magic-number range (4,000,000–5,000,000). If any line fails, the entire file is rejected and the last-known-good cache is kept. Pre-fix risk: a single bad byte in the file would have allowed all magics to trade.pm_heartbeat.txt) with epoch-second timestamp, rewritten every tick. EAs check it's <90s old before trusting the disable lists. Stale → fall back to cached state, log warning.PM_AllowTrade=false response writes a row to MQL5/Files/pm_guard_blocks.csv (rate-limited 1/min per (magic,symbol)) so we can quantify spread-tax savings.The whole system fails CLOSED on validation errors (better to halt than to trade rogue) and fails OPEN on first-ever boot (no info to act on) — a defensible compromise.
Real numbers from the lab since the 2026-05-12 account reset:
| Rule | Trades | % of total |
|---|
| Magic | Trades | Win rate | Net P&L |
|---|
Most rule additions (V13 onward) come from a structured AI council process. The PM's full state — rules, recent stats, known incidents — is briefed to six different LLMs simultaneously, each given a distinct risk-management persona (Renaissance quant, DE Shaw risk manager, Druckenmiller macro, ICT microstructure, etc.). Each model independently reviews the same data and proposes their highest- leverage next change.
When 4 of 6 models surface the same failure mode independently, it's signal not noise. That's how the disabled-magic firewall in §5 got picked as the next ship — four models from four different vendors all flagged it as #1 priority without seeing each other's answers.
After shipping, the council is re-run with the new state ("we just fixed X, what's next?"). The round-2 review of the firewall is what identified the four failure modes Phase 2a addresses. This is iterative: ship → review → ship → review.