293 lines
22 KiB
Plaintext
293 lines
22 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| TOL LANGIT Neural Quant Advisor.mq5 |
|
|
//| Copyright 2026, Adithyo Dewangga Wijaya |
|
|
//| https://www.mql5.com/en/users/adithyodw |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2026, Adithyo Dewangga Wijaya"
|
|
#property link "https://www.mql5.com/en/users/adithyodw"
|
|
#property version "28.00"
|
|
#property strict
|
|
#property description "Institutional Gold System with Neural-Inspired Regime Detection and Volatility Filters"
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
#include <Trade\SymbolInfo.mqh>
|
|
|
|
//--- INPUTS
|
|
input string s0 = "======= TRADING HOURS (GMT) =======";
|
|
input int InpLondonOpen = 8; // London open hour
|
|
input int InpSessionEnd = 18; // Session end hour
|
|
input string s1 = "======= INSTITUTIONAL SETUP =======";
|
|
input int InpEMA = 200; // EMA period for macro trend
|
|
input double InpATR_Mult = 3.5; // SuperTrend ATR multiplier
|
|
input int InpADX_Period = 14; // ADX period
|
|
input double InpADX_Thresh = 25.0;// ADX threshold for trending regime
|
|
input string s2 = "======= RISK MANAGEMENT =======";
|
|
input double InpRiskPercent = 0.25; // Risk per trade (%)
|
|
input double InpSL_Mult = 2.0; // SL ATR multiplier
|
|
input double InpTP_Mult = 3.0; // TP ATR multiplier
|
|
input double InpMaxSpread = 0.5; // Max allowed spread (in price units)
|
|
input double InpDailyLossPercent = 1.0; // Daily loss limit (%)
|
|
input int InpMaxTradesDay = 5; // Max trades per day
|
|
input double InpMaxDDPercent = 5.0; // Max drawdown percent for equity stop
|
|
input long InpMagic = 123456; // Magic number
|
|
|
|
//--- Globals
|
|
CTrade m_trade;
|
|
CPositionInfo m_pos;
|
|
CSymbolInfo m_sym;
|
|
int hEMA = INVALID_HANDLE;
|
|
int hATR = INVALID_HANDLE;
|
|
int hADX = INVALID_HANDLE;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit() {
|
|
if (!m_sym.Name(_Symbol)) return(INIT_FAILED);
|
|
m_trade.SetExpertMagicNumber(InpMagic);
|
|
|
|
// Load indicator handles
|
|
hEMA = iMA(_Symbol, _Period, InpEMA, 0, MODE_EMA, PRICE_CLOSE);
|
|
hATR = iATR(_Symbol, _Period, 14);
|
|
hADX = iADX(_Symbol, _Period, InpADX_Period);
|
|
|
|
if (hEMA == INVALID_HANDLE || hATR == INVALID_HANDLE || hADX == INVALID_HANDLE) {
|
|
Print("Failed to initialize indicators");
|
|
return(INIT_FAILED);
|
|
}
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason) {
|
|
if (hEMA != INVALID_HANDLE) IndicatorRelease(hEMA);
|
|
if (hATR != INVALID_HANDLE) IndicatorRelease(hATR);
|
|
if (hADX != INVALID_HANDLE) IndicatorRelease(hADX);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick() {
|
|
if (!m_sym.RefreshRates()) return;
|
|
if (Bars(_Symbol, _Period) < InpEMA) return;
|
|
|
|
// Spread filter
|
|
double spread = m_sym.Ask() - m_sym.Bid();
|
|
if (spread > InpMaxSpread) return;
|
|
|
|
// Session filter
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(), dt);
|
|
bool isTradeTime = (dt.hour >= InpLondonOpen && dt.hour <= InpSessionEnd);
|
|
if (!isTradeTime) return;
|
|
|
|
// Equity stop protection
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
if (equity < balance * (1 - InpMaxDDPercent / 100)) {
|
|
CloseAllPositions();
|
|
ExpertRemove();
|
|
return;
|
|
}
|
|
|
|
// Daily loss limit
|
|
double daily_profit = GetDailyProfit();
|
|
double daily_loss_limit = equity * InpDailyLossPercent / 100;
|
|
if (daily_profit < -daily_loss_limit) return;
|
|
|
|
// Max trades per day
|
|
int trades_today = GetTradesToday();
|
|
if (trades_today >= InpMaxTradesDay) return;
|
|
|
|
// Get indicator values
|
|
double ema[1], atr[1], adx[1], close[1];
|
|
ArraySetAsSeries(ema, true); ArraySetAsSeries(atr, true); ArraySetAsSeries(adx, true); ArraySetAsSeries(close, true);
|
|
if (CopyBuffer(hEMA, 0, 0, 1, ema) < 1 ||
|
|
CopyBuffer(hATR, 0, 0, 1, atr) < 1 ||
|
|
CopyBuffer(hADX, 0, 0, 1, adx) < 1 ||
|
|
CopyClose(_Symbol, _Period, 0, 1, close) < 1) return;
|
|
|
|
// SuperTrend bands using previous bar (non-repainting)
|
|
double atr1[1];
|
|
ArraySetAsSeries(atr1, true);
|
|
if (CopyBuffer(hATR, 0, 1, 1, atr1) < 1) return;
|
|
double mid1 = (iHigh(_Symbol, _Period, 1) + iLow(_Symbol, _Period, 1)) / 2.0;
|
|
double upB = mid1 + (InpATR_Mult * atr1[0]);
|
|
double dnB = mid1 - (InpATR_Mult * atr1[0]);
|
|
|
|
// Signal logic with regime filter
|
|
int signal = 0;
|
|
if (adx[0] > InpADX_Thresh) {
|
|
if (close[0] > ema[0] && close[0] > dnB) signal = 1;
|
|
if (close[0] < ema[0] && close[0] < upB) signal = -1;
|
|
}
|
|
|
|
// Execute trade
|
|
ExecuteTrade(signal, atr[0]);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Trade execution function |
|
|
//+------------------------------------------------------------------+
|
|
void ExecuteTrade(int signal, double current_atr) {
|
|
int current_dir = GetCurrentDirection();
|
|
if (signal != 0 && current_dir == -signal) {
|
|
CloseAllPositions();
|
|
}
|
|
if (signal != 0 && current_dir == 0) {
|
|
double sl_dist = current_atr * InpSL_Mult;
|
|
double tp_dist = current_atr * InpTP_Mult;
|
|
double lot = CalculateLot(sl_dist);
|
|
if (lot == 0) return;
|
|
|
|
double sl, tp;
|
|
ENUM_ORDER_TYPE order_type;
|
|
double price;
|
|
|
|
if (signal == 1) {
|
|
order_type = ORDER_TYPE_BUY;
|
|
price = m_sym.Ask();
|
|
sl = price - sl_dist;
|
|
tp = price + tp_dist;
|
|
} else {
|
|
order_type = ORDER_TYPE_SELL;
|
|
price = m_sym.Bid();
|
|
sl = price + sl_dist;
|
|
tp = price - tp_dist;
|
|
}
|
|
|
|
sl = m_sym.NormalizePrice(sl);
|
|
tp = m_sym.NormalizePrice(tp);
|
|
m_trade.PositionOpen(_Symbol, order_type, lot, 0, sl, tp);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Adjust volume to comply with symbol specifications |
|
|
//+------------------------------------------------------------------+
|
|
double AdjustVolume(double vol) {
|
|
if (vol <= 0) return 0.0;
|
|
double min_vol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
double max_vol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
if (step == 0) return 0.0;
|
|
|
|
// Calculate volume digits for normalization
|
|
int vol_digits = (int) MathMax(0, -MathLog10(step) + 1e-10); // Add epsilon to avoid log issues
|
|
|
|
// Normalize input volume to avoid floating-point precision errors
|
|
vol = NormalizeDouble(vol, vol_digits + 2); // Higher precision initially
|
|
|
|
// Round to nearest step multiple
|
|
long ratio = (long) MathRound(vol / step);
|
|
double adjusted = ratio * step;
|
|
|
|
// Final normalization to exact digits
|
|
adjusted = NormalizeDouble(adjusted, vol_digits);
|
|
|
|
if (adjusted < min_vol || adjusted > max_vol) return 0.0;
|
|
|
|
return adjusted;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate position size based on risk |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateLot(double sl_dist) {
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double risk = equity * InpRiskPercent / 100.0;
|
|
double points = sl_dist / _Point;
|
|
double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
if (tick_value == 0 || points == 0) return 0.0;
|
|
|
|
double lot = risk / (points * tick_value);
|
|
|
|
lot = AdjustVolume(lot);
|
|
|
|
return lot;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current position direction |
|
|
//+------------------------------------------------------------------+
|
|
int GetCurrentDirection() {
|
|
int total = PositionsTotal();
|
|
for (int i = total - 1; i >= 0; i--) {
|
|
if (m_pos.SelectByIndex(i) && m_pos.Symbol() == _Symbol && m_pos.Magic() == InpMagic) {
|
|
if (m_pos.PositionType() == POSITION_TYPE_BUY) return 1;
|
|
if (m_pos.PositionType() == POSITION_TYPE_SELL) return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close all positions |
|
|
//+------------------------------------------------------------------+
|
|
void CloseAllPositions() {
|
|
int total = PositionsTotal();
|
|
for (int i = total - 1; i >= 0; i--) {
|
|
if (m_pos.SelectByIndex(i) && m_pos.Symbol() == _Symbol && m_pos.Magic() == InpMagic) {
|
|
m_trade.PositionClose(m_pos.Ticket());
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get daily closed profit |
|
|
//+------------------------------------------------------------------+
|
|
double GetDailyProfit() {
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(), dt);
|
|
dt.hour = 0; dt.min = 0; dt.sec = 0;
|
|
datetime start = StructToTime(dt);
|
|
datetime end = TimeCurrent() + 1; // Include current time
|
|
|
|
if (!HistorySelect(start, end)) return 0.0;
|
|
|
|
double profit = 0.0;
|
|
int deals = HistoryDealsTotal();
|
|
for (int i = deals - 1; i >= 0; i--) {
|
|
ulong ticket = HistoryDealGetTicket(i);
|
|
if (ticket > 0 &&
|
|
HistoryDealGetInteger(ticket, DEAL_MAGIC) == InpMagic &&
|
|
HistoryDealGetString(ticket, DEAL_SYMBOL) == _Symbol &&
|
|
HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT) { // Only closed deals
|
|
profit += HistoryDealGetDouble(ticket, DEAL_PROFIT) +
|
|
HistoryDealGetDouble(ticket, DEAL_SWAP) +
|
|
HistoryDealGetDouble(ticket, DEAL_COMMISSION);
|
|
}
|
|
}
|
|
return profit;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get number of trades opened today |
|
|
//+------------------------------------------------------------------+
|
|
int GetTradesToday() {
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(), dt);
|
|
dt.hour = 0; dt.min = 0; dt.sec = 0;
|
|
datetime start = StructToTime(dt);
|
|
datetime end = TimeCurrent() + 1;
|
|
|
|
if (!HistorySelect(start, end)) return 0;
|
|
|
|
int count = 0;
|
|
int deals = HistoryDealsTotal();
|
|
for (int i = deals - 1; i >= 0; i--) {
|
|
ulong ticket = HistoryDealGetTicket(i);
|
|
if (ticket > 0 &&
|
|
HistoryDealGetInteger(ticket, DEAL_MAGIC) == InpMagic &&
|
|
HistoryDealGetString(ticket, DEAL_SYMBOL) == _Symbol &&
|
|
HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
} |