788 lines
39 KiB
Plaintext
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;
|
|
}
|