IC Markets

Support/Resistance Bounce Scalping

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 1114
Joined: Mon Jul 20, 2026 1:28 pm

Re: Support/Resistance Bounce Scalping

Post by PTScalper »

And what about ECN brokers?

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]);
}
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1114
Joined: Mon Jul 20, 2026 1:28 pm

Re: Support/Resistance Bounce Scalping

Post by PTScalper »

Moving this architecture to MetaTrader 5 (MQL5) is a massive structural upgrade. Because MQL5 is fully object-oriented and relies heavily on the standard library, we can completely eliminate the manual two-step ECN execution workaround.

The standard <Trade\Trade.mqh> library utilizes the CTrade class, which automatically detects your broker's execution mode and routes the order properly under the hood. This makes scaling execution across high-volume forex and silver pairs much cleaner and faster.

Additionally, indicator data and price data in MT5 are accessed via memory buffers (CopyBuffer, CopyRates) rather than direct arrays, which significantly improves backtesting speed.

MQL5 Expert Advisor: Price Action Bounce (MT5)

Here is the exact logic translated natively into MQL5. Save this as an .mq5 file in your MetaEditor.

Code: Select all

//+------------------------------------------------------------------+
//|                                          PriceActionBounceEA.mq5 |
//|                     Contextual Support/Resistance & Price Action |
//+------------------------------------------------------------------+
#property copyright "Gemini"
#property version   "1.00"

#include <Trade\Trade.mqh>

//--- 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 ulong  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

//--- Trade Management Inputs
input bool   InpUseBreakEven = true;      // Move SL to Entry at 1R Profit

