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

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

Post by PTScalper »

3. The Execution Gap Vulnerability

Waiting for HISTORY_ADD creates a vulnerability window. If you are filling hundreds of lots across a fragmented order book, it might take 100–300 milliseconds for the final partial deal to execute. During this fraction of a second, your massive position is completely naked on the broker side.

To protect the equity during this micro-window, the professional standard is to enforce a Virtual Disaster Stop immediately upon the first DEAL_ADD.

While the state machine waits for the order to finish filling so it can attach the hard broker-side SL, your local OnTick or OnTimer loop actively monitors the position's shifting VWAP against the real-time bid/ask. If a flash crash occurs mid-execution, your local EA intercepts it and immediately fires an asynchronous market close for the filled_volume amount, overriding the pending state machine.
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 »

Calculating your stops dynamically from the Volume Weighted Average Price (VWAP) rather than the requested entry price ensures your absolute risk-to-reward ratio remains perfectly mathematically intact, even if a massive volume sweep causes heavy slippage across the order book.

To do this robustly, the calculation logic must handle three core mechanical hurdles: fractional tick scaling (3-digit vs. 5-digit brokers), directional asymmetry (Buy vs. Sell offsets), and floating-point sterilization.

Here is the implementation to finalize the absolute prices once TRADE_TRANSACTION_HISTORY_ADD fires.

The VWAP Stop Calculation Logic

This function assumes you have logged the intended position direction (pos_type) into your TradeContext struct at the time of the initial async request.

Code: Select all

void CalculateAbsoluteStopsFromVWAP(int index) 
{
    // 1. Data Integrity Check
    // A VWAP of 0.0 means the array is corrupted or deals didn't process correctly
    if (ActiveTrades[index].vwap_price <= 0.0) 
    {
        PrintFormat("CRITICAL: Invalid VWAP (%f) for Order %I64u", 
                    ActiveTrades[index].vwap_price, ActiveTrades[index].order_ticket);
        return;
    }

    double vwap = ActiveTrades[index].vwap_price;
    ENUM_POSITION_TYPE posType = ActiveTrades[index].pos_type; 
    
    // 2. Fractional Tick Scaling
    // Safely scales pips to points regardless of the broker's decimal structure
    double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    double pipMultiplier = (digits == 3 || digits == 5) ? 10.0 : 1.0;
    
    // Convert the struct's intended pip limits into raw price distances
    double slDistance = ActiveTrades[index].target_sl_pips * pipMultiplier * point;
    double tpDistance = ActiveTrades[index].target_tp_pips * pipMultiplier * point;

    double rawSL = 0.0;
    double rawTP = 0.0;

    // 3. Directional Application
    // Buys subtract SL and add TP. Sells add SL and subtract TP.
    if (posType == POSITION_TYPE_BUY) 
    {
        if (slDistance > 0) rawSL = vwap - slDistance;
        if (tpDistance > 0) rawTP = vwap + tpDistance;
    }
    else if (posType == POSITION_TYPE_SELL) 
    {
        if (slDistance > 0) rawSL = vwap + slDistance;
        if (tpDistance > 0) rawTP = vwap - tpDistance;
    }

    // 4. Floating-Point Sterilization
    // OrderSend will reject un-normalized doubles with Error 130/10016 instantly.
    ActiveTrades[index].target_sl = (rawSL > 0.0) ? NormalizeDouble(rawSL, digits) : 0.0;
    ActiveTrades[index].target_tp = (rawTP > 0.0) ? NormalizeDouble(rawTP, digits) : 0.0;
    
    PrintFormat("Order %I64u VWAP Sweep Complete -> VWAP: %.*f | Hard SL: %.*f | Hard TP: %.*f", 
                ActiveTrades[index].order_ticket, 
                digits, vwap, 
                digits, ActiveTrades[index].target_sl, 
                digits, ActiveTrades[index].target_tp);
}
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 Integration

