//+------------------------------------------------------------------+ //| TriDivergenceEA.mq5 | //| Copyright 2025, Teenodi Ltd. | //| https://www.jukwaese.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Teenodi Ltd." #property link "val.chioke@gmail.com" #property version "1.00" #property description "EA that trades based on divergence signals from RSI, Stochastic, and Klinger indicators" #property strict #property tester_indicator "Divergence RSI.ex5" #property tester_indicator "StochasticsDivergence.ex5" #property tester_indicator "KlingerDivergence.ex5" #property indicator_plots 3 #property indicator_separate_window //============================ Enums =============================== enum SIGNAL_TYPE { SIGNAL_NULL, // No Signal SIGNAL_BUY, // Buy Signal SIGNAL_SELL // Sell Signal }; enum VOLUME_TYPE { VOLUME_FIXED = 1, // Fixed Lots VOLUME_RISK = 2 // Percentage Risk }; enum POSITION_TYPE_FILTER { POSITION_BUY_ONLY = 1, // Buy Only POSITION_SELL_ONLY = 2, // Sell Only POSITION_BOTH = 3 // Both }; enum TRAILING_TYPE { TRAIL_CONTINUOUS, // Continuous Trail TRAIL_STEP // Step Trail }; //============================ Structures =========================== struct SignalInfo { SIGNAL_TYPE signal; int shift; SignalInfo() { signal = SIGNAL_NULL; shift = -1; } }; //============================ Inputs =============================== input group "Trade Execution Settings" input int MagicNumber = 100001; // Magic number for trade identification input string Commentary = "DivergenceEA"; // Trade comment field input VOLUME_TYPE VolumeType = VOLUME_RISK; // Volume type: 1=Fixed lots, 2=Percentage risk input double Risk = 2.0; // Risk percentage per trade (0.1-30%) input double Lots = 0.1; // Fixed lot size (0.01-100) input int SlPointPip = 100; // Stop loss in pips input int TpPointPip = 200; // Take profit in pips input bool PendingOrder = false; // Enable pending orders input POSITION_TYPE_FILTER Position = POSITION_BOTH; // Trade direction input int NumberOfCandlesBack = 20; // Lookback period for analysis (1-100) input int ConfluenceCount = 2; // Confluence count (1 to 3) input group "Klinger Oscillator Settings" input bool KO = true; // Enable Klinger Oscillator input int KLINGER_LENGTH1 = 34; // First Klinger parameter (0-100) input int KLINGER_LENGTH2 = 55; // Second Klinger parameter (0-100) input int SIGNAL_LONG = 13; // Klinger signal length (0-100) input group "RSI Settings" input bool RSI = true; // Enable RSI indicator input int RSI_UPPER = 70; // RSI upper threshold (0-100) input int RSI_LOWER = 30; // RSI lower threshold (0-100) input int RSI_LONG = 14; // RSI signal length (0-100) input group "Stochastic Settings" input bool STOCH = false; // Enable Stochastic indicator input int K = 5; // Stochastic %K value (0-100) input int D = 3; // Stochastic %D value (0-100) input int STOCHASTIC_LONG = 5; // Stochastic signal length (0-100) input group "Trade Management" input bool BreakEven = true; // Enable breakeven function input int BreakEvenShift = 10; // Distance to move stop above breakeven (in pips) input int StopNachzienWenn = 50; // Profit level to trigger breakeven (in pips) input bool Trail = false; // Enable trailing stop input TRAILING_TYPE TrailingStop = TRAIL_CONTINUOUS; // Trailing algorithm type input int TralStop = 30; // Trailing distance behind price (in pips) input int TralStep = 10; // Minimum move before trail adjusts (in pips) input group "Visual Settings" input color RectangleColor = clrDarkSlateGray; // Rectangle color //============================ Global Variables ===================== int rsiDivergenceHandle = INVALID_HANDLE; int stochasticsDivergenceHandle = INVALID_HANDLE; int klingerDivergenceHandle = INVALID_HANDLE; datetime prevBarTime = 0; int globalConfluenceCount; string rectangleName = "LookbackRectangle"; double pointValue; ENUM_ORDER_TYPE_FILLING orderFill; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Check autotrading if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Alert("Please enable autotrading on terminal to take trades"); //return INIT_FAILED; } // Initialize point value pointValue = SymbolInfoDouble(_Symbol, SYMBOL_POINT); // Set filling policy uint filling = (uint)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK) { orderFill = ORDER_FILLING_FOK; } else if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC) { orderFill = ORDER_FILLING_IOC; } else { orderFill = ORDER_FILLING_RETURN; } // Initialize confluence count int enabledIndicators = 0; if(RSI) enabledIndicators++; if(STOCH) enabledIndicators++; if(KO) enabledIndicators++; if(enabledIndicators == 0) { Alert("At least one indicator must be enabled!"); return INIT_FAILED; } globalConfluenceCount = MathMin(ConfluenceCount, enabledIndicators); // Create indicator handles if(RSI) { rsiDivergenceHandle = iCustom(_Symbol, PERIOD_CURRENT, "Divergence RSI"); if(rsiDivergenceHandle == INVALID_HANDLE) { Print("Failed to create RSI Divergence indicator handle"); return INIT_FAILED; } ChartIndicatorAdd(0,1,rsiDivergenceHandle); } if(STOCH) { stochasticsDivergenceHandle = iCustom(_Symbol, PERIOD_CURRENT, "StochasticsDivergence"); if(stochasticsDivergenceHandle == INVALID_HANDLE) { Print("Failed to create Stochastics Divergence indicator handle"); return INIT_FAILED; } ChartIndicatorAdd(0,2,stochasticsDivergenceHandle); } if(KO) { klingerDivergenceHandle = iCustom(_Symbol, PERIOD_CURRENT, "KlingerDivergence"); if(klingerDivergenceHandle == INVALID_HANDLE) { Print("Failed to create Klinger Divergence indicator handle"); return INIT_FAILED; } ChartIndicatorAdd(0,3,klingerDivergenceHandle); } // Initialize previous bar time prevBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); // Draw initial rectangle DrawLookbackRectangle(); Print("DivergenceEA initialized successfully"); Print("Enabled indicators: RSI=", RSI, " STOCH=", STOCH, " KO=", KO); Print("Confluence count: ", globalConfluenceCount); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Release indicator handles if(rsiDivergenceHandle != INVALID_HANDLE) { IndicatorRelease(rsiDivergenceHandle); } if(stochasticsDivergenceHandle != INVALID_HANDLE) { IndicatorRelease(stochasticsDivergenceHandle); } if(klingerDivergenceHandle != INVALID_HANDLE) { IndicatorRelease(klingerDivergenceHandle); } // Delete rectangle ObjectDelete(0, rectangleName); Print("DivergenceEA deinitialized, reason: ", reason); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Trade management (runs on every tick) ManageOpenPositions(); // Check for new bar if(IsNewBar()) { Print("New bar detected, analyzing signals..."); // Create signal array and analyze SignalInfo signals[]; AnalyzeSignals(signals); // Check for entry signals CheckEntrySignals(signals); // Update rectangle DrawLookbackRectangle(); } } //+------------------------------------------------------------------+ //| Check if new bar formed | //+------------------------------------------------------------------+ bool IsNewBar() { datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); if(currentBarTime != prevBarTime) { prevBarTime = currentBarTime; return true; } return false; } //+------------------------------------------------------------------+ //| Analyze signals from all enabled indicators | //+------------------------------------------------------------------+ void AnalyzeSignals(SignalInfo &signals[]) { int signalCount = 0; // Count enabled indicators if(RSI) signalCount++; if(STOCH) signalCount++; if(KO) signalCount++; ArrayResize(signals, signalCount); int index = 0; // Analyze RSI Divergence if(RSI && rsiDivergenceHandle != INVALID_HANDLE) { signals[index] = GetLatestSignal(rsiDivergenceHandle, 0, 1); // Buy buffer 0, Sell buffer 1 index++; } // Analyze Stochastics Divergence if(STOCH && stochasticsDivergenceHandle != INVALID_HANDLE) { signals[index] = GetLatestSignal(stochasticsDivergenceHandle, 1, 2); // Buy buffer 1, Sell buffer 2 index++; } // Analyze Klinger Divergence if(KO && klingerDivergenceHandle != INVALID_HANDLE) { signals[index] = GetLatestSignal(klingerDivergenceHandle, 2, 3); // Buy buffer 2, Sell buffer 3 index++; } } //+------------------------------------------------------------------+ //| Get latest signal from indicator within lookback period | //+------------------------------------------------------------------+ SignalInfo GetLatestSignal(int handle, int buyBuffer, int sellBuffer) { SignalInfo signal; double buyValues[], sellValues[]; ArraySetAsSeries(buyValues, true); ArraySetAsSeries(sellValues, true); // Copy buffers for the lookback period if(CopyBuffer(handle, buyBuffer, 1, NumberOfCandlesBack, buyValues) <= 0 || CopyBuffer(handle, sellBuffer, 1, NumberOfCandlesBack, sellValues) <= 0) { Print("Failed to copy indicator buffers"); return signal; } // Search for latest signal (starting from most recent) for(int i = 0; i < NumberOfCandlesBack; i++) { // Check for buy signal if(buyValues[i] != EMPTY_VALUE && buyValues[i] != 0) { signal.signal = SIGNAL_BUY; signal.shift = i + 1; // Adjust for the fact we're looking at completed bars break; } // Check for sell signal if(sellValues[i] != EMPTY_VALUE && sellValues[i] != 0) { signal.signal = SIGNAL_SELL; signal.shift = i + 1; break; } } return signal; } //+------------------------------------------------------------------+ //| Check entry signals and execute trades | //+------------------------------------------------------------------+ void CheckEntrySignals(SignalInfo &signals[]) { int buySignals = 0; int sellSignals = 0; bool hasRecentBuySignal = false; bool hasRecentSellSignal = false; // Count signals and check for recent signals on last formed candle (shift = 1) for(int i = 0; i < ArraySize(signals); i++) { if(signals[i].signal == SIGNAL_BUY) { buySignals++; if(signals[i].shift == 1) hasRecentBuySignal = true; } else if(signals[i].signal == SIGNAL_SELL) { sellSignals++; if(signals[i].shift == 1) hasRecentSellSignal = true; } } Print("Signal analysis - Buy signals: ", buySignals, " (recent: ", hasRecentBuySignal, "), Sell signals: ", sellSignals, " (recent: ", hasRecentSellSignal, ")"); // Execute buy trade if(hasRecentBuySignal && buySignals >= globalConfluenceCount && (Position == POSITION_BOTH || Position == POSITION_BUY_ONLY)) { Print("Buy entry conditions met - executing buy trade"); ExecuteTrade(ORDER_TYPE_BUY); } // Execute sell trade if(hasRecentSellSignal && sellSignals >= globalConfluenceCount && (Position == POSITION_BOTH || Position == POSITION_SELL_ONLY)) { Print("Sell entry conditions met - executing sell trade"); ExecuteTrade(ORDER_TYPE_SELL); } } //+------------------------------------------------------------------+ //| Execute trade | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE orderType) { double volume = CalculateVolume(); if(volume <= 0) { Print("Invalid volume calculated: ", volume); return; } double price, sl, tp; if(orderType == ORDER_TYPE_BUY) { price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); sl = price - SlPointPip * pointValue * 10; tp = price + TpPointPip * pointValue * 10; } else { price = SymbolInfoDouble(_Symbol, SYMBOL_BID); sl = price + SlPointPip * pointValue * 10; tp = price - TpPointPip * pointValue * 10; } MqlTradeRequest request = {}; MqlTradeResult result = {}; request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = NormalizeDouble(volume, 2); request.type = orderType; request.price = price; request.sl = NormalizeDouble(sl, _Digits); request.tp = NormalizeDouble(tp, _Digits); request.deviation = 10; request.type_filling = orderFill; request.magic = MagicNumber; request.comment = Commentary; bool success = OrderSend(request, result); if(success && result.retcode == TRADE_RETCODE_DONE) { Print("Trade executed successfully - ", (orderType == ORDER_TYPE_BUY ? "BUY" : "SELL"), " Volume: ", volume, " Price: ", price); } else { Print("Trade execution failed - Result code: ", result.retcode, " Comment: ", result.comment); } } //+------------------------------------------------------------------+ //| Calculate volume based on risk management | //+------------------------------------------------------------------+ double CalculateVolume() { double volume = Lots; if(VolumeType == VOLUME_RISK) { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double riskAmount = balance * Risk / 100.0; double slPoints = SlPointPip * pointValue * 10; double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); if(slPoints > 0 && tickValue > 0) { volume = riskAmount / (slPoints * tickValue / pointValue); } } // Normalize volume double minVolume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxVolume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double volumeStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); volume = MathMax(volume, minVolume); volume = MathMin(volume, maxVolume); volume = NormalizeDouble(MathRound(volume / volumeStep) * volumeStep, 2); return volume; } //+------------------------------------------------------------------+ //| Manage open positions (breakeven and trailing stop) | //+------------------------------------------------------------------+ void ManageOpenPositions() { for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { // Apply breakeven if(BreakEven) { ApplyBreakeven(ticket); } // Apply trailing stop if(Trail) { ApplyTrailingStop(ticket); } } } } //+------------------------------------------------------------------+ //| Apply breakeven to position | //+------------------------------------------------------------------+ void ApplyBreakeven(ulong ticket) { if(!PositionSelectByTicket(ticket)) return; double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentSL = PositionGetDouble(POSITION_SL); double currentTP = PositionGetDouble(POSITION_TP); ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double currentPrice = (posType == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK); double profitPoints = MathAbs(currentPrice - openPrice) / (pointValue * 10); if(profitPoints >= StopNachzienWenn) { double newSL = 0; bool shouldModify = false; if(posType == POSITION_TYPE_BUY) { newSL = openPrice + BreakEvenShift * pointValue * 10; if(newSL > currentSL) shouldModify = true; } else { newSL = openPrice - BreakEvenShift * pointValue * 10; if(newSL < currentSL || currentSL == 0) shouldModify = true; } if(shouldModify) { ModifyPosition(ticket, newSL, currentTP); } } } //+------------------------------------------------------------------+ //| Apply trailing stop to position | //+------------------------------------------------------------------+ void ApplyTrailingStop(ulong ticket) { if(!PositionSelectByTicket(ticket)) return; double currentSL = PositionGetDouble(POSITION_SL); double currentTP = PositionGetDouble(POSITION_TP); ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double currentPrice = (posType == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK); double newSL = 0; bool shouldModify = false; if(posType == POSITION_TYPE_BUY) { newSL = currentPrice - TralStop * pointValue * 10; if(TrailingStop == TRAIL_STEP) { double moveRequired = TralStep * pointValue * 10; if((newSL - currentSL) >= moveRequired) shouldModify = true; } else { if(newSL > currentSL) shouldModify = true; } } else { newSL = currentPrice + TralStop * pointValue * 10; if(TrailingStop == TRAIL_STEP) { double moveRequired = TralStep * pointValue * 10; if((currentSL - newSL) >= moveRequired || currentSL == 0) shouldModify = true; } else { if(newSL < currentSL || currentSL == 0) shouldModify = true; } } if(shouldModify) { ModifyPosition(ticket, newSL, currentTP); } } //+------------------------------------------------------------------+ //| Modify position stop loss and take profit | //+------------------------------------------------------------------+ bool ModifyPosition(ulong ticket, double newSL, double tp) { MqlTradeRequest request = {}; MqlTradeResult result = {}; request.action = TRADE_ACTION_SLTP; request.position = ticket; request.symbol = _Symbol; request.sl = NormalizeDouble(newSL, _Digits); request.tp = NormalizeDouble(tp, _Digits); bool success = OrderSend(request, result); if(!success || result.retcode != TRADE_RETCODE_DONE) { Print("Failed to modify position SL. Error: ", GetLastError(), " Result code: ", result.retcode); return false; } return true; } //+------------------------------------------------------------------+ //| Draw lookback rectangle | //+------------------------------------------------------------------+ void DrawLookbackRectangle() { datetime currentTime = iTime(_Symbol, PERIOD_CURRENT, 0); datetime lookbackTime = iTime(_Symbol, PERIOD_CURRENT, NumberOfCandlesBack); double highPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK) * 1.1; // Chart top double lowPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID) * 0.9; // Chart bottom // Delete existing rectangle if it exists ObjectDelete(0, rectangleName); // Create new rectangle if(ObjectCreate(0, rectangleName, OBJ_RECTANGLE, 0, lookbackTime, lowPrice, currentTime, highPrice)) { ObjectSetInteger(0, rectangleName, OBJPROP_COLOR, RectangleColor); ObjectSetInteger(0, rectangleName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, rectangleName, OBJPROP_WIDTH, 1); ObjectSetInteger(0, rectangleName, OBJPROP_BACK, true); ObjectSetInteger(0, rectangleName, OBJPROP_FILL, true); ObjectSetInteger(0, rectangleName, OBJPROP_HIDDEN, true); ObjectSetString(0, rectangleName, OBJPROP_TOOLTIP, "Lookback Period: " + IntegerToString(NumberOfCandlesBack) + " candles"); // Make it almost transparent color rectColor = RectangleColor; ObjectSetInteger(0, rectangleName, OBJPROP_COLOR, ColorToARGB(rectColor, 20)); // 20 out of 255 alpha } ChartRedraw(0); } /* //+------------------------------------------------------------------+ //| Convert color to ARGB with alpha transparency | //+------------------------------------------------------------------+ uint ColorToARGB(color clr, uchar alpha) { return (uint)(alpha << 24) | (uint)(clr & 0x00FFFFFF); } */ //+------------------------------------------------------------------+