//--- Global Variables
CTrade trade;
int    atrHandle;
int    maHandle;
double pips;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
    if(_Digits == 3 || _Digits == 5) pips = 10.0 * _Point;
    else pips = _Point;
    
    trade.SetExpertMagicNumber(InpMagicNumber);
    
    // Initialize Indicator Handles
    atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14);
    maHandle  = iMA(_Symbol, InpMATimeframe, InpMAPeriod, 0, InpMAMethod, PRICE_CLOSE);
    
    if(atrHandle == INVALID_HANDLE || maHandle == INVALID_HANDLE) {
        Print("Error initializing indicators");
        return INIT_FAILED;
    }
    
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
    
    // 1. Manage open positions continuously
    ManageBreakEven();
    
    // 2. Check for open positions for this specific strategy
    if(CountOpenPositions() > 0) return; 
    
    // 3. New candle detection in MT5
    static datetime lastTime = 0;
    datetime currentTime = iTime(_Symbol, PERIOD_CURRENT, 0);
    if(currentTime == lastTime || currentTime == 0) return;

    // Retrieve recent Highs and Lows for S/R mapping
    double high[], low[];
    ArraySetAsSeries(high, true);
    ArraySetAsSeries(low, true);
    if(CopyHigh(_Symbol, PERIOD_CURRENT, 1, InpSwingLookback, high) <= 0) return;
    if(CopyLow(_Symbol, PERIOD_CURRENT, 1, InpSwingLookback, low) <= 0) return;
    
    double resistanceLevel = high[ArrayMaximum(high, 0, InpSwingLookback)];
    double supportLevel    = low[ArrayMinimum(low, 0, InpSwingLookback)];

    // Retrieve Indicator Data
    double atr[], ma[];
    ArraySetAsSeries(atr, true);
    ArraySetAsSeries(ma, true);
    if(CopyBuffer(atrHandle, 0, 1, 1, atr) <= 0) return;
    if(CopyBuffer(maHandle, 0, 1, 1, ma) <= 0) return;

    // Retrieve Price Data (Candlesticks)
    MqlRates rates[];
    ArraySetAsSeries(rates, true);
    if(CopyRates(_Symbol, PERIOD_CURRENT, 0, 3, rates) <= 0) return;

    // Trend Filter evaluation
    bool isUptrend   = (!InpUseTrendFilter || rates[1].close > ma[0]);
    bool isDowntrend = (!InpUseTrendFilter || rates[1].close < ma[0]);
    
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

    // --- BUY CONDITION ---
    if(isUptrend) {
        if(rates[1].low <= supportLevel + (InpZonePips * pips) && rates[1].low >= supportLevel - (InpZonePips * pips)) {
            if(IsBullishPinBar(rates[1]) || IsBullishEngulfing(rates[2], rates[1])) {
                
                double sl   = supportLevel - (atr[0] * InpATRMultiplier);
                double risk = ask - sl;
                double tp   = ask + (risk * InpRiskReward);
                double lotSize = CalculateLotSize(ask, sl);
                
                if(lotSize > 0) {
                    if(trade.Buy(lotSize, _Symbol, ask, sl, tp, "PA Bounce Buy")) {
                        lastTime = currentTime;
                    } else {
                        Print("Buy execution failed: ", GetLastError());
                    }
                }
            }
        }
    }

    // --- SELL CONDITION ---
    if(isDowntrend) {
        if(rates[1].high >= resistanceLevel - (InpZonePips * pips) && rates[1].high <= resistanceLevel + (InpZonePips * pips)) {
            if(IsBearishPinBar(rates[1]) || IsBearishEngulfing(rates[2], rates[1])) {
                
                double sl   = resistanceLevel + (atr[0] * InpATRMultiplier);
                double risk = sl - bid;
                double tp   = bid - (risk * InpRiskReward);
                double lotSize = CalculateLotSize(bid, sl);
                
                if(lotSize > 0) {
                    if(trade.Sell(lotSize, _Symbol, bid, sl, tp, "PA Bounce Sell")) {
                        lastTime = currentTime;
                    } else {
                        Print("Sell execution failed: ", GetLastError());
                    }
                }
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Dynamic Position Sizing                                          |
//+------------------------------------------------------------------+
double CalculateLotSize(double entryPrice, double stopLossPrice) {
    double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent / 100.0);
    
    double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    
    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  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    
    double normalizedLots = MathRound(rawLots / lotStep) * lotStep;
    
    if(normalizedLots < minLot) normalizedLots = minLot;
    if(normalizedLots > maxLot) normalizedLots = maxLot;
    
    return normalizedLots;
}

//+------------------------------------------------------------------+
//| Break Even Management (MT5 Position Architecture)                |
//+------------------------------------------------------------------+
void ManageBreakEven() {
    if(!InpUseBreakEven) return;

    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if(ticket > 0) {
            if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) {
                
                double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
                double currentSL = PositionGetDouble(POSITION_SL);
                double currentTP = PositionGetDouble(POSITION_TP);
                long   posType   = PositionGetInteger(POSITION_TYPE);
                
                if(posType == POSITION_TYPE_BUY) {
                    if(currentSL < openPrice && currentSL != 0) {
                        double initialRisk = openPrice - currentSL;
                        double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
                        
                        if(bid >= openPrice + initialRisk) {
                            trade.PositionModify(ticket, openPrice, currentTP);
                        }
                    }
                }
                else if(posType == POSITION_TYPE_SELL) {
                    if(currentSL > openPrice && currentSL != 0) {
                        double initialRisk = currentSL - openPrice;
                        double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
                        
                        if(ask <= openPrice - initialRisk) {
                            trade.PositionModify(ticket, openPrice, currentTP);
                        }
                    }
                }
            }
        }
    }
}

int CountOpenPositions() {
    int count = 0;
    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) {
            count++;
        }
    }
    return count;
}

//+------------------------------------------------------------------+
//| Pattern Recognition (Using MqlRates Structure)                   |
//+------------------------------------------------------------------+
bool IsBullishPinBar(MqlRates &rate) {
    double bodySize  = MathAbs(rate.open - rate.close);
    double lowerWick = MathMin(rate.open, rate.close) - rate.low;
    double upperWick = rate.high - MathMax(rate.open, rate.close);
    double totalSize = rate.high - rate.low;
    
    if(totalSize == 0) return false;
    return (lowerWick > bodySize * 2.0 && upperWick < bodySize && rate.close > (rate.high + rate.low)/2.0);
}

bool IsBearishPinBar(MqlRates &rate) {
    double bodySize  = MathAbs(rate.open - rate.close);
    double lowerWick = MathMin(rate.open, rate.close) - rate.low;
    double upperWick = rate.high - MathMax(rate.open, rate.close);
    double totalSize = rate.high - rate.low;
    
    if(totalSize == 0) return false;
    return (upperWick > bodySize * 2.0 && lowerWick < bodySize && rate.close < (rate.high + rate.low)/2.0);
}

