mql5/Experts/FractalGrid.mq5

313 lines
12 KiB
MQL5

2026-01-30 15:40:10 +03:00
//+------------------------------------------------------------------+
//| FractalGrid.mq5 |
//| Copyright 2026, Google Gemini |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Google Gemini"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
//--- Input Parameters
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; // Timeframe to Trade
input double InpLots = 0.1; // Trading Lots
input long InpMagic = 123456; // Magic Number
input double InpBulkExitPercent = 20.0; // % to close above Mean
input int InpExitDistancePoints = 1000; // Min Points from Mean to Exit
//--- Global Handles
int handleFractals;
//--- Trading Variable
CTrade trade;
//--- State Tracking
datetime lastSignalTime = 0;
datetime lastBuyExitSignalTime = 0; // Tracks the fractal time chosen for Buy exit
datetime lastSellExitSignalTime = 0; // Tracks the fractal time chosen for Sell exit
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handleFractals = iFractals(_Symbol, InpTimeframe);
if(handleFractals == INVALID_HANDLE)
{
Print("Failed to create fractal handle");
return(INIT_FAILED);
}
trade.SetExpertMagicNumber(InpMagic);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
IndicatorRelease(handleFractals);
ObjectsDeleteAll(0, "GRID_");
Comment("");
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Calculate and Visualize Means (Every Tick for UI)
double bM = 0, bL = 0, sM = 0, sL = 0;
int bCount = 0, sCount = 0;
CalculateMeans(bM, bL, bCount, sM, sL, sCount);
UpdateMeanVisuals(bM, sM);
// 2. Dashboard Status
UpdateDashboard(bM, bL, bCount, sM, sL, sCount);
// 3. Signal-Based Entry & Exit logic
int dir = 0;
datetime sigTime = 0;
int sigIndex = -1;
dir = GetLastFractal(_Symbol, InpTimeframe, handleFractals, sigTime, sigIndex);
// A Check for Signal-Based Exits EVERY TICK (as long as opposite signal exists)
if(dir != 0)
{
ManagedGridExits(dir, sigTime, bM, bL, bCount, sM, sL, sCount);
}
// B Handle Current Signal Entry (Accumulation - Once per signal)
if(dir != 0 && sigTime != lastSignalTime)
{
MqlTick lastTick;
if(!SymbolInfoTick(_Symbol, lastTick)) return;
if(dir == 1) // Signal is BUY
{
Print("New Buy Fractal detected at ", sigTime);
if(trade.Buy(InpLots, _Symbol, lastTick.ask, 0, 0, "Fractal Grid Entry"))
{
Print("Buy Order Placed - Grid Accumulation");
}
else Print("Buy failed: ", GetLastError());
}
else if(dir == -1) // Signal is SELL
{
Print("New Sell Fractal detected at ", sigTime);
if(trade.Sell(InpLots, _Symbol, lastTick.bid, 0, 0, "Fractal Grid Entry"))
{
Print("Sell Order Placed - Grid Accumulation");
}
else Print("Sell failed: ", GetLastError());
}
lastSignalTime = sigTime;
}
}
//+------------------------------------------------------------------+
//| Bulk Exit logic when price is in profit AND opposite signal |
//+------------------------------------------------------------------+
void ManagedGridExits(int currentSignal, datetime signalTime, double bMean, double bLots, int bCount, double sMean, double sLots, int sCount)
{
MqlTick lastTick;
if(!SymbolInfoTick(_Symbol, lastTick)) return;
// 0. Imbalance Safety Check
int maxC = MathMax(bCount, sCount);
double imbalance = (maxC > 0) ? (double)MathAbs(bCount - sCount) / maxC : 0;
if(imbalance > 0.20) return; // Difference > 20% blocks exits
// Robust pip size calculation for diagnostic display only if needed
double minDist = InpExitDistancePoints * _Point;
// 1. Buy Positions Bulk Exit: Need Sell Signal (-1) and Profit > minDist
if(bLots > 0 && currentSignal == -1)
{
double profitDist = lastTick.bid - bMean;
if(signalTime == lastBuyExitSignalTime) return; // Already used this fractal for an exit
if(profitDist >= minDist)
{
Print("Conditions met for Buy Exit. Triggering 20% close...");
ResetLastError();
if(ClosePartially(POSITION_TYPE_BUY, bLots * InpBulkExitPercent / 100.0))
{
lastBuyExitSignalTime = signalTime;
Print("SUCCESS: Buy Exit triggered. Dist: ", DoubleToString(profitDist/_Point, 0), " points");
}
else Print("ERROR: Buy exit failed. Error: ", GetLastError());
}
}
// 2. Sell Positions Bulk Exit: Need Buy Signal (1) and Profit > minDist
if(sLots > 0 && currentSignal == 1)
{
double profitDist = sMean - lastTick.ask;
if(signalTime == lastSellExitSignalTime) return; // Already used this fractal for an exit
if(profitDist >= minDist)
{
Print("Conditions met for Sell Exit. Triggering 20% close...");
ResetLastError();
if(ClosePartially(POSITION_TYPE_SELL, sLots * InpBulkExitPercent / 100.0))
{
lastSellExitSignalTime = signalTime;
Print("SUCCESS: Sell Exit triggered. Dist: ", DoubleToString(profitDist/_Point, 0), " points");
}
else Print("ERROR: Sell exit failed. Error: ", GetLastError());
}
}
}
//+------------------------------------------------------------------+
//| Calculate directional mean entry prices and counts |
//+------------------------------------------------------------------+
void CalculateMeans(double &bMean, double &bLots, int &bCount, double &sMean, double &sLots, int &sCount)
{
bMean = 0; bLots = 0; bCount = 0;
sMean = 0; sLots = 0; sCount = 0;
double bSum = 0, sSum = 0;
for(int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetInteger(POSITION_MAGIC) == InpMagic && PositionGetString(POSITION_SYMBOL) == _Symbol)
{
double vol = PositionGetDouble(POSITION_VOLUME);
double price = PositionGetDouble(POSITION_PRICE_OPEN);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
bLots += vol;
bCount++;
bSum += price * vol;
}
else
{
sLots += vol;
sCount++;
sSum += price * vol;
}
}
}
}
if(bLots > 0) bMean = bSum / bLots;
if(sLots > 0) sMean = sSum / sLots;
}
//+------------------------------------------------------------------+
//| Close a specific volume from a direction (Oldest First) |
//+------------------------------------------------------------------+
bool ClosePartially(long type, double targetVolume)
{
if(targetVolume <= 0) return false;
double volumeRemaining = targetVolume;
bool success = false;
// Iterate and close oldest first
for(int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetInteger(POSITION_MAGIC) == InpMagic && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_TYPE) == type)
{
double posVol = PositionGetDouble(POSITION_VOLUME);
double toClose = MathMin(posVol, volumeRemaining);
if(trade.PositionClose(ticket, toClose))
{
volumeRemaining -= toClose;
success = true;
if(volumeRemaining <= 0) break;
}
}
}
}
return success;
}
//+------------------------------------------------------------------+
//| Visualize Means |
//+------------------------------------------------------------------+
void UpdateMeanVisuals(double bMean, double sMean)
{
if(bMean > 0)
{
ObjectCreate(0, "GRID_BUY_MEAN", OBJ_HLINE, 0, 0, bMean);
ObjectSetInteger(0, "GRID_BUY_MEAN", OBJPROP_COLOR, clrLime);
ObjectSetInteger(0, "GRID_BUY_MEAN", OBJPROP_STYLE, STYLE_DASH);
ObjectSetString(0, "GRID_BUY_MEAN", OBJPROP_TOOLTIP, "Buy Mean: " + DoubleToString(bMean, _Digits));
}
else ObjectDelete(0, "GRID_BUY_MEAN");
if(sMean > 0)
{
ObjectCreate(0, "GRID_SELL_MEAN", OBJ_HLINE, 0, 0, sMean);
ObjectSetInteger(0, "GRID_SELL_MEAN", OBJPROP_COLOR, clrRed);
ObjectSetInteger(0, "GRID_SELL_MEAN", OBJPROP_STYLE, STYLE_DASH);
ObjectSetString(0, "GRID_SELL_MEAN", OBJPROP_TOOLTIP, "Sell Mean: " + DoubleToString(sMean, _Digits));
}
else ObjectDelete(0, "GRID_SELL_MEAN");
}
//+------------------------------------------------------------------+
//| Fractal Detection logic from template |
//+------------------------------------------------------------------+
int GetLastFractal(string sym, ENUM_TIMEFRAMES period, int handle, datetime &outTime, int &outIndex)
{
double upper[], lower[];
ArraySetAsSeries(upper, true);
ArraySetAsSeries(lower, true);
if(CopyBuffer(handle, 0, 0, 1000, upper) <= 0) return 0;
if(CopyBuffer(handle, 1, 0, 1000, lower) <= 0) return 0;
for(int i = 2; i < 1000; i++)
{
if(upper[i] != EMPTY_VALUE && upper[i] > 0) { outTime = iTime(sym, period, i); outIndex = i; return -1; }
if(lower[i] != EMPTY_VALUE && lower[i] > 0) { outTime = iTime(sym, period, i); outIndex = i; return 1; }
}
return 0;
}
//+------------------------------------------------------------------+
//| Dashboard Status |
//+------------------------------------------------------------------+
void UpdateDashboard(double bM, double bL, int bC, double sM, double sL, int sC)
{
MqlTick lastTick;
SymbolInfoTick(_Symbol, lastTick);
double pipSize = (_Digits == 3 || _Digits == 5) ? 10 * _Point : _Point;
string bInfo = (bL > 0) ? DoubleToString((lastTick.bid - bM) / _Point, 0) + " / " + (string)InpExitDistancePoints : "N/A";
string sInfo = (sL > 0) ? DoubleToString((sM - lastTick.ask) / _Point, 0) + " / " + (string)InpExitDistancePoints : "N/A";
int maxC = MathMax(bC, sC);
double imb = (maxC > 0) ? (double)MathAbs(bC - sC) / maxC * 100.0 : 0;
string imbStatus = (imb > 20.0) ? "EXITS BLOCKED (Imbalance > 20%)" : "Exits Allowed";
datetime dummyTime;
int dummyIndex;
int lastFractal = GetLastFractal(_Symbol, InpTimeframe, handleFractals, dummyTime, dummyIndex);
Comment("FRACTAL GRID - DIAGNOSTICS",
"\n---------------------------",
"\nStatus: ", imbStatus,
"\nCount Imbalance: ", DoubleToString(imb, 1), "% (", bC, "B | ", sC, "S)",
"\nBuy Points/Target: ", bInfo,
"\nSell Points/Target: ", sInfo,
"\nBuy Mean: ", DoubleToString(bM, _Digits), " (Lots: ", DoubleToString(bL, 2), ")",
"\nSell Mean: ", DoubleToString(sM, _Digits), " (Lots: ", DoubleToString(sL, 2), ")",
"\nLast Fractal Type: ", (lastFractal == 1 ? "BUY" : (lastFractal == -1 ? "SELL" : "NONE")),
"\n---------------------------");
}