Class 7: How to Add Trend Filtration System in the Ai Code?
Ready to Paste Code here:
#property copyright "TradingBotLab"
#property link "https://tradingbotlab.com"
#property version "1.20"
#property strict
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
//--- Enums
enum LotSizingMode
{
FIXED_LOT = 0,
DYNAMIC_RISK = 1
};
enum LogLevel
{
LOG_NONE = 0, // Absolute Silence (Production)
LOG_ERRORS = 1, // Critical Failures Only
LOG_INFO = 2, // Trade Executions & State Changes
LOG_DEBUG = 3 // Heavy Diagnostic Tracking
};
//--- Input Parameters: Logging
input group "=== Institutional Logging ==="
input LogLevel InpLogLevel = LOG_DEBUG; // Console Output Level
//--- Input Parameters: Lot Sizing
input group "=== Lot Sizing & Risk ==="
input LotSizingMode InpLotMode = FIXED_LOT; // Lot Sizing Method
input double InpFixedLotSize = 0.10; // Fixed Lot Size
input double InpRiskPercent = 1.0; // Dynamic Risk % (Free Margin)
//--- 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 InpUseTrendMA = true; // Use MTF Trend MA Filter
input ENUM_TIMEFRAMES InpTrendMATF = PERIOD_D1; // Trend MA Timeframe
input int InpTrendMAPeriod = 200; // Trend MA Period
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 (Points)
//--- Input Parameters: Trade Management
input group "=== Trade Management ==="
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;
//+------------------------------------------------------------------+
//| Helper: Centralized Logging Engine |
//+------------------------------------------------------------------+
void TBL_Log(string message, LogLevel level)
{
if(level <= InpLogLevel && level != LOG_NONE)
{
string prefix = "";
if(level == LOG_ERRORS) prefix = "[ERROR] ";
else if(level == LOG_INFO) prefix = "[INFO] ";
else if(level == LOG_DEBUG) prefix = "[DEBUG] ";
Print(prefix, message);
}
}
//+------------------------------------------------------------------+
//| Class: CFilterEngine |
//| Purpose: Modular wrapper for Directional and Volatility filters |
//+------------------------------------------------------------------+
class CFilterEngine
{
private:
int m_handleADX;
int m_handleATR;
int m_handleTrendMA;
double m_adxBuffer[];
double m_atrBuffer[];
double m_trendMABuffer[];
public:
CFilterEngine() : m_handleADX(INVALID_HANDLE), m_handleATR(INVALID_HANDLE), m_handleTrendMA(INVALID_HANDLE) {}
~CFilterEngine() { Deinit(); }
bool Init(const string symbol, const ENUM_TIMEFRAMES timeframe);
void Deinit(void);
bool IsPassed(const string symbol, ENUM_ORDER_TYPE direction, double triggerPrice);
};
bool CFilterEngine::Init(const string symbol, const ENUM_TIMEFRAMES timeframe)
{
if(InpUseTrendMA)
{
m_handleTrendMA = iMA(symbol, InpTrendMATF, InpTrendMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
if(m_handleTrendMA == INVALID_HANDLE) return false;
ArraySetAsSeries(m_trendMABuffer, true);
}
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_handleTrendMA != INVALID_HANDLE) { IndicatorRelease(m_handleTrendMA); m_handleTrendMA = INVALID_HANDLE; }
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, ENUM_ORDER_TYPE direction, double triggerPrice)
{
// 1. Directional MTF Trend MA Filter
if(InpUseTrendMA)
{
// Synchronize HTF data to current LTF bar open time
datetime currentTime = iTime(symbol, PERIOD_CURRENT, 0);
if(CopyBuffer(m_handleTrendMA, 0, currentTime, 1, m_trendMABuffer) <= 0)
{
TBL_Log("Trend MA Filter: Failed to copy buffer.", LOG_ERRORS);
return false;
}
if(direction == ORDER_TYPE_BUY && triggerPrice <= m_trendMABuffer[0])
{
TBL_Log("Trend MA Filter Failed: Buy Trigger (" + DoubleToString(triggerPrice, 5) + ") is <= Trend MA (" + DoubleToString(m_trendMABuffer[0], 5) + ").", LOG_DEBUG);
return false;
}
if(direction == ORDER_TYPE_SELL && triggerPrice >= m_trendMABuffer[0])
{
TBL_Log("Trend MA Filter Failed: Sell Trigger (" + DoubleToString(triggerPrice, 5) + ") is >= Trend MA (" + DoubleToString(m_trendMABuffer[0], 5) + ").", LOG_DEBUG);
return false;
}
}
// 2. Trend Strength Filter (ADX)
if(InpUseADX)
{
if(CopyBuffer(m_handleADX, 0, 1, 1, m_adxBuffer) <= 0) return false;
if(m_adxBuffer[0] < InpADXMinLevel)
{
TBL_Log("ADX Filter Failed. Current: " + DoubleToString(m_adxBuffer[0], 2) + " < Min: " + DoubleToString(InpADXMinLevel, 2), LOG_DEBUG);
return false;
}
}
// 3. Volatility Filter (ATR)
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)
{
TBL_Log("ATR Filter Failed. Volatility too low.", LOG_DEBUG);
return false;
}
}
TBL_Log("All Active Filters Passed.", LOG_DEBUG);
return true;
}
CFilterEngine filterEngine;
//+------------------------------------------------------------------+
//| Helper: Calculate Lot Size (With Native Institutional Profit Sim)|
//+------------------------------------------------------------------+
double CalculateLotSize(double stopLossPrice, ENUM_ORDER_TYPE orderType)
{
if(InpLotMode == DYNAMIC_RISK)
{
if(stopLossPrice == 0.0)
{
TBL_Log("CalculateLotSize: SL is 0.0. Defaulting to Min Lot.", LOG_ERRORS);
return symInfo.LotsMin();
}
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
double riskAmount = freeMargin * (InpRiskPercent / 100.0);
double entryPrice = (orderType == ORDER_TYPE_BUY) ? symInfo.Ask() : symInfo.Bid();
double simulatedProfit = 0.0;
bool calcResult = OrderCalcProfit(orderType, _Symbol, 1.0, entryPrice, stopLossPrice, simulatedProfit);
if(!calcResult || simulatedProfit >= 0.0)
{
TBL_Log("CalculateLotSize: OrderCalcProfit failed or SL too tight. Defaulting to Min Lot.", LOG_ERRORS);
return symInfo.LotsMin();
}
double maxLossForOneLot = MathAbs(simulatedProfit);
double rawLotSize = riskAmount / maxLossForOneLot;
double volStep = symInfo.LotsStep();
double volMin = symInfo.LotsMin();
double volMax = symInfo.LotsMax();
double finalLotSize = MathRound(rawLotSize / volStep) * volStep;
if(finalLotSize < volMin) finalLotSize = volMin;
if(finalLotSize > volMax) finalLotSize = volMax;
TBL_Log("Calculated Dynamic Lot: " + DoubleToString(finalLotSize, 2) + " (Risking " + DoubleToString(riskAmount, 2) + ")", LOG_DEBUG);
return finalLotSize;
}
return InpFixedLotSize;
}
//+------------------------------------------------------------------+
//| 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()
{
trade.SetExpertMagicNumber(InpMagicNumber);
trade.SetMarginMode();
trade.SetTypeFillingBySymbol(_Symbol);
if(!symInfo.Name(_Symbol))
{
TBL_Log("Failed to initialize CSymbolInfo.", LOG_ERRORS);
return INIT_FAILED;
}
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)
{
TBL_Log("Error creating SMA handles.", LOG_ERRORS);
return INIT_FAILED;
}
if(!filterEngine.Init(_Symbol, _Period))
{
TBL_Log("Error creating Filter Engine handles.", LOG_ERRORS);
return INIT_FAILED;
}
TBL_Log("EA Initialized Successfully.", LOG_INFO);
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();
TBL_Log("EA Deinitialized cleanly.", LOG_INFO);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if(!IsNewBar()) return;
TBL_Log("New Bar Detected. Evaluating logic...", LOG_DEBUG);
if(!symInfo.RefreshRates()) return;
double fastMA[2], slowMA[2];
if(CopyBuffer(handleFastMA, 0, 1, 2, fastMA) <= 0) return;
if(CopyBuffer(handleSlowMA, 0, 1, 2, slowMA) <= 0) return;
bool isBuyCross = (fastMA[0] <= slowMA[0] && fastMA[1] > slowMA[1]);
bool isSellCross = (fastMA[0] >= slowMA[0] && fastMA[1] < slowMA[1]);
if(isBuyCross || isSellCross)
{
string crossType = isBuyCross ? "BUY CROSS" : "SELL CROSS";
TBL_Log("Crossover Triggered: " + crossType + " (Fast[1]: " + DoubleToString(fastMA[1], 5) + ", Slow[1]: " + DoubleToString(slowMA[1], 5) + ")", LOG_DEBUG);
}
else
{
return; // No signal, wait for next bar
}
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)
{
currentTicket = posInfo.Ticket();
if(posInfo.PositionType() == POSITION_TYPE_BUY) hasBuy = true;
if(posInfo.PositionType() == POSITION_TYPE_SELL) hasSell = true;
}
}
}
// --- Exit Logic ---
if(isBuyCross && hasSell)
{
TBL_Log("Closing existing SELL position due to BUY cross.", LOG_INFO);
if(trade.PositionClose(currentTicket)) hasSell = false;
}
if(isSellCross && hasBuy)
{
TBL_Log("Closing existing BUY position due to SELL cross.", LOG_INFO);
if(trade.PositionClose(currentTicket)) hasBuy = false;
}
// --- Entry Logic & Filter Evaluation ---
double pt = symInfo.Point();
double ask = symInfo.Ask();
double bid = symInfo.Bid();
// Use the closed bar's Fast MA value as the crossover trigger price for the Trend MA check
double triggerPrice = fastMA[1];
if(isBuyCross && !hasBuy && !hasSell)
{
if(filterEngine.IsPassed(_Symbol, ORDER_TYPE_BUY, triggerPrice))
{
double sl = (InpSLPoints > 0) ? symInfo.NormalizePrice(ask - InpSLPoints * pt) : 0.0;
double tp = (InpTPPoints > 0) ? symInfo.NormalizePrice(ask + InpTPPoints * pt) : 0.0;
double lotSize = CalculateLotSize(sl, ORDER_TYPE_BUY);
TBL_Log("Sending BUY Order: Lot " + DoubleToString(lotSize, 2) + " @ " + DoubleToString(ask, 5), LOG_INFO);
trade.Buy(lotSize, Symbol, ask, sl, tp, "TBLSMA_BUY");
}
}
if(isSellCross && !hasSell && !hasBuy)
{
if(filterEngine.IsPassed(_Symbol, ORDER_TYPE_SELL, triggerPrice))
{
double sl = (InpSLPoints > 0) ? symInfo.NormalizePrice(bid + InpSLPoints * pt) : 0.0;
double tp = (InpTPPoints > 0) ? symInfo.NormalizePrice(bid - InpTPPoints * pt) : 0.0;
double lotSize = CalculateLotSize(sl, ORDER_TYPE_SELL);
TBL_Log("Sending SELL Order: Lot " + DoubleToString(lotSize, 2) + " @ " + DoubleToString(bid, 5), LOG_INFO);
trade.Sell(lotSize, Symbol, bid, sl, tp, "TBLSMA_SELL");
}
}
}
//+------------------------------------------------------------------+