Zero-Value Handling: Notice the if (slDistance > 0) checks. High-volume scalping algorithms often utilize asynchronous TP trailing mechanisms or purely virtual take profits, meaning the broker-side TP is intentionally left at zero. The code explicitly preserves 0.0 to avoid sending a garbage NormalizeDouble value to the server.

Asymmetric Slippage Adjustments: If your VWAP suffers massive negative slippage (meaning you got filled at a worse price than anticipated), calculating your Stop Loss based on VWAP automatically drags the Stop Loss further away from your intended entry price. While this maintains your exact pip risk, it increases your absolute dollar risk. If maintaining a strict maximum dollar drawdown per trade is your priority, you should clamp the rawSL calculation against your maximum acceptable monetary loss.
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 »

To protect high-volume orders during the critical microsecond window between the first partial fill (DEAL_ADD) and the final sweep completion (HISTORY_ADD), your EA must evaluate the risk on every single tick. If a macroeconomic shock or flash crash hits the book while the order is only 40% filled, the state machine cannot wait for the execution sweep to finish before reacting.

The Virtual Disaster Stop intercepts this vulnerability by comparing the shifting VWAP directly against the raw tick stream, completely bypassing the broker's stop-loss infrastructure.

Here is how to implement this high-performance tick loop.

1. Upgrade the Struct for Disaster Metrics

Add a disaster threshold and a new emergency state to your TradeContext struct to prevent the primary state machine from attempting to attach hard stops to a position you are currently trying to abort.

Code: Select all

enum ENUM_ORDER_STATE 
{
    // ... previous states ...
    STATE_EMERGENCY_LIQUIDATING  // Disaster stop hit, async close dispatched
};

struct TradeContext 
{
    // ... previous variables ...
    double disaster_sl_pips;     // E.g., 15.0 pips (well outside normal scalping range)
};
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 High-Performance OnTick Interceptor

This function must be extremely lightweight. It should be called at the very top of your OnTick() loop, before any signal generation, indicator updates, or standard trailing logic.

Code: Select all

void CheckVirtualDisasterStops() 
{
    // 1. Grab the absolute latest memory tick (bypasses potential OnTick variable lag)
    MqlTick latestTick;
    if (!SymbolInfoTick(_Symbol, latestTick)) return; 

    // Calculate conversion multipliers once per tick, not per array iteration
    double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    double pipMultiplier = (digits == 3 || digits == 5) ? 10.0 : 1.0;

    for (int i = 0; i < ArraySize(ActiveTrades); i++) 
    {
        // 2. State Filter: Only evaluate vulnerable, partially or fully filled positions
        if ((ActiveTrades[i].state == STATE_ENTRY_PENDING || ActiveTrades[i].state == STATE_FILLED_NO_STOPS) 
             && ActiveTrades[i].filled_volume > 0.0) 
        {
            double vwap = ActiveTrades[i].vwap_price;
            double disasterDist = ActiveTrades[i].disaster_sl_pips * pipMultiplier * point;
            
            bool triggerLiquidation = false;

            // 3. Directional VWAP vs. Real-Time Tick Check
            if (ActiveTrades[i].pos_type == POSITION_TYPE_BUY) 
            {
                // Buy stops trigger on the BID
                if (latestTick.bid <= (vwap - disasterDist)) triggerLiquidation = true;
            }
            else if (ActiveTrades[i].pos_type == POSITION_TYPE_SELL) 
            {
                // Sell stops trigger on the ASK
                if (latestTick.ask >= (vwap + disasterDist)) triggerLiquidation = true;
            }

            // 4. Execute Emergency Protocol
            if (triggerLiquidation) 
            {
                PrintFormat("CRITICAL: Flash crash detected. VWAP: %f | Tick: %f/%f. Firing Emergency Close.", 
                            vwap, latestTick.bid, latestTick.ask);
                
                ExecuteEmergencyLiquidation(i, latestTick);
            }
        }
    }
}
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 Emergency Liquidation Dispatcher

