Files
Valentine Chibuike OzoigboanugoandClaude Opus 4.8 f934e9a90f Initial commit: TriDivergenceEA
Proprietary trading software. © Teenodi Ltd. All rights reserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:32:12 +02:00

608 lines
22 KiB
Plaintext

//+------------------------------------------------------------------+
//| 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);
}
*/
//+------------------------------------------------------------------+