bool IsBullishEngulfing(MqlRates &prevRate, MqlRates &currRate) {
    return (prevRate.close < prevRate.open && currRate.close > currRate.open && 
            currRate.close > prevRate.open && currRate.open < prevRate.close);
}

bool IsBearishEngulfing(MqlRates &prevRate, MqlRates &currRate) {
    return (prevRate.close > prevRate.open && currRate.close < currRate.open && 
            currRate.close < prevRate.open && currRate.open > prevRate.close);
}
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1114
Joined: Mon Jul 20, 2026 1:28 pm

Re: Support/Resistance Bounce Scalping

Post by PTScalper »

Key Architectural Changes from MT4

Order vs. Position Mechanics: In MT5, multiple fills aggregate into a single "Position" per symbol (unless you run a hedging account). The ManageBreakEven() block utilizes PositionsTotal() and PositionGetDouble() rather than parsing an orders list, drastically improving processing efficiency.

The MqlRates Structure: Passing an entire candlestick object (open, high, low, close) directly into the recognition functions is cleaner and safer than tracking indices across four separate parallel arrays like MQL4 requires.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1114
Joined: Mon Jul 20, 2026 1:28 pm

Re: Support/Resistance Bounce Scalping

Post by PTScalper »

Transforming a single-chart EA into a multi-currency engine requires a fundamental shift in how the program handles data.

Instead of pulling data for _Symbol (the chart the EA is attached to), the architecture must store state, indicator handles, and timing data for an array of symbols simultaneously. Furthermore, because OnTick() is only triggered by price changes on the host chart, we must abandon it in favor of OnTimer() to ensure the EA polls all symbols equally, even if the host chart is inactive.

Architectural Upgrades for Multi-Currency

The CSymbolData Struct: This acts as a memory container for each currency pair, holding its specific indicator handles and recording the timestamp of its last processed candle.

EventSetTimer() Engine: We run the loop every 1 second, completely detaching the EA's execution speed from the tick volume of the host chart.

Symbol Selection: The EA automatically adds your requested pairs to the Market Watch to ensure the MT5 terminal actively streams data for them.

MQL5 Multi-Currency Engine Code

Here is the fully adapted MQL5 multi-currency framework. You can attach this to a single chart (e.g., EURUSD M15), and it will execute across all pairs defined in the input string.

Code: Select all

//+------------------------------------------------------------------+
//|                                     MultiCurrencyBounceEA.mq5    |
//|                     Contextual Support/Resistance & Price Action |
//+------------------------------------------------------------------+
#property copyright "Gemini"
#property version   "2.00"

#include <Trade\Trade.mqh>

//--- Multi-Currency Input
input string InpSymbols          = "EURUSD,GBPUSD,USDJPY,AUDUSD,USDCAD"; // Comma-separated pairs

//--- Risk & Money Management
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 ulong  InpMagicNumber      = 123456;    // Magic Number

//--- Trend Filter Inputs
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

//--- Trade Management Inputs
input bool   InpUseBreakEven = true;      // Move SL to Entry at 1R Profit

//--- Global Variables
CTrade trade;

// Struct to hold individual data for each symbol
struct CSymbolData {
    string   Name;
    int      ATR_Handle;
    int      MA_Handle;
    datetime LastCandleTime;
    double   Pips;
};

