//+------------------------------------------------------------------+ //| 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 #include #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; //+------------------------------------------------------------------+ //| 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; static bool gridPlaced = false; 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; }