This is a classic hurdle when transitioning from a standard dealing desk to a true ECN/STP broker. In a Market Execution environment, the broker must fill the order at the best available market price first before they can attach conditional orders (like Stop Loss and Take Profit) to that specific fill price.
To resolve this, we change the execution to a two-step process:
Fire the OrderSend() with 0 for both the SL and TP parameters.
Capture the returned order ticket, select it via OrderSelect(), and immediately apply the calculated SL and TP using OrderModify().
I have also added GetLastError() error logging. As someone working with high-volume automated systems, catching modification errors (like ERR_INVALID_STOPS if price moves too fast before the modification) in your terminal journal is critical for debugging.
Updated Complete EA Code (ECN Execution)
Here is the fully updated code with the two-step ECN execution integrated into the trade logic:
Code: Select all
//+------------------------------------------------------------------+
//| PriceActionBounceEA.mq4 |
//| Contextual Support/Resistance & Price Action |
//+------------------------------------------------------------------+
#property copyright "Gemini"
#property version "1.50"
#property strict
//--- Risk & Money Management Inputs
input double InpRiskPercent = 1.0; // Risk per Trade (% of Balance)
input int InpSwingLookback = 50; // Bars to define S/R Level
input int InpZonePips = 15; // Proximity to S/R to validate trade (Pips)
input double InpATRMultiplier = 1.0; // SL Buffer beyond level (ATR)
input double InpRiskReward = 1.5; // TP Multiplier (Reward to Risk)
input int InpMagicNumber = 123456; // Magic Number
//--- Trend Filter Inputs (Multi-Timeframe)
input bool InpUseTrendFilter = true; // Enable Trend Filter
input ENUM_TIMEFRAMES InpMATimeframe = PERIOD_D1; // Trend Timeframe (MTF)
input int InpMAPeriod = 200; // Moving Average Period
input ENUM_MA_METHOD InpMAMethod = MODE_EMA; // Moving Average Method (EMA)
//--- Trade Management Inputs
input bool InpUseBreakEven = true; // Move SL to Entry at 1R Profit
double pips;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit() {
if(Digits == 3 || Digits == 5) pips = 10.0 * Point;
else pips = Point;
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick() {
// 1. Manage open positions on every tick
ManageBreakEven();
// 2. Check if we already have an open position for this specific strategy
if(CountOpenPositions() > 0) return;
// 3. Only execute new trade logic on a new candle open
static datetime lastTime = 0;
if(Time[0] == lastTime) return;
// Identify S/R Levels (Recent Swing High/Low on current TF)
int lowestIndex = iLowest(Symbol(), 0, MODE_LOW, InpSwingLookback, 1);
int highestIndex = iHighest(Symbol(), 0, MODE_HIGH, InpSwingLookback, 1);
double supportLevel = Low[lowestIndex];
double resistanceLevel = High[highestIndex];
double atr = iATR(Symbol(), 0, 14, 1);
// MTF Trend Filter Calculation
double ema = iMA(Symbol(), InpMATimeframe, InpMAPeriod, 0, InpMAMethod, PRICE_CLOSE, 1);
bool isUptrend = (!InpUseTrendFilter || Close[1] > ema);
bool isDowntrend = (!InpUseTrendFilter || Close[1] < ema);
// Buy Condition: Rejection at Support in an Uptrend
if(isUptrend) {
if(Low[1] <= supportLevel + (InpZonePips * pips) && Low[1] >= supportLevel - (InpZonePips * pips)) {
if(IsBullishPinBar(1) || IsBullishEngulfing(1)) {
double sl = supportLevel - (atr * InpATRMultiplier);
double risk = Ask - sl;
double tp = Ask + (risk * InpRiskReward);
double lotSize = CalculateLotSize(Ask, sl);
if(lotSize > 0) {
// Step 1: Execute with 0 SL and 0 TP
int ticket = OrderSend(Symbol(), OP_BUY, lotSize, Ask, 3, 0, 0, "PA Bounce Buy", InpMagicNumber, 0, clrGreen);
if(ticket > 0) {
// Step 2: Modify the order immediately to add SL and TP
if(OrderSelect(ticket, SELECT_BY_TICKET)) {
bool modified = OrderModify(ticket, OrderOpenPrice(), sl, tp, 0, clrGreen);
if(!modified) Print("Error setting Buy SL/TP: ", GetLastError());
}
lastTime = Time[0]; // Register candle time only on successful entry
} else {
Print("OrderSend Buy Error: ", GetLastError());
}
}
}
}
}
// Sell Condition: Rejection at Resistance in a Downtrend
if(isDowntrend) {
if(High[1] >= resistanceLevel - (InpZonePips * pips) && High[1] <= resistanceLevel + (InpZonePips * pips)) {
if(IsBearishPinBar(1) || IsBearishEngulfing(1)) {
double sl = resistanceLevel + (atr * InpATRMultiplier);
double risk = sl - Bid;
double tp = Bid - (risk * InpRiskReward);
double lotSize = CalculateLotSize(Bid, sl);
if(lotSize > 0) {
// Step 1: Execute with 0 SL and 0 TP
int ticket = OrderSend(Symbol(), OP_SELL, lotSize, Bid, 3, 0, 0, "PA Bounce Sell", InpMagicNumber, 0, clrRed);
if(ticket > 0) {
// Step 2: Modify the order immediately to add SL and TP
if(OrderSelect(ticket, SELECT_BY_TICKET)) {
bool modified = OrderModify(ticket, OrderOpenPrice(), sl, tp, 0, clrRed);
if(!modified) Print("Error setting Sell SL/TP: ", GetLastError());
}
lastTime = Time[0]; // Register candle time only on successful entry
} else {
Print("OrderSend Sell Error: ", GetLastError());
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| Dynamic Position Sizing Function |
//+------------------------------------------------------------------+
double CalculateLotSize(double entryPrice, double stopLossPrice) {
double riskAmount = AccountBalance() * (InpRiskPercent / 100.0);
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
if(tickValue == 0 || tickSize == 0) return 0;
double lossPerLot = (MathAbs(entryPrice - stopLossPrice) / tickSize) * tickValue;
if(lossPerLot == 0) return 0;
double rawLots = riskAmount / lossPerLot;
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double normalizedLots = MathRound(rawLots / lotStep) * lotStep;
if(normalizedLots < minLot) normalizedLots = minLot;
if(normalizedLots > maxLot) normalizedLots = maxLot;
return normalizedLots;
}
//+------------------------------------------------------------------+
//| Trade Management Functions |
//+------------------------------------------------------------------+
void ManageBreakEven() {
if(!InpUseBreakEven) return;
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == InpMagicNumber) {
double openPrice = OrderOpenPrice();
double currentSL = OrderStopLoss();
if(OrderType() == OP_BUY) {
if(currentSL < openPrice && currentSL != 0) {
double initialRisk = openPrice - currentSL;
if(Bid >= openPrice + initialRisk) {
bool res = OrderModify(OrderTicket(), openPrice, openPrice, OrderTakeProfit(), 0, clrBlue);
}
}
}
else if(OrderType() == OP_SELL) {
if(currentSL > openPrice && currentSL != 0) {
double initialRisk = currentSL - openPrice;
if(Ask <= openPrice - initialRisk) {
bool res = OrderModify(OrderTicket(), openPrice, openPrice, OrderTakeProfit(), 0, clrBlue);
}
}
}
}
}
}
}
int CountOpenPositions() {
int count = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == InpMagicNumber) {
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| Pattern Recognition Functions |
//+------------------------------------------------------------------+
bool IsBullishPinBar(int i) {
double bodySize = MathAbs(Open[i] - Close[i]);
double lowerWick = MathMin(Open[i], Close[i]) - Low[i];
double upperWick = High[i] - MathMax(Open[i], Close[i]);
double totalSize = High[i] - Low[i];
if(totalSize == 0) return false;
return (lowerWick > bodySize * 2.0 && upperWick < bodySize && Close[i] > (High[i] + Low[i])/2.0);
}
bool IsBearishPinBar(int i) {
double bodySize = MathAbs(Open[i] - Close[i]);
double lowerWick = MathMin(Open[i], Close[i]) - Low[i];
double upperWick = High[i] - MathMax(Open[i], Close[i]);
double totalSize = High[i] - Low[i];
if(totalSize == 0) return false;
return (upperWick > bodySize * 2.0 && lowerWick < bodySize && Close[i] < (High[i] + Low[i])/2.0);
}
bool IsBullishEngulfing(int i) {
return (Close[i+1] < Open[i+1] && Close[i] > Open[i] && Close[i] > Open[i+1] && Open[i] < Close[i+1]);
}
bool IsBearishEngulfing(int i) {
return (Close[i+1] > Open[i+1] && Close[i] < Open[i] && Close[i] < Open[i+1] && Open[i] > Close[i+1]);
}
//+------------------------------------------------------------------+