IC Markets

MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Share, develop, and backtest custom MQL4/MQL5 Expert Advisors, Python data-scraping scripts, trading bots, and automated market alert systems.
PTScalper
Site Admin
Posts: 939
Joined: Mon Jul 20, 2026 1:28 pm

MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Post by PTScalper »

Hi traders.

If you are porting an M1 scalper from MQL4 to MQL5, or testing a new EA on a live MT5 account, you have likely run into the dreaded OrderSend Error 130.In MT4, this was known as ERR_INVALID_STOPS. In the MQL5 environment, it shows up as trade server return code 10016 (TRADE_RETCODE_INVALID_STOPS). This error means the trade server rejected your OrderSend request because your Stop Loss (SL) or Take Profit (TP) parameters violated broker constraints. When scalping on the M1 timeframe, you are hunting for micro-movements, which means your stops are incredibly tight. Here is how to programmatically solve this issue and ensure your EA executes flawlessly.

1. Account for SYMBOL_TRADE_STOPS_LEVEL

Brokers require a minimum distance (in points) between the current market price and any resting SL/TP. If you place a stop within this zone, the server rejects it. The MQL5 Fix:Always query the stop level dynamically. Since spreads widen wildly during M1 volatility, a robust scalping EA should use the spread as a safety multiplier if the broker returns a stop level of zero.

Code: Select all

long stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);

// Some brokers return 0 for stopLevel but secretly enforce limits based on spread
long actualStopLevel = MathMax(stopLevel, spread * 2); 
double minStopDist = actualStopLevel * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
2. Respect Bid/Ask Mechanics

A very common logic mistake is calculating a Buy order's Stop Loss from the Ask price instead of Bid, which pushes it too close to the invalid zone.
Buy Orders: Entry happens at Ask. The SL must be validated relative to Bid (i.e., SL < Bid - minStopDist).
Sell Orders: Entry happens at Bid. The SL must be validated relative to Ask (i.e., SL > Ask + minStopDist).

3. Normalize Your Doubles

Floating-point precision errors will instantly trigger a 10016 invalid stops error. Never send raw calculated variables directly to OrderSend. You must wrap your final SL and TP prices in NormalizeDouble() so they match the exact decimal structure of the symbol.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 939
Joined: Mon Jul 20, 2026 1:28 pm

Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Post by PTScalper »

And here is code for it:

Code: Select all

double rawSL = SymbolInfoDouble(_Symbol, SYMBOL_BID) - minStopDist;
double cleanSL = NormalizeDouble(rawSL, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
4. The Execution Mode Workaround (ECN Tip)

If your code is mathematically perfect but you still get 10016 on a live ECN account, it is likely due to Market Execution limits. Many MT5 ECN/STP brokers do not allow you to attach an SL/TP during the initial market entry.

The MT5 Solution:
Send the initial market order with request.sl = 0 and request.tp = 0. Wait for the order to fill and generate a position ticket.Send a second request using TRADE_ACTION_SLTP to apply your stops to the open position.

The Ultimate M1 Scalper Tip (Virtual Stops):
Because M1 scalpers need 2-3 pip stops, broker limits will constantly block you. The best way around this is to use Virtual (Hidden) Stops. Keep your SL and TP variables entirely inside your MQL5 code rather than sending them to the broker. Compare the real-time OnTick() price to your internal variables, and close the position via a standard market order when your limit is reached. This completely bypasses broker SYMBOL_TRADE_STOPS_LEVEL restrictions and hides your ultra-tight exit strategies from the server!

Hope this saves you some debugging time. Happy scalping!
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 939
Joined: Mon Jul 20, 2026 1:28 pm

Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Post by PTScalper »

I thought about it, how to make it in better way and here is the upgraded solution:

The logic outlined in your forum post covers the fundamental mechanics perfectly. Normalizing doubles, validating relative to the correct Bid/Ask side, and utilizing a two-step execution for ECN brokers are all mandatory for MQL5.

However, moving from a script that works to a professional, institutional-grade scalping architecture requires shifting from procedural error-catching to predictive, event-driven state management.

Here is how to handle TRADE_RETCODE_INVALID_STOPS (10016) at an advanced engineering level.

1. Implement Predictive Spread & Liquidity Profiling

Relying on a static MathMax(stopLevel, spread * 2) is reactive and can still fail during severe volatility (like NFP or central bank events). A professional scalper tracks spread dynamically to predict execution safety before the OrderSend request is ever built.

Tick Arrays: Maintain a rolling array of the last 100–500 ticks.

Volatility Multipliers: Calculate the standard deviation of the spread. If the current spread spikes beyond 2 standard deviations, temporarily pause order modification or widen the virtual stops dynamically.

Tick Staleness: Never calculate a stop based on a stale tick. Always validate time_msc from SymbolInfoTick() against your local system time to ensure the quote is fresh (under 50ms old) before calculating the stop distance.

2. Check SYMBOL_TRADE_FREEZE_LEVEL

Most developers check STOPS_LEVEL, but completely ignore FREEZE_LEVEL. If a position is moving heavily in your favor and approaches your Take Profit, the broker's freeze level locks the order. If your trailing stop attempts to modify an order inside this zone, it will return an error.

Code: Select all

long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
long freezeLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
long currentSpread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);

