810 lines
26 KiB
Plaintext
810 lines
26 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| OrdersEA_Smart_Grid.mq5 |
|
|
//| Copyright 2024, Garfield Heron |
|
|
//| https://fetcherpay.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2024, Garfield Heron"
|
|
#property link "https://fetcherpay.com"
|
|
#property version "3.1"
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
|
|
#define VERSION "Version 3.1 Smart Grid MT5"
|
|
#define MAX_TRADES 600
|
|
#define MAX_LOG_TRADES 1200
|
|
|
|
//--- Input Parameters
|
|
input string Email= "garfield@fetcherpay.com";
|
|
input int MagicNum= 333;
|
|
|
|
//--- Smart Grid Settings
|
|
input string GridSettings = "=== Smart Grid Settings ===";
|
|
input bool UseAutoPivots = true;
|
|
input double InpManualHigh= 0; // Manual HIGH level (0 = use AutoPivots)
|
|
input double InpManualLow= 0; // Manual LOW level (0 = use AutoPivots)
|
|
input double Entry= 10;
|
|
input double TP= 15;
|
|
input double Lots=0.01;
|
|
input int MaxLevels = 10;
|
|
|
|
//--- Range Filter Settings
|
|
input string FilterSettings = "=== Range Filters ===";
|
|
input bool UseRSIFilter = true;
|
|
input int RSIPeriod = 14;
|
|
input int RSILower = 35;
|
|
input int RSIUpper = 65;
|
|
input bool UseADXFilter = true;
|
|
input int ADXPeriod = 14;
|
|
input double ADXMax = 25;
|
|
input bool UseATRFilter = true;
|
|
input int ATRPeriod = 14;
|
|
input double ATRMultiplier = 1.5;
|
|
|
|
//--- Risk Management
|
|
input string RiskSettings = "=== Risk Management ===";
|
|
input int StopLoss=0;
|
|
input int TRADE_RANGE= 50;
|
|
input double LongLimit= 0;
|
|
input double ShortLimit= 0;
|
|
input string GetOut= "N";
|
|
input string OpenNewTrades="Y";
|
|
input bool Opposite= false;
|
|
input int TakeProfitLevelPercent= 50;
|
|
input int TakeProfitLevelDollarAmount= 2000;
|
|
input int EquityFactorPercent= 0;
|
|
input int LotsFactorPercent= 0;
|
|
input int BaseEquity= 10000;
|
|
input bool Master= false;
|
|
input bool DiagnosticModeOn= false;
|
|
input double InpMaxDailyDrawdown = 3.0; // Max daily drawdown % (0=disable)
|
|
|
|
//--- Weekend Protection
|
|
input string WeekendSettings = "=== Weekend Protection ===";
|
|
input bool InpCloseBeforeWeekend = true; // Close positions Friday before market close
|
|
input int InpWeekendCloseHour = 17; // Hour to close (17 = 5 PM broker time)
|
|
input bool InpCancelPendingBeforeWeekend = true; // Cancel pending orders too
|
|
|
|
//--- Trade Object
|
|
CTrade trade;
|
|
CPositionInfo positionInfo;
|
|
|
|
//--- Indicator Handles
|
|
int RSIHandle = INVALID_HANDLE;
|
|
int ADXHandle = INVALID_HANDLE;
|
|
int ATRHandle = INVALID_HANDLE;
|
|
|
|
//--- Pivot Point Variables
|
|
double PivotP = 0;
|
|
double PivotR1 = 0;
|
|
double PivotR2 = 0;
|
|
double PivotS1 = 0;
|
|
double PivotS2 = 0;
|
|
double GridHigh = 0; // Actual high level used for trading (manual or auto)
|
|
double GridLow = 0; // Actual low level used for trading (manual or auto)
|
|
|
|
//--- Original OrdersEA Variables
|
|
int initialCycleEquity= 0;
|
|
int longs, shorts;
|
|
ulong longsTicket[MAX_TRADES];
|
|
ulong shortsTicket[MAX_TRADES];
|
|
ulong logTickets[MAX_LOG_TRADES];
|
|
datetime logTicketsTime[MAX_LOG_TRADES];
|
|
int logTicketsCounter;
|
|
int Levels;
|
|
double price[MAX_TRADES];
|
|
int stoplevel;
|
|
bool bFirstTick= false;
|
|
int logId;
|
|
bool bEnableLongs;
|
|
bool bEnableShorts;
|
|
double tickValue;
|
|
int MaximalLoss;
|
|
ulong lastTicketSentOpen= 0, lastTicketSentClose= 0;
|
|
double longAvgPrice= 0, longAvgLots= 0;
|
|
double shortAvgPrice= 0, shortAvgLots= 0;
|
|
double longProfit= 0;
|
|
double shortProfit= 0;
|
|
bool bInit= false;
|
|
int TakeProfitVal=0;
|
|
bool AboveHigh;
|
|
bool BelowLow;
|
|
int tradeLogId;
|
|
double closeOnProfit;
|
|
bool bGetOutOK, bOpenNewTradesOK;
|
|
double initEquity;
|
|
int lotDigits;
|
|
bool bWithdrawMailSent= false;
|
|
bool bGetOutHandled;
|
|
int PingTimeMinutes= 240;
|
|
bool bValidSettings;
|
|
string errMsg;
|
|
datetime PivotCalculationTime = 0;
|
|
bool TradeExecutedToday = false;
|
|
bool R1HitToday = false;
|
|
bool S1HitToday = false;
|
|
|
|
//--- Daily Drawdown Protection Variables
|
|
double dailyStartEquity = 0;
|
|
datetime lastEquityReset = 0;
|
|
|
|
//--- Weekend Protection Variables
|
|
bool weekendCloseExecuted = false;
|
|
datetime lastWeekendCheck = 0;
|
|
|
|
//--- Grid State
|
|
static bool gridPlaced = false;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check Daily Drawdown Protection |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckDailyDrawdown()
|
|
{
|
|
static bool warningPrinted = false;
|
|
|
|
if(InpMaxDailyDrawdown <= 0)
|
|
return true; // Protection disabled
|
|
|
|
datetime today = TimeCurrent() / 86400 * 86400;
|
|
if(today != lastEquityReset)
|
|
{
|
|
// New day - reset equity tracking
|
|
dailyStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
lastEquityReset = today;
|
|
warningPrinted = false; // Reset warning for new day
|
|
Print("Daily equity reset: $", DoubleToString(dailyStartEquity, 2));
|
|
return true;
|
|
}
|
|
|
|
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
if(dailyStartEquity <= 0)
|
|
return true;
|
|
|
|
double drawdownPercent = (dailyStartEquity - currentEquity) / dailyStartEquity * 100;
|
|
|
|
bool overLimit = (drawdownPercent >= InpMaxDailyDrawdown);
|
|
|
|
if(overLimit)
|
|
{
|
|
if(!warningPrinted)
|
|
{
|
|
Print("⚠️ DAILY DRAWDOWN LIMIT REACHED: ", DoubleToString(drawdownPercent, 2),
|
|
"% (Limit: ", InpMaxDailyDrawdown, "%)");
|
|
SendNotification("Grid EA: Daily drawdown limit reached!");
|
|
warningPrinted = true;
|
|
}
|
|
return false; // Block new trades
|
|
}
|
|
else
|
|
{
|
|
warningPrinted = false; // Reset when below limit
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check Weekend Protection |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckWeekendProtection()
|
|
{
|
|
if(!InpCloseBeforeWeekend) return true;
|
|
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(), dt);
|
|
|
|
// Only check on Friday
|
|
if(dt.day_of_week != FRIDAY)
|
|
{
|
|
// Reset flag on other days
|
|
if(weekendCloseExecuted)
|
|
weekendCloseExecuted = false;
|
|
return true;
|
|
}
|
|
|
|
// Already executed this Friday
|
|
if(weekendCloseExecuted) return true;
|
|
|
|
// Check if it's close to weekend close time
|
|
if(dt.hour >= InpWeekendCloseHour)
|
|
{
|
|
Print("⚠️ WEEKEND CLOSE: It's Friday ", dt.hour, ":00 - Closing all positions!");
|
|
SendNotificationEx("WEEKEND CLOSE", "Closing all positions before weekend");
|
|
|
|
// Close all open positions
|
|
CloseAllPositions("Weekend protection - Friday close");
|
|
|
|
// Cancel pending orders if enabled
|
|
if(InpCancelPendingBeforeWeekend)
|
|
CancelAllOrders("Weekend protection - Friday cancel pending");
|
|
|
|
weekendCloseExecuted = true;
|
|
gridPlaced = false;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate Pivot Points |
|
|
//+------------------------------------------------------------------+
|
|
void CalculatePivotPoints()
|
|
{
|
|
MqlRates rates[1];
|
|
int copied = CopyRates(_Symbol, PERIOD_D1, 1, 1, rates);
|
|
|
|
if(copied < 1)
|
|
{
|
|
Print("Failed to get daily rates for pivot calculation");
|
|
return;
|
|
}
|
|
|
|
double prevHigh = rates[0].high;
|
|
double prevLow = rates[0].low;
|
|
double prevClose = rates[0].close;
|
|
|
|
PivotP = (prevHigh + prevLow + prevClose) / 3.0;
|
|
PivotR1 = (2.0 * PivotP) - prevLow;
|
|
PivotS1 = (2.0 * PivotP) - prevHigh;
|
|
PivotR2 = PivotP + (prevHigh - prevLow);
|
|
PivotS2 = PivotP - (prevHigh - prevLow);
|
|
|
|
if(UseAutoPivots)
|
|
{
|
|
double atr = 0;
|
|
if(ATRHandle != INVALID_HANDLE)
|
|
{
|
|
double atrBuf[1];
|
|
if(CopyBuffer(ATRHandle, 0, 0, 1, atrBuf) > 0)
|
|
atr = atrBuf[0];
|
|
}
|
|
|
|
if(UseATRFilter && atr > 0)
|
|
{
|
|
GridHigh = NormalizeDouble(PivotP + (atr * ATRMultiplier), _Digits);
|
|
GridLow = NormalizeDouble(PivotP - (atr * ATRMultiplier), _Digits);
|
|
}
|
|
else
|
|
{
|
|
// Use standard pivot levels
|
|
GridHigh = PivotR1;
|
|
GridLow = PivotS1;
|
|
}
|
|
|
|
Print("AutoPivots Set: HIGH=", GridHigh, " LOW=", GridLow, " ATR=", atr);
|
|
}
|
|
|
|
Print("Pivot Calculated: P=", PivotP, " R1=", PivotR1, " S1=", PivotS1);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if Market is Ranging |
|
|
//+------------------------------------------------------------------+
|
|
bool IsRangingMarket()
|
|
{
|
|
bool isRanging = true;
|
|
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
if(UseRSIFilter && RSIHandle != INVALID_HANDLE)
|
|
{
|
|
double rsiBuf[1];
|
|
if(CopyBuffer(RSIHandle, 0, 0, 1, rsiBuf) > 0)
|
|
{
|
|
double rsi = rsiBuf[0];
|
|
if(rsi < RSILower || rsi > RSIUpper)
|
|
{
|
|
Print("RSI Filter: Market at extremes (RSI=", rsi, ")");
|
|
isRanging = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(UseADXFilter && ADXHandle != INVALID_HANDLE && isRanging)
|
|
{
|
|
double adxBuf[1];
|
|
if(CopyBuffer(ADXHandle, 0, 0, 1, adxBuf) > 0)
|
|
{
|
|
double adx = adxBuf[0];
|
|
if(adx > ADXMax)
|
|
{
|
|
Print("ADX Filter: Market trending (ADX=", adx, ")");
|
|
isRanging = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
double actualHigh = (InpManualHigh > 0) ? InpManualHigh : PivotR1;
|
|
double actualLow = (InpManualLow > 0) ? InpManualLow : PivotS1;
|
|
|
|
if(currentPrice > actualHigh || currentPrice < actualLow)
|
|
{
|
|
Print("Price outside grid range");
|
|
isRanging = false;
|
|
}
|
|
|
|
return isRanging;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate Lot Size |
|
|
//+------------------------------------------------------------------+
|
|
double CalcLots()
|
|
{
|
|
double tmp = (AccountInfoDouble(ACCOUNT_EQUITY) - initEquity);
|
|
double a = EquityFactorPercent;
|
|
double b = LotsFactorPercent;
|
|
double lots;
|
|
|
|
if(0 == EquityFactorPercent || 0 == LotsFactorPercent)
|
|
lots = Lots;
|
|
else
|
|
{
|
|
a = initEquity * a / 100.0;
|
|
b = b / 100.0;
|
|
if(tmp > 0)
|
|
tmp = MathPow(1 + b, (tmp / a));
|
|
else if(tmp < 0)
|
|
tmp = MathPow(1 - b, MathAbs(tmp / a));
|
|
else
|
|
tmp = 1;
|
|
|
|
lots = NormalizeDouble(Lots * tmp, lotDigits);
|
|
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
if(lots < minLot)
|
|
lots = minLot;
|
|
}
|
|
|
|
return lots;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Add Trade to Log |
|
|
//+------------------------------------------------------------------+
|
|
void AddTradeToLog(ulong ticket)
|
|
{
|
|
bool rc = false;
|
|
int i = 0;
|
|
for(i = 0; i < logTicketsCounter; i++)
|
|
{
|
|
if(logTickets[i] == ticket)
|
|
{
|
|
rc = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!rc && i < MAX_LOG_TRADES)
|
|
{
|
|
logTickets[logTicketsCounter] = ticket;
|
|
logTicketsTime[logTicketsCounter] = TimeCurrent();
|
|
logTicketsCounter++;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Send Notification |
|
|
//+------------------------------------------------------------------+
|
|
void SendNotificationEx(string title, string subject)
|
|
{
|
|
if(!MQLInfoInteger(MQL_OPTIMIZATION))
|
|
{
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
string msg = title + ": " + subject;
|
|
msg = msg + " | Price: " + DoubleToString(bid, _Digits);
|
|
msg = msg + " | Longs: " + IntegerToString(longs) + " @ " + DoubleToString(longAvgPrice, _Digits);
|
|
msg = msg + " | Shorts: " + IntegerToString(shorts) + " @ " + DoubleToString(shortAvgPrice, _Digits);
|
|
msg = msg + " | Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2);
|
|
|
|
SendNotification(msg);
|
|
Print(msg);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Log function |
|
|
//+------------------------------------------------------------------+
|
|
void Log(string st)
|
|
{
|
|
if(DiagnosticModeOn)
|
|
{
|
|
Print(TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + ": " + st);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
trade.SetExpertMagicNumber(MagicNum);
|
|
trade.SetDeviationInPoints(10);
|
|
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
|
|
|
initEquity = (double)BaseEquity;
|
|
lotDigits = 2;
|
|
|
|
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
double tickValueCurr = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
tickValue = (tickSize > 0) ? tickValueCurr / tickSize : 0;
|
|
|
|
stoplevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
|
if(stoplevel <= 0) stoplevel = 0;
|
|
|
|
// Initialize indicators
|
|
if(UseRSIFilter)
|
|
RSIHandle = iRSI(_Symbol, PERIOD_CURRENT, RSIPeriod, PRICE_CLOSE);
|
|
if(UseADXFilter)
|
|
ADXHandle = iADX(_Symbol, PERIOD_CURRENT, ADXPeriod);
|
|
if(UseATRFilter)
|
|
ATRHandle = iATR(_Symbol, PERIOD_CURRENT, ATRPeriod);
|
|
|
|
CalculatePivotPoints();
|
|
|
|
Print("Smart Grid EA MT5 Initialized");
|
|
Print("Magic: ", MagicNum);
|
|
Print("AutoPivots: ", UseAutoPivots);
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
if(RSIHandle != INVALID_HANDLE) IndicatorRelease(RSIHandle);
|
|
if(ADXHandle != INVALID_HANDLE) IndicatorRelease(ADXHandle);
|
|
if(ATRHandle != INVALID_HANDLE) IndicatorRelease(ATRHandle);
|
|
|
|
Print("Smart Grid EA MT5 Deinitialized");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Place Buy Stop Order |
|
|
//+------------------------------------------------------------------+
|
|
bool PlaceBuyStop(double priceLevel, int level)
|
|
{
|
|
if(level >= MaxLevels) return false;
|
|
|
|
double lots = CalcLots();
|
|
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
|
|
|
|
// Ensure price is above current Ask + stop level
|
|
double minDistance = currentAsk + stopLevel + (Entry * point);
|
|
if(priceLevel < minDistance)
|
|
{
|
|
Print("Buy Stop too close to price. Adjusting from ", priceLevel, " to ", minDistance);
|
|
priceLevel = minDistance;
|
|
}
|
|
|
|
double sl = (StopLoss > 0) ? NormalizeDouble(priceLevel - (StopLoss * point), _Digits) : 0;
|
|
double tp = NormalizeDouble(priceLevel + (TP * point), _Digits);
|
|
|
|
// Ensure TP is valid distance from price
|
|
if(tp <= priceLevel + stopLevel)
|
|
tp = NormalizeDouble(priceLevel + stopLevel + (TP * point), _Digits);
|
|
|
|
MqlTradeRequest request = {};
|
|
MqlTradeResult result = {};
|
|
|
|
request.action = TRADE_ACTION_PENDING;
|
|
request.symbol = _Symbol;
|
|
request.volume = lots;
|
|
request.price = NormalizeDouble(priceLevel, _Digits);
|
|
request.sl = sl;
|
|
request.tp = tp;
|
|
request.deviation = 10;
|
|
request.magic = MagicNum;
|
|
request.comment = "Smart Grid Buy " + IntegerToString(level);
|
|
request.type = ORDER_TYPE_BUY_STOP;
|
|
request.type_filling = ORDER_FILLING_IOC;
|
|
|
|
if(!trade.OrderSend(request, result))
|
|
{
|
|
Print("Error placing buy stop: ", trade.ResultRetcodeDescription(), " (", result.retcode, ")");
|
|
return false;
|
|
}
|
|
|
|
if(result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED)
|
|
{
|
|
Print("Buy Stop Level ", level, " placed at ", request.price, " TP: ", tp, " Ticket: ", result.order);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Place Sell Stop Order |
|
|
//+------------------------------------------------------------------+
|
|
bool PlaceSellStop(double priceLevel, int level)
|
|
{
|
|
if(level >= MaxLevels) return false;
|
|
|
|
double lots = CalcLots();
|
|
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
|
|
|
|
// Ensure price is below current Bid - stop level
|
|
double minDistance = currentBid - stopLevel - (Entry * point);
|
|
if(priceLevel > minDistance)
|
|
{
|
|
Print("Sell Stop too close to price. Adjusting from ", priceLevel, " to ", minDistance);
|
|
priceLevel = minDistance;
|
|
}
|
|
|
|
double sl = (StopLoss > 0) ? NormalizeDouble(priceLevel + (StopLoss * point), _Digits) : 0;
|
|
double tp = NormalizeDouble(priceLevel - (TP * point), _Digits);
|
|
|
|
// Ensure TP is valid distance from price
|
|
if(tp >= priceLevel - stopLevel)
|
|
tp = NormalizeDouble(priceLevel - stopLevel - (TP * point), _Digits);
|
|
|
|
MqlTradeRequest request = {};
|
|
MqlTradeResult result = {};
|
|
|
|
request.action = TRADE_ACTION_PENDING;
|
|
request.symbol = _Symbol;
|
|
request.volume = lots;
|
|
request.price = NormalizeDouble(priceLevel, _Digits);
|
|
request.sl = sl;
|
|
request.tp = tp;
|
|
request.deviation = 10;
|
|
request.magic = MagicNum;
|
|
request.comment = "Smart Grid Sell " + IntegerToString(level);
|
|
request.type = ORDER_TYPE_SELL_STOP;
|
|
request.type_filling = ORDER_FILLING_IOC;
|
|
|
|
if(!trade.OrderSend(request, result))
|
|
{
|
|
Print("Error placing sell stop: ", trade.ResultRetcodeDescription(), " (", result.retcode, ")");
|
|
return false;
|
|
}
|
|
|
|
if(result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED)
|
|
{
|
|
Print("Sell Stop Level ", level, " placed at ", request.price, " TP: ", tp, " Ticket: ", result.order);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Count pending orders |
|
|
//+------------------------------------------------------------------+
|
|
int CountPendingOrders(int type)
|
|
{
|
|
int count = 0;
|
|
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = OrderGetTicket(i);
|
|
if(ticket <= 0) continue;
|
|
if(OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
|
|
if(OrderGetInteger(ORDER_MAGIC) != MagicNum) continue;
|
|
if(OrderGetInteger(ORDER_TYPE) == type) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Cancel all pending orders |
|
|
//+------------------------------------------------------------------+
|
|
void CancelAllOrders(string reason)
|
|
{
|
|
int cancelled = 0;
|
|
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = OrderGetTicket(i);
|
|
if(ticket <= 0) continue;
|
|
if(OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
|
|
if(OrderGetInteger(ORDER_MAGIC) != MagicNum) continue;
|
|
|
|
if(trade.OrderDelete(ticket))
|
|
{
|
|
cancelled++;
|
|
Print("Cancelled order #", ticket, " - Reason: ", reason);
|
|
}
|
|
}
|
|
if(cancelled > 0)
|
|
Print("Cancelled ", cancelled, " orders. Reason: ", reason);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close all positions |
|
|
//+------------------------------------------------------------------+
|
|
void CloseAllPositions(string reason)
|
|
{
|
|
int closed = 0;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket <= 0) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) != MagicNum) continue;
|
|
|
|
if(trade.PositionClose(ticket))
|
|
{
|
|
closed++;
|
|
Print("Closed position #", ticket, " - Reason: ", reason);
|
|
}
|
|
}
|
|
if(closed > 0)
|
|
Print("Closed ", closed, " positions. Reason: ", reason);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if price broke out of range |
|
|
//+------------------------------------------------------------------+
|
|
bool IsBreakout()
|
|
{
|
|
GridHigh = (InpManualHigh > 0) ? InpManualHigh : PivotR1;
|
|
GridLow = (InpManualLow > 0) ? InpManualLow : PivotS1;
|
|
double s2 = PivotS2;
|
|
double r2 = PivotR2;
|
|
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
// If price breaks S2 or R2, it's a breakout
|
|
if(currentPrice < s2 || currentPrice > r2)
|
|
{
|
|
Print("BREAKOUT detected! Price=", currentPrice, " S2=", s2, " R2=", r2);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
static datetime lastBarTime = 0;
|
|
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(), dt);
|
|
|
|
// Check daily drawdown limit
|
|
if(!CheckDailyDrawdown())
|
|
{
|
|
// Drawdown limit reached - don't place new grids
|
|
if(gridPlaced)
|
|
{
|
|
Print("Daily drawdown limit reached - not placing new grids");
|
|
gridPlaced = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Check weekend protection (close Friday before weekend)
|
|
if(!CheckWeekendProtection())
|
|
return;
|
|
|
|
// Recalculate pivots at new day (hour 0, first 5 minutes)
|
|
// Only cancel PENDING orders, let positions run to SL/TP
|
|
if(dt.hour == 0 && dt.min < 5 && gridPlaced)
|
|
{
|
|
Print("NEW DAY - Cancelling pending orders and recalculating pivots");
|
|
CancelAllOrders("End of day - new pivot calculation");
|
|
// NOTE: Positions are NOT closed - they continue to SL/TP
|
|
CalculatePivotPoints();
|
|
gridPlaced = false; // Reset grid for new day
|
|
}
|
|
|
|
if(currentBarTime == lastBarTime) return;
|
|
lastBarTime = currentBarTime;
|
|
|
|
// Check if we should trade
|
|
if(!IsRangingMarket())
|
|
{
|
|
Print("Market not suitable for grid trading - Cancelling all orders");
|
|
CancelAllOrders("ADX or RSI filter - no longer ranging");
|
|
gridPlaced = false;
|
|
return;
|
|
}
|
|
|
|
// Check for breakout (price beyond S2/R2)
|
|
if(IsBreakout())
|
|
{
|
|
Print("BREAKOUT! Cancelling grid orders");
|
|
CancelAllOrders("Price broke S2/R2 - breakout");
|
|
CloseAllPositions("Breakout - close positions");
|
|
gridPlaced = false;
|
|
return;
|
|
}
|
|
|
|
// Only place grid once, then monitor
|
|
if(gridPlaced)
|
|
{
|
|
// Check if grid needs replenishing (orders filled)
|
|
int buyStops = CountPendingOrders(ORDER_TYPE_BUY_STOP);
|
|
int sellStops = CountPendingOrders(ORDER_TYPE_SELL_STOP);
|
|
int buyPositions = 0, sellPositions = 0;
|
|
|
|
// Count open positions
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket <= 0) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) != MagicNum) continue;
|
|
|
|
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) buyPositions++;
|
|
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) sellPositions++;
|
|
}
|
|
|
|
// If buy positions opened, cancel remaining buy stops (avoid overexposure)
|
|
if(buyPositions > 0 && buyStops > 0)
|
|
{
|
|
Print("Buy positions filled (", buyPositions, "). Cancelling remaining buy stops.");
|
|
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = OrderGetTicket(i);
|
|
if(ticket <= 0) continue;
|
|
if(OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
|
|
if(OrderGetInteger(ORDER_MAGIC) != MagicNum) continue;
|
|
if(OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_BUY_STOP)
|
|
trade.OrderDelete(ticket);
|
|
}
|
|
}
|
|
|
|
// If sell positions opened, cancel remaining sell stops
|
|
if(sellPositions > 0 && sellStops > 0)
|
|
{
|
|
Print("Sell positions filled (", sellPositions, "). Cancelling remaining sell stops.");
|
|
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = OrderGetTicket(i);
|
|
if(ticket <= 0) continue;
|
|
if(OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
|
|
if(OrderGetInteger(ORDER_MAGIC) != MagicNum) continue;
|
|
if(OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_SELL_STOP)
|
|
trade.OrderDelete(ticket);
|
|
}
|
|
}
|
|
|
|
// If all orders filled, reset for next grid
|
|
if(buyStops == 0 && sellStops == 0 && buyPositions == 0 && sellPositions == 0)
|
|
{
|
|
Print("All grid orders filled or cancelled. Resetting grid.");
|
|
gridPlaced = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Get grid boundaries
|
|
double actualHigh = (InpManualHigh > 0) ? InpManualHigh : PivotR1;
|
|
double actualLow = (InpManualLow > 0) ? InpManualLow : PivotS1;
|
|
GridHigh = actualHigh;
|
|
GridLow = actualLow;
|
|
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
double entryPips = Entry * point;
|
|
|
|
// Log status
|
|
Print("Smart Grid: Placing grid. Price=", currentPrice, " Range: ", actualLow, " - ", actualHigh);
|
|
|
|
// Place grid orders
|
|
int buyCount = 0, sellCount = 0;
|
|
for(int i = 0; i < MaxLevels; i++)
|
|
{
|
|
double buyLevel = actualLow + (i * entryPips);
|
|
double sellLevel = actualHigh - (i * entryPips);
|
|
|
|
// Only place if valid distance from current price
|
|
if(buyLevel < currentPrice - (Entry * 2 * point))
|
|
{
|
|
if(PlaceBuyStop(buyLevel, i)) buyCount++;
|
|
}
|
|
if(sellLevel > currentPrice + (Entry * 2 * point))
|
|
{
|
|
if(PlaceSellStop(sellLevel, i)) sellCount++;
|
|
}
|
|
}
|
|
|
|
Print("Grid placed: ", buyCount, " buy stops, ", sellCount, " sell stops");
|
|
if(buyCount > 0 || sellCount > 0) gridPlaced = true;
|
|
}
|