//+------------------------------------------------------------------+ //| ConcreteWebEa.mq5 | //| Concrete Goonies - https://concrete-goonies.web.app | //+------------------------------------------------------------------+ #property copyright "Concrete Goonies © 2025" #property link "https://concrete-goonies.web.app" #property version "1.00" #include #include #include #include #include #include #include #include // Callback for notifications typedef void (*TradeNotificationCallback)(const string &message); //+------------------------------------------------------------------+ //| EA Config Structure | //+------------------------------------------------------------------+ struct EAConfig { string name; bool enabled; int tradeDuration; int tradeCount; double lotSize; int stopLoss; int takeProfit; ulong magicNumber; bool allowBuy; bool allowSell; bool useMM; double riskPercent; bool hidden; ENUM_TIMEFRAMES timeframe; }; //+------------------------------------------------------------------+ //| Expert Advisor Class | //+------------------------------------------------------------------+ class ExpertAdvisor : public CObject { protected: EAConfig cfg; CBars bar; CTradeHedge trade; CPositions positions; CTrailing trail; CNewBar newBar; CTimer Timer; Signal glSignal; CWallPaper backgroundImage; ulong glBuyTicket; ulong glSellTicket; void GetTickets(bool isBuy); bool CanOpenPosition(bool isBuy); void HandleTrade(bool isBuy, double volume, double sl = 0, double tp = 0, string comment = "Concrete Goonies"); public: TradeNotificationCallback Notify; ExpertAdvisor(const EAConfig &config) : cfg(config) { trade.Deviation(0.3); trade.MagicNumber(cfg.magicNumber); glSignal = SIGNAL_NONE; Notify = NULL; } ~ExpertAdvisor() {} string GetSymbol() const { return cfg.name; } ulong GetMagic() const { return cfg.magicNumber; } ENUM_TIMEFRAMES GetTimeframe() const { return cfg.timeframe; } EAConfig Config() const { return cfg; } void SetConfig(const EAConfig &newCfg) { cfg = newCfg; } virtual void OnTick(); virtual int OnInit(); virtual void Release(); bool InitBackGround(const string path, const char visibility = 100); void Resize(); double GetVolume(); void ClosePositions(); bool OnNewBar(); bool IsBullish(int index); bool IsBearish(int index); }; //+------------------------------------------------------------------+ //| OnTick implementation | //+------------------------------------------------------------------+ void ExpertAdvisor::OnTick() { bar.Update(GetSymbol(), cfg.timeframe); double volume = GetVolume(); // Update tickets GetTickets(true); GetTickets(false); // Signal execution if(cfg.enabled) { if(OnNewBar()) { ClosePositions(); if(GetSymbol() == "Boom 1000 Index" || GetSymbol() == "Boom 500 Index" || GetSymbol() == "Boom 300 Index") { if(cfg.allowSell) { if(IsBullish(3) && IsBearish(2) && IsBearish(1)) { glSignal = SIGNAL_SELL; } } else if(cfg.allowBuy) { if(IsBullish(3) && IsBearish(2) && IsBearish(1)) { glSignal = SIGNAL_BUY; } } } else if(GetSymbol() == "Crash 1000 Index" || GetSymbol() == "Crash 500 Index" || GetSymbol() == "Crash 300 Index") { if(cfg.allowSell) { if(IsBearish(3) && IsBullish(2) && IsBullish(1)) { glSignal = SIGNAL_SELL; } } else if(cfg.allowBuy) { if(IsBearish(3) && IsBullish(2) && IsBullish(1)) { glSignal = SIGNAL_BUY; } } } else { if(IsBearish(3) && IsBullish(2) && IsBullish(1)) { glSignal = SIGNAL_BUY; } if(IsBullish(3) && IsBearish(2) && IsBearish(1)) { glSignal = SIGNAL_SELL; } } if(glSignal == SIGNAL_BUY && cfg.allowBuy && CanOpenPosition(true)) HandleTrade(true, volume); if(glSignal == SIGNAL_SELL && cfg.allowSell && CanOpenPosition(false)) HandleTrade(false, volume); } } glSignal = SIGNAL_NONE; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ExpertAdvisor::IsBullish(int index) { return bar.Close(index) > bar.Open(index); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ExpertAdvisor::IsBearish(int index) { return bar.Close(index) < bar.Open(index); } //+------------------------------------------------------------------+ //| Init | //+------------------------------------------------------------------+ int ExpertAdvisor::OnInit() { return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Get volume (MM or fixed) | //+------------------------------------------------------------------+ double ExpertAdvisor::GetVolume() { return cfg.useMM ? MoneyManagement(cfg.name, cfg.lotSize, cfg.riskPercent, cfg.stopLoss) : VerifyVolume(cfg.name, cfg.lotSize); } //+------------------------------------------------------------------+ //| Unified ticket fetching | //+------------------------------------------------------------------+ void ExpertAdvisor::GetTickets(bool isBuy) { ulong tickets[]; if(isBuy) positions.GetBuyTickets(cfg.magicNumber, tickets); else positions.GetSellTickets(cfg.magicNumber, tickets); if(ArraySize(tickets) > 0) isBuy ? glBuyTicket = tickets[0] : glSellTicket = tickets[0]; } //+------------------------------------------------------------------+ //| Check if we can open position | //+------------------------------------------------------------------+ bool ExpertAdvisor::CanOpenPosition(bool isBuy) { return isBuy ? positions.Buy(cfg.magicNumber) < cfg.tradeCount : positions.Sell(cfg.magicNumber) < cfg.tradeCount; } //+------------------------------------------------------------------+ //| Unified trade handler | //+------------------------------------------------------------------+ void ExpertAdvisor::HandleTrade(bool isBuy, double volume, double sl = 0, double tp = 0, string comment = "Concrete Goonies") { ulong ticket = isBuy ? glBuyTicket : glSellTicket; ulong opposite = isBuy ? glSellTicket : glBuyTicket; // Close opposite ticket if exists if(opposite > 0) { if(trade.Close(opposite) > 0 && Notify != NULL) { string msg = "Closed " + (isBuy ? "SELL" : "BUY") + " position ticket: " + IntegerToString(opposite); // Open trade Notify(msg); } } ticket = isBuy ? trade.Buy(cfg.name, volume, 0, 0, "Zane Strategy") : trade.Sell(cfg.name, volume, 0, 0, "Zane Strategy"); if(ticket > 0) { double openPrice = PositionOpenPrice(ticket); double stop = isBuy ? BuyStopLoss(cfg.name, cfg.stopLoss, openPrice) : SellStopLoss(cfg.name, cfg.stopLoss, openPrice); double profit = isBuy ? BuyTakeProfit(cfg.name, cfg.takeProfit, openPrice) : SellTakeProfit(cfg.name, cfg.takeProfit, openPrice); if(stop > 0 || profit > 0) trade.ModifyPosition(ticket, stop, profit); // Notify trade opened if(Notify != NULL) { string msg = "Opened " + (isBuy ? "BUY" : "SELL") + " position ticket: " + IntegerToString(ticket) + " Symbol: " + cfg.name + " Volume: " + DoubleToString(volume, 2); Notify(msg); } } } //+------------------------------------------------------------------+ //| Release resources | //+------------------------------------------------------------------+ void ExpertAdvisor::Release() { backgroundImage.Destroy(); } //+------------------------------------------------------------------+ //| Initialize background image | //+------------------------------------------------------------------+ bool ExpertAdvisor::InitBackGround(const string path, const char visibility) { return backgroundImage.Init(path, visibility); } //+------------------------------------------------------------------+ //| Resize background image | //+------------------------------------------------------------------+ void ExpertAdvisor::Resize() { backgroundImage.Resize(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ExpertAdvisor::OnNewBar() { return newBar.CheckNewBar(GetSymbol(), GetTimeframe()); } //+------------------------------------------------------------------+ //| Close positions based on duration | //+------------------------------------------------------------------+ void ExpertAdvisor::ClosePositions() { int total = PositionsTotal(); for(int c = 0; c < total; c++) { ulong ticket = PositionGetTicket(c); if(PositionSelectByTicket(ticket)) { long magic = PositionGetInteger(POSITION_MAGIC); if(magic == cfg.magicNumber) { datetime open_time = PositionGetInteger(POSITION_TIME); int bar_count = Bars(_Symbol, cfg.timeframe, open_time, TimeCurrent()); if(bar_count >= cfg.tradeDuration) { if(trade.Close(ticket) > 0 && Notify != NULL) { string msg = "Closed position ticket: " + IntegerToString(ticket) + " Symbol: " + cfg.name; Notify(msg); } } } } } } //+------------------------------------------------------------------+ // ---------- helper ---------- string EAConfigToString(const EAConfig & c) { string s; s = "name:\"" + c.name + "\""; s += ", enabled:" + (c.enabled); s += ", tradeDuration:" + IntegerToString(c.tradeDuration); s += ", tradeCount:" + IntegerToString(c.tradeCount); s += ", lotSize:" + DoubleToString(c.lotSize, 5); s += ", stopLoss:" + IntegerToString(c.stopLoss); s += ", takeProfit:" + IntegerToString(c.takeProfit); s += ", magicNumber:" + IntegerToString((int)c.magicNumber); s += ", allowBuy:" + (c.allowBuy ? "true" : "false"); s += ", allowSell:" + (c.allowSell ? "true" : "false"); s += ", useMM:" + (c.useMM ? "true" : "false"); s += ", riskPercent:" + DoubleToString(c.riskPercent, 2); s += ", hidden:" + (c.hidden ? "true" : "false"); s += ""; return s; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+