// The true invalid zone is the largest of these constraints
long effectiveLevel = MathMax(stopsLevel, MathMax(freezeLevel, currentSpread));
double minStopDist = effectiveLevel * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
Your effective minimum distance must be calculated against the absolute maximum of all three broker constraints:
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 939
Joined: Mon Jul 20, 2026 1:28 pm

Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Post by PTScalper »

3. Transition to Asynchronous Execution (OrderSendAsync)

Blocking functions like OrderSend() leave your EA paralyzed while waiting for the server to reply. In an M1 scalping environment, milliseconds matter.

Professional EAs use OrderSendAsync(). This sends the request to the broker without waiting for the result, allowing your EA to immediately return to processing the next tick. The results (including Error 130/10016) are handled via a state machine inside the OnTradeTransaction() event handler.

4. State Machine Retry Logic with Exponential Backoff

If a stop modification fails due to a momentary spread spike, a professional system does not simply throw an error to the journal and give up. It queues the action.

Queue the Action: Store the intended SL/TP modification in an internal struct.

Evaluate: On the next tick, re-evaluate the market conditions. Is the spread still too wide?

Backoff: Implement a micro-backoff. If it fails twice, wait 3 ticks.

Timeout: If the conditions remain invalid for more than 500ms, destroy the queue request to prevent sending a deeply outdated modification.

5. Bulletproofing Virtual Stops

As you noted, keeping stops virtual (hidden in memory) is the ultimate solution for avoiding server-side constraints. However, a professional implementation of virtual stops requires extreme fail-safes:

Slippage Bounds: When the internal virtual SL is hit, you must send a market close order. You must define an aggressive slippage parameter in that request. If you don't, extreme momentum might fill your "tight 2-pip" virtual stop 10 pips away.

Disaster Recovery (Hard Stops): Never run virtual stops naked. Always deploy a "catastrophe" hard stop on the broker side (e.g., 15 pips away) to protect against a sudden internet outage or server crash, while your virtual logic handles the 2-3 pip exits locally.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 939
Joined: Mon Jul 20, 2026 1:28 pm

Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Post by PTScalper »

Transitioning to an asynchronous, event-driven architecture in MQL5 requires decoupling the trade request from the execution confirmation. Because OrderSendAsync() returns immediately, your EA must manage the lifecycle of every order in memory and react to broker responses as they arrive.

To build an institutional-grade state machine for M1 scalping, you need three core components: a State Tracker, the Transaction Router, and the Tick-Driven Retry Queue.

Here is how to structure this architecture.

1. Define the State Lifecycle & Tracking Struct

First, establish the states a scalping order can occupy. Because ECN brokers often prohibit setting Stop Loss (SL) and Take Profit (TP) on the initial market entry, the state machine must track the order through a two-phase execution.

Code: Select all

enum ENUM_ORDER_STATE 
{
    STATE_NONE,
    STATE_ENTRY_PENDING,       // Sent async entry, waiting for broker confirmation
    STATE_FILLED_NO_STOPS,     // Entry filled, position exists, awaiting SL/TP
    STATE_STOPS_PENDING,       // Sent async SL/TP modification
    STATE_ACTIVE,              // Position is fully secured with stops
    STATE_RETRY_STOPS          // SL/TP rejected (e.g. error 10016), queued for retry
};