When the disaster stop is breached, you must instantly dispatch an OrderSendAsync to close the currently exposed volume. You cannot use the original requested_volume, as attempting to close volume you do not yet own will result in TRADE_RETCODE_INVALID_VOLUME (10014).

Code: Select all

void ExecuteEmergencyLiquidation(int index, const MqlTick &latestTick) 
{
    MqlTradeRequest request = {};
    MqlTradeResult result = {};
    
    request.action       = TRADE_ACTION_DEAL;
    request.position     = ActiveTrades[index].position_ticket;
    request.symbol       = _Symbol;
    // CRITICAL: Close only what has been filled so far
    request.volume       = ActiveTrades[index].filled_volume;  
    request.deviation    = 50; // Wide deviation permitted during a flash crash
    request.magic        = ActiveTrades[index].magic_number;

    if (ActiveTrades[index].pos_type == POSITION_TYPE_BUY) 
    {
        request.type = ORDER_TYPE_SELL;
        request.price = latestTick.bid;
    }
    else 
    {
        request.type = ORDER_TYPE_BUY;
        request.price = latestTick.ask;
    }

    // Lock the state so OnTradeTransaction ignores subsequent HISTORY_ADD sweeps
    ActiveTrades[index].state = STATE_EMERGENCY_LIQUIDATING;

    if (!OrderSendAsync(request, result)) 
    {
        PrintFormat("Failed to dispatch async emergency liquidation! Error: %d", GetLastError());
        // Failsafe: Revert state to allow the next tick to retry the liquidation
        ActiveTrades[index].state = STATE_FILLED_NO_STOPS; 
    }
    else 
    {
        // Store the new request ID so OnTradeTransaction can track the close
        ActiveTrades[index].request_id = result.request_id;
    }
}
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

Residual Fills: If your initial market entry order was 50 lots, and the disaster stop triggers at 20 lots filled, your ExecuteEmergencyLiquidation will close those 20 lots. However, the matching engine might still be processing the remaining 30 lots. To handle this properly, your broker's matching rules must be factored in. If you are not using ORDER_FILLING_IOC (Immediate or Cancel), you may also need to dispatch a TRADE_ACTION_REMOVE request to cancel the remainder of the original pending market order, preventing it from continuing to fill against the crashing book.

Latency Cost: Calling SymbolInfoTick inside OnTick is highly optimized in MQL5, pulling directly from local terminal memory without a network call. It adds virtually zero latency to the execution loop, making it safe for high-frequency tracking.
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 dealing with high-volume execution, the order of operations during a disaster scenario is critical. If you dispatch the market close for the filled volume before canceling the working remainder, the matching engine might fill the remaining volume immediately after your close executes. This would leave you with a brand new, unprotected position right in the middle of a flash crash.

You must dispatch the TRADE_ACTION_REMOVE request to kill the original order first, and immediately follow it with the TRADE_ACTION_DEAL request to liquidate whatever volume you currently hold.

Here is how to upgrade the emergency dispatcher to handle both actions simultaneously.

1. The Dual-Dispatch Emergency Sequence

You will need to construct two separate MqlTradeRequest objects and fire them asynchronously back-to-back.

Code: Select all

