313 lines
12 KiB
MQL5
313 lines
12 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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---------------------------");
|
||
|
|
}
|