Ready to Paste Code:
#property copyright 'TradingBotLab'
#property link "https://tradingbotlab.com"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
//--- Input Parameters: Moving Averages
input group "=== Moving Averages ==="
input int InpFastMAPeriod = 10; // Fast SMA Period
input int InpSlowMAPeriod = 20; // Slow SMA Period
//--- Input Parameters: Filters
input group "=== Trend & Volatility Filters ==="
input bool InpUseADX = true; // Use ADX Trend Filter
input int InpADXPeriod = 14; // ADX Period
input double InpADXMinLevel = 25.0; // ADX Minimum Level
input bool InpUseATR = true; // Use ATR Volatility Filter
input int InpATRPeriod = 14; // ATR Period
input int InpATRMinPoints = 50; // ATR Minimum Volatility (in Points)
//--- Input Parameters: Risk Management
input group "=== Risk Management ==="
input double InpLotSize = 0.1; // Trade Volume
input int InpSLPoints = 200; // Stop Loss (Points)
input int InpTPPoints = 400; // Take Profit (Points)
input ulong InpMagicNumber = 777888; // Expert Magic Number
//--- Global Objects
CTrade trade;
CSymbolInfo symInfo;
CPositionInfo posInfo;
//--- Global Handles
int handleFastMA = INVALID_HANDLE;
int handleSlowMA = INVALID_HANDLE;
//+------------------------------------------------------------------+
//| Class: CFilterEngine |
//| Purpose: Modular wrapper for evaluating ADX and ATR filter logic |
//+------------------------------------------------------------------+
class CFilterEngine
{
private:
int m_handleADX;
int m_handleATR;
double m_adxBuffer[];
double m_atrBuffer[];
public:
CFilterEngine() : m_handleADX(INVALID_HANDLE), m_handleATR(INVALID_HANDLE) {}
~CFilterEngine() { Deinit(); }
bool Init(const string symbol, const ENUM_TIMEFRAMES timeframe);
void Deinit(void);
bool IsPassed(const string symbol);
};
bool CFilterEngine::Init(const string symbol, const ENUM_TIMEFRAMES timeframe)
{
if(InpUseADX)
{
m_handleADX = iADX(symbol, timeframe, InpADXPeriod);
if(m_handleADX == INVALID_HANDLE)
return false;
ArraySetAsSeries(m_adxBuffer, true);
}
if(InpUseATR)
{
m_handleATR = iATR(symbol, timeframe, InpATRPeriod);
if(m_handleATR == INVALID_HANDLE)
return false;
ArraySetAsSeries(m_atrBuffer, true);
}
return true;
}
void CFilterEngine::Deinit(void)
{
if(m_handleADX != INVALID_HANDLE) { IndicatorRelease(m_handleADX); m_handleADX = INVALID_HANDLE; }
if(m_handleATR != INVALID_HANDLE) { IndicatorRelease(m_handleATR); m_handleATR = INVALID_HANDLE; }
}
bool CFilterEngine::IsPassed(const string symbol)
{
// 1. Check ADX (Is trend strong enough?)
if(InpUseADX)
{
if(CopyBuffer(m_handleADX, 0, 1, 1, m_adxBuffer) <= 0) return false;
if(m_adxBuffer[0] < InpADXMinLevel) return false;
}
// 2. Check ATR (Is market volatile enough?)
if(InpUseATR)
{
if(CopyBuffer(m_handleATR, 0, 1, 1, m_atrBuffer) <= 0) return false;
double atrThreshold = InpATRMinPoints * SymbolInfoDouble(symbol, SYMBOL_POINT);
if(m_atrBuffer[0] < atrThreshold) return false;
}
return true;
}
//--- Instantiate Filter Engine
CFilterEngine filterEngine;
//+------------------------------------------------------------------+
//| Helper: Detect New Bar |
//+------------------------------------------------------------------+
bool IsNewBar()
{
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime != lastBarTime)
{
lastBarTime = currentBarTime;
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Configure Trade Class
trade.SetExpertMagicNumber(InpMagicNumber);
trade.SetMarginMode();
trade.SetTypeFillingBySymbol(_Symbol);
// Initialize Symbol Info
if(!symInfo.Name(_Symbol))
{
Print("Failed to initialize CSymbolInfo.");
return INIT_FAILED;
}
// Initialize SMA Handles
handleFastMA = iMA(_Symbol, Period, InpFastMAPeriod, 0, MODESMA, PRICE_CLOSE);
handleSlowMA = iMA(_Symbol, Period, InpSlowMAPeriod, 0, MODESMA, PRICE_CLOSE);
if(handleFastMA == INVALID_HANDLE || handleSlowMA == INVALID_HANDLE)
{
Print("Error creating SMA handles.");
return INIT_FAILED;
}
// Initialize Filter Engine
if(!filterEngine.Init(_Symbol, _Period))
{
Print("Error creating ADX/ATR filter handles.");
return INIT_FAILED;
}
Print("TradingBotLab EA Initialized Successfully.");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(handleFastMA != INVALID_HANDLE) IndicatorRelease(handleFastMA);
if(handleSlowMA != INVALID_HANDLE) IndicatorRelease(handleSlowMA);
filterEngine.Deinit();
Print("TradingBotLab EA Deinitialized cleanly.");
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Strict rule: Only evaluate logic on bar open
if(!IsNewBar()) return;
// Update symbol quotes safely
if(!symInfo.RefreshRates()) return;
double fastMA[2], slowMA[2];
// Copy Buffer. Index 0 = Candle 2 (Older), Index 1 = Candle 1 (Newer)
if(CopyBuffer(handleFastMA, 0, 1, 2, fastMA) <= 0) return;
if(CopyBuffer(handleSlowMA, 0, 1, 2, slowMA) <= 0) return;
// Determine Crossovers on fully closed candles
bool isBuyCross = (fastMA[0] <= slowMA[0] && fastMA[1] > slowMA[1]);
bool isSellCross = (fastMA[0] >= slowMA[0] && fastMA[1] < slowMA[1]);
// If no signal, suspend execution until next bar
if(!isBuyCross && !isSellCross) return;
// Scan open positions for this EA
int openPositions = 0;
bool hasBuy = false, hasSell = false;
ulong currentTicket = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(posInfo.SelectByIndex(i))
{
if(posInfo.Symbol() == _Symbol && posInfo.Magic() == InpMagicNumber)
{
openPositions++;
currentTicket = posInfo.Ticket();
if(posInfo.PositionType() == POSITION_TYPE_BUY) hasBuy = true;
if(posInfo.PositionType() == POSITION_TYPE_SELL) hasSell = true;
}
}
}
// --- Exit Logic ---
// Close Sell position on Buy Cross
if(isBuyCross && hasSell)
{
if(trade.PositionClose(currentTicket))
hasSell = false;
}
// Close Buy position on Sell Cross
if(isSellCross && hasBuy)
{
if(trade.PositionClose(currentTicket))
hasBuy = false;
}
// --- Filter Evaluation (Only run if we are about to open a trade) ---
if(!filterEngine.IsPassed(_Symbol)) return;
// --- Entry Logic (Strict ONE position rule) ---
double pt = symInfo.Point();
double ask = symInfo.Ask();
double bid = symInfo.Bid();
if(isBuyCross && !hasBuy && !hasSell)
{
double sl = (InpSLPoints > 0) ? symInfo.NormalizePrice(ask - InpSLPoints * pt) : 0.0;
double tp = (InpTPPoints > 0) ? symInfo.NormalizePrice(ask + InpTPPoints * pt) : 0.0;
trade.Buy(InpLotSize, Symbol, ask, sl, tp, "TBLSMA_BUY");
}
if(isSellCross && !hasSell && !hasBuy)
{
double sl = (InpSLPoints > 0) ? symInfo.NormalizePrice(bid + InpSLPoints * pt) : 0.0;
double tp = (InpTPPoints > 0) ? symInfo.NormalizePrice(bid - InpTPPoints * pt) : 0.0;
trade.Sell(InpLotSize, Symbol, bid, sl, tp, "TBLSMA_SELL");
}
}
//+------------------------------------------------------------------+