Page 2 of 3
Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping
Posted: Wed Sep 02, 2026 6:07 pm
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.
Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping
Posted: Wed Sep 02, 2026 6:07 pm
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);
}
Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping
Posted: Wed Sep 02, 2026 6:08 pm
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.
Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping
Posted: Wed Sep 02, 2026 6:09 pm
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)
};
Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping
Posted: Wed Sep 02, 2026 6:09 pm
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);
}
}
}
}
Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping
Posted: Wed Sep 02, 2026 6:10 pm
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;
}
}
Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping
Posted: Wed Sep 02, 2026 6:10 pm
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.
Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping
Posted: Wed Sep 02, 2026 6:11 pm
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;
}
}
Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping
Posted: Wed Sep 02, 2026 6:11 pm
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 ...
}
Re: MQL5: How to efficiently resolve "OrderSend error 130" during M1 scalping
Posted: Wed Sep 02, 2026 6:12 pm
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.