CSymbolData Pairs[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
    trade.SetExpertMagicNumber(InpMagicNumber);
    
    // Parse the comma-separated symbols string
    ushort separator = StringGetCharacter(",", 0);
    string result[];
    int count = StringSplit(InpSymbols, separator, result);
    
    ArrayResize(Pairs, count);
    
    for(int i = 0; i < count; i++) {
        string sym = result[i];
        StringTrimLeft(sym); StringTrimRight(sym);
        
        Pairs[i].Name = sym;
        Pairs[i].LastCandleTime = 0;
        
        // Ensure symbol is in Market Watch
        SymbolSelect(sym, true);
        
        long digits = SymbolInfoInteger(sym, SYMBOL_DIGITS);
        double point = SymbolInfoDouble(sym, SYMBOL_POINT);
        Pairs[i].Pips = (digits == 3 || digits == 5) ? point * 10.0 : point;
        
        // Initialize Handles per symbol
        Pairs[i].ATR_Handle = iATR(sym, PERIOD_CURRENT, 14);
        Pairs[i].MA_Handle  = iMA(sym, InpMATimeframe, InpMAPeriod, 0, InpMAMethod, PRICE_CLOSE);
        
        if(Pairs[i].ATR_Handle == INVALID_HANDLE || Pairs[i].MA_Handle == INVALID_HANDLE) {
            Print("Error initializing indicators for ", sym);
            return INIT_FAILED;
        }
    }
    
    // Run the engine every 1 second
    EventSetTimer(1);
    
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
    EventKillTimer();
}

//+------------------------------------------------------------------+
//| Expert timer function (Replaces OnTick)                          |
//+------------------------------------------------------------------+
void OnTimer() {
    
    ManageBreakEvenMulti(); // Runs continuously for all managed positions
    
    // Loop through all monitored symbols
    for(int i = 0; i < ArraySize(Pairs); i++) {
        string sym = Pairs[i].Name;
        
        if(CountOpenPositionsMulti(sym) > 0) continue; 
        
        datetime currentTime = iTime(sym, PERIOD_CURRENT, 0);
        if(currentTime == Pairs[i].LastCandleTime || currentTime == 0) continue;

        double high[], low[];
        ArraySetAsSeries(high, true);
        ArraySetAsSeries(low, true);
        if(CopyHigh(sym, PERIOD_CURRENT, 1, InpSwingLookback, high) <= 0) continue;
        if(CopyLow(sym, PERIOD_CURRENT, 1, InpSwingLookback, low) <= 0) continue;
        
        double resistanceLevel = high[ArrayMaximum(high, 0, InpSwingLookback)];
        double supportLevel    = low[ArrayMinimum(low, 0, InpSwingLookback)];

        double atr[], ma[];
        ArraySetAsSeries(atr, true);
        ArraySetAsSeries(ma, true);
        if(CopyBuffer(Pairs[i].ATR_Handle, 0, 1, 1, atr) <= 0) continue;
        if(CopyBuffer(Pairs[i].MA_Handle, 0, 1, 1, ma) <= 0) continue;

        MqlRates rates[];
        ArraySetAsSeries(rates, true);
        if(CopyRates(sym, PERIOD_CURRENT, 0, 3, rates) <= 0) continue;

        bool isUptrend   = (!InpUseTrendFilter || rates[1].close > ma[0]);
        bool isDowntrend = (!InpUseTrendFilter || rates[1].close < ma[0]);
        
        double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
        double bid = SymbolInfoDouble(sym, SYMBOL_BID);
        double pips = Pairs[i].Pips;

        // --- BUY CONDITION ---
        if(isUptrend) {
            if(rates[1].low <= supportLevel + (InpZonePips * pips) && rates[1].low >= supportLevel - (InpZonePips * pips)) {
                if(IsBullishPinBar(rates[1]) || IsBullishEngulfing(rates[2], rates[1])) {
                    
                    double sl   = supportLevel - (atr[0] * InpATRMultiplier);
                    double risk = ask - sl;
                    double tp   = ask + (risk * InpRiskReward);
                    double lotSize = CalculateLotSizeMulti(sym, ask, sl);
                    
                    if(lotSize > 0) {
                        if(trade.Buy(lotSize, sym, ask, sl, tp, "PA Bounce Buy")) {
                            Pairs[i].LastCandleTime = currentTime;
                        }
                    }
                }
            }
        }

        // --- SELL CONDITION ---
        if(isDowntrend) {
            if(rates[1].high >= resistanceLevel - (InpZonePips * pips) && rates[1].high <= resistanceLevel + (InpZonePips * pips)) {
                if(IsBearishPinBar(rates[1]) || IsBearishEngulfing(rates[2], rates[1])) {
                    
                    double sl   = resistanceLevel + (atr[0] * InpATRMultiplier);
                    double risk = sl - bid;
                    double tp   = bid - (risk * InpRiskReward);
                    double lotSize = CalculateLotSizeMulti(sym, bid, sl);
                    
                    if(lotSize > 0) {
                        if(trade.Sell(lotSize, sym, bid, sl, tp, "PA Bounce Sell")) {
                            Pairs[i].LastCandleTime = currentTime;
                        }
                    }
                }
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Dynamic Position Sizing (Multi-Currency)                         |
//+------------------------------------------------------------------+
double CalculateLotSizeMulti(string sym, double entryPrice, double stopLossPrice) {
    double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent / 100.0);
    double tickValue = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);
    double tickSize  = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
    
    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  = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
    double maxLot  = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
    double lotStep = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
    
    double normalizedLots = MathRound(rawLots / lotStep) * lotStep;
    
    if(normalizedLots < minLot) normalizedLots = minLot;
    if(normalizedLots > maxLot) normalizedLots = maxLot;
    
    return normalizedLots;
}

//+------------------------------------------------------------------+
//| Trade Management Functions (Multi-Currency)                      |
//+------------------------------------------------------------------+
void ManageBreakEvenMulti() {
    if(!InpUseBreakEven) return;

    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if(ticket > 0 && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) {
            
            string posSymbol = PositionGetString(POSITION_SYMBOL);
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double currentSL = PositionGetDouble(POSITION_SL);
            double currentTP = PositionGetDouble(POSITION_TP);
            long   posType   = PositionGetInteger(POSITION_TYPE);
            
            if(posType == POSITION_TYPE_BUY) {
                if(currentSL < openPrice && currentSL != 0) {
                    double initialRisk = openPrice - currentSL;
                    double bid = SymbolInfoDouble(posSymbol, SYMBOL_BID);
                    if(bid >= openPrice + initialRisk) {
                        trade.PositionModify(ticket, openPrice, currentTP);
                    }
                }
            }
            else if(posType == POSITION_TYPE_SELL) {
                if(currentSL > openPrice && currentSL != 0) {
                    double initialRisk = currentSL - openPrice;
                    double ask = SymbolInfoDouble(posSymbol, SYMBOL_ASK);
                    if(ask <= openPrice - initialRisk) {
                        trade.PositionModify(ticket, openPrice, currentTP);
                    }
                }
            }
        }
    }
}

int CountOpenPositionsMulti(string sym) {
    int count = 0;
    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if(PositionGetString(POSITION_SYMBOL) == sym && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) {
            count++;
        }
    }
    return count;
}

