Class 8: Build Smarter AI Trading Bots with Session Filtration | TradingBotLab
Ready to Paste Code here:
------------------------------------
#property copyright "TradingBotLab"
#property link "https://tradingbotlab.com"
#property version "1.50"
#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: Weekend Risk Management
input group "=== Friday Exit Logic ==="
input bool InpUseFridayExit = true; // Enable Friday Risk Management
input string InpFridayNoNew = "19:00"; // Friday: Stop opening new trades (HH:MM)
input string InpFridayCloseAll = "21:00"; // Friday: Close ALL open trades (HH:MM)
//--- Input Parameters: Time Filtration
input group "=== Daily Time Sessions (HH:MM) ==="
input bool InpUseTimeFilter = true; // [MASTER] Enable Time Filtration
input bool InpUseLocalTime = false; // Use Local Time (false = Server Time)
input bool InpUseSession1 = true; // Enable Session 1
input string InpSess1Start = "08:00"; // Session 1: Start Time
input string InpSess1Stop = "12:00"; // Session 1: Stop Time
input bool InpUseSession2 = true; // Enable Session 2
input string InpSess2Start = "20:00"; // Session 2: Start Time (Midnight Crossover)
input string InpSess2Stop = "03:00"; // Session 2: Stop Time
//--- 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: CWeekendManager |
//| Purpose: Aggressive Friday Trade Blocking & Position Liquidation |
//+------------------------------------------------------------------+
class CWeekendManager
{
private:
bool m_active;
int m_no_new_time;
int m_close_all_time;
int ParseTime(string timeStr);
public:
void Init(bool active, string noNewStr, string closeAllStr);
bool IsBlockTime(bool useLocalTime);
bool IsCloseTime(bool useLocalTime);
};
int CWeekendManager::ParseTime(string timeStr)
{
string temp[];
if(StringSplit(timeStr, ':', temp) == 2)
return (int)StringToInteger(temp[0]) * 60 + (int)StringToInteger(temp[1]);
return 0;
}
void CWeekendManager::Init(bool active, string noNewStr, string closeAllStr)
{
m_active = active;
m_no_new_time = ParseTime(noNewStr);
m_close_all_time = ParseTime(closeAllStr);
}
bool CWeekendManager::IsBlockTime(bool useLocalTime)
{
if(!m_active) return false;
MqlDateTime dt;
if(useLocalTime) TimeLocal(dt);
else TimeCurrent(dt);
if(dt.day_of_week != 5) return false; // 5 = Friday
return ((dt.hour * 60 + dt.min) >= m_no_new_time);
}
bool CWeekendManager::IsCloseTime(bool useLocalTime)
{
if(!m_active) return false;
MqlDateTime dt;
if(useLocalTime) TimeLocal(dt);
else TimeCurrent(dt);
if(dt.day_of_week != 5) return false; // 5 = Friday
return ((dt.hour * 60 + dt.min) >= m_close_all_time);
}
CWeekendManager weekendManager;
//+------------------------------------------------------------------+
//| Class: CTimeEngine |
//| Purpose: Parses and evaluates dual-session time filtration |
//+------------------------------------------------------------------+
class CTimeEngine
{
private:
int m_s1_start, m_s1_stop;
int m_s2_start, m_s2_stop;
bool m_s1_active, m_s2_active;
int ParseTime(string timeStr);
bool IsInSession(int start, int stop, int current);
public:
void Init(bool s1_on, string s1_start, string s1_stop, bool s2_on, string s2_start, string s2_stop);
bool IsTradingAllowed(bool useLocalTime);
};
int CTimeEngine::ParseTime(string timeStr)
{
string temp[];
if(StringSplit(timeStr, ':', temp) == 2)
return (int)StringToInteger(temp[0]) * 60 + (int)StringToInteger(temp[1]);
return 0;
}
void CTimeEngine::Init(bool s1_on, string s1_start, string s1_stop, bool s2_on, string s2_start, string s2_stop)
{
m_s1_start = ParseTime(s1_start);
m_s1_stop = ParseTime(s1_stop);
m_s2_start = ParseTime(s2_start);
m_s2_stop = ParseTime(s2_stop);
m_s1_active = s1_on && (m_s1_start != m_s1_stop);
m_s2_active = s2_on && (m_s2_start != m_s2_stop);
}
bool CTimeEngine::IsInSession(int start, int stop, int current)
{
if(start < stop)
return (current >= start && current <= stop);
else
return (current >= start || current <= stop);
}
bool CTimeEngine::IsTradingAllowed(bool useLocalTime)
{
MqlDateTime dt;
if(useLocalTime) TimeLocal(dt);
else TimeCurrent(dt);
int currentMinute = dt.hour * 60 + dt.min;
bool passS1 = m_s1_active && IsInSession(m_s1_start, m_s1_stop, currentMinute);
bool passS2 = m_s2_active && IsInSession(m_s2_start, m_s2_stop, currentMinute);
return (passS1 || passS2);
}
CTimeEngine timeEngine;
//+------------------------------------------------------------------+
//| 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)
{
if(InpUseTrendMA)
{
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;
}
}
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;
}
}
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 |
//+------------------------------------------------------------------+
double CalculateLotSize(double stopLossPrice, ENUM_ORDER_TYPE orderType)
{
if(InpLotMode == DYNAMIC_RISK)
{
if(stopLossPrice == 0.0) 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) 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;
}
if(InpUseTimeFilter && !InpUseSession1 && !InpUseSession2)
{
TBL_Log("CONFIGURATION DEADLOCK: Master Time Filter is ON, but both sessions are OFF. Bot cannot trade.", 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;
}
timeEngine.Init(InpUseSession1, InpSess1Start, InpSess1Stop, InpUseSession2, InpSess2Start, InpSess2Stop);
weekendManager.Init(InpUseFridayExit, InpFridayNoNew, InpFridayCloseAll);
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()
{
// --- 1. Aggressive Friday Liquidation (Tick-Level Execution) ---
if(weekendManager.IsCloseTime(InpUseLocalTime))
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(posInfo.SelectByIndex(i))
{
if(posInfo.Symbol() == _Symbol && posInfo.Magic() == InpMagicNumber)
{
TBL_Log("Friday Risk Manager: Force closing Position " + IntegerToString(posInfo.Ticket()), LOG_INFO);
trade.PositionClose(posInfo.Ticket());
}
}
}
return; // Absolute lock: halt all further EA execution for the rest of the week
}
// --- 2. Standard Logic (Bar-Level Execution) ---
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;
}
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 (Executes Regardless of Time Filter Blocks) ---
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;
}
// --- Friday Block Validaton ---
if(weekendManager.IsBlockTime(InpUseLocalTime))
{
TBL_Log("Friday Risk Manager Block: No new trades allowed before close. Signal ignored.", LOG_DEBUG);
return;
}
// --- Daily Time Session Validation ---
if(InpUseTimeFilter && !timeEngine.IsTradingAllowed(InpUseLocalTime))
{
TBL_Log("Time Session Block: Current time is outside allowed windows. Signal ignored.", LOG_DEBUG);
return;
}
// --- Entry Logic & Technical Filter Evaluation ---
double pt = symInfo.Point();
double ask = symInfo.Ask();
double bid = symInfo.Bid();
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");
}
}
}
//+------------------------------------------------------------------+