struct TradeContext 
{
    ulong   magic_number;
    ulong   ticket;            // Order or Position ticket
    uint    request_id;        // Links async request to transaction response
    double  target_sl;
    double  target_tp;
    int     retry_count;
    ulong   last_attempt_time; // For exponential backoff
    
    ENUM_ORDER_STATE state;
};

// Array or Hash Map to track active trades
TradeContext ActiveTrades[];
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 939
Joined: Mon Jul 20, 2026 1:28 pm

Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Post by PTScalper »

2. The OnTradeTransaction Router

The OnTradeTransaction handler is the heart of the state machine. It acts as an interrupt, catching updates from the trade server. You must parse the trans_type to determine what happened.

The two most critical transaction types for this workflow are TRADE_TRANSACTION_DEAL_ADD (when your entry actually fills) and TRADE_TRANSACTION_REQUEST (when the broker formally accepts or rejects your async request).

Code: Select all

void OnTradeTransaction(const MqlTradeTransaction& trans, 
                        const MqlTradeRequest& request, 
                        const MqlTradeResult& result) 
{
    // 1. Detect when an entry order translates into an actual executed deal
    if (trans.type == TRADE_TRANSACTION_DEAL_ADD) 
    {
        int index = FindTradeByMagicOrTicket(trans.position);
        if (index >= 0 && ActiveTrades[index].state == STATE_ENTRY_PENDING) 
        {
            ActiveTrades[index].ticket = trans.position;
            ActiveTrades[index].state = STATE_FILLED_NO_STOPS;
            
            // Immediately attempt to apply stops now that the position exists
            ApplyStopsAsync(index); 
        }
        return;
    }

    // 2. Detect the final result of an asynchronous request
    if (trans.type == TRADE_TRANSACTION_REQUEST) 
    {
        int index = FindTradeByRequestId(result.request_id);
        if (index < 0) return;

        // Success: Stops successfully applied
        if (result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED) 
        {
            if (ActiveTrades[index].state == STATE_STOPS_PENDING)
                ActiveTrades[index].state = STATE_ACTIVE;
        }
        // Failure: Invalid Stops (10016), Requote, or Freeze Level hit
        else if (result.retcode == TRADE_RETCODE_INVALID_STOPS || result.retcode == TRADE_RETCODE_REQUOTE) 
        {
            ActiveTrades[index].state = STATE_RETRY_STOPS;
            ActiveTrades[index].last_attempt_time = GetMicrosecondCount();
            ActiveTrades[index].retry_count++;
            
            Print("Error 10016: Queuing stops for retry. Attempt: ", ActiveTrades[index].retry_count);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 939
Joined: Mon Jul 20, 2026 1:28 pm

Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Post by PTScalper »

3. The OnTick Retry Manager (Backoff & Spread Validation)

If OnTradeTransaction flags a trade as STATE_RETRY_STOPS, it means the broker rejected it (usually due to a spread spike crossing the SYMBOL_TRADE_STOPS_LEVEL).

Instead of spamming the broker with immediate retries—which can result in IP throttling—use OnTick to process the retry queue with conditions and exponential backoff.

Code: Select all

void OnTick() 
{
    ulong currentTime = GetMicrosecondCount();
    
    // Evaluate the retry queue
    for (int i = 0; i < ArraySize(ActiveTrades); i++) 
    {
        if (ActiveTrades[i].state == STATE_RETRY_STOPS) 
        {
            // Failsafe: Destroy request if retried too many times
            if (ActiveTrades[i].retry_count > 5) 
            {
                Print("Max retries hit. Converting to Virtual Stops or closing position.");
                FallbackToVirtualStops(i);
                continue;
            }

            // Exponential Backoff: Wait longer between each failed attempt (e.g., 50ms * retry_count)
            ulong waitTime = 50000 * ActiveTrades[i].retry_count; 
            if (currentTime - ActiveTrades[i].last_attempt_time < waitTime) 
                continue; 

            // Validate constraints before firing another async request
            if (IsSpreadSafe(ActiveTrades[i].target_sl, ActiveTrades[i].target_tp)) 
            {
                ApplyStopsAsync(i);
            }
        }
    }
    
    // Normal EA logic follows...
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 939
Joined: Mon Jul 20, 2026 1:28 pm

Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Post by PTScalper »

Architectural Considerations for Scalping

Concurrency: If you are executing high volume across multiple currency pairs on a single EA, wrap your tracking array in a class and ensure you index by magic_number combined with the position_ticket.

The Request ID: When you call OrderSendAsync(), the function returns a boolean, but it populates the request.request_id passed into it by reference. You must store this ID immediately. It is the only way to link the broker's later TRADE_TRANSACTION_REQUEST response back to your specific order.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 939
Joined: Mon Jul 20, 2026 1:28 pm

Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Post by PTScalper »

When you push institutional volume into an ECN book, your single market order will inevitably sweep multiple price levels. The broker matches your order against several liquidity providers, splitting your single request into multiple deals.

In MT5, this creates a race condition for a basic state machine: every new partial deal dynamically alters the position's Volume Weighted Average Price (VWAP). If you attempt to attach a Stop Loss while the server is still executing subsequent partial deals, the broker will frequently reject your modification request with TRADE_RETCODE_LOCKED (10026) because the position is currently being modified by the matching engine.

To handle high-volume flow, the state machine must transition from tracking Deals to tracking the Order Lifecycle and calculating VWAP.

Here is how to adapt the architecture.

1. Upgrade the Tracker for VWAP and Volume

The state struct needs to monitor the requested volume versus the filled volume, and track the shifting entry price so your SL/TP calculations remain mathematically accurate to your intended Risk:Reward ratio.

Code: Select all

struct TradeContext 
{
    ulong   magic_number;
    ulong   order_ticket;      // The original request
    ulong   position_ticket;   // The resulting position
    uint    request_id;
    
    double  requested_volume;
    double  filled_volume;
    double  vwap_price;        // Dynamically shifts with partial fills
    
    double  target_sl_pips;    // Store pips/points instead of absolute prices
    double  target_tp_pips;
    
    int     retry_count;
    ulong   last_attempt_time;
    ENUM_ORDER_STATE state;
};
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 939
Joined: Mon Jul 20, 2026 1:28 pm

Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping

Post by PTScalper »

2. Shift the Trigger to HISTORY_ADD

Instead of firing your SL/TP modification on TRADE_TRANSACTION_DEAL_ADD (which happens on the very first partial fill), you must wait for the order to be moved to the history pool.

TRADE_TRANSACTION_HISTORY_ADD is the broker's definitive signal that the liquidity sweep is complete. The order is either fully filled, or partially filled and the remainder was canceled (Fill or Kill / Immediate or Cancel).

Code: Select all

void OnTradeTransaction(const MqlTradeTransaction& trans, 
                        const MqlTradeRequest& request, 
                        const MqlTradeResult& result) 
{
    // 1. Track Partial Fills to calculate accurate VWAP
    if (trans.type == TRADE_TRANSACTION_DEAL_ADD) 
    {
        int index = FindTradeByOrderTicket(trans.order);
        if (index >= 0 && ActiveTrades[index].state == STATE_ENTRY_PENDING) 
        {
            // Link the position ticket (crucial for hedging accounts)
            ActiveTrades[index].position_ticket = trans.position;
            
            // Calculate new VWAP based on the incoming partial deal
            double deal_price = trans.price;
            double deal_volume = trans.volume;
            
            double total_cost = (ActiveTrades[index].vwap_price * ActiveTrades[index].filled_volume) + (deal_price * deal_volume);
            ActiveTrades[index].filled_volume += deal_volume;
            ActiveTrades[index].vwap_price = total_cost / ActiveTrades[index].filled_volume;
        }
        return;
    }

    // 2. The Execution Sweep is Complete
    if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) 
    {
        int index = FindTradeByOrderTicket(trans.order);
        if (index >= 0 && ActiveTrades[index].state == STATE_ENTRY_PENDING) 
        {
            // Order is fully processed. Transition state.
            ActiveTrades[index].state = STATE_FILLED_NO_STOPS;
            
            // Calculate absolute SL/TP prices based on the final VWAP
            CalculateAbsoluteStopsFromVWAP(index);
            
            // Now it is safe to apply stops without hitting a locked position error
            ApplyStopsAsync(index); 
        }
        return;
    }

    // 3. Handle the async modification result (same as before)
    if (trans.type == TRADE_TRANSACTION_REQUEST) 
    {
        // ... Retry logic for Error 10016 / 10026 ...
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply