Why AccountBalance() Is Wrong for Multi-Position EAs (and How to Fix It)
12 May 2026, 08:20
0
36
The Problem
Three EA rescue projects arrived at barmenteros FX this year with identical symptoms: excellent single-trade backtest results, inconsistent live performance during periods when two or more positions ran simultaneously. All three had the same root cause — `AccountBalance()` in the lot-size function.
`AccountBalance()` returns the sum of all closed trade results. It does not include floating P&L from open positions. In a single-position EA, this is correct — when no trades are open, balance equals equity. In a multi-position EA, it creates a progressively worse distortion.
Consider a $10,000 account with 1% risk per trade and a $400 stop loss per position. Position 1 opens at 0.25 lots — correct. That position moves $300 into loss. `AccountBalance()` still returns $10,000. Position 2 opens at 0.25 lots, but the account's real equity is now $9,700. By position 3, with $600 of floating losses across two open trades, each new lot size is calculated from a balance figure that hasn't updated. The cumulative exposure is understated by the losses in progress.
The problem is invisible in Strategy Tester because backtesting calculates performance metrics on closed trades. You only see the distortion in live accounts where multiple positions run simultaneously for extended periods.
Where the Pattern Comes From
`AccountBalance()` appears in every MQL tutorial, AI-generated code template, and starter EA I have encountered. It is correct for the context in which it is always demonstrated — a single-trade system. Nobody catches the error during development because single-position backtests don't trigger it.
In rescue and optimization work, the pattern is almost always the same: an EA originally built and tested with one position at a time, then adapted to a scaling-in or multi-symbol strategy without revisiting the lot-size function. The function compiles, passes basic testing, and deploys — then produces unexpected exposure in live markets.
Three Correct Approaches
AccountEquity() — the default fix for most multi-position EAs
`AccountEquity()` returns balance plus all open trade floating P&L. When positions are losing, equity is lower than balance. A lot-size formula based on equity automatically sizes each new position from the account's real current value.
// Before (wrong for multi-position EAs)
double lots = (AccountBalance() * riskPercent) / (stopLossPoints * tickValue);
// After (correct for most multi-position EAs)
double lots = (AccountEquity() * riskPercent) / (stopLossPoints * tickValue);
This one substitution is the complete fix for scaling-in strategies. As equity drops with open losing positions, each subsequent lot opens smaller. The formula functions as a natural drawdown brake — it does not prevent losses, but it prevents the sizing from amplifying them.
AccountFreeMargin() — for margin-constrained dense grids
`AccountFreeMargin()` returns available margin not yet committed to open positions. Use this as a secondary binding constraint — not as a standalone replacement — when margin consumption is the primary risk factor. It caps your margin usage but does not directly control percentage exposure. Best applied on top of an equity-based formula as a hard floor rather than the primary sizing reference.
Pre-allocated budget — for fixed grid EAs
If your EA opens a known maximum number of positions, pre-allocate the full risk budget at session start and divide it by the maximum slot count. For a 3-level grid with 3% total risk: each level gets 1% of session-start equity. Calculate lot sizes once at session open and hold them constant for the session.
This eliminates dynamic accumulation entirely. The trade-off: if equity drops significantly mid-session, pre-allocated lot sizes are proportionally larger than the current-equity target. For most grid EAs operating within a single trading session, this is an acceptable trade-off against the more serious problem of unbounded accumulation.
The Grid Edge Case
`AccountEquity()`-based sizing solves most multi-position scenarios but does not prevent aggregate overexposure in grid EAs. A grid adds positions as price moves against you — equity is falling at exactly the moment new levels are opening. Each level opens smaller than the previous (correct behaviour). But "smaller at each step" does not prevent the aggregate from becoming unworkable.
I worked with a client whose grid EA on XAUUSD used `AccountEquity()`-based sizing at 1% risk per position. During a 600-pip directional move, 9 positions accumulated simultaneously. Each individual level was correctly sized smaller than the previous as equity declined. The aggregate reached 8% of the starting account — even though no single position had been sized above 1% of its respective equity reference at the time it opened. The trader had verified individual position sizing and found it correct. The aggregate had not been modelled.
For grid EAs, use pre-allocated exposure budgeting with an explicit maximum level cap enforced in code. Decide in advance: this grid will open at most N levels with X% total budget. Each level gets X%/N. The level cap is a hard stop — not a preference.
Diagnostic Checklist
Three checks to run on any multi-position EA:
- Find the lot-size function. It is usually named `CalculateLotSize()`, `GetLotSize()`, or similar — or inline before `OrderSend()` in MT4 / `trade.Buy()` or `trade.Sell()` in MT5.
- Search for `AccountBalance()`. If the EA can open more than one simultaneous position and this function is in the lot-size calculation, the calculation is wrong.
- Apply the fix. `AccountEquity()` for scaling-in strategies. Pre-allocated budgeting for fixed grid patterns.
Verification: run in Strategy Tester with a configuration that produces multiple simultaneous positions in a drawdown phase. Lot sizes for new positions should visibly decrease during drawdown. If they remain flat, an `AccountBalance()` reference is still active — check secondary lot calculations, copy-pasted logic in hedge order handling, or separate order-type functions.


