If you're a DARWIN provider, capacity is the number that decides how far your strategy can scale. It's the ceiling on how much investor AUM your DARWIN can absorb before performance starts to degrade under the weight of its own execution. It's also one of the axes the Darwinex Labs team uses when deciding how to allocate to the INDX master fund across the DARWIN pool, and being weak on capacity hard-caps your allocation regardless of how good your strategy is.
>> Lee este artículo en español aquí.
The mechanics are unforgiving. Your underlying trading account places an order. That order is then processed by the Risk Manager and replicated, with the correct scaling, onto every investor account allocated to your DARWIN, including INDX's. What was a 0.30 lot order on your side becomes, in aggregate, a much larger order hitting the market at the same instant.
A single market order fired at full size might eat through multiple levels of the order book. The bigger your AUM grows, the wider the fills spread, and the more of your strategy's edge is silently transferred to the liquidity providers on the other side. Every pip of avoidable slippage on a live fill translates, in aggregate across all replications of your strategy, into a lower capacity score and a lower AUM ceiling.
The fix is simple in principle: don't send one big order. Send several smaller ones, spaced a few seconds apart, giving the order book time to refill between fills. Institutional execution desks have done this for decades; it's the basic idea behind TWAP (Time-Weighted Average Price) execution. Applied to a DARWIN, it directly lifts the capacity ceiling: the same strategy, executing the same signals, but with a materially lower slippage footprint per unit of AUM.
The problem is that implementing this in MQL5 is fiddly. You need a state machine to track the child orders, handle the timing, deal with failures, manage the exit side, and do the lot math correctly. It's the kind of plumbing code that's tedious to write, easy to get wrong, and identical across every EA that needs it.
So we built a small open-source library that handles all of it, purpose-built for DARWIN providers. You change three lines in your EA and the splitting happens transparently.
⚠️ Alpha Software. Use at your own risk.
This is alpha-stage code and has not been battle-tested across brokers, symbols, or live market conditions. It is your responsibility to review the source, run the library on a demo account, and verify its behaviour end-to-end before deploying it with real funds.
The authors and contributors accept no liability for trading losses, execution errors, partial fills, missed closes, slippage, broker rejections, or any other financial or operational consequence arising from the use of this software. The library is provided "AS IS", without warranties of any kind. See Sections 7 and 8 of the Apache License 2.0 in the repo for the full disclaimer.
You give it your intended lot size and it splits the execution into N equal orders fired sequentially with a configurable delay. Here is the full lifecycle:
Your EA calculates its normal lot size, say 0.30 lots. The library divides that into N parts (say 3), respecting the minimum lot and lot step on the underlying symbol. Your EA sends the first order normally. The library then fires the remaining two automatically, 10 seconds apart, each with the same stop loss as the original.
The library watches your first position. If it hits SL, each child position has its own broker-side SL, so they close independently. Nothing special needed. But if you close via TP or manually, the library detects this and sequentially closes the remaining children with the same delay. This prevents slippage on the exit side too, which people often forget about, and which counts towards your DARWIN's capacity score too.
If a child order gets a requote or fails for any reason, the library retries a configurable number of times before skipping that slice and continuing. The volume of the failed slice is logged so you know exactly what happened.
Copy the Include/SplitOrder/ folder into your MQL5 data directory under Include/. That gives you three files:
SplitOrder.mqh: the core librarySplitOrderConfig.mqh: the configuration structSplitOrderSQX.mqh: the StrategyQuant X adapter (ignore this one if you're not an SQX user)That's it. No DLLs, no dependencies, no compilation steps beyond your normal EA build.
Let's walk through a real example. Say your EA currently opens a buy like this:
void OpenBuy() { double lots = 0.30; double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double sl = NormalizeDouble(ask - 200 * _Point, _Digits); MqlTradeRequest req = {}; MqlTradeResult res = {}; req.action = TRADE_ACTION_DEAL; req.symbol = Symbol(); req.volume = lots; req.type = ORDER_TYPE_BUY; req.price = ask; req.sl = sl; req.deviation = 5; req.magic = 12345; req.type_filling = ORDER_FILLING_IOC; OrderSend(req, res); }
Here's the same function with splitting enabled. The lines marked with ★ are the only additions:
#include <SplitOrder/SplitOrder.mqh> // ★ Include the library SplitConfig splitCfg; // ★ Declare once globally SplitState splitLong; SplitState splitShort; void OpenBuy() { double lots = 0.30; // ★ Step 1: Adjust the lot size for pos0 lots = SplitAdjustLots(Symbol(), lots, splitCfg); double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double sl = NormalizeDouble(ask - 200 * _Point, _Digits); MqlTradeRequest req = {}; MqlTradeResult res = {}; req.action = TRADE_ACTION_DEAL; req.symbol = Symbol(); req.volume = lots; // Now holds the pos0 portion req.type = ORDER_TYPE_BUY; req.price = ask; req.sl = sl; req.deviation = 5; req.magic = 12345; req.type_filling = ORDER_FILLING_IOC; if(!OrderSend(req, res)) return; // ★ Step 2: Register the fill. Library handles the rest SplitRegister(splitLong, res.order, splitCfg, 0.30); // Note: pass the ORIGINAL total lots (0.30), not the adjusted amount }
Then in your OnInit(), set up the config and start a 1-second timer:
int OnInit() { SplitConfigInit(splitCfg); splitCfg.splitCount = 3; // Split into 3 orders splitCfg.delaySeconds = 10; // 10 seconds between each splitCfg.magic = 12345; SplitReset(splitLong); SplitReset(splitShort); EventSetTimer(1); return INIT_SUCCEEDED; }
And in OnTimer(), call the engine:
void OnTimer() { SplitManage(splitLong, splitCfg); SplitManage(splitShort, splitCfg); }
That's the entire integration. Your signal logic, risk management, and everything else stays exactly as it was.
A lot of modern EAs use the CTrade class from <Trade/Trade.mqh> instead of building MqlTradeRequest structs by hand. If that's you, good news: the library doesn't care how you place your first order. It only needs the resulting ticket.
Here's the same integration pattern using CTrade:
#include <Trade/Trade.mqh> #include <SplitOrder/SplitOrder.mqh> CTrade trade; SplitConfig splitCfg; SplitState splitLong; void OpenBuy() { double totalLots = 0.30; double pos0Lots = SplitAdjustLots(Symbol(), totalLots, splitCfg); // ★ double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double sl = NormalizeDouble(ask - 200 * _Point, _Digits); if(!trade.Buy(pos0Lots, Symbol(), ask, sl, 0)) return; SplitRegister(splitLong, trade.ResultOrder(), splitCfg, totalLots); // ★ }
Same three lines of change, same behavior. Internally, the library uses MqlTradeRequest for its child orders rather than borrowing your CTrade instance. This keeps your trade.ResultXxx() values clean so any code you have checking trade.ResultRetcode() or trade.ResultPrice() after your buy/sell still reflects your order, not the library's internal activity.
One subtle thing worth mentioning: different instruments support different order filling modes. Some require IOC (Immediate-or-Cancel), some require FOK (Fill-or-Kill), some exchange-traded symbols default to RETURN. Getting this wrong results in orders being rejected with Invalid fill policy errors.
The library reads each symbol's SYMBOL_FILLING_MODE property and automatically picks the right mode: IOC if available, FOK as fallback, RETURN as last resort. This is the same detection logic that CTrade::SetTypeFillingBySymbol() uses internally, so it works reliably across every symbol on the Darwinex feed.
You don't need to configure anything for this to work. It just does.
A large fraction of DARWIN providers generate their strategies with StrategyQuant X (SQX). Every SQX-generated strategy routes all its order placement through a single function called openPosition(), and on recent SQX builds that function has a stable signature.
That consistency lets us ship a dedicated SQX adapter. It provides a function called openPositionSplit() with the exact same signature as SQX's openPosition(), meaning integration comes down to a find-and-replace. Four steps, all mechanical:
// 1. Add the include at the top of your .mq5 file: #include <SplitOrder/SplitOrderSQX.mqh> // 2. In OnInit(), add: SQXSplitInit(3, 10); EventSetTimer(1); // 3. In OnTimer(), add: SQXSplitOnTimer(); // 4. Find-and-replace: openPosition( → openPositionSplit(
The adapter handles all the complexity internally. You don't declare any state variables, you don't call any register functions, you don't manage any configuration struct. The decision of whether to split a given order is made automatically based on its type:
BUY_STOP, SELL_LIMIT, etc.) pass through unchanged. Splitting a pending order into N pending orders at the same price doesn't reduce slippage, since they all fill at the same moment when the level is touched.isExitLevel = true) pass through unchanged. These are typically EOD closes, stop-reverses, or signal-driven exits: situations where the position is urgently being unwound and splitting would leave partial exposure during the close window. Not worth the risk.Crucially, pos0 still goes through SQX's own openPosition(), so all the SQX-native protections stay intact: margin checks, duplicate detection, the sqHandleTradingOptions gate (weekend filter, EOD cutoff, session hours, max trades per day), pending order replacement logic, and SQX's own retry mechanism. We don't replace any of that. We just wrap it.
If you use an LLM like Claude or ChatGPT to help modify your SQX strategy, you can hand it exactly those four steps above and it will apply them correctly. The transformation is mechanical enough that it's hard to get wrong.
Note: Child orders bypass the
sqHandleTradingOptionsgate because they're fired directly by the library's engine rather than throughopenPosition(). In practice, this matters only if a child is scheduled to fire across a gate boundary. For typical split delays of 5–30 seconds, this is a narrow edge case. If it matters for your setup, avoid placing entries in the last minute before an SQX cutoff.
This is the part that trips people up when implementing splitting manually, so it's worth understanding what the library does.
Say you want 0.30 lots split 4 ways, and the symbol uses a 0.01 lot step. A naive division gives 0.075 per split, which isn't a valid lot size. The library floors each child to the nearest valid step: floor(0.075 / 0.01) * 0.01 = 0.07. Four children at 0.07 gives 0.28, so you've lost 0.02 lots.
The library solves this by giving the remainder to the first order (pos0). So the actual distribution becomes 0.09 + 0.07 + 0.07 + 0.07 = 0.30. Total volume is always preserved. That matters, because mismatched total volume would change the effective risk on your underlying account and, by extension, misrepresent the signal being replicated across investor allocations.
If your total volume is so small that the per-child size would fall below the minimum lot, the library automatically reduces the split count until it works, and logs a message telling you what happened. If it can't split at all, it falls back to a single order. Your trade still goes through, just without splitting.
If your EA has an end-of-day close, a signal reversal, or any other reason to unwind all positions immediately, call SplitCancelAndClose():
if(SplitIsActive(splitLong)) SplitCancelAndClose(splitLong, splitCfg);
This stops any pending child entries and sequentially closes all open split positions with the same configured delay. The staggered closing prevents the same slippage problem on the exit side.
The exit asymmetry is a deliberate design choice worth understanding.
When your first position (pos0) hits its Stop Loss (SL), the market is moving against you and speed matters. Each child position has its own identical SL set at the broker level, so they'll each be stopped out independently by the broker's execution engine: no delay, no sequential closing, no intervention from the library. The library simply resets and gets out of the way.
When pos0 closes via Take Profit (TP) or manual close, the situation is different. There's no urgency, and closing all remaining children at once would cause the same slippage you were trying to avoid. So the library starts a close chain: it closes one child every N seconds, just like it opened them.
All settings live in the SplitConfig struct:
OrderSend.[SplitOrder][LONG] or [SplitOrder][SHORT] so you can trace exactly what happened in the Experts tab.Before you plug this into the strategy that drives your live DARWIN, it's worth seeing the library in action with your own eyes on a demo account. The repo includes SplitOrder_TestEA.mq5 for exactly that purpose.
The test EA opens a random BUY or SELL on EURUSD every minute (provided no position is already open) with symmetric 20-pip SL and TP. That symmetry is deliberate: you get a roughly 50/50 mix of SL and TP exits, which means you'll observe both close paths without having to wait for a specific market condition.
Attach it to an EURUSD chart on a Darwinex demo account with default settings (0.05 lots split into 3, 10 seconds apart) and open the Experts tab. Within a few minutes you'll see the full lifecycle:
[TestEA] BUY signal — entry=... pos0=0.03 (total=0.05). Confirms pos0 got the rounding remainder (0.03 = 0.01 base + 0.02 remainder, children get 0.01 each).[SplitOrder][LONG] Registered BUY #... — 3 splits, child lots=0.01000.... Library took over after pos0 filled.Entry X/3 fired log lines spaced 10 seconds apart.SL=YES with children stopping out independently on their broker SLs, or SL=NO (TP/manual) followed by the sequential close chain.Watching this happen once is the fastest way to build confidence that the library does what it says before you point it at the account that feeds your DARWIN.
This is a v1 release, and there are some things it intentionally doesn't do:
We built this library, and we're giving it away under Apache 2.0, because capacity isn't a zero-sum contest between DARWIN providers. It's closer to the opposite.
INDX (Darwinex's systematic, multi-manager portfolio) is growing quickly, and its pipeline is increasingly filled by institutional capital. That money has to be allocated somewhere, and INDX's ability to put it to work is bottlenecked not by demand for allocation (there's plenty of that) but by the supply of investable edge—meaning DARWINs with enough capacity headroom to usefully absorb a bigger allocation without their execution costs ballooning.
When that supply is narrow, INDX is forced to concentrate allocation into a handful of high-capacity names or reduce its overall allocation, and most providers never see serious flow regardless of how interesting their strategy is. When that supply is wide, with many DARWINs across the pool having done the work to reduce their execution footprint, INDX can allocate meaningfully across more of the pool, and the total flow from institutional investors into community-run strategies grows.
Every DARWIN that raises its capacity enlarges the pool of allocatable AUM for everyone. The provider next to you shipping splitting on their EA isn't competing with you. They're expanding the ceiling on the flow INDX can put to work, and a share of that lift eventually lands on your balance sheet too.
That's why this library is open-sourced rather than a paid tool, and why pull requests are welcome. If you find a bug, add a feature, or extend it to handle an execution edge case we missed, the fix propagates to everyone running it. Community infrastructure gets better when the community contributes back to it.
The library is open source under the Apache 2.0 license. You can find it on GitHub at marticastany/darwin-capacity-optimiser, along with a complete example EA showing the full integration pattern.
If you find bugs or have ideas for improvements, issues and pull requests are welcome. Just please test on a demo account first. And if splitting meaningfully moves your DARWIN's capacity score, we'd genuinely love to hear about it.
Martí Castany
Quant - Darwinex Labs Team
Adjust the parameters below to see the impact of market depth and liquidity replenishment on your average fill price.
*Darwinex Zero and the domain www.darwinexzero.com are trade names used by Tradeslide Technologies, a company registered in the United Kingdom under number 14398381.
The contents of this blog post and video are for educational purposes only and should not be construed as financial and/or investment advice.