//+------------------------------------------------------------------+
//| Pattern Recognition Functions                                    |
//+------------------------------------------------------------------+
bool IsBullishPinBar(MqlRates &rate) {
    double bodySize  = MathAbs(rate.open - rate.close);
    double lowerWick = MathMin(rate.open, rate.close) - rate.low;
    double upperWick = rate.high - MathMax(rate.open, rate.close);
    double totalSize = rate.high - rate.low;
    if(totalSize == 0) return false;
    return (lowerWick > bodySize * 2.0 && upperWick < bodySize && rate.close > (rate.high + rate.low)/2.0);
}

bool IsBearishPinBar(MqlRates &rate) {
    double bodySize  = MathAbs(rate.open - rate.close);
    double lowerWick = MathMin(rate.open, rate.close) - rate.low;
    double upperWick = rate.high - MathMax(rate.open, rate.close);
    double totalSize = rate.high - rate.low;
    if(totalSize == 0) return false;
    return (upperWick > bodySize * 2.0 && lowerWick < bodySize && rate.close < (rate.high + rate.low)/2.0);
}

bool IsBullishEngulfing(MqlRates &prevRate, MqlRates &currRate) {
    return (prevRate.close < prevRate.open && currRate.close > currRate.open && 
            currRate.close > prevRate.open && currRate.open < prevRate.close);
}

bool IsBearishEngulfing(MqlRates &prevRate, MqlRates &currRate) {
    return (prevRate.close > prevRate.open && currRate.close < currRate.open && 
            currRate.close < prevRate.open && currRate.open > prevRate.close);
}
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1114
Joined: Mon Jul 20, 2026 1:28 pm

Re: Support/Resistance Bounce Scalping

Post by PTScalper »

Critical Setup Notes

