//+------------------------------------------------------------------+
//| Gold grid.mq5 |
//| Linear lot grid · bucket close (My Bot UI) |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026"
#property version "1.00"
#property description "Gold grid — linear lots · bucket TP % · BG/SG comments"
#include <Trade/Trade.mqh>
CTrade trade;
//+------------------------------------------------------------------+
//| Naming (edit here only — not in inputs) |
//+------------------------------------------------------------------+
#define BOT_NAME "Gold grid"
#define BOT_DESCRIPTION "Contact Vikas Kumar => +91 9885200021"
#define BOT_VERSION "1.00"
#define BUY_GRID_COMMENT_PREFIX "BG" // leg comment = prefix + step (BG1, BG2…)
#define SELL_GRID_COMMENT_PREFIX "SG" // leg comment = prefix + step (SG1, SG2…)
#define DASH_PFX "GG_DASH_"
#define BTN_BOTH "GG_BTN_BOTH"
#define DASH_W 340
#define DASH_H 318
enum ENUM_MAX_GRID_STEPS
{
GRID_STEPS_2=2, GRID_STEPS_3=3, GRID_STEPS_4=4, GRID_STEPS_5=5,
GRID_STEPS_6=6, GRID_STEPS_7=7, GRID_STEPS_8=8, GRID_STEPS_9=9,
GRID_STEPS_10=10, GRID_STEPS_11=11, GRID_STEPS_12=12, GRID_STEPS_13=13,
GRID_STEPS_14=14, GRID_STEPS_15=15, GRID_STEPS_16=16, GRID_STEPS_17=17,
GRID_STEPS_18=18, GRID_STEPS_19=19, GRID_STEPS_20=20, GRID_STEPS_25=25,
GRID_STEPS_30=30, GRID_STEPS_40=40, GRID_STEPS_50=50, GRID_STEPS_75=75,
GRID_STEPS_100=100
};
input group "---- EA ----"
input ulong InpMagicNumber = 123460;
input group "---- Linear grid lots ----"
input double InpInitialLot = 0.01; // Step 1 lot (BG1 / SG1)
input double InpLinearStep = 0.01; // Add per step (+0.01 → 0.02,0.03…)
input ENUM_MAX_GRID_STEPS InpMaxSteps = GRID_STEPS_10; // Max grid steps (2–100)
input double InpGridStepPoints = 300.00; // Price distance between steps (points)
input group "---- Bucket close (% of basket margin, not $) ----"
input double InpBucketProfitPct = 30.00; // Close grid when profit >= this % of basket margin
input double InpBucketLossPct = 0.00; // Close grid at loss % of margin (0=off)
input group "---- Close positive steps ----"
input int InpClosePositiveSteps = 0; // 0=off — when this many legs are in profit, close those winners
input group "---- First leg (EMA9) ----"
input int InpEMAPeriod = 9;
input ENUM_TIMEFRAMES InpEntryTF = PERIOD_CURRENT;
input double InpEMAMinSlopePts = 0.50;
input group "---- Both-side grid ----"
input bool InpBothSideDefault = false; // Start with both-side ON
input group "---- Dashboard ----"
input bool InpShowDashboard = true;
input int InpDashX = 16;
input int InpDashY = 24;
bool g_bothSideGrid = false;
int g_hEMA = INVALID_HANDLE;
string g_status = "Init…";
color g_statusClr = clrSilver;
struct SBasketState
{
int legs;
double lastPrice;
datetime lastOpenTime;
};
SBasketState g_buySt;
SBasketState g_sellSt;
int MaxGridSteps()
{
return (int)InpMaxSteps;
}
bool IsTester()
{
return (bool)MQLInfoInteger(MQL_TESTER);
}
bool DashOn()
{
return InpShowDashboard && !IsTester();
}
double Pt()
{
return SymbolInfoDouble(_Symbol, SYMBOL_POINT);
}
ENUM_TIMEFRAMES Tf()
{
if(InpEntryTF == PERIOD_CURRENT) return (ENUM_TIMEFRAMES)_Period;
return InpEntryTF;
}
double LinearLot(const int step1Based)
{
const int s = MathMax(1, MathMin(step1Based, MaxGridSteps()));
double lot = InpInitialLot + (double)(s - 1) * InpLinearStep;
const double mn = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
const double mx = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
const double st = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(st > 0.0) lot = MathFloor(lot / st + 0.00001) * st;
lot = MathMax(mn, MathMin(mx, lot));
return NormalizeDouble(lot, 2);
}
string LegComment(const int dir, const int step)
{
if(dir == 1)
return BUY_GRID_COMMENT_PREFIX + IntegerToString(step);
return SELL_GRID_COMMENT_PREFIX + IntegerToString(step);
}
bool CommentMatchesDir(const string cmt, const int dir)
{
if(dir == 1)
return (StringFind(cmt, BUY_GRID_COMMENT_PREFIX) == 0);
return (StringFind(cmt, SELL_GRID_COMMENT_PREFIX) == 0);
}
int ParseStepFromComment(const string cmt)
{
int p = StringFind(cmt, "G");
if(p < 0) return 0;
string num = StringSubstr(cmt, p + 1);
return (int)StringToInteger(num);
}
bool SelectPosIndex(const int i)
{
const ulong t = PositionGetTicket(i);
return (t > 0 && PositionSelectByTicket(t));
}
int CountGridLegs(const int dir)
{
int c = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!SelectPosIndex(i)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
if(!CommentMatchesDir(PositionGetString(POSITION_COMMENT), dir)) continue;
c++;
}
return c;
}
double BasketProfit(const int dir)
{
double p = 0.0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!SelectPosIndex(i)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
if(!CommentMatchesDir(PositionGetString(POSITION_COMMENT), dir)) continue;
p += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
}
return p;
}
double LegFloatingPnL(const ulong ticket)
{
if(!PositionSelectByTicket(ticket)) return 0.0;
return PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
}
int CountPositiveLegs(const int dir)
{
int c = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!SelectPosIndex(i)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
if(!CommentMatchesDir(PositionGetString(POSITION_COMMENT), dir)) continue;
if(LegFloatingPnL((ulong)PositionGetInteger(POSITION_TICKET)) > 0.01)
c++;
}
return c;
}
bool ClosePositiveLegs(const int dir)
{
if(InpClosePositiveSteps <= 0) return false;
ulong tickets[];
ArrayResize(tickets, 0);
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!SelectPosIndex(i)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
if(!CommentMatchesDir(PositionGetString(POSITION_COMMENT), dir)) continue;
const ulong ticket = (ulong)PositionGetInteger(POSITION_TICKET);
if(LegFloatingPnL(ticket) <= 0.01) continue;
const int n = ArraySize(tickets);
ArrayResize(tickets, n + 1);
tickets[n] = ticket;
}
const int posCnt = ArraySize(tickets);
if(posCnt < InpClosePositiveSteps) return false;
int closed = 0;
double sum = 0.0;
for(int j = 0; j < posCnt; j++)
{
sum += LegFloatingPnL(tickets[j]);
if(trade.PositionClose(tickets[j]))
closed++;
}
if(closed > 0)
{
const string side = (dir == 1) ? BUY_GRID_COMMENT_PREFIX : SELL_GRID_COMMENT_PREFIX;
g_status = StringFormat("Closed %d positive %s legs (+%.2f)", closed, side, sum);
g_statusClr = C'80,220,120';
Print(BOT_NAME, ": ", g_status);
SyncBasketState();
return true;
}
return false;
}
void CheckPositiveStepClose()
{
if(InpClosePositiveSteps <= 0) return;
ClosePositiveLegs(1);
ClosePositiveLegs(-1);
}
double BasketMarginSum(const int dir)
{
double m = 0.0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!SelectPosIndex(i)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
if(!CommentMatchesDir(PositionGetString(POSITION_COMMENT), dir)) continue;
const double vol = PositionGetDouble(POSITION_VOLUME);
const ENUM_POSITION_TYPE t = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
const double px = PositionGetDouble(POSITION_PRICE_OPEN);
const ENUM_ORDER_TYPE ot = (t == POSITION_TYPE_BUY) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
double legMargin = 0.0;
if(OrderCalcMargin(ot, _Symbol, vol, px, legMargin))
m += legMargin;
}
return m;
}
double BucketTargetProfit(const int dir)
{
const double margin = BasketMarginSum(dir);
if(margin <= 0.0) return 0.0;
return margin * InpBucketProfitPct / 100.0;
}
double BucketTargetLoss(const int dir)
{
if(InpBucketLossPct <= 0.0) return 0.0;
const double margin = BasketMarginSum(dir);
if(margin <= 0.0) return 0.0;
return margin * InpBucketLossPct / 100.0;
}
double LastLegPrice(const int dir)
{
double px = 0.0;
datetime latest = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!SelectPosIndex(i)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
if(!CommentMatchesDir(PositionGetString(POSITION_COMMENT), dir)) continue;
const datetime ot = (datetime)PositionGetInteger(POSITION_TIME);
if(ot >= latest)
{
latest = ot;
px = PositionGetDouble(POSITION_PRICE_OPEN);
}
}
return px;
}
void SyncBasketState()
{
g_buySt.legs = CountGridLegs(1);
g_sellSt.legs = CountGridLegs(-1);
if(g_buySt.legs > 0) g_buySt.lastPrice = LastLegPrice(1);
if(g_sellSt.legs > 0) g_sellSt.lastPrice = LastLegPrice(-1);
}
void SetFilling()
{
const int f = (int)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if((f & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK) trade.SetTypeFilling(ORDER_FILLING_FOK);
else if((f & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC) trade.SetTypeFilling(ORDER_FILLING_IOC);
else trade.SetTypeFilling(ORDER_FILLING_RETURN);
}
bool CloseBasket(const int dir, const string reason)
{
bool any = false;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
const ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
if(!CommentMatchesDir(PositionGetString(POSITION_COMMENT), dir)) continue;
if(trade.PositionClose(ticket)) any = true;
}
if(any) Print(BOT_NAME, ": ", reason, " — ", dir == 1 ? "BUY grid" : "SELL grid");
SyncBasketState();
return any;
}
void CheckBucketClose()
{
const double pb = BasketProfit(1);
const double ps = BasketProfit(-1);
if(g_buySt.legs > 0)
{
const double tp = BucketTargetProfit(1);
const double sl = BucketTargetLoss(1);
if(tp > 0.0 && pb >= tp)
CloseBasket(1, StringFormat("Bucket TP %.1f%% (P/L %.2f / target %.2f)",
InpBucketProfitPct, pb, tp));
else if(sl > 0.0 && pb <= -sl)
CloseBasket(1, StringFormat("Bucket loss %.1f%%", InpBucketLossPct));
}
if(g_sellSt.legs > 0)
{
const double tp = BucketTargetProfit(-1);
const double sl = BucketTargetLoss(-1);
if(tp > 0.0 && ps >= tp)
CloseBasket(-1, StringFormat("Bucket TP %.1f%% (P/L %.2f / target %.2f)",
InpBucketProfitPct, ps, tp));
else if(sl > 0.0 && ps <= -sl)
CloseBasket(-1, StringFormat("Bucket loss %.1f%%", InpBucketLossPct));
}
}
int EMASignalDir(string &why)
{
why = "";
if(g_hEMA == INVALID_HANDLE) return 0;
double ema[];
ArraySetAsSeries(ema, true);
if(CopyBuffer(g_hEMA, 0, 1, 3, ema) < 3) return 0;
const double slope = (ema[0] - ema[1]) / Pt();
if(slope >= InpEMAMinSlopePts) { why = "EMA9 up"; return 1; }
if(slope <= -InpEMAMinSlopePts) { why = "EMA9 down"; return -1; }
why = "EMA9 flat";
return 0;
}
bool OpenLeg(const int dir, const int step)
{
SetFilling();
trade.SetExpertMagicNumber(InpMagicNumber);
const double lot = LinearLot(step);
const string cmt = LegComment(dir, step);
bool ok = false;
if(dir == 1)
ok = trade.Buy(lot, _Symbol, 0, 0, 0, cmt);
else
ok = trade.Sell(lot, _Symbol, 0, 0, 0, cmt);
if(!ok)
g_status = StringFormat("Open %s fail %d", cmt, trade.ResultRetcode());
else
g_status = StringFormat("Opened %s lot %.2f", cmt, lot);
SyncBasketState();
return ok;
}
bool CanStartDirection(const int dir)
{
if(g_bothSideGrid) return true;
if(CountGridLegs(1) == 0 && CountGridLegs(-1) == 0) return true;
if(dir == 1 && CountGridLegs(-1) > 0) return false;
if(dir == -1 && CountGridLegs(1) > 0) return false;
return true;
}
void TryStartGrids()
{
string why = "";
if(g_bothSideGrid)
{
if(g_buySt.legs == 0) OpenLeg(1, 1);
if(g_sellSt.legs == 0) OpenLeg(-1, 1);
if(g_buySt.legs > 0 || g_sellSt.legs > 0)
g_status = StringFormat("Both-side · %s* / %s*",
BUY_GRID_COMMENT_PREFIX, SELL_GRID_COMMENT_PREFIX);
return;
}
int sig = EMASignalDir(why);
if(sig == 1 && g_buySt.legs == 0 && CanStartDirection(1))
OpenLeg(1, 1);
else if(sig == -1 && g_sellSt.legs == 0 && CanStartDirection(-1))
OpenLeg(-1, 1);
else if(g_buySt.legs == 0 && g_sellSt.legs == 0)
{
g_status = why;
g_statusClr = clrSilver;
}
}
void TryGridAdds()
{
const double pt = Pt();
const double dist = InpGridStepPoints * pt;
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(g_buySt.legs > 0 && g_buySt.legs < MaxGridSteps())
{
if(bid <= g_buySt.lastPrice - dist)
{
const int next = g_buySt.legs + 1;
if(next <= MaxGridSteps()) OpenLeg(1, next);
}
}
if(g_sellSt.legs > 0 && g_sellSt.legs < MaxGridSteps())
{
if(ask >= g_sellSt.lastPrice + dist)
{
const int next = g_sellSt.legs + 1;
if(next <= MaxGridSteps()) OpenLeg(-1, next);
}
}
}
color PnlColor(const double v)
{
if(v > 0.0) return C'80,220,120';
if(v < 0.0) return C'255,90,90';
return C'180,190,200';
}
string FmtMoney(const double v)
{
return (v >= 0.0 ? "+" : "") + DoubleToString(v, 2);
}
void DashRect(const string id, const int x, const int y, const int w, const int h,
const color bg, const color border)
{
const string nm = DASH_PFX + id;
if(ObjectFind(0, nm) < 0) ObjectCreate(0, nm, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, nm, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, nm, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, nm, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, nm, OBJPROP_XSIZE, w);
ObjectSetInteger(0, nm, OBJPROP_YSIZE, h);
ObjectSetInteger(0, nm, OBJPROP_BGCOLOR, bg);
ObjectSetInteger(0, nm, OBJPROP_COLOR, border);
ObjectSetInteger(0, nm, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, nm, OBJPROP_BACK, false);
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, true);
}
void DashBtn(const string id, const string txt, const int x, const int y, const int w, const int h,
const color bg, const color fg)
{
if(ObjectFind(0, id) < 0) ObjectCreate(0, id, OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, id, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, id, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, id, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, id, OBJPROP_XSIZE, w);
ObjectSetInteger(0, id, OBJPROP_YSIZE, h);
ObjectSetString(0, id, OBJPROP_TEXT, txt);
ObjectSetInteger(0, id, OBJPROP_BGCOLOR, bg);
ObjectSetInteger(0, id, OBJPROP_COLOR, fg);
ObjectSetInteger(0, id, OBJPROP_FONTSIZE, 9);
ObjectSetString(0, id, OBJPROP_FONT, "Segoe UI Semibold");
ObjectSetInteger(0, id, OBJPROP_SELECTABLE, false);
}
void DashLbl(const string id, const int x, const int y, const string t, const color c,
const int fs, const bool bold = false)
{
const string nm = DASH_PFX + id;
if(ObjectFind(0, nm) < 0) ObjectCreate(0, nm, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, nm, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, nm, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, nm, OBJPROP_YDISTANCE, y);
ObjectSetString(0, nm, OBJPROP_TEXT, t);
ObjectSetInteger(0, nm, OBJPROP_COLOR, c);
ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, fs);
ObjectSetString(0, nm, OBJPROP_FONT, bold ? "Segoe UI Semibold" : "Segoe UI");
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, true);
}
void CreateDashboard()
{
if(!DashOn()) return;
const int x = InpDashX, y = InpDashY;
DashRect("BG", x, y, DASH_W, DASH_H, C'16,20,28', C'212,175,55');
DashRect("HDR", x + 2, y + 2, DASH_W - 4, 48, C'120,90,20', C'255,215,100');
DashRect("BUY", x + 10, y + 58, 152, 88, C'28,34,46', C'80,220,120');
DashRect("SEL", x + 178, y + 58, 152, 88, C'28,34,46', C'255,90,90');
DashRect("CFG", x + 10, y + 154, DASH_W - 20, 72, C'28,34,46', C'55,65,82');
DashRect("SIG", x + 10, y + 234, DASH_W - 20, 40, C'28,34,46', C'55,65,82');
}
void UpdateDashboard()
{
if(!DashOn()) return;
if(ObjectFind(0, DASH_PFX + "BG") < 0) CreateDashboard();
const int x = InpDashX, y = InpDashY;
const double pb = BasketProfit(1);
const double ps = BasketProfit(-1);
const double tb = BucketTargetProfit(1);
const double ts = BucketTargetProfit(-1);
const string cur = AccountInfoString(ACCOUNT_CURRENCY);
DashLbl("title", x + 14, y + 10, BOT_NAME + " v" + BOT_VERSION, clrWhite, 12, true);
DashLbl("desc", x + 14, y + 29, BOT_DESCRIPTION, C'230,210,160', 12, false);
DashLbl("bl1", x + 18, y + 66, "BUY · " + BUY_GRID_COMMENT_PREFIX + "1…" + IntegerToString(MaxGridSteps()),
C'120,200,140', 8, true);
DashLbl("bl2", x + 18, y + 84, StringFormat("Legs %d / %d (+ %d win)",
g_buySt.legs, MaxGridSteps(), CountPositiveLegs(1)), clrWhite, 9, false);
DashLbl("bl3", x + 18, y + 102, FmtMoney(pb) + " " + cur, PnlColor(pb), 11, true);
if(tb > 0.0)
DashLbl("bl4", x + 18, y + 122, StringFormat("Bucket %.0f%% · target %.2f", InpBucketProfitPct, tb),
C'160,170,185', 8, false);
else
DashLbl("bl4", x + 18, y + 122, "Bucket idle", C'120,120,130', 8, false);
DashLbl("sl1", x + 186, y + 66, "SELL · " + SELL_GRID_COMMENT_PREFIX + "1…" + IntegerToString(MaxGridSteps()),
C'255,140,120', 8, true);
DashLbl("sl2", x + 186, y + 84, StringFormat("Legs %d / %d (+ %d win)",
g_sellSt.legs, MaxGridSteps(), CountPositiveLegs(-1)), clrWhite, 9, false);
DashLbl("sl3", x + 186, y + 102, FmtMoney(ps) + " " + cur, PnlColor(ps), 11, true);
if(ts > 0.0)
DashLbl("sl4", x + 186, y + 122, StringFormat("Bucket %.0f%% · target %.2f", InpBucketProfitPct, ts),
C'160,170,185', 8, false);
else
DashLbl("sl4", x + 186, y + 122, "Bucket idle", C'120,120,130', 8, false);
DashLbl("cf1", x + 18, y + 162, StringFormat("Lot linear: %.2f + %.2f / step | Grid: %.0f pts",
InpInitialLot, InpLinearStep, InpGridStepPoints), C'200,210,220', 8, false);
DashLbl("cf2", x + 18, y + 180, StringFormat("Book profit: %.1f%% margin | Close + steps: %s",
InpBucketProfitPct,
InpClosePositiveSteps > 0 ? IntegerToString(InpClosePositiveSteps) : "off"), C'180,190,200', 8, false);
DashLbl("cf3", x + 18, y + 198, StringFormat("Magic %I64u | Mode: %s",
InpMagicNumber, g_bothSideGrid ? "Both-side grids" : "Single-side grid"),
C'160,170,185', 8, false);
g_statusClr = (StringFind(g_status, "fail") >= 0) ? clrOrange : clrSilver;
DashLbl("st", x + 18, y + 244, g_status, g_statusClr, 8, true);
const color cOn = C'0,140,90', cOff = C'120,50,50';
DashBtn(BTN_BOTH, g_bothSideGrid ? "BOTH GRIDS: ON" : "BOTH GRIDS: OFF",
x + 10, y + 278, DASH_W - 20, 30, g_bothSideGrid ? cOn : cOff, clrWhite);
ChartRedraw(0);
}
void DashClear()
{
for(int i = ObjectsTotal(0, 0, -1) - 1; i >= 0; i--)
{
const string n = ObjectName(0, i, 0, -1);
if(StringFind(n, DASH_PFX) == 0 || n == BTN_BOTH) ObjectDelete(0, n);
}
}
int OnInit()
{
trade.SetExpertMagicNumber(InpMagicNumber);
SetFilling();
g_bothSideGrid = InpBothSideDefault;
g_hEMA = iMA(_Symbol, Tf(), InpEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(g_hEMA == INVALID_HANDLE) return INIT_FAILED;
SyncBasketState();
if(DashOn())
{
CreateDashboard();
UpdateDashboard();
EventSetTimer(1);
}
Print(BOT_NAME, " v", BOT_VERSION, " | ", BOT_DESCRIPTION,
" | ", BUY_GRID_COMMENT_PREFIX, "*/", SELL_GRID_COMMENT_PREFIX, "* | bucket ",
InpBucketProfitPct, "%");
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
EventKillTimer();
if(g_hEMA != INVALID_HANDLE) IndicatorRelease(g_hEMA);
DashClear();
}
void OnTimer()
{
UpdateDashboard();
}
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id != CHARTEVENT_OBJECT_CLICK) return;
if(sparam != BTN_BOTH) return;
g_bothSideGrid = !g_bothSideGrid;
ObjectSetInteger(0, BTN_BOTH, OBJPROP_STATE, false);
g_status = g_bothSideGrid ? "Both-side grids enabled" : "Regular grid (one side)";
UpdateDashboard();
}
void OnTick()
{
SyncBasketState();
CheckPositiveStepClose();
SyncBasketState();
CheckBucketClose();
SyncBasketState();
TryGridAdds();
TryStartGrids();
UpdateDashboard();
}
//+------------------------------------------------------------------+