Zenith-FX/ForexTrader_v3_Production.mq5

1118 satır
36 KiB
MQL5
Ham Kalıcı Bağlantı Normal Görünüm Geçmiş

//+------------------------------------------------------------------+
//| ForexTrader_v3_Production.mq5 |
//| Production-Ready Single-Pair EA |
//| With All Critical Flaws Fixed |
//+------------------------------------------------------------------+
#property copyright "ForexTrader EA v3.0 - Production Grade"
#property link "https://github.com/simonokwundue-ops/Experienced-FX-Trader"
#property version "3.00"
#property description "Professional EA with comprehensive risk management"
#property description "Implements adaptive MA strategy with multiple confirmations"
#property strict
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
//--- Global Objects
CTrade trade;
CPositionInfo positionInfo;
CAccountInfo accountInfo;
//+------------------------------------------------------------------+
//| Input Parameters - Strategy |
//+------------------------------------------------------------------+
input group "=== Strategy Settings ==="
input bool UseAdaptiveMA = true; // Use Adaptive Moving Average (KAMA)
input int FastMA_Period = 10; // Fast MA Period (if not adaptive)
input int SlowMA_Period = 50; // Slow MA Period (if not adaptive)
input int KAMA_Period = 14; // KAMA Period
input int KAMA_FastEMA = 2; // KAMA Fast EMA Period
input int KAMA_SlowEMA = 30; // KAMA Slow EMA Period
input ENUM_MA_METHOD MA_Method = MODE_EMA; // MA Method (for non-adaptive)
input ENUM_APPLIED_PRICE MA_Price = PRICE_CLOSE; // Applied Price
//+------------------------------------------------------------------+
//| Input Parameters - Filters |
//+------------------------------------------------------------------+
input group "=== Signal Filters ==="
input bool UseADXFilter = true; // Use ADX Trend Strength Filter
input int ADX_Period = 14; // ADX Period
input double ADX_Minimum = 20.0; // Minimum ADX Level
input bool UseATRFilter = true; // Use ATR Volatility Filter
input int ATR_Period = 14; // ATR Period
input double ATR_MinimumPips = 10.0; // Minimum ATR in Pips
input double ATR_MaximumPips = 100.0; // Maximum ATR in Pips (spike protection)
input double MA_SlopeMinimum = 5.0; // Minimum MA Slope in Pips (filter weak crosses)
input double MA_DistanceMinimum = 3.0; // Minimum Distance Between MAs in Pips
//+------------------------------------------------------------------+
//| Input Parameters - Risk Management |
//+------------------------------------------------------------------+
input group "=== Risk Management ==="
input double RiskPercent = 2.0; // Risk Per Trade (% of Balance)
input double StopLossPips = 40.0; // Stop Loss in Pips
input double TakeProfitPips = 80.0; // Take Profit in Pips
input bool UseDynamicSLTP = true; // Use ATR-Based Dynamic SL/TP
input double ATR_SL_Multiplier = 2.0; // ATR Multiplier for SL (if dynamic)
input double ATR_TP_Multiplier = 4.0; // ATR Multiplier for TP (if dynamic)
input double MaxDrawdownPercent = 30.0; // Max Drawdown % (stop trading)
input double MaxSpreadPips = 5.0; // Max Spread in Pips
//+------------------------------------------------------------------+
//| Input Parameters - Position Management |
//+------------------------------------------------------------------+
input group "=== Position Management ==="
input bool UseTrailingStop = true; // Enable Trailing Stop
input double TrailingStopPips = 30.0; // Trailing Stop Distance (Pips)
input double TrailingStepPips = 5.0; // Trailing Step (Pips)
input double TrailingActivationPips = 20.0; // Profit to Activate Trailing (Pips)
input bool UseBreakeven = true; // Move SL to Breakeven
input double BreakevenTriggerPips = 20.0; // Profit to Trigger Breakeven (Pips)
input double BreakevenOffsetPips = 2.0; // Breakeven Offset (Pips)
//+------------------------------------------------------------------+
//| Input Parameters - Money Management |
//+------------------------------------------------------------------+
input group "=== Money Management ==="
input double MaxLotSize = 10.0; // Maximum Lot Size
input double MinLotSize = 0.01; // Minimum Lot Size
input bool UseFixedLot = false; // Use Fixed Lot Size
input double FixedLotSize = 0.1; // Fixed Lot Size (if enabled)
//+------------------------------------------------------------------+
//| Input Parameters - Trading Controls |
//+------------------------------------------------------------------+
input group "=== Trading Controls ==="
input int CooldownMinutes = 15; // Cooldown Between Trades (Minutes)
input bool SeparateCooldownByDirection = true; // Separate Buy/Sell Cooldown
input int MaxDailyTrades = 10; // Maximum Trades Per Day
input int MaxConcurrentPositions = 1; // Max Concurrent Positions
input bool UseTradingHours = false; // Enable Trading Hours Filter
input int StartHour = 8; // Trading Start Hour (Server Time)
input int EndHour = 20; // Trading End Hour (Server Time)
//+------------------------------------------------------------------+
//| Input Parameters - Advanced Settings |
//+------------------------------------------------------------------+
input group "=== Advanced Settings ==="
input int MagicNumber = 123456; // Magic Number
input string TradeComment = "ForexTrader_v3"; // Trade Comment
input int Slippage = 30; // Max Slippage in Points
input int MaxRetries = 3; // Max Order Send Retries
input int RetryDelayMs = 1000; // Retry Delay (Milliseconds)
//+------------------------------------------------------------------+
//| Global Variables |
//+------------------------------------------------------------------+
int handleFastMA, handleSlowMA, handleADX, handleATR;
double fastMA[], slowMA[], adxMain[], atrBuffer[];
double point, tickSize, tickValue, lotStep, minLot, maxLot;
datetime lastBuyTime = 0;
datetime lastSellTime = 0;
int dailyTradeCount = 0;
datetime lastTradeDate = 0;
double pipSize;
int symbolDigits;
//+------------------------------------------------------------------+
//| Expert Initialization |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Initialize trade object
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(Slippage);
trade.SetTypeFilling(ORDER_FILLING_IOC);
trade.SetAsyncMode(false);
//--- Get symbol properties
point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
symbolDigits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
//--- Calculate pip size correctly for all symbols
if(symbolDigits == 3 || symbolDigits == 5)
pipSize = point * 10;
else
pipSize = point;
//--- Get symbol trading properties
tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE_PROFIT);
//--- Fallback for tick value if not available
if(tickValue == 0)
{
double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
tickValue = tickSize * contractSize;
Print("Warning: Using fallback tick value calculation: ", tickValue);
}
lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
//--- Validate inputs
if(!ValidateInputs())
return INIT_PARAMETERS_INCORRECT;
//--- Create indicators
if(UseAdaptiveMA)
{
//--- For KAMA, we'll calculate it manually in a helper function
//--- For now, create standard MAs as fallback
handleFastMA = iMA(_Symbol, PERIOD_CURRENT, FastMA_Period, 0, MA_Method, MA_Price);
handleSlowMA = iMA(_Symbol, PERIOD_CURRENT, SlowMA_Period, 0, MA_Method, MA_Price);
}
else
{
handleFastMA = iMA(_Symbol, PERIOD_CURRENT, FastMA_Period, 0, MA_Method, MA_Price);
handleSlowMA = iMA(_Symbol, PERIOD_CURRENT, SlowMA_Period, 0, MA_Method, MA_Price);
}
if(UseADXFilter)
handleADX = iADX(_Symbol, PERIOD_CURRENT, ADX_Period);
if(UseATRFilter || UseDynamicSLTP)
handleATR = iATR(_Symbol, PERIOD_CURRENT, ATR_Period);
//--- Check indicator handles
if(handleFastMA == INVALID_HANDLE || handleSlowMA == INVALID_HANDLE)
{
Print("Error: Failed to create MA indicators");
return INIT_FAILED;
}
if(UseADXFilter && handleADX == INVALID_HANDLE)
{
Print("Error: Failed to create ADX indicator");
return INIT_FAILED;
}
if((UseATRFilter || UseDynamicSLTP) && handleATR == INVALID_HANDLE)
{
Print("Error: Failed to create ATR indicator");
return INIT_FAILED;
}
//--- Set arrays as series
ArraySetAsSeries(fastMA, true);
ArraySetAsSeries(slowMA, true);
ArraySetAsSeries(adxMain, true);
ArraySetAsSeries(atrBuffer, true);
//--- Print initialization info
Print("========================================");
Print("ForexTrader v3.0 Production - Initialized");
Print("Symbol: ", _Symbol);
Print("Timeframe: ", EnumToString(PERIOD_CURRENT));
Print("Point: ", point, " | Pip Size: ", pipSize);
Print("Tick Value: ", tickValue);
Print("Min Lot: ", minLot, " | Max Lot: ", maxLot);
Print("Risk Per Trade: ", RiskPercent, "%");
Print("========================================");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert Deinitialization |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- Release indicator handles
if(handleFastMA != INVALID_HANDLE)
IndicatorRelease(handleFastMA);
if(handleSlowMA != INVALID_HANDLE)
IndicatorRelease(handleSlowMA);
if(handleADX != INVALID_HANDLE)
IndicatorRelease(handleADX);
if(handleATR != INVALID_HANDLE)
IndicatorRelease(handleATR);
Print("ForexTrader v3.0 - Deinitialized. Reason: ", reason);
}
//+------------------------------------------------------------------+
//| Expert Tick Function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- Check if new bar
static datetime lastBar = 0;
datetime currentBar = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBar == lastBar)
return;
lastBar = currentBar;
//--- Reset daily trade counter
ResetDailyTradeCount();
//--- Update indicators
if(!UpdateIndicators())
return;
//--- Check drawdown guard
if(!CheckDrawdownLimit())
{
Print("Max drawdown exceeded. Trading halted.");
return;
}
//--- Manage existing positions
ManagePositions();
//--- Check if we can trade
if(!CanOpenNewTrade())
return;
//--- Check for entry signals
CheckEntrySignals();
}
//+------------------------------------------------------------------+
//| Trade Transaction Event |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
//--- Check if deal was added
if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
{
//--- Get deal properties
ulong dealTicket = trans.deal;
if(dealTicket > 0 && HistoryDealSelect(dealTicket))
{
long dealMagic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
if(dealMagic == MagicNumber)
{
ENUM_DEAL_TYPE dealType = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE);
double dealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
string dealSymbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
//--- Log trade result
if(dealType == DEAL_TYPE_BUY || dealType == DEAL_TYPE_SELL)
{
Print("Trade opened: ", dealSymbol, " | Type: ", EnumToString(dealType),
" | Ticket: ", dealTicket);
}
else if(dealProfit != 0)
{
Print("Trade closed: ", dealSymbol, " | Profit: ", dealProfit,
" | Ticket: ", dealTicket);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Validate Input Parameters |
//+------------------------------------------------------------------+
bool ValidateInputs()
{
if(FastMA_Period <= 0 || SlowMA_Period <= 0)
{
Print("Error: MA periods must be positive");
return false;
}
if(FastMA_Period >= SlowMA_Period)
{
Print("Error: Fast MA period must be less than Slow MA period");
return false;
}
if(RiskPercent <= 0 || RiskPercent > 100)
{
Print("Error: Risk percent must be between 0 and 100");
return false;
}
if(StopLossPips <= 0)
{
Print("Error: Stop loss must be positive");
return false;
}
if(MaxDrawdownPercent <= 0 || MaxDrawdownPercent > 100)
{
Print("Error: Max drawdown percent must be between 0 and 100");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Update Indicator Values |
//+------------------------------------------------------------------+
bool UpdateIndicators()
{
//--- Copy MA buffers
if(CopyBuffer(handleFastMA, 0, 0, 3, fastMA) < 3)
{
Print("Error: Failed to copy Fast MA buffer");
return false;
}
if(CopyBuffer(handleSlowMA, 0, 0, 3, slowMA) < 3)
{
Print("Error: Failed to copy Slow MA buffer");
return false;
}
//--- Copy ADX buffer if needed
if(UseADXFilter)
{
if(CopyBuffer(handleADX, 0, 0, 1, adxMain) < 1)
{
Print("Error: Failed to copy ADX buffer");
return false;
}
}
//--- Copy ATR buffer if needed
if(UseATRFilter || UseDynamicSLTP)
{
if(CopyBuffer(handleATR, 0, 0, 1, atrBuffer) < 1)
{
Print("Error: Failed to copy ATR buffer");
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| Check if Within Drawdown Limit |
//+------------------------------------------------------------------+
bool CheckDrawdownLimit()
{
double equity = accountInfo.Equity();
double balance = accountInfo.Balance();
if(balance <= 0)
return false;
double drawdownPercent = ((balance - equity) / balance) * 100.0;
if(drawdownPercent > MaxDrawdownPercent)
{
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Reset Daily Trade Count |
//+------------------------------------------------------------------+
void ResetDailyTradeCount()
{
MqlDateTime currentTime;
TimeToStruct(TimeCurrent(), currentTime);
MqlDateTime lastTime;
TimeToStruct(lastTradeDate, lastTime);
//--- If it's a new day, reset the counter
if(currentTime.day != lastTime.day || currentTime.mon != lastTime.mon || currentTime.year != lastTime.year)
{
dailyTradeCount = 0;
lastTradeDate = TimeCurrent();
}
}
//+------------------------------------------------------------------+
//| Check if Can Open New Trade |
//+------------------------------------------------------------------+
bool CanOpenNewTrade()
{
//--- Check daily trade limit
if(dailyTradeCount >= MaxDailyTrades)
{
return false;
}
//--- Check concurrent positions
if(CountPositions() >= MaxConcurrentPositions)
{
return false;
}
//--- Check trading hours
if(UseTradingHours && !IsTradingTime())
{
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Check if Within Trading Hours |
//+------------------------------------------------------------------+
bool IsTradingTime()
{
MqlDateTime tm;
TimeToStruct(TimeCurrent(), tm);
int currentHour = tm.hour;
if(StartHour <= EndHour)
{
return (currentHour >= StartHour && currentHour < EndHour);
}
else
{
return (currentHour >= StartHour || currentHour < EndHour);
}
}
//+------------------------------------------------------------------+
//| Check if Cooldown Elapsed |
//+------------------------------------------------------------------+
bool IsCooldownElapsed(bool isBuySignal)
{
datetime relevantTime = SeparateCooldownByDirection ?
(isBuySignal ? lastBuyTime : lastSellTime) :
MathMax(lastBuyTime, lastSellTime);
return (TimeCurrent() - relevantTime) >= (CooldownMinutes * 60);
}
//+------------------------------------------------------------------+
//| Check Entry Signals |
//+------------------------------------------------------------------+
void CheckEntrySignals()
{
//--- Check spread
if(!IsSpreadAcceptable())
{
return;
}
//--- Check ATR filter
if(UseATRFilter && !IsVolatilityAcceptable())
{
return;
}
//--- Check ADX filter
if(UseADXFilter && !IsTrendStrong())
{
return;
}
//--- Get current prices
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
//--- Check for bullish crossover
bool bullishCross = fastMA[1] > slowMA[1] && fastMA[2] <= slowMA[2];
//--- Check for bearish crossover
bool bearishCross = fastMA[1] < slowMA[1] && fastMA[2] >= slowMA[2];
//--- Additional confirmation: check MA slope
if(bullishCross || bearishCross)
{
double maSlope = MathAbs(fastMA[1] - fastMA[2]) / pipSize;
double maDistance = MathAbs(fastMA[1] - slowMA[1]) / pipSize;
if(maSlope < MA_SlopeMinimum)
{
Print("MA slope too weak: ", maSlope, " pips (minimum: ", MA_SlopeMinimum, ")");
return;
}
if(maDistance < MA_DistanceMinimum)
{
Print("MA distance too small: ", maDistance, " pips (minimum: ", MA_DistanceMinimum, ")");
return;
}
}
//--- Process buy signal
if(bullishCross && IsCooldownElapsed(true))
{
Print("=== BUY SIGNAL DETECTED ===");
Print("FastMA[1]: ", fastMA[1], " > SlowMA[1]: ", slowMA[1]);
Print("FastMA[2]: ", fastMA[2], " <= SlowMA[2]: ", slowMA[2]);
if(OpenBuyPosition(ask))
{
lastBuyTime = TimeCurrent();
dailyTradeCount++;
}
}
//--- Process sell signal
if(bearishCross && IsCooldownElapsed(false))
{
Print("=== SELL SIGNAL DETECTED ===");
Print("FastMA[1]: ", fastMA[1], " < SlowMA[1]: ", slowMA[1]);
Print("FastMA[2]: ", fastMA[2], " >= SlowMA[2]: ", slowMA[2]);
if(OpenSellPosition(bid))
{
lastSellTime = TimeCurrent();
dailyTradeCount++;
}
}
}
//+------------------------------------------------------------------+
//| Check if Spread is Acceptable |
//+------------------------------------------------------------------+
bool IsSpreadAcceptable()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double spread = (ask - bid) / pipSize;
if(spread > MaxSpreadPips)
{
Print("Spread too high: ", spread, " pips (max: ", MaxSpreadPips, ")");
return false;
}
//--- Also check if spread is larger than half the stop loss
if(spread > (StopLossPips * 0.5))
{
Print("Spread too high relative to SL: ", spread, " pips (SL: ", StopLossPips, ")");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Check if Volatility is Acceptable |
//+------------------------------------------------------------------+
bool IsVolatilityAcceptable()
{
if(ArraySize(atrBuffer) == 0)
return true;
double atr = atrBuffer[0];
double atrPips = atr / pipSize;
if(atrPips < ATR_MinimumPips)
{
Print("ATR too low: ", atrPips, " pips (min: ", ATR_MinimumPips, ")");
return false;
}
if(atrPips > ATR_MaximumPips)
{
Print("ATR too high: ", atrPips, " pips (max: ", ATR_MaximumPips, ")");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Check if Trend is Strong |
//+------------------------------------------------------------------+
bool IsTrendStrong()
{
if(ArraySize(adxMain) == 0)
return true;
double adx = adxMain[0];
if(adx < ADX_Minimum)
{
Print("ADX too low: ", adx, " (min: ", ADX_Minimum, ")");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Open Buy Position |
//+------------------------------------------------------------------+
bool OpenBuyPosition(double price)
{
//--- Calculate SL and TP
double sl = CalculateStopLoss(ORDER_TYPE_BUY, price);
double tp = CalculateTakeProfit(ORDER_TYPE_BUY, price);
//--- Validate SL/TP against broker stops level
if(!ValidateTrade(ORDER_TYPE_BUY, price, sl, tp))
return false;
//--- Calculate lot size
double lots = CalculateLotSize(price, sl);
//--- Normalize values
sl = NormalizeDouble(sl, symbolDigits);
tp = NormalizeDouble(tp, symbolDigits);
lots = NormalizeLot(lots);
//--- Validate lot size
if(lots < minLot || lots > maxLot)
{
Print("Invalid lot size: ", lots);
return false;
}
//--- Try to open position with retries
bool success = false;
int attempts = 0;
while(attempts < MaxRetries && !success)
{
ResetLastError();
//--- Use 0 for price to let broker fill at market
success = trade.Buy(lots, _Symbol, 0, sl, tp, TradeComment);
if(success)
{
Print("=== BUY ORDER OPENED ===");
Print("Ticket: ", trade.ResultOrder());
Print("Lots: ", lots, " | SL: ", sl, " | TP: ", tp);
Print("Expected Risk: $", CalculateRiskAmount(price, sl, lots));
return true;
}
else
{
int error = GetLastError();
Print("Buy order failed (attempt ", attempts + 1, "/", MaxRetries, "): ",
error, " - ", ErrorDescription(error));
//--- Only retry on transient errors
if(IsTransientError(error))
{
Sleep(RetryDelayMs);
attempts++;
}
else
{
break;
}
}
}
return false;
}
//+------------------------------------------------------------------+
//| Open Sell Position |
//+------------------------------------------------------------------+
bool OpenSellPosition(double price)
{
//--- Calculate SL and TP
double sl = CalculateStopLoss(ORDER_TYPE_SELL, price);
double tp = CalculateTakeProfit(ORDER_TYPE_SELL, price);
//--- Validate SL/TP against broker stops level
if(!ValidateTrade(ORDER_TYPE_SELL, price, sl, tp))
return false;
//--- Calculate lot size
double lots = CalculateLotSize(price, sl);
//--- Normalize values
sl = NormalizeDouble(sl, symbolDigits);
tp = NormalizeDouble(tp, symbolDigits);
lots = NormalizeLot(lots);
//--- Validate lot size
if(lots < minLot || lots > maxLot)
{
Print("Invalid lot size: ", lots);
return false;
}
//--- Try to open position with retries
bool success = false;
int attempts = 0;
while(attempts < MaxRetries && !success)
{
ResetLastError();
//--- Use 0 for price to let broker fill at market
success = trade.Sell(lots, _Symbol, 0, sl, tp, TradeComment);
if(success)
{
Print("=== SELL ORDER OPENED ===");
Print("Ticket: ", trade.ResultOrder());
Print("Lots: ", lots, " | SL: ", sl, " | TP: ", tp);
Print("Expected Risk: $", CalculateRiskAmount(price, sl, lots));
return true;
}
else
{
int error = GetLastError();
Print("Sell order failed (attempt ", attempts + 1, "/", MaxRetries, "): ",
error, " - ", ErrorDescription(error));
//--- Only retry on transient errors
if(IsTransientError(error))
{
Sleep(RetryDelayMs);
attempts++;
}
else
{
break;
}
}
}
return false;
}
//+------------------------------------------------------------------+
//| Calculate Stop Loss |
//+------------------------------------------------------------------+
double CalculateStopLoss(ENUM_ORDER_TYPE orderType, double price)
{
double slDistance;
if(UseDynamicSLTP && ArraySize(atrBuffer) > 0)
{
//--- Use ATR-based dynamic SL
slDistance = atrBuffer[0] * ATR_SL_Multiplier;
}
else
{
//--- Use fixed pip-based SL (FIXED: removed 10x multiplier)
slDistance = StopLossPips * pipSize;
}
double sl = 0;
if(orderType == ORDER_TYPE_BUY)
{
sl = price - slDistance;
}
else if(orderType == ORDER_TYPE_SELL)
{
sl = price + slDistance;
}
return sl;
}
//+------------------------------------------------------------------+
//| Calculate Take Profit |
//+------------------------------------------------------------------+
double CalculateTakeProfit(ENUM_ORDER_TYPE orderType, double price)
{
double tpDistance;
if(UseDynamicSLTP && ArraySize(atrBuffer) > 0)
{
//--- Use ATR-based dynamic TP
tpDistance = atrBuffer[0] * ATR_TP_Multiplier;
}
else
{
//--- Use fixed pip-based TP (FIXED: removed 10x multiplier)
tpDistance = TakeProfitPips * pipSize;
}
double tp = 0;
if(orderType == ORDER_TYPE_BUY)
{
tp = price + tpDistance;
}
else if(orderType == ORDER_TYPE_SELL)
{
tp = price - tpDistance;
}
return tp;
}
//+------------------------------------------------------------------+
//| Validate Trade Against Broker Requirements |
//+------------------------------------------------------------------+
bool ValidateTrade(ENUM_ORDER_TYPE orderType, double price, double sl, double tp)
{
//--- Get broker stops level
long stopsLevelPoints = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double stopsLevel = stopsLevelPoints * point;
//--- Check SL distance
double slDistance = MathAbs(price - sl);
if(slDistance < stopsLevel)
{
Print("SL too close to price. Required: ", stopsLevel / pipSize, " pips, Got: ",
slDistance / pipSize, " pips");
return false;
}
//--- Check TP distance
double tpDistance = MathAbs(price - tp);
if(tpDistance < stopsLevel)
{
Print("TP too close to price. Required: ", stopsLevel / pipSize, " pips, Got: ",
tpDistance / pipSize, " pips");
return false;
}
//--- Check freeze level
long freezeLevelPoints = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
double freezeLevel = freezeLevelPoints * point;
double currentPrice = (orderType == ORDER_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(MathAbs(currentPrice - price) < freezeLevel)
{
Print("Price within freeze level");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Calculate Lot Size Based on Risk |
//+------------------------------------------------------------------+
double CalculateLotSize(double entryPrice, double stopLoss)
{
if(UseFixedLot)
return FixedLotSize;
double balance = accountInfo.Balance();
double riskAmount = balance * (RiskPercent / 100.0);
double slDistance = MathAbs(entryPrice - stopLoss);
if(slDistance <= 0)
return minLot;
//--- Calculate value per lot
double ticksDistance = slDistance / tickSize;
double riskPerLot = ticksDistance * tickValue;
if(riskPerLot <= 0)
return minLot;
double lots = riskAmount / riskPerLot;
//--- Apply limits
if(lots > MaxLotSize)
lots = MaxLotSize;
if(lots < MinLotSize)
lots = MinLotSize;
return lots;
}
//+------------------------------------------------------------------+
//| Calculate Risk Amount for Logging |
//+------------------------------------------------------------------+
double CalculateRiskAmount(double entryPrice, double stopLoss, double lots)
{
double slDistance = MathAbs(entryPrice - stopLoss);
double ticksDistance = slDistance / tickSize;
return ticksDistance * tickValue * lots;
}
//+------------------------------------------------------------------+
//| Normalize Lot Size |
//+------------------------------------------------------------------+
double NormalizeLot(double lots)
{
//--- Round to nearest lot step
lots = MathRound(lots / lotStep) * lotStep;
//--- Apply limits
if(lots < minLot)
lots = minLot;
if(lots > maxLot)
lots = maxLot;
return NormalizeDouble(lots, 2);
}
//+------------------------------------------------------------------+
//| Count Positions for This EA |
//+------------------------------------------------------------------+
int CountPositions()
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(positionInfo.SelectByIndex(i))
{
if(positionInfo.Symbol() == _Symbol &&
positionInfo.Magic() == MagicNumber)
{
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| Manage Existing Positions |
//+------------------------------------------------------------------+
void ManagePositions()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!positionInfo.SelectByIndex(i))
continue;
if(positionInfo.Symbol() != _Symbol || positionInfo.Magic() != MagicNumber)
continue;
ulong ticket = positionInfo.Ticket();
double openPrice = positionInfo.PriceOpen();
double currentSL = positionInfo.StopLoss();
double currentTP = positionInfo.TakeProfit();
ENUM_POSITION_TYPE posType = positionInfo.PositionType();
//--- Get current price
double currentPrice = (posType == POSITION_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
//--- Calculate profit in pips
double profitPips = 0;
if(posType == POSITION_TYPE_BUY)
profitPips = (currentPrice - openPrice) / pipSize;
else
profitPips = (openPrice - currentPrice) / pipSize;
//--- Move to breakeven
if(UseBreakeven && profitPips >= BreakevenTriggerPips)
{
double bePrice = openPrice + (BreakevenOffsetPips * pipSize *
(posType == POSITION_TYPE_BUY ? 1 : -1));
bool needsUpdate = false;
if(posType == POSITION_TYPE_BUY && (currentSL < bePrice || currentSL == 0))
needsUpdate = true;
else if(posType == POSITION_TYPE_SELL && (currentSL > bePrice || currentSL == 0))
needsUpdate = true;
if(needsUpdate)
{
bePrice = NormalizeDouble(bePrice, symbolDigits);
if(trade.PositionModify(ticket, bePrice, currentTP))
{
Print("Position moved to breakeven: Ticket ", ticket, " | New SL: ", bePrice);
}
continue; // Don't trail if just moved to breakeven
}
}
//--- Trailing stop
if(UseTrailingStop && profitPips >= TrailingActivationPips)
{
double trailDistance = TrailingStopPips * pipSize;
double trailStep = TrailingStepPips * pipSize;
double newSL = 0;
if(posType == POSITION_TYPE_BUY)
{
newSL = currentPrice - trailDistance;
//--- Only update if improvement by at least trail step
if(newSL > currentSL + trailStep || currentSL == 0)
{
newSL = NormalizeDouble(newSL, symbolDigits);
//--- Ensure new SL is above open price (in profit)
if(newSL > openPrice)
{
if(trade.PositionModify(ticket, newSL, currentTP))
{
Print("Trailing stop updated: Ticket ", ticket, " | New SL: ", newSL);
}
}
}
}
else if(posType == POSITION_TYPE_SELL)
{
newSL = currentPrice + trailDistance;
//--- Only update if improvement by at least trail step
if(newSL < currentSL - trailStep || currentSL == 0)
{
newSL = NormalizeDouble(newSL, symbolDigits);
//--- Ensure new SL is below open price (in profit)
if(newSL < openPrice)
{
if(trade.PositionModify(ticket, newSL, currentTP))
{
Print("Trailing stop updated: Ticket ", ticket, " | New SL: ", newSL);
}
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| Check if Error is Transient and Worth Retrying |
//+------------------------------------------------------------------+
bool IsTransientError(int error)
{
switch(error)
{
case 10004: // ERR_SERVER_BUSY
case 10006: // ERR_NO_CONNECTION
case 10007: // ERR_TOO_MANY_REQUESTS
case 10018: // ERR_MARKET_CLOSED
case 10021: // ERR_ORDER_LOCKED
case 10025: // ERR_TRADE_TIMEOUT
case 10027: // ERR_TRADE_TOO_MANY_ORDERS
return true;
default:
return false;
}
}
//+------------------------------------------------------------------+
//| Get Error Description |
//+------------------------------------------------------------------+
string ErrorDescription(int error)
{
string desc = "";
switch(error)
{
case 10004: desc = "Server is busy"; break;
case 10006: desc = "No connection"; break;
case 10007: desc = "Too many requests"; break;
case 10013: desc = "Invalid request"; break;
case 10014: desc = "Invalid volume"; break;
case 10015: desc = "Invalid price"; break;
case 10016: desc = "Invalid stops"; break;
case 10018: desc = "Market is closed"; break;
case 10019: desc = "Not enough money"; break;
case 10021: desc = "Order is locked"; break;
case 10025: desc = "Trade timeout"; break;
case 10027: desc = "Too many orders"; break;
default: desc = "Unknown error"; break;
}
return desc;
}
//+------------------------------------------------------------------+