Because the EA pulls historical arrays (CopyHigh, CopyLow, CopyBuffer) for symbols not currently on your active chart, the terminal must sometimes fetch this data from the broker server in real-time. If the data is not immediately ready, CopyRates will return -1, and the EA will gracefully continue; to the next pair, trying again on the next 1-second timer tick.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1114
Joined: Mon Jul 20, 2026 1:28 pm

Re: Support/Resistance Bounce Scalping

Post by PTScalper »

To implement a global account failsafe, we need to monitor your floating Equity relative to your Balance in real-time.

If a basket of correlated pairs suddenly goes against you (e.g., a massive USD flash crash), the EA will detect the floating loss and instantly halt all new trade executions across all monitored pairs, while continuing to manage the open positions (like moving them to break-even if they recover).

Here are the three additions you need to make to your MT5 multi-currency engine.

1. Add the Global Input

Add this line to your Risk & Money Management inputs section at the top of the script:

Code: Select all

input double InpMaxDrawdownPercent = 5.0;       // Max Global Drawdown (%) to halt entries
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1114
Joined: Mon Jul 20, 2026 1:28 pm

Re: Support/Resistance Bounce Scalping

Post by PTScalper »

2. Add the Drawdown Calculation Function

Place this new helper function at the bottom of your script. It calculates the current floating loss as a percentage of your total balance.

Code: Select all

//+------------------------------------------------------------------+
//| Global Drawdown Failsafe                                         |
//+------------------------------------------------------------------+
bool IsDrawdownExceeded() {
    if(InpMaxDrawdownPercent <= 0) return false; // Failsafe disabled if set to 0
    
    double balance = AccountInfoDouble(ACCOUNT_BALANCE);
    double equity  = AccountInfoDouble(ACCOUNT_EQUITY);
    
    // If equity is greater than or equal to balance, we are not in drawdown
    if(equity >= balance) return false;
    
    double floatingLoss = balance - equity;
    double currentDrawdownPct = (floatingLoss / balance) * 100.0;
    
    if(currentDrawdownPct >= InpMaxDrawdownPercent) {
        return true;
    }
    
    return false;
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1114
Joined: Mon Jul 20, 2026 1:28 pm

Re: Support/Resistance Bounce Scalping

Post by PTScalper »

3. Integrate into the Execution Engine (OnTimer)

Update your OnTimer() function to evaluate the failsafe after managing open trades, but before scanning for new setups. We also add an on-chart Comment() so you visually know when the EA has frozen itself.

Code: Select all

//+------------------------------------------------------------------+
//| Expert timer function                                            |
//+------------------------------------------------------------------+
void OnTimer() {
    
    // 1. ALWAYS manage existing trades, even in heavy drawdown
    ManageBreakEvenMulti(); 
    
    // 2. GLOBAL FAILSAFE: Check floating equity drawdown
    if(IsDrawdownExceeded()) {
        Comment("⚠️ MAX DRAWDOWN EXCEEDED: New entries halted. Managing open positions.");
        return; // Aborts the rest of the timer loop, preventing new trade scans
    } else {
        Comment(""); // Clears the warning when drawdown recovers or trades are closed
    }
    
    // 3. Loop through all monitored symbols for new entries
    for(int i = 0; i < ArraySize(Pairs); i++) {
        string sym = Pairs[i].Name;
        
        if(CountOpenPositionsMulti(sym) > 0) continue; 
        
        datetime currentTime = iTime(sym, PERIOD_CURRENT, 0);
        if(currentTime == Pairs[i].LastCandleTime || currentTime == 0) continue;

        // ... (Rest of the trade execution logic remains exactly the same) ...
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1114
Joined: Mon Jul 20, 2026 1:28 pm

Re: Support/Resistance Bounce Scalping

Post by PTScalper »

Technical Note: Floating vs. Realized Drawdown
This implementation protects against floating equity drawdown. If your account starts at $10,000 and your open trades fall to $9,500 in equity, the EA freezes.

If you meant High-Water Mark Drawdown (e.g., your balance grew to $12,000, and you want to stop trading if equity falls 5% from that specific peak back to $11,400), you would need to declare a global variable double peakBalance; and update it dynamically every time a trade closes in profit. For automated systemic risk management, tracking immediate equity-to-balance drawdown is usually the safer, more responsive metric.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply