Initialize project in MT5 Experts directory

This commit is contained in:
Huthayfa
2026-05-28 19:06:07 +03:00
commit 7545b842a4
21 changed files with 3213 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
//+------------------------------------------------------------------+
//| Core/Config.mqh |
//| Universal Multi-Timeframe EA - Configuration & Type Definitions|
//+------------------------------------------------------------------+
#ifndef __CONFIG_MQH__
#define __CONFIG_MQH__
#property strict
//+------------------------------------------------------------------+
//| ENUMERATIONS |
//+------------------------------------------------------------------+
enum ENUM_BIAS
{
BIAS_BULL,
BIAS_BEAR,
BIAS_NEUTRAL
};
enum ENUM_REGIME
{
REGIME_TREND,
REGIME_RANGE,
REGIME_CHOP
};
enum ENUM_PATTERN
{
PATTERN_PIN_BAR,
PATTERN_ENGULFING,
PATTERN_INSIDE_BAR,
PATTERN_NONE
};
enum ENUM_EXIT_REASON
{
EXIT_TP1,
EXIT_TP2,
EXIT_SL,
EXIT_BE,
EXIT_TIME,
EXIT_REGIME_CHANGE,
EXIT_MANUAL,
EXIT_TRAILING_STOP
};
enum ENUM_ASSET_CLASS
{
ASSET_FOREX_MAJOR,
ASSET_FOREX_CROSS,
ASSET_METAL,
ASSET_INDEX,
ASSET_COMMODITY,
ASSET_CRYPTO
};
//+------------------------------------------------------------------+
//| DATA STRUCTURES |
//+------------------------------------------------------------------+
struct SignalData
{
bool isValid;
bool isBuy;
double entryPrice;
double slPrice;
double tp1Price;
double tp2Price;
ENUM_PATTERN pattern;
string patternName;
string rejectionReason;
datetime signalTime;
double atrValue;
};
struct TradeParams
{
double lotSize;
double riskAmount;
double riskPercent;
double slDistance;
double tp1Distance;
double tp2Distance;
double marginRequired;
bool isValid;
string rejectReason;
};
struct AssetProfile
{
ENUM_ASSET_CLASS assetClass;
double atrMultiplierSL;
double maxSpreadPoints;
int londonOpenHour;
int nyOpenHour;
bool trade24_7;
bool skipWeekend;
int sessionStartHour;
int sessionEndHour;
double minVolumeRatio;
double partialCloseRatio;
double beBufferPoints;
double trailingATRMult;
int maxTradeDuration;
string description;
};
struct VWAPState
{
double vwapValue;
double vwapSlope;
datetime sessionStart;
double sumPV;
double sumV;
bool isValid;
};
struct CorrelationData
{
string symbol;
double correlation;
int barsUsed;
datetime calcTime;
};
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| CONSTANTS |
//+------------------------------------------------------------------+
#define MAX_RETRIES 3
#define RETRY_BASE_MS 500
#define CORR_LOOKBACK 50
#define VWAP_SLOPE_BARS 5
#define SWING_LOOKBACK 20
#define VOLUME_MA_PERIOD 20
#define BB_PERIOD 20
#define BB_DEVIATIONS 2.0
#define ADX_PERIOD 14
#define ADX_TREND_LEVEL 25.0
#define ADX_RANGE_LEVEL 20.0
#define ADX_CHOP_LEVEL 15.0
#define ATR_TREND_RATIO 1.0
#define ATR_CHOP_RATIO 0.8
#define VOLUME_CONFIRM 1.2
#define MIN_VOLUME_RATIO 0.8
#define PIN_BAR_WICK_MULT 2.0
#define ENGULF_VOLUME_MULT 1.2
#define PARTIAL_CLOSE_R 1.5
#define BE_BUFFER_ATR_MULT 0.2
#define SLIPPAGE_ATR_MULT 0.5
#define MIN_SLIPPAGE_PTS 10
#define MAX_SLIPPAGE_PTS 50
#define ATR_TO_POINTS(atrValue) ((int)MathRound((atrValue) / _Point))
#define VALIDATE_SHIFT(shift, context) ((shift) >= 1 ? true : (Print("[REPAINT_GUARD] Violation in ", (context), ": shift=", (shift), " < 1. Using shift=1."), false))
#define RELEASE_HANDLE(handle) do { if((handle) != INVALID_HANDLE) { IndicatorRelease(handle); (handle) = INVALID_HANDLE; } } while(0)
//+------------------------------------------------------------------+
#endif // __CONFIG_MQH__
+276
View File
@@ -0,0 +1,276 @@
//+------------------------------------------------------------------+
//| Core/Logger.mqh |
//+------------------------------------------------------------------+
#ifndef __LOGGER_MQH__
#define __LOGGER_MQH__
#include "Config.mqh"
#include "State.mqh"
extern EAState g_state;
class CLogger
{
private:
string m_logPath;
string m_label;
ulong m_magic;
int m_fileTrade;
int m_fileSignal;
int m_fileError;
bool m_initialized;
string m_panelName;
string m_objects[];
int m_objCount;
string TimeStampMicro() const
{
datetime t = TimeCurrent();
long msec = GetTickCount() % 1000;
return TimeToString(t, TIME_DATE|TIME_SECONDS) + "." + IntegerToString(msec, 3, '0');
}
bool EnsureDirectory(string path)
{
string dirs[];
int count = StringSplit(path, '\\', dirs);
string current = "";
for(int i = 0; i < count; i++)
{
if(i > 0) current += "\\";
current += dirs[i];
if(current == "") continue;
if(!FolderCreate(current, 0))
{
int err = GetLastError();
if(err != 183 && err != 0) return false; // 183 = already exists
}
}
return true;
}
int OpenLogFile(string filename, string header)
{
string filepath = m_logPath + filename;
bool exists = FileIsExist(filepath);
int handle = FileOpen(filepath, FILE_WRITE|FILE_READ|FILE_CSV|FILE_COMMON|FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_ANSI, ',');
if(handle == INVALID_HANDLE) return INVALID_HANDLE;
FileSeek(handle, 0, SEEK_END);
if(!exists || FileTell(handle) == 0)
{
FileWrite(handle, header);
FileFlush(handle);
}
return handle;
}
void WriteCSV(int handle, string data)
{
if(handle == INVALID_HANDLE) return;
FileSeek(handle, 0, SEEK_END);
FileWriteString(handle, data + "\r\n");
FileFlush(handle);
}
void CreatePanel()
{
m_panelName = "MTF_Dashboard_" + IntegerToString((int)m_magic);
ObjectCreate(0, m_panelName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, m_panelName, OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, m_panelName, OBJPROP_YDISTANCE, 30);
ObjectSetInteger(0, m_panelName, OBJPROP_XSIZE, 320);
ObjectSetInteger(0, m_panelName, OBJPROP_YSIZE, 280);
ObjectSetInteger(0, m_panelName, OBJPROP_BGCOLOR, C'20,20,30');
ObjectSetInteger(0, m_panelName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, m_panelName, OBJPROP_COLOR, C'60,60,80');
CreateLabel("Title", 15, 35, "Universal MTF EA v2.0", 12, clrWhite, true);
CreateLabel("Section1", 15, 55, "=== MARKET STATE ===", 10, C'150,150,170');
CreateLabel("BiasLabel", 15, 72, "HTF Bias:", 9, clrSilver);
CreateLabel("BiasValue", 120, 72, "NEUTRAL", 9, clrYellow);
CreateLabel("RegimeLabel", 15, 88, "Regime:", 9, clrSilver);
CreateLabel("RegimeValue", 120, 88, "RANGE", 9, clrYellow);
CreateLabel("MLLabel", 15, 104, "ML Confidence:", 9, clrSilver);
CreateLabel("MLValue", 120, 104, "0.00", 9, clrYellow);
CreateLabel("Section2", 15, 122, "=== PERFORMANCE ===", 10, C'150,150,170');
CreateLabel("DailyLabel", 15, 139, "Daily PnL:", 9, clrSilver);
CreateLabel("DailyValue", 120, 139, "0.00", 9, clrWhite);
CreateLabel("WeeklyLabel", 15, 155, "Weekly PnL:", 9, clrSilver);
CreateLabel("WeeklyValue", 120, 155, "0.00", 9, clrWhite);
CreateLabel("TradesLabel", 15, 171, "Trades Today:", 9, clrSilver);
CreateLabel("TradesValue", 120, 171, "0", 9, clrWhite);
CreateLabel("Section3", 15, 189, "=== RISK STATUS ===", 10, C'150,150,170');
CreateLabel("OpenPosLabel", 15, 206, "Open Positions:", 9, clrSilver);
CreateLabel("OpenPosValue", 120, 206, "0", 9, clrWhite);
CreateLabel("RiskLabel", 15, 222, "Total Risk:", 9, clrSilver);
CreateLabel("RiskValue", 120, 222, "0.00%", 9, clrWhite);
CreateLabel("StatusLabel", 15, 238, "Status:", 9, clrSilver);
CreateLabel("StatusValue", 120, 238, "ACTIVE", 9, clrLime);
CreateLabel("Section4", 15, 256, "=== NEWS ===", 10, C'150,150,170');
CreateLabel("NewsLabel", 15, 273, "Next Event:", 9, clrSilver);
CreateLabel("NewsValue", 120, 273, "None", 9, clrWhite);
m_initialized = true;
}
void CreateLabel(string name, int x, int y, string text, int fontSize, color clr, bool bold = false)
{
string fullName = m_panelName + "_" + name;
ObjectCreate(0, fullName, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, fullName, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, fullName, OBJPROP_YDISTANCE, y);
ObjectSetString(0, fullName, OBJPROP_FONT, bold ? "Arial Bold" : "Arial");
ObjectSetInteger(0, fullName, OBJPROP_FONTSIZE, fontSize);
ObjectSetInteger(0, fullName, OBJPROP_COLOR, clr);
ObjectSetString(0, fullName, OBJPROP_TEXT, text);
ObjectSetInteger(0, fullName, OBJPROP_SELECTABLE, false);
int idx = ArraySize(m_objects);
ArrayResize(m_objects, idx + 1);
m_objects[idx] = fullName;
}
void UpdateLabel(string name, string text, color clr)
{
string fullName = m_panelName + "_" + name;
if(ObjectFind(0, fullName) >= 0)
{
ObjectSetString(0, fullName, OBJPROP_TEXT, text);
ObjectSetInteger(0, fullName, OBJPROP_COLOR, clr);
}
}
public:
CLogger() : m_fileTrade(INVALID_HANDLE), m_fileSignal(INVALID_HANDLE),
m_fileError(INVALID_HANDLE), m_initialized(false), m_objCount(0) {}
bool Init(string basePath, string label, ulong magic)
{
m_label = label; m_magic = magic; m_logPath = basePath;
if(StringLen(m_logPath) > 0 && StringSubstr(m_logPath, StringLen(m_logPath)-1) != "\\")
m_logPath += "\\";
m_logPath += label + "_" + IntegerToString((int)magic) + "\\";
if(!EnsureDirectory(m_logPath)) m_logPath = "";
string tradeHeader = "Timestamp,Symbol,Direction,EntryPrice,SL,TP1,TP2,Lot,RiskPercent,ATR_Value,Regime,HTF_Bias,Pattern,ExitPrice,ExitReason,PnL_USD,PnL_Percent,Duration_Minutes";
m_fileTrade = OpenLogFile("TradeJournal.csv", tradeHeader);
string signalHeader = "Timestamp,Symbol,HTF_Bias,MTF_Regime,LTF_Pattern,IsValid,RejectionReason";
m_fileSignal = OpenLogFile("SignalLog.csv", signalHeader);
string errorHeader = "Timestamp,Function,ErrorCode,ErrorMessage,RetryCount,Resolution";
m_fileError = OpenLogFile("ErrorLog.csv", errorHeader);
m_initialized = (m_fileTrade != INVALID_HANDLE && m_fileSignal != INVALID_HANDLE && m_fileError != INVALID_HANDLE);
if(m_initialized)
{
Print("[Logger] Audit trail active. Path: ", m_logPath);
CreatePanel();
}
return m_initialized;
}
void Shutdown()
{
if(m_fileTrade != INVALID_HANDLE) { FileClose(m_fileTrade); m_fileTrade = INVALID_HANDLE; }
if(m_fileSignal != INVALID_HANDLE) { FileClose(m_fileSignal); m_fileSignal = INVALID_HANDLE; }
if(m_fileError != INVALID_HANDLE) { FileClose(m_fileError); m_fileError = INVALID_HANDLE; }
for(int i = 0; i < ArraySize(m_objects); i++) ObjectDelete(0, m_objects[i]);
ObjectDelete(0, m_panelName);
Print("[Logger] Log files closed and dashboard cleared.");
}
void LogEvent(string category, string message)
{
if(InpDebugMode) Print("[", category, "] ", message);
}
void LogSignal(const SignalData &signal, const EAState &state)
{
if(m_fileSignal == INVALID_HANDLE) return;
string line = StringFormat("%s,%s,%s,%s,%s,%s,%s",
TimeStampMicro(), _Symbol, EnumToString(state.currentBias),
EnumToString(state.currentRegime), signal.patternName,
signal.isValid ? "YES" : "NO", signal.rejectionReason);
WriteCSV(m_fileSignal, line);
}
void LogError(string function, int code, string message, int retryCount)
{
if(m_fileError == INVALID_HANDLE) return;
string line = StringFormat("%s,%s,%d,%s,%d,%s",
TimeStampMicro(), function, code, message, retryCount, "PENDING");
WriteCSV(m_fileError, line);
}
void LogTradeOpen(const SignalData &signal, const TradeParams &params, ulong ticket)
{
if(m_fileTrade == INVALID_HANDLE) return;
string dir = signal.isBuy ? "BUY" : "SELL";
string line = StringFormat("%s,%s,%s,%.5f,%.5f,%.5f,%.5f,%.2f,%.2f,%.5f,%s,%s,%s,%s,%.2f,%.2f,%d",
TimeStampMicro(), _Symbol, dir, signal.entryPrice, signal.slPrice,
signal.tp1Price, signal.tp2Price, params.lotSize, params.riskPercent,
signal.atrValue, EnumToString(g_state.currentRegime),
EnumToString(g_state.currentBias), signal.patternName, "", "", 0, 0, 0);
WriteCSV(m_fileTrade, line);
}
void LogTradeClose(const EAState &state)
{
if(m_fileTrade == INVALID_HANDLE) return;
string line = StringFormat("%s,%s,,%s,,,,,,,,,%.2f,%s,%.2f,%.0f",
TimeStampMicro(), _Symbol, EnumToString(state.lastExitReason),
state.lastTradePnL, EnumToString(state.lastExitReason),
(state.lastTradePnL / AccountInfoDouble(ACCOUNT_EQUITY)) * 100.0,
(TimeCurrent() - state.lastTradeClose) / 60.0);
WriteCSV(m_fileTrade, line);
}
void UpdateDashboard(const EAState &state)
{
color biasClr = clrYellow;
string biasText = EnumToString(state.currentBias);
if(state.currentBias == BIAS_BULL) biasClr = clrLime;
else if(state.currentBias == BIAS_BEAR) biasClr = clrRed;
UpdateLabel("BiasValue", biasText, biasClr);
color regimeClr = clrYellow;
string regimeText = EnumToString(state.currentRegime);
if(state.currentRegime == REGIME_TREND) regimeClr = clrLime;
else if(state.currentRegime == REGIME_CHOP) regimeClr = clrRed;
UpdateLabel("RegimeValue", regimeText, regimeClr);
color dailyClr = state.dailyPnL >= 0 ? clrLime : clrRed;
UpdateLabel("DailyValue", StringFormat("%.2f", state.dailyPnL), dailyClr);
color weeklyClr = state.weeklyPnL >= 0 ? clrLime : clrRed;
UpdateLabel("WeeklyValue", StringFormat("%.2f", state.weeklyPnL), weeklyClr);
UpdateLabel("TradesValue", IntegerToString(state.totalTradesToday), clrWhite);
UpdateLabel("OpenPosValue", IntegerToString(state.openPositions), state.openPositions > 0 ? clrLime : clrWhite);
color riskClr = state.totalOpenRisk > InpMaxTotalRisk * 0.8 ? clrRed :
state.totalOpenRisk > InpMaxTotalRisk * 0.5 ? clrYellow : clrWhite;
UpdateLabel("RiskValue", StringFormat("%.2f%%", state.totalOpenRisk), riskClr);
string status = "ACTIVE";
color statusClr = clrLime;
if(state.dailyLimitHit) { status = "DAILY LIMIT"; statusClr = clrRed; }
else if(state.weeklyLimitHit) { status = "WEEKLY LIMIT"; statusClr = clrRed; }
else if(state.consecLossHalted) { status = "CONSEC LOSS"; statusClr = clrRed; }
else if(state.circuitBreakerUntil > TimeCurrent()) { status = "HALTED"; statusClr = clrRed; }
UpdateLabel("StatusValue", status, statusClr);
string dash = StringFormat(
"\n=== Universal_MTF_EA v2.0 | %s ===\n"
"Bias: %s | Regime: %s | Volume: %s\n"
"Daily PnL: %.2f | Weekly PnL: %.2f\n"
"Open Pos: %d | Total Risk: %.2f%%\n"
"Last Trade: %.2f (%s)\n"
"Status: %s\n"
"====================",
_Symbol, EnumToString(state.currentBias), EnumToString(state.currentRegime),
state.volumeConfirmed ? "OK" : "LOW", state.dailyPnL, state.weeklyPnL,
state.openPositions, state.totalOpenRisk, state.lastTradePnL,
EnumToString(state.lastExitReason), status);
Comment(dash);
}
};
#endif // __LOGGER_MQH__
+142
View File
@@ -0,0 +1,142 @@
//+------------------------------------------------------------------+
//| Core/State.mqh |
//+------------------------------------------------------------------+
#ifndef __STATE_MQH__
#define __STATE_MQH__
#include "Config.mqh"
struct EAState
{
double dailyPnL;
double weeklyPnL;
double totalOpenRisk;
int consecutiveLosses;
int totalTradesToday;
int totalTradesWeek;
bool dailyLimitHit;
bool weeklyLimitHit;
bool consecLossHalted;
bool spreadHalted;
datetime circuitBreakerUntil;
string circuitBreakerReason;
ENUM_BIAS currentBias;
ENUM_REGIME currentRegime;
ENUM_PATTERN lastPattern;
bool volumeConfirmed;
bool isBarClosedHTF;
bool isBarClosedMTF;
bool isBarClosedLTF;
datetime lastHTFBarTime;
datetime lastMTFBarTime;
datetime lastLTFBarTime;
VWAPState vwapState;
double swingHigh;
double swingLow;
bool bosBullish;
bool bosBearish;
AssetProfile assetProfile;
int openPositions;
double equityAtStart;
double equityAtWeekStart;
datetime lastTradeClose;
ENUM_EXIT_REASON lastExitReason;
double lastTradePnL;
string logDirectory;
bool loggerReady;
datetime lastDashboardUpdate;
};
class CSessionManager
{
private:
datetime m_lastSessionCheck;
int m_serverOffset;
datetime GetGMTTime() const
{
return TimeGMT();
}
public:
CSessionManager() : m_lastSessionCheck(0), m_serverOffset(0) {}
bool Init()
{
datetime serverNow = TimeCurrent();
datetime gmtNow = TimeGMT();
m_serverOffset = (int)((serverNow - gmtNow) / 3600);
if(InpDebugMode)
Print("[SessionManager] Server-GMT offset: ", m_serverOffset, " hours");
return true;
}
bool IsSessionValid(const AssetProfile &profile) const
{
if(profile.trade24_7) return true;
MqlDateTime dt;
TimeToStruct(TimeGMT(), dt);
int currentHour = dt.hour;
int currentDay = dt.day_of_week;
if(profile.skipWeekend && (currentDay == 0 || currentDay == 6))
return false;
if(profile.assetClass == ASSET_METAL && currentDay == 5 && currentHour >= 21)
return false;
if(profile.assetClass == ASSET_METAL && currentDay == 1 && currentHour < 1)
return false;
if(currentHour >= profile.sessionStartHour && currentHour < profile.sessionEndHour)
return true;
return false;
}
bool IsNewSession(const AssetProfile &profile) const
{
MqlDateTime dt;
TimeToStruct(TimeGMT(), dt);
if((profile.assetClass == ASSET_FOREX_MAJOR || profile.assetClass == ASSET_FOREX_CROSS ||
profile.assetClass == ASSET_METAL) && dt.hour == 8 && dt.min == 0)
return true;
if(profile.assetClass == ASSET_INDEX && dt.hour == 13 && dt.min == 30)
return true;
if(profile.assetClass == ASSET_CRYPTO && dt.hour == 0 && dt.min == 0)
return true;
return false;
}
datetime GetSessionStart(const AssetProfile &profile) const
{
datetime gmtNow = TimeGMT();
MqlDateTime dt;
TimeToStruct(gmtNow, dt);
datetime sessionStart = 0;
if(profile.assetClass == ASSET_INDEX)
{
if(dt.hour < 13 || (dt.hour == 13 && dt.min < 30))
sessionStart = StringToTime(StringFormat("%04d.%02d.%02d 13:30:00", dt.year, dt.mon, dt.day)) - 86400;
else
sessionStart = StringToTime(StringFormat("%04d.%02d.%02d 13:30:00", dt.year, dt.mon, dt.day));
}
else
{
if(dt.hour < 8)
sessionStart = StringToTime(StringFormat("%04d.%02d.%02d 08:00:00", dt.year, dt.mon, dt.day)) - 86400;
else
sessionStart = StringToTime(StringFormat("%04d.%02d.%02d 08:00:00", dt.year, dt.mon, dt.day));
}
return sessionStart + (m_serverOffset * 3600);
}
bool IsRolloverTime() const
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if((dt.hour == 23 && dt.min >= 50) || (dt.hour == 0 && dt.min <= 10))
return true;
return false;
}
};
EAState g_state;
CSessionManager g_session;
#endif // __STATE_MQH__
+170
View File
@@ -0,0 +1,170 @@
//+------------------------------------------------------------------+
//| Core/SymbolProfiler.mqh |
//+------------------------------------------------------------------+
#ifndef __SYMBOL_PROFILER_MQH__
#define __SYMBOL_PROFILER_MQH__
#include "Config.mqh"
class CSymbolProfiler
{
public:
bool Init(AssetProfile &profile)
{
string sym = _Symbol;
profile.description = sym;
if(IsMetal(sym))
{
profile.assetClass = ASSET_METAL;
profile.atrMultiplierSL = 2.5;
profile.maxSpreadPoints = 30.0 * _Point * 10;
profile.londonOpenHour = 8;
profile.nyOpenHour = 13;
profile.trade24_7 = false;
profile.skipWeekend = true;
profile.sessionStartHour = 0;
profile.sessionEndHour = 23;
profile.minVolumeRatio = 0.7;
profile.partialCloseRatio = 0.5;
profile.beBufferPoints = 20.0 * _Point * 10;
profile.trailingATRMult = 2.0;
profile.maxTradeDuration = 360;
profile.description = "Precious Metal (XAU/XAG)";
}
else if(IsIndex(sym))
{
profile.assetClass = ASSET_INDEX;
profile.atrMultiplierSL = 3.0;
profile.maxSpreadPoints = 5.0 * _Point;
profile.londonOpenHour = 8;
profile.nyOpenHour = 13;
profile.trade24_7 = false;
profile.skipWeekend = true;
profile.sessionStartHour = 14;
profile.sessionEndHour = 21;
profile.minVolumeRatio = 0.6;
profile.partialCloseRatio = 0.5;
profile.beBufferPoints = 10.0 * _Point;
profile.trailingATRMult = 2.5;
profile.maxTradeDuration = 240;
profile.description = "Equity Index";
}
else if(IsCrypto(sym))
{
profile.assetClass = ASSET_CRYPTO;
profile.atrMultiplierSL = 2.0;
profile.maxSpreadPoints = 50.0 * _Point;
profile.londonOpenHour = 0;
profile.nyOpenHour = 0;
profile.trade24_7 = true;
profile.skipWeekend = false;
profile.sessionStartHour = 0;
profile.sessionEndHour = 23;
profile.minVolumeRatio = 0.5;
profile.partialCloseRatio = 0.5;
profile.beBufferPoints = 50.0 * _Point;
profile.trailingATRMult = 1.5;
profile.maxTradeDuration = 720;
profile.description = "Cryptocurrency";
}
else if(IsCommodity(sym))
{
profile.assetClass = ASSET_COMMODITY;
profile.atrMultiplierSL = 2.0;
profile.maxSpreadPoints = 20.0 * _Point;
profile.londonOpenHour = 8;
profile.nyOpenHour = 13;
profile.trade24_7 = false;
profile.skipWeekend = true;
profile.sessionStartHour = 0;
profile.sessionEndHour = 22;
profile.minVolumeRatio = 0.7;
profile.partialCloseRatio = 0.5;
profile.beBufferPoints = 15.0 * _Point;
profile.trailingATRMult = 2.0;
profile.maxTradeDuration = 360;
profile.description = "Commodity (Oil)";
}
else if(IsForexMajor(sym))
{
profile.assetClass = ASSET_FOREX_MAJOR;
profile.atrMultiplierSL = 1.5;
profile.maxSpreadPoints = 2.0 * _Point * 10;
profile.londonOpenHour = 8;
profile.nyOpenHour = 13;
profile.trade24_7 = false;
profile.skipWeekend = true;
profile.sessionStartHour = 0;
profile.sessionEndHour = 23;
profile.minVolumeRatio = 0.8;
profile.partialCloseRatio = 0.5;
profile.beBufferPoints = 5.0 * _Point * 10;
profile.trailingATRMult = 1.5;
profile.maxTradeDuration = 300;
profile.description = "Forex Major";
}
else
{
profile.assetClass = ASSET_FOREX_CROSS;
profile.atrMultiplierSL = 1.5;
profile.maxSpreadPoints = 3.0 * _Point * 10;
profile.londonOpenHour = 8;
profile.nyOpenHour = 13;
profile.trade24_7 = false;
profile.skipWeekend = true;
profile.sessionStartHour = 0;
profile.sessionEndHour = 23;
profile.minVolumeRatio = 0.8;
profile.partialCloseRatio = 0.5;
profile.beBufferPoints = 5.0 * _Point * 10;
profile.trailingATRMult = 1.5;
profile.maxTradeDuration = 300;
profile.description = "Forex Cross";
}
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(tickSize > 0 && point > 0)
{
double pipMultiplier = (tickSize / point);
profile.maxSpreadPoints *= pipMultiplier;
profile.beBufferPoints *= pipMultiplier;
}
Print("[SymbolProfiler] ", sym, " classified as: ", profile.description);
return true;
}
private:
bool IsMetal(string sym) const
{
return (StringFind(sym, "XAU") >= 0 || StringFind(sym, "XAG") >= 0 ||
StringFind(sym, "GOLD") >= 0 || StringFind(sym, "SILVER") >= 0);
}
bool IsIndex(string sym) const
{
return (StringFind(sym, "US30") >= 0 || StringFind(sym, "NAS") >= 0 ||
StringFind(sym, "SPX") >= 0 || StringFind(sym, "GER") >= 0 ||
StringFind(sym, "UK100") >= 0 || StringFind(sym, "JP225") >= 0 ||
StringFind(sym, "AUS") >= 0);
}
bool IsCrypto(string sym) const
{
return (StringFind(sym, "BTC") >= 0 || StringFind(sym, "ETH") >= 0 ||
StringFind(sym, "XRP") >= 0 || StringFind(sym, "LTC") >= 0 ||
StringFind(sym, "SOL") >= 0);
}
bool IsCommodity(string sym) const
{
return (StringFind(sym, "OIL") >= 0 || StringFind(sym, "BRENT") >= 0 ||
StringFind(sym, "WTI") >= 0 || StringFind(sym, "GAS") >= 0);
}
bool IsForexMajor(string sym) const
{
string majors[] = {"EURUSD", "GBPUSD", "USDJPY", "USDCHF", "AUDUSD",
"USDCAD", "NZDUSD", "EURJPY", "GBPJPY"};
for(int i = 0; i < ArraySize(majors); i++)
if(sym == majors[i]) return true;
return false;
}
};
#endif // __SYMBOL_PROFILER_MQH__
+127
View File
@@ -0,0 +1,127 @@
//+------------------------------------------------------------------+
//| Core/TelegramNotifier.mqh |
//+------------------------------------------------------------------+
#ifndef __TELEGRAM_NOTIFIER_MQH__
#define __TELEGRAM_NOTIFIER_MQH__
#include "Config.mqh"
#include "State.mqh"
class CTelegramNotifier
{
private:
string m_botToken;
string m_chatId;
string m_discordWebhook;
bool m_useTelegram;
bool m_useDiscord;
bool m_initialized;
int m_timeoutMs;
public:
CTelegramNotifier() : m_useTelegram(false), m_useDiscord(false),
m_initialized(false), m_timeoutMs(5000) {}
bool Init(string botToken, string chatId, string discordWebhook = "")
{
m_botToken = botToken;
m_chatId = chatId;
m_discordWebhook = discordWebhook;
m_useTelegram = (StringLen(botToken) > 0 && StringLen(chatId) > 0);
m_useDiscord = (StringLen(discordWebhook) > 0);
if(!m_useTelegram && !m_useDiscord)
{
Print("[TelegramNotifier] No notification channels configured.");
return true;
}
m_initialized = true;
Print("[TelegramNotifier] Initialized | Telegram: ", m_useTelegram ? "ON" : "OFF",
" | Discord: ", m_useDiscord ? "ON" : "OFF");
return true;
}
void SendTradeOpen(const SignalData &signal, const TradeParams &params, ulong ticket)
{
if(!m_initialized) return;
string emoji = signal.isBuy ? "BUY" : "SELL";
string message = StringFormat(
"*NEW TRADE OPENED*\n\nSymbol: %s\nDirection: %s\nEntry: %.5f\nSL: %.5f\nTP1: %.5f\nTP2: %.5f\nLots: %.2f\nRisk: %.2f%%\nTicket: %llu",
_Symbol, emoji, signal.entryPrice, signal.slPrice, signal.tp1Price, signal.tp2Price,
params.lotSize, params.riskPercent, ticket);
SendMessage(message);
}
void SendTradeClose(const EAState &state)
{
if(!m_initialized) return;
string pnlStr = state.lastTradePnL >= 0 ? StringFormat("+%.2f", state.lastTradePnL) : StringFormat("%.2f", state.lastTradePnL);
string message = StringFormat(
"*TRADE CLOSED*\n\nSymbol: %s\nPnL: %s USD\nReason: %s",
_Symbol, pnlStr, EnumToString(state.lastExitReason));
SendMessage(message);
}
void SendCircuitBreaker(const EAState &state)
{
if(!m_initialized) return;
string message = StringFormat(
"*CIRCUIT BREAKER ACTIVATED*\n\nSymbol: %s\nReason: %s\nDaily PnL: %.2f\nWeekly PnL: %.2f\nResumes: %s",
_Symbol, state.circuitBreakerReason, state.dailyPnL, state.weeklyPnL,
TimeToString(state.circuitBreakerUntil, TIME_DATE|TIME_SECONDS));
SendMessage(message);
}
void SendRegimeChange(ENUM_REGIME oldRegime, ENUM_REGIME newRegime)
{
if(!m_initialized) return;
string message = StringFormat(
"*REGIME CHANGE*\n\nSymbol: %s\nFrom: %s\nTo: %s",
_Symbol, EnumToString(oldRegime), EnumToString(newRegime));
SendMessage(message);
}
void SendDailySummary(const EAState &state)
{
if(!m_initialized) return;
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
string message = StringFormat(
"*DAILY SUMMARY*\n\nSymbol: %s\nDaily PnL: %.2f\nWeekly PnL: %.2f\nTrades: %d\nEquity: %.2f\nStatus: %s",
_Symbol, state.dailyPnL, state.weeklyPnL, state.totalTradesToday, equity,
state.circuitBreakerUntil > TimeCurrent() ? "HALTED" : "ACTIVE");
SendMessage(message);
}
void SendMessage(string message)
{
if(m_useTelegram) SendTelegram(message);
if(m_useDiscord) SendDiscord(message);
}
private:
void SendTelegram(string message)
{
string url = "https://api.telegram.org/bot" + m_botToken + "/sendMessage";
string headers;
string data = "chat_id=" + m_chatId + "&text=" + message + "&parse_mode=Markdown";
char dataChar[];
StringToCharArray(data, dataChar);
char result[];
string resultHeaders;
int res = WebRequest("POST", url, headers, 5000, dataChar, result, resultHeaders);
if(res != 200) Print("[TelegramNotifier] Telegram send failed. HTTP: ", res);
}
void SendDiscord(string message)
{
string headers;
string jsonPayload = "{\"content\":\"" + message + "\"}";
char dataChar[];
StringToCharArray(jsonPayload, dataChar);
char result[];
string resultHeaders;
int res = WebRequest("POST", m_discordWebhook, headers, 5000, dataChar, result, resultHeaders);
if(res != 200 && res != 204) Print("[TelegramNotifier] Discord send failed. HTTP: ", res);
}
};
#endif // __TELEGRAM_NOTIFIER_MQH__