321 lines
25 KiB
MQL5
321 lines
25 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| 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 <Mql5Book\TradeHedge.mqh>
| |
#include <Mql5Book\Price.mqh>
| |
#include <Mql5Book\MoneyManagement.mqh>
| |
#include <Mql5Book\TrailingStops.mqh>
| |
#include <Mql5Book\Timer.mqh>
| |
#include <Mql5Book\Indicators.mqh>
| |
#include <WallPaper.mqh>
| |
#include <Arrays\ArrayObj.mqh>
| |
| |
// 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;
| |
}
| |
//+------------------------------------------------------------------+
| |
| |
//+------------------------------------------------------------------+
|