void ExecuteEmergencyLiquidation(int index, const MqlTick &latestTick) 
{
    // ==========================================
    // STEP 1: CANCEL THE REMAINING WORKING ORDER
    // ==========================================
    // This stops the bleeding and prevents new volume from filling
    
    MqlTradeRequest cancelRequest = {};
    MqlTradeResult  cancelResult  = {};
    
    cancelRequest.action = TRADE_ACTION_REMOVE;
    cancelRequest.order  = ActiveTrades[index].order_ticket;
    
    // Dispatch the cancel. We do not check for failure here because the 
    // order may have fully filled a millisecond ago, which would cause this to fail.
    OrderSendAsync(cancelRequest, cancelResult);


    // ==========================================
    // STEP 2: LIQUIDATE THE HELD POSITION
    // ==========================================
    
    MqlTradeRequest closeRequest = {};
    MqlTradeResult  closeResult  = {};
    
    closeRequest.action       = TRADE_ACTION_DEAL;
    closeRequest.position     = ActiveTrades[index].position_ticket;
    closeRequest.symbol       = _Symbol;
    closeRequest.volume       = ActiveTrades[index].filled_volume;  
    closeRequest.deviation    = 50; // Extreme deviation permitted to guarantee exit
    closeRequest.magic        = ActiveTrades[index].magic_number;

    if (ActiveTrades[index].pos_type == POSITION_TYPE_BUY) 
    {
        closeRequest.type  = ORDER_TYPE_SELL;
        closeRequest.price = latestTick.bid;
    }
    else 
    {
        closeRequest.type  = ORDER_TYPE_BUY;
        closeRequest.price = latestTick.ask;
    }

    // Lock the state to prevent the standard architecture from interfering
    ActiveTrades[index].state = STATE_EMERGENCY_LIQUIDATING;

    if (!OrderSendAsync(closeRequest, closeResult)) 
    {
        PrintFormat("CRITICAL: Failed to dispatch liquidation! Error: %d", GetLastError());
        // Failsafe: unlock state so the next OnTick loop tries again
        ActiveTrades[index].state = STATE_FILLED_NO_STOPS; 
    }
    else 
    {
        // Store the close request ID to track its success in OnTradeTransaction
        ActiveTrades[index].request_id = closeResult.request_id;
    }
}
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. Managing the OnTradeTransaction Fallout

Because you are firing asynchronous requests during a volatile market event, you are intentionally creating a race condition against the broker's matching engine. You must instruct your OnTradeTransaction router to safely ignore the inevitable error codes that will bounce back.

When tracking the TRADE_TRANSACTION_REQUEST type, add a filter for the STATE_EMERGENCY_LIQUIDATING state:

Code: Select all

if (trans.type == TRADE_TRANSACTION_REQUEST) 
{
    int index = FindTradeByRequestId(result.request_id);
    
    // If the cancel request failed because the order finished filling 
    // immediately before our cancel arrived (Error 10013: Invalid Request), ignore it.
    if (result.retcode == TRADE_RETCODE_INVALID) 
    {
        Print("Notice: Cancel request rejected. Order likely already fully filled or closed.");
        return; 
    }

    if (index >= 0 && ActiveTrades[index].state == STATE_EMERGENCY_LIQUIDATING) 
    {
        if (result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED) 
        {
            Print("Emergency Liquidation successful.");
            // Optional: Remove the trade from your ActiveTrades array here
        }
        else 
        {
            PrintFormat("Emergency Liquidation failed! Retcode: %d. Re-queuing.", result.retcode);
            // Revert state to allow OnTick to fire another close attempt
            ActiveTrades[index].state = STATE_FILLED_NO_STOPS; 
        }
        return;
    }
    
    // ... standard retry logic for normal operations ...
}
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 »

The Prevention Architecture: Order Filling Policies

Writing logic to clean up residual orders is a necessary failsafe, but the most robust way to handle this on an ECN broker is to prevent the residual order from existing in the first place.

When dispatching your initial entry order, investigate your broker's supported ORDER_FILLING modes using SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE).

If your broker supports ORDER_FILLING_IOC (Immediate or Cancel), this is the optimal mode for algorithmic scalping. It instructs the liquidity provider to fill as much of the requested volume as possible instantly at the current price layer, and to automatically cancel any unfilled remainder. By using IOC, you completely eliminate the need to manually send a TRADE_ACTION_REMOVE request, as the broker will never leave your unfilled volume sitting passively on the book.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply