Files
EA/Buffer EA/Indicator Signal EA base code.mq4
Kunthawat Greethong 04aa2eb2e6 New EA and Indi
2026-01-25 10:34:54 +07:00

788 lines
39 KiB
Plaintext

//+------------------------------------------------------------------+
//| Universal_Buffer_Reader_EA.mq4 |
//| Copyright 2024, Your Name/Company |
//| http://yourwebsite.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Your Name/Company"
#property link "http://yourwebsite.com"
#property version "1.84" // FINAL: Rewrote Trailing Stop with unified, safe ratchet logic
#property strict
#include <stdlib.mqh> // For ErrorDescription()
// --- Trade Mode Enum ---
enum TradeMode {
MODE_INDICATOR,
MODE_MANUAL
};
// --- New Input Parameters ---
extern TradeMode Trade_Mode = MODE_INDICATOR; // Trade Mode Selector
extern bool UsePercentBalanceLot = true; // Use custom % of Balance based on TP
extern double PercentOfBalanceForProfit = 1.0; // Profit target as % of balance
extern double DailyProfitTargetPercent = 2.0; // Daily profit target as % of balance at 01:00
// --- Original Input Parameters ---
extern string General_Settings = "--- General Settings ---";
extern double LotSize = 0.03; // Initial Lot Size
extern int Slippage = 3; // Slippage in Pips
extern int MagicNumber = 24680;
extern bool ClearPersistentLossOnStart = false; // !! SET TO TRUE FOR BACKTESTING TO RESET LOSS !!
extern bool TakeScreenshotOnOpen = true;
extern bool EnableDebugPrints = true;
extern bool ExitOnOppositeSignal = false;
// --- Recovery & Safety Settings ---
extern string Recovery_Settings_Header = "--- Recovery & Safety Settings ---";
extern bool EnableRecoveryMode = true; // Enable/Disable recovery mode completely
extern double HighLossThresholdPercent = 30.0; // Trigger conservative mode if loss > this % of balance. (0=Off)
extern double HighLossRecoveryDampener = 3.0; // Divide recovery lot by this factor in conservative mode. Must be > 1.
extern int ConsecutiveLossesToReset = 3; // Reset accumulated loss after X losses in a row. (0=Off)
// --- Time Filtering Settings ---
extern string Time_Filter_Settings_Header = "--- Time Filtering Settings ---";
extern bool EnableTimeFiltering = false; // Enable/Disable time filtering
extern string Day_Filter_Header = "--- Day of Week Filter ---";
extern bool TradeMondayEnabled = true; // Allow trading on Monday
extern bool TradeTuesdayEnabled = true; // Allow trading on Tuesday
extern bool TradeWednesdayEnabled = true; // Allow trading on Wednesday
extern bool TradeThursdayEnabled = true; // Allow trading on Thursday
extern bool TradeFridayEnabled = true; // Allow trading on Friday
extern bool TradeSaturdayEnabled = false; // Allow trading on Saturday
extern bool TradeSundayEnabled = false; // Allow trading on Sunday
extern string Session_Filter_Header = "--- Trading Session Filter ---";
extern bool TradeAsianSessionEnabled = true; // Allow trading during Asian session (00:00-08:00 GMT)
extern bool TradeEuropeSessionEnabled = true; // Allow trading during Europe session (07:00-16:00 GMT)
extern bool TradeAmericaSessionEnabled = true; // Allow trading during America session (13:00-22:00 GMT)
// --- Trailing Stop Settings ---
extern string Trailing_Stop_Settings_Header = "--- Trailing Stop Settings ---";
extern bool UseTrailingStop = false; // Enable/Disable the trailing stop feature
extern int TrailingStopPips = 300; // Trailing distance in Pips (e.g., 300 on XAUUSD = $3 move)
// --- Indicator Settings ---
extern string Indicator_Settings = "--- Indicator Settings ---";
extern string IndicatorFileName = "My_Indicator";
extern string Indicator_Params_Help = "Manual settings on chart indicator.";
// --- Signal Buffers ---
extern int BuySignalBuffer = 0;
extern int SellSignalBuffer = 1;
extern string Signal_Value_Type_Help = "Buy/Sell buffers contain PRICE levels.";
// --- SL/TP Mode Settings ---
extern string SLTP_Settings_Header = "--- SL/TP Mode Settings ---";
extern int SLTP_Mode = 0;
// --- Take Profit Settings ---
extern string TP_Settings_Header = "--- Take Profit Settings ---";
extern int MinimumTakeProfitPips = 300; // Enforce a minimum TP distance in Pips (0=disabled)
// --- ATR Settings (Mode 0) ---
extern string Settings_Mode_0 = "--- (Mode 0) ATR Settings ---";
extern int AtrPeriod = 14;
extern double StopLossAtrMultiplier = 1.5;
extern double TakeProfitAtrMultiplier= 3.0;
// --- Indicator SL/TP Buffers (Mode 1) ---
extern string Settings_Mode_1 = "--- (Mode 1) Indicator SL/TP ---";
extern int StopLossBuffer = 2;
extern int TakeProfitBuffer = 3;
extern string Mode_1_Value_Type_Help = "SL/TP buffers contain PRICE levels.";
// ... (Global Variables, OnInit, OnDeinit, CreateTradeUI etc. are unchanged from v1.82) ...
// --- Global Variables ---
int slippagePoints;
double pointValue;
double pipValue;
int digits;
string eaCommentPrefix;
datetime lastTradeSignalBarTime = 0;
long chartID;
double minLotSize;
double maxLotSize;
double lotStep;
double accumulatedLoss = 0;
int lastProcessedOrderTicket = -1;
int consecutiveLosses = 0;
string gvAccumLossName;
string gvConsecLossName;
double dailyProfitStartBalance = 0;
datetime lastDailyResetTime = 0;
double dailyProfitAccumulated = 0;
double manualTP = 0;
double manualSL = 0;
bool manualTradeButtonPressed = false;
//+------------------------------------------------------------------+
//| Get Pip Value (Correctly handles 2/3 and 4/5 digit brokers) |
//+------------------------------------------------------------------+
double GetPipValue()
{
if(digits == 3 || digits == 5) return (10 * pointValue);
else return pointValue;
}
//+------------------------------------------------------------------+
//| Check if current day of week is allowed for trading |
//+------------------------------------------------------------------+
bool IsDayOfWeekAllowed()
{
if(!EnableTimeFiltering) return true;
int dayOfWeek = DayOfWeek();
switch(dayOfWeek)
{
case 1: return TradeMondayEnabled; // Monday
case 2: return TradeTuesdayEnabled; // Tuesday
case 3: return TradeWednesdayEnabled; // Wednesday
case 4: return TradeThursdayEnabled; // Thursday
case 5: return TradeFridayEnabled; // Friday
case 6: return TradeSaturdayEnabled; // Saturday
case 0: return TradeSundayEnabled; // Sunday
default: return false;
}
}
//+------------------------------------------------------------------+
//| Check if current time is within allowed trading sessions |
//+------------------------------------------------------------------+
bool IsSessionTimeAllowed()
{
if(!EnableTimeFiltering) return true;
// Get current GMT time
datetime gmtTime = TimeGMT();
int hour = TimeHour(gmtTime);
// Asian Session: 00:00 - 08:00 GMT
bool isAsianSession = (hour >= 0 && hour < 8);
// Europe Session: 07:00 - 16:00 GMT
bool isEuropeSession = (hour >= 7 && hour < 16);
// America Session: 13:00 - 22:00 GMT
bool isAmericaSession = (hour >= 13 && hour < 22);
// Check if current time falls within any enabled session
if(isAsianSession && TradeAsianSessionEnabled) return true;
if(isEuropeSession && TradeEuropeSessionEnabled) return true;
if(isAmericaSession && TradeAmericaSessionEnabled) return true;
// If no session is active or enabled, return false
return false;
}
//+------------------------------------------------------------------+
//| Check if trading is allowed based on time filters |
//+------------------------------------------------------------------+
bool IsTimeFilterAllowed()
{
if(!EnableTimeFiltering) return true;
bool dayAllowed = IsDayOfWeekAllowed();
bool sessionAllowed = IsSessionTimeAllowed();
if(EnableDebugPrints && (!dayAllowed || !sessionAllowed))
{
Print("Time Filter Check: Day allowed=", dayAllowed, ", Session allowed=", sessionAllowed);
}
return (dayAllowed && sessionAllowed);
}
//+------------------------------------------------------------------+
//| Helper function to update and persist loss |
//+------------------------------------------------------------------+
void UpdatePersistentLoss(double newLoss)
{
newLoss = MathMax(0, newLoss);
if (accumulatedLoss != newLoss)
{
if(EnableDebugPrints) Print("Updating accumulated loss from ", DoubleToString(accumulatedLoss, 2), " to ", DoubleToString(newLoss, 2));
accumulatedLoss = newLoss;
GlobalVariableSet(gvAccumLossName, accumulatedLoss);
}
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
chartID = ChartID();
digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
pointValue = MarketInfo(Symbol(), MODE_POINT);
pipValue = GetPipValue();
minLotSize = MarketInfo(Symbol(), MODE_MINLOT);
maxLotSize = MarketInfo(Symbol(), MODE_MAXLOT);
lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
string tfString;
switch(Period())
{
case 1: tfString = "M1"; break; case 5: tfString = "M5"; break;
case 15: tfString = "M15"; break; case 30: tfString = "M30"; break;
case 60: tfString = "H1"; break; case 240: tfString = "H4"; break;
case 1440: tfString = "D1"; break; case 10080: tfString = "W1"; break;
case 43200: tfString = "MN"; break; default: tfString = StringFormat("%d", Period());
}
eaCommentPrefix = "UnivBufEA_" + IntegerToString(MagicNumber) + "_" + tfString;
lastTradeSignalBarTime = 0;
if (digits == 3 || digits == 5) slippagePoints = Slippage * 10;
else slippagePoints = Slippage;
gvAccumLossName = "UnivBufEA_" + Symbol() + "_" + IntegerToString(MagicNumber) + "_AccumLoss";
gvConsecLossName = "UnivBufEA_" + Symbol() + "_" + IntegerToString(MagicNumber) + "_ConsecLoss";
if(ClearPersistentLossOnStart)
{
if(GlobalVariableCheck(gvAccumLossName)) GlobalVariableDel(gvAccumLossName);
if(GlobalVariableCheck(gvConsecLossName)) GlobalVariableDel(gvConsecLossName);
Print("Persistent variables have been cleared as per user setting.");
}
if(GlobalVariableCheck(gvAccumLossName)) { accumulatedLoss = GlobalVariableGet(gvAccumLossName); }
else { accumulatedLoss = 0; GlobalVariableSet(gvAccumLossName, 0); }
Print("Loaded persistent accumulated loss: ", DoubleToString(accumulatedLoss, 2));
if(GlobalVariableCheck(gvConsecLossName)) { consecutiveLosses = (int)GlobalVariableGet(gvConsecLossName); }
else { consecutiveLosses = 0; GlobalVariableSet(gvConsecLossName, 0); }
Print("Loaded persistent consecutive losses: ", consecutiveLosses);
datetime now = TimeCurrent();
datetime midnight = now - (now % 86400);
datetime resetTime = midnight + 3600;
if (now >= resetTime && (lastDailyResetTime < resetTime || (TimeDayOfYear(now) != TimeDayOfYear(lastDailyResetTime))))
{
dailyProfitStartBalance = AccountBalance();
dailyProfitAccumulated = 0;
lastDailyResetTime = now;
Print("Daily Profit Tracking Reset at 01:00. Start Balance: ", DoubleToString(dailyProfitStartBalance, 2));
}
ObjectCreate(chartID, "AccumLossLabel", OBJ_LABEL, 0, 0, 0);
ObjectSetString(chartID, "AccumLossLabel", OBJPROP_TEXT, "Accum. Loss: Loading...");
ObjectSetInteger(chartID, "AccumLossLabel", OBJPROP_CORNER, CORNER_LEFT_LOWER);
ObjectSetInteger(chartID, "AccumLossLabel", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(chartID, "AccumLossLabel", OBJPROP_YDISTANCE, 40);
ObjectSetInteger(chartID, "AccumLossLabel", OBJPROP_FONTSIZE, 10);
ObjectCreate(chartID, "AccumLossPercentLabel", OBJ_LABEL, 0, 0, 0);
ObjectSetString(chartID, "AccumLossPercentLabel", OBJPROP_TEXT, "Loss % of Bal: Loading...");
ObjectSetInteger(chartID, "AccumLossPercentLabel", OBJPROP_CORNER, CORNER_LEFT_LOWER);
ObjectSetInteger(chartID, "AccumLossPercentLabel", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(chartID, "AccumLossPercentLabel", OBJPROP_YDISTANCE, 20);
ObjectSetInteger(chartID, "AccumLossPercentLabel", OBJPROP_FONTSIZE, 10);
CreateTradeUI();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(!IsTesting())
{
GlobalVariableSet(gvAccumLossName, accumulatedLoss);
GlobalVariableSet(gvConsecLossName, consecutiveLosses);
Print("EA deinitializing (Reason: ", reason, "). Saved persistent state.");
}
ObjectDelete(chartID, "ManualTPLabel");
ObjectDelete(chartID, "ManualTPEdit");
ObjectDelete(chartID, "ManualSLLabel");
ObjectDelete(chartID, "ManualSLEdit");
ObjectDelete(chartID, "ManualTradeButton");
ObjectDelete(chartID, "AccumLossLabel");
ObjectDelete(chartID, "AccumLossPercentLabel");
}
//+------------------------------------------------------------------+
//| Create Trade UI Elements |
//+------------------------------------------------------------------+
void CreateTradeUI()
{
ObjectDelete(chartID, "ManualTPLabel");
ObjectDelete(chartID, "ManualTPEdit");
ObjectDelete(chartID, "ManualSLLabel");
ObjectDelete(chartID, "ManualSLEdit");
ObjectDelete(chartID, "ManualTradeButton");
if(Trade_Mode == MODE_MANUAL)
{
ObjectCreate(chartID, "ManualTPLabel", OBJ_LABEL, 0, 0, 0);
ObjectSetString(chartID, "ManualTPLabel", OBJPROP_TEXT, "Take Profit:");
ObjectSetInteger(chartID, "ManualTPLabel", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(chartID, "ManualTPLabel", OBJPROP_XDISTANCE, 180);
ObjectSetInteger(chartID, "ManualTPLabel", OBJPROP_YDISTANCE, 50);
ObjectCreate(chartID, "ManualTPEdit", OBJ_EDIT, 0, 0, 0);
ObjectSetInteger(chartID, "ManualTPEdit", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(chartID, "ManualTPEdit", OBJPROP_XDISTANCE, 110);
ObjectSetInteger(chartID, "ManualTPEdit", OBJPROP_YDISTANCE, 50);
ObjectSetInteger(chartID, "ManualTPEdit", OBJPROP_XSIZE, 60);
ObjectCreate(chartID, "ManualSLLabel", OBJ_LABEL, 0, 0, 0);
ObjectSetString(chartID, "ManualSLLabel", OBJPROP_TEXT, "Stop Loss:");
ObjectSetInteger(chartID, "ManualSLLabel", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(chartID, "ManualSLLabel", OBJPROP_XDISTANCE, 180);
ObjectSetInteger(chartID, "ManualSLLabel", OBJPROP_YDISTANCE, 70);
ObjectCreate(chartID, "ManualSLEdit", OBJ_EDIT, 0, 0, 0);
ObjectSetInteger(chartID, "ManualSLEdit", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(chartID, "ManualSLEdit", OBJPROP_XDISTANCE, 110);
ObjectSetInteger(chartID, "ManualSLEdit", OBJPROP_YDISTANCE, 70);
ObjectSetInteger(chartID, "ManualSLEdit", OBJPROP_XSIZE, 60);
ObjectCreate(chartID, "ManualTradeButton", OBJ_BUTTON, 0, 0, 0);
ObjectSetString(chartID, "ManualTradeButton", OBJPROP_TEXT, "Open Market Order");
ObjectSetInteger(chartID, "ManualTradeButton", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(chartID, "ManualTradeButton", OBJPROP_XDISTANCE, 110);
ObjectSetInteger(chartID, "ManualTradeButton", OBJPROP_YDISTANCE, 95);
ObjectSetInteger(chartID, "ManualTradeButton", OBJPROP_XSIZE, 130);
ObjectSetInteger(chartID, "ManualTradeButton", OBJPROP_YSIZE, 25);
}
}
//+------------------------------------------------------------------+
//| Custom Trade Allowed Function |
//+------------------------------------------------------------------+
bool IsDailyTradeAllowed()
{
if (DailyProfitTargetPercent <= 0) return true;
double targetProfit = dailyProfitStartBalance * DailyProfitTargetPercent / 100.0;
if (dailyProfitAccumulated >= targetProfit)
{
if (EnableDebugPrints) Print("Daily profit target reached. Target: ", DoubleToString(targetProfit, 2), " Accumulated: ", DoubleToString(dailyProfitAccumulated, 2));
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| OnTick function (main loop) |
//+------------------------------------------------------------------+
void OnTick()
{
// UI and State Updates
double lossPercentOfBalance = 0;
if(AccountBalance() > 0) lossPercentOfBalance = (accumulatedLoss / AccountBalance()) * 100.0;
string lossText = "Accum. Loss: " + DoubleToString(accumulatedLoss, 2);
string percentText = "Loss % of Bal: " + DoubleToString(lossPercentOfBalance, 2) + "%";
color labelColor = clrWhite;
if(HighLossThresholdPercent > 0 && lossPercentOfBalance >= HighLossThresholdPercent) labelColor = clrOrangeRed;
ObjectSetString(chartID, "AccumLossLabel", OBJPROP_TEXT, lossText);
ObjectSetInteger(chartID, "AccumLossLabel", OBJPROP_COLOR, labelColor);
ObjectSetString(chartID, "AccumLossPercentLabel", OBJPROP_TEXT, percentText);
ObjectSetInteger(chartID, "AccumLossPercentLabel", OBJPROP_COLOR, labelColor);
// Trailing Stop Management
if(UseTrailingStop)
{
ManageTrailingStop();
}
// Bar and Time Management
static datetime lastBarTime = 0;
datetime currentBarTime = Time[0];
bool isNewBar = false;
if (currentBarTime != lastBarTime)
{
isNewBar = true;
lastBarTime = currentBarTime;
if (EnableDebugPrints) Print("========== New Bar Detected: ", TimeToString(currentBarTime), " ==========");
}
// Daily profit tracking update
{
datetime now = TimeCurrent();
datetime midnight = now - (now % 86400);
datetime resetTime = midnight + 3600;
if (now >= resetTime && (lastDailyResetTime < resetTime || (TimeDayOfYear(now) != TimeDayOfYear(lastDailyResetTime))))
{
dailyProfitStartBalance = AccountBalance();
dailyProfitAccumulated = 0;
lastDailyResetTime = now;
if (EnableDebugPrints) Print("Daily Profit Tracking Reset at 01:00. New Balance: ", DoubleToString(dailyProfitStartBalance, 2));
}
dailyProfitAccumulated = 0;
for (int i = OrdersHistoryTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber &&
OrderCloseTime() > 0 && OrderCloseTime() >= lastDailyResetTime)
{
dailyProfitAccumulated += OrderProfit() + OrderCommission() + OrderSwap();
}
}
}
// Manual Trade Processing
if(Trade_Mode == MODE_MANUAL && manualTradeButtonPressed)
{
manualTradeButtonPressed = false;
string tpStr = ObjectGetString(chartID, "ManualTPEdit", OBJPROP_TEXT);
string slStr = ObjectGetString(chartID, "ManualSLEdit", OBJPROP_TEXT);
if(StringLen(tpStr) > 0 && StringLen(slStr) > 0)
{
manualTP = StrToDouble(tpStr);
manualSL = StrToDouble(slStr);
RefreshRates();
if(manualTP > Bid && manualSL < Bid) { if (!ExecuteTrade(OP_BUY, LotSize, Bid, manualSL, manualTP)) Print("Manual BUY trade execution failed."); }
else if(manualTP < Bid && manualSL > Bid) { if (!ExecuteTrade(OP_SELL, LotSize, Bid, manualSL, manualTP)) Print("Manual SELL trade execution failed."); }
else { Print("Invalid SL/TP values for a market order."); }
}
else { Print("Please fill both Stop Loss and Take Profit values for manual trade."); }
}
// Indicator Trade Processing
if(Trade_Mode == MODE_INDICATOR && isNewBar)
{
datetime signalBarTime = Time[1];
if (!IsDailyTradeAllowed() || !IsTimeFilterAllowed() || signalBarTime == 0) return;
double buySignalValue = GetIndicatorValue(BuySignalBuffer, 1);
double sellSignalValue = GetIndicatorValue(SellSignalBuffer, 1);
bool buyCondition = (buySignalValue != EMPTY_VALUE && buySignalValue != 0);
bool sellCondition = (sellSignalValue != EMPTY_VALUE && sellSignalValue != 0);
if (ExitOnOppositeSignal)
{
if (buyCondition) CloseExistingOppositeTrade(OP_SELL);
else if (sellCondition) CloseExistingOppositeTrade(OP_BUY);
}
if ((buyCondition || sellCondition) && (signalBarTime <= lastTradeSignalBarTime)) return;
if (!IsTradeOpen(Symbol(), MagicNumber))
{
for (int i = OrdersHistoryTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderCloseTime() > 0)
{
if (OrderTicket() != lastProcessedOrderTicket)
{
double lastProfit = OrderProfit() + OrderCommission() + OrderSwap();
double currentLoss = accumulatedLoss;
if (lastProfit < 0)
{
currentLoss += MathAbs(lastProfit);
consecutiveLosses++;
if(EnableDebugPrints) Print("Consecutive loss count increased to: ", consecutiveLosses);
}
else
{
currentLoss -= lastProfit;
if (consecutiveLosses > 0)
{
if(EnableDebugPrints) Print("Winning trade. Resetting consecutive loss count from ", consecutiveLosses, " to 0.");
consecutiveLosses = 0;
}
}
UpdatePersistentLoss(currentLoss);
GlobalVariableSet(gvConsecLossName, consecutiveLosses);
if(ConsecutiveLossesToReset > 0 && consecutiveLosses >= ConsecutiveLossesToReset)
{
Print("CONSECUTIVE LOSS RESET: Reached ", consecutiveLosses, " losses in a row. Clearing accumulated loss.");
UpdatePersistentLoss(0);
consecutiveLosses = 0;
GlobalVariableSet(gvConsecLossName, consecutiveLosses);
}
lastProcessedOrderTicket = OrderTicket();
}
break;
}
}
double stopLossPrice = 0, takeProfitPrice = 0, atrValue = 0;
if(SLTP_Mode == 0) atrValue = iATR(Symbol(), Period(), AtrPeriod, 1);
RefreshRates();
if (buyCondition)
{
double openPrice = Bid;
if (SLTP_Mode == 0) { if(atrValue > 0 && StopLossAtrMultiplier > 0) stopLossPrice = NormalizeDouble(openPrice - atrValue * StopLossAtrMultiplier, digits); }
else { double rawSL = GetIndicatorValue(StopLossBuffer, 1); if(rawSL != EMPTY_VALUE && rawSL != 0) stopLossPrice = NormalizeDouble(rawSL, digits); }
if (SLTP_Mode == 0) { if(atrValue > 0 && TakeProfitAtrMultiplier > 0) takeProfitPrice = NormalizeDouble(openPrice + atrValue * TakeProfitAtrMultiplier, digits); }
else { double rawTP = GetIndicatorValue(TakeProfitBuffer, 1); if(rawTP != EMPTY_VALUE && rawTP != 0) takeProfitPrice = NormalizeDouble(rawTP, digits); }
if (MinimumTakeProfitPips > 0 && takeProfitPrice > 0)
{
double minTpLevel = NormalizeDouble(openPrice + MinimumTakeProfitPips * pipValue, digits);
if(EnableDebugPrints) Print("Min TP Check (BUY): Open=", openPrice, ", Initial TP=", takeProfitPrice, ", Min TP Level=", minTpLevel);
if (takeProfitPrice < minTpLevel)
{
if(EnableDebugPrints) Print("Minimum TP logic TRIGGERED for BUY. Overriding initial TP.");
takeProfitPrice = minTpLevel;
}
}
double baseLot = LotSize;
if(UsePercentBalanceLot && takeProfitPrice > 0)
{
double tpPoints = (takeProfitPrice - openPrice) / pointValue;
if(tpPoints > 0) baseLot = (AccountBalance() * (PercentOfBalanceForProfit / 100.0)) / (tpPoints * MarketInfo(Symbol(), MODE_TICKVALUE));
}
double recoveryLots = 0;
if (EnableRecoveryMode && accumulatedLoss > 0 && takeProfitPrice > 0)
{
double tpPoints = (takeProfitPrice - openPrice) / pointValue;
if (tpPoints > 0)
{
double profitPerLot = tpPoints * MarketInfo(Symbol(), MODE_TICKVALUE);
if (profitPerLot > 0)
{
recoveryLots = accumulatedLoss / profitPerLot;
double highLossTriggerAmount = AccountBalance() * (HighLossThresholdPercent / 100.0);
if (HighLossThresholdPercent > 0 && accumulatedLoss > highLossTriggerAmount && HighLossRecoveryDampener > 1.0)
recoveryLots /= HighLossRecoveryDampener;
}
}
}
double currentLotSize = baseLot + recoveryLots;
currentLotSize = NormalizeDouble(currentLotSize, 2);
currentLotSize = MathMax(currentLotSize, minLotSize);
currentLotSize = MathMin(currentLotSize, maxLotSize);
ExecuteTrade(OP_BUY, currentLotSize, openPrice, stopLossPrice, takeProfitPrice);
}
else if (sellCondition)
{
double openPrice = Bid;
if (SLTP_Mode == 0) { if(atrValue > 0 && StopLossAtrMultiplier > 0) stopLossPrice = NormalizeDouble(openPrice + atrValue * StopLossAtrMultiplier, digits); }
else { double rawSL = GetIndicatorValue(StopLossBuffer, 1); if(rawSL != EMPTY_VALUE && rawSL != 0) stopLossPrice = NormalizeDouble(rawSL, digits); }
if (SLTP_Mode == 0) { if(atrValue > 0 && TakeProfitAtrMultiplier > 0) takeProfitPrice = NormalizeDouble(openPrice - atrValue * TakeProfitAtrMultiplier, digits); }
else { double rawTP = GetIndicatorValue(TakeProfitBuffer, 1); if(rawTP != EMPTY_VALUE && rawTP != 0) takeProfitPrice = NormalizeDouble(rawTP, digits); }
if (MinimumTakeProfitPips > 0 && takeProfitPrice > 0)
{
double minTpLevel = NormalizeDouble(openPrice - MinimumTakeProfitPips * pipValue, digits);
if(EnableDebugPrints) Print("Min TP Check (SELL): Open=", openPrice, ", Initial TP=", takeProfitPrice, ", Min TP Level=", minTpLevel);
if (takeProfitPrice > minTpLevel)
{
if(EnableDebugPrints) Print("Minimum TP logic TRIGGERED for SELL. Overriding initial TP.");
takeProfitPrice = minTpLevel;
}
}
double baseLot = LotSize;
if(UsePercentBalanceLot && takeProfitPrice > 0)
{
double tpPoints = (openPrice - takeProfitPrice) / pointValue;
if(tpPoints > 0) baseLot = (AccountBalance() * (PercentOfBalanceForProfit / 100.0)) / (tpPoints * MarketInfo(Symbol(), MODE_TICKVALUE));
}
double recoveryLots = 0;
if (EnableRecoveryMode && accumulatedLoss > 0 && takeProfitPrice > 0)
{
double tpPoints = (openPrice - takeProfitPrice) / pointValue;
if (tpPoints > 0)
{
double profitPerLot = tpPoints * MarketInfo(Symbol(), MODE_TICKVALUE);
if (profitPerLot > 0)
{
recoveryLots = accumulatedLoss / profitPerLot;
double highLossTriggerAmount = AccountBalance() * (HighLossThresholdPercent / 100.0);
if (HighLossThresholdPercent > 0 && accumulatedLoss > highLossTriggerAmount && HighLossRecoveryDampener > 1.0)
recoveryLots /= HighLossRecoveryDampener;
}
}
}
double spread = MarketInfo(Symbol(), MODE_SPREAD) * pointValue;
if (takeProfitPrice > 0) takeProfitPrice += spread;
if (stopLossPrice > 0) stopLossPrice += spread;
double currentLotSize = baseLot + recoveryLots;
currentLotSize = NormalizeDouble(currentLotSize, 2);
currentLotSize = MathMax(currentLotSize, minLotSize);
currentLotSize = MathMin(currentLotSize, maxLotSize);
ExecuteTrade(OP_SELL, currentLotSize, openPrice, stopLossPrice, takeProfitPrice);
}
}
}
}
//+------------------------------------------------------------------+
//| Manage Trailing Stop (REWRITTEN with safe, unified logic) |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
if(TrailingStopPips <= 0) return;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
// --- Get all necessary order info ---
string comment = OrderComment();
int ticket = OrderTicket();
int orderType = OrderType();
double openPrice = OrderOpenPrice();
double currentSL = OrderStopLoss();
// --- Determine if the order is a trailing candidate ---
bool is_active_trail = (StringFind(comment, ";TS=ACTIVE") != -1);
int vtp_pos = StringFind(comment, ";VTP=");
bool is_pre_trail = (vtp_pos != -1);
// If it's not a trailing trade at all, skip it.
if (!is_active_trail && !is_pre_trail) continue;
// --- Check if the order is ready to be trailed ---
bool ready_to_trail = false;
if (is_active_trail)
{
ready_to_trail = true; // Already active, so it's always ready.
}
else // It's a pre-trail order, check for activation.
{
double virtual_tp = StrToDouble(StringSubstr(comment, vtp_pos + 5));
if (virtual_tp > 0)
{
RefreshRates();
double activation_price;
if (orderType == OP_BUY)
{
activation_price = NormalizeDouble(virtual_tp + TrailingStopPips * pipValue, digits);
if(EnableDebugPrints) Print("TS Pre-Trail (BUY): Ticket #", ticket, ", VTP=", virtual_tp, ", Watching Activation Price=", activation_price);
if(Bid >= activation_price) ready_to_trail = true;
}
else // OP_SELL
{
activation_price = NormalizeDouble(virtual_tp - TrailingStopPips * pipValue, digits);
if(EnableDebugPrints) Print("TS Pre-Trail (SELL): Ticket #", ticket, ", VTP=", virtual_tp, ", Watching Activation Price=", activation_price);
if(Bid <= activation_price) ready_to_trail = true;
}
}
}
// --- If not ready, skip to the next order ---
if (!ready_to_trail) continue;
// --- If we get here, the order MUST be trailed. Calculate and apply the new SL. ---
if(EnableDebugPrints && !is_active_trail) Print("TS: ACTIVATION PRICE HIT on Ticket #", ticket, "! Attempting to start trail...");
RefreshRates();
double potentialNewSL;
if (orderType == OP_BUY) potentialNewSL = NormalizeDouble(Bid - TrailingStopPips * pipValue, digits);
else potentialNewSL = NormalizeDouble(Bid + TrailingStopPips * pipValue, digits);
// The Unbreakable Ratchet Rule: New SL must be an improvement over the current one.
if ((orderType == OP_BUY && potentialNewSL > currentSL) || (orderType == OP_SELL && potentialNewSL < currentSL))
{
string new_comment = comment;
// If it was a pre-trail order, update its comment to make it active.
if (is_pre_trail)
{
string base_comment = StringSubstr(comment, 0, vtp_pos);
new_comment = base_comment + ";TS=ACTIVE";
}
if(OrderModify(ticket, openPrice, potentialNewSL, 0, new_comment, clrNONE))
{
Print("TS SUCCESS: Stop loss for #", ticket, " set/trailed to ", potentialNewSL);
if(is_pre_trail) Print("TS INFO: Order #", ticket, " is now in active trail mode.");
} else {
Print("TS ERROR: Failed to modify stop loss for #", ticket, ". Error: ", ErrorDescription(GetLastError()));
}
}
}
}
}
//+------------------------------------------------------------------+
//| Execute Trade (Using Comment for Virtual TP) |
//+------------------------------------------------------------------+
bool ExecuteTrade(int orderType, double lots, double openPrice, double slPrice, double tpPrice)
{
RefreshRates();
openPrice = Bid; // Use Bid price for all orders
double tpForServer = tpPrice;
string orderComment = StringSubstr(eaCommentPrefix + "_" + IndicatorFileName, 0, 15);
if(UseTrailingStop && tpPrice > 0)
{
tpForServer = 0;
orderComment += ";VTP=" + DoubleToString(tpPrice, digits);
if(EnableDebugPrints) Print("Trailing Stop active. Embedding Virtual TP in comment.");
}
orderComment = StringSubstr(orderComment, 0, 31);
double stopLevelPoints = MarketInfo(Symbol(), MODE_STOPLEVEL) * pointValue;
if(orderType == OP_BUY)
{
if(slPrice != 0 && openPrice - slPrice < stopLevelPoints) slPrice = NormalizeDouble(openPrice - stopLevelPoints, digits);
if(tpForServer != 0 && tpForServer - openPrice < stopLevelPoints) tpForServer = NormalizeDouble(openPrice + stopLevelPoints, digits);
}
else // OP_SELL
{
if(slPrice != 0 && slPrice - openPrice < stopLevelPoints) slPrice = NormalizeDouble(openPrice + stopLevelPoints, digits);
if(tpForServer != 0 && openPrice - tpForServer < stopLevelPoints) tpForServer = NormalizeDouble(openPrice - stopLevelPoints, digits);
}
if (EnableDebugPrints) Print("DEBUG ExecuteTrade: Type=", orderType, " Lots=", lots, " Price=", openPrice, " SL=", slPrice, " TP(ToServer)=", tpForServer, " Comment='", orderComment,"'");
int ticket = OrderSend(Symbol(), orderType, lots, openPrice, slippagePoints, slPrice, tpForServer, orderComment, MagicNumber, 0, (orderType == OP_BUY ? clrGreen : clrRed));
if (ticket < 0)
{
Print("OrderSend failed: ", ErrorDescription(GetLastError()), " (Code: ", GetLastError(), ")");
return (false);
}
Print("OrderSend successful. Ticket: ", ticket);
if(TakeScreenshotOnOpen)
{
string timeStr = MyStringReplace(MyStringReplace(TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS), ":", "_"),".","_");
string filename = Symbol() + "_" + EnumToString((ENUM_TIMEFRAMES)Period()) + "_T" + IntegerToString(ticket) + "_" + timeStr + ".gif";
if(!ChartScreenShot(chartID, filename, (int)ChartGetInteger(chartID, CHART_WIDTH_IN_PIXELS), (int)ChartGetInteger(chartID, CHART_HEIGHT_IN_PIXELS), ALIGN_RIGHT))
Print("Failed to save screenshot. Error: ", ErrorDescription(GetLastError()));
}
lastTradeSignalBarTime = Time[1];
return (true);
}
//+------------------------------------------------------------------+
//| ChartEvent handler |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_OBJECT_CLICK && Trade_Mode == MODE_MANUAL && sparam == "ManualTradeButton")
{
manualTradeButtonPressed = true;
if(EnableDebugPrints) Print("Manual trade button clicked");
}
}
//+------------------------------------------------------------------+
//| Get Indicator Value |
//+------------------------------------------------------------------+
double GetIndicatorValue(int bufferIndex, int barShift)
{
if(IndicatorFileName == "" || IndicatorFileName == "My_Indicator" || bufferIndex < 0) return EMPTY_VALUE;
double value = iCustom(Symbol(), Period(), IndicatorFileName, bufferIndex, barShift);
if (value == EMPTY_VALUE || (value == 0 && GetLastError() != ERR_NO_ERROR))
{
if (EnableDebugPrints) Print("DEBUG GetIndicatorValue: iCustom error for '", IndicatorFileName, "', Buffer ", bufferIndex, ". Error: ", ErrorDescription(GetLastError()));
return EMPTY_VALUE;
}
return value;
}
//+------------------------------------------------------------------+
//| Check if a trade is already open |
//+------------------------------------------------------------------+
bool IsTradeOpen(string symbol, int magic)
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == symbol && OrderMagicNumber() == magic)
return (true);
return (false);
}
//+------------------------------------------------------------------+
//| Close Existing Opposite Trade |
//+------------------------------------------------------------------+
bool CloseExistingOppositeTrade(int oppositeOrderType)
{
bool closedAny = false;
RefreshRates();
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == oppositeOrderType)
{
if (OrderClose(OrderTicket(), OrderLots(), (oppositeOrderType == OP_BUY) ? Bid : Ask, slippagePoints, clrYellow))
{
Print("Closed opposite trade Ticket#", OrderTicket(), " due to new signal.");
closedAny = true;
}
else Print("Failed to close opposite trade Ticket#", OrderTicket(), ". Error: ", GetLastError());
}
}
return closedAny;
}
//+------------------------------------------------------------------+
//| Custom String Replace Function |
//+------------------------------------------------------------------+
string MyStringReplace(string text, string replace_what, string replace_with)
{
string temp = text;
int pos = StringFind(temp, replace_what, 0);
while(pos >= 0)
{
temp = StringSubstr(temp, 0, pos) + replace_with + StringSubstr(temp, pos + StringLen(replace_what));
pos = StringFind(temp, replace_what, pos + StringLen(replace_with));
}
return temp;
}