1
0
Derivar 0
mql5/Experts/MidBarFractalEA.mq5

351 linhas
14 KiB
MQL5

//+------------------------------------------------------------------+
//| MidBarFractalEA.mq5 |
//| Copyright 2026, Google Gemini |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Google Gemini"
#property link "https://www.mql5.com"
#property version "2.10"
#property strict
#include <Trade\Trade.mqh>
//--- Prototypes
bool IsFractal(const MqlRates &rates[], int index, int strength, bool &isUp, bool &isDown);
bool FindNearestFractal(ENUM_TIMEFRAMES tf, int start, int strength, bool lookForUp, double &price);
void ManagePositions(ENUM_TIMEFRAMES tf, int strength, bool oppositeForBuy, bool oppositeForSell, double meanBuy, double meanSell, int totalBuys, int totalSells);
void UpdateCollectiveSL(long posType, double sl);
double CalculateNextLot(int currentCount);
bool IsGridDistanceOK(long type, double price);
//--- Input Parameters
input double InpLots = 0.1; // Base Lots
input double InpLotMultiplier = 1.2; // Lot Factor (Consecutive Orders)
input int InpSLPadding = 5; // SL Padding from Fractal (Points)
input int InpTPPadding = 5; // TP Padding from Fractal (Points)
input long InpMagic = 987654; // Magic Number
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Chart Timeframe
input int InpFractalIndex = 2; // Fractal Strength (Bars on each side)
input int InpMaxPosPerSide = 1; // Max Positions Per Side Per Bar
input int InpMaxSpread = 2; // Max Spread (Points)
input int InpGridDistance = 100; // Min Distance Between Orders (Points)
//--- MA Filter Parameters
input bool InpUseMAFilter = true; // Use MA Filter?
input int InpMAPeriod = 50; // MA Period
input ENUM_MA_METHOD InpMAMethod = MODE_SMA; // MA Method
input ENUM_APPLIED_PRICE InpMAPrice = PRICE_CLOSE;// MA Applied Price
//--- HTF Fractal Filter Parameters
input bool InpUseHTFFilter = true; // Use HTF Fractal Filter?
input ENUM_TIMEFRAMES InpHTF = PERIOD_H1; // Higher Timeframe
//--- Global Variables
CTrade trade;
int handleMA = INVALID_HANDLE;
datetime lastBarTime = 0;
int buyCountOnBar = 0;
int sellCountOnBar = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(InpMagic);
if(InpUseMAFilter)
{
handleMA = iMA(_Symbol, InpTimeframe, InpMAPeriod, 0, InpMAMethod, InpMAPrice);
if(handleMA == INVALID_HANDLE)
{
Print("Failed to create MA handle");
return(INIT_FAILED);
}
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(handleMA != INVALID_HANDLE) IndicatorRelease(handleMA);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Bar Time & Reset
datetime barTime = iTime(_Symbol, InpTimeframe, 0);
int barDuration = PeriodSeconds(InpTimeframe);
datetime currentTime = TimeCurrent();
if(barTime != lastBarTime && barTime != 0)
{
lastBarTime = barTime;
buyCountOnBar = 0;
sellCountOnBar = 0;
}
// 2. Gather Position Data
int totalBuys = 0, totalSells = 0;
double sumBuyPrice = 0, sumSellPrice = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagic || PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
long type = PositionGetInteger(POSITION_TYPE);
double price = PositionGetDouble(POSITION_PRICE_OPEN);
if(type == POSITION_TYPE_BUY) { totalBuys++; sumBuyPrice += price; }
else if(type == POSITION_TYPE_SELL) { totalSells++; sumSellPrice += price; }
}
double meanBuy = (totalBuys > 0) ? sumBuyPrice / totalBuys : 0;
double meanSell = (totalSells > 0) ? sumSellPrice / totalSells : 0;
// 3. CTF Fractal Status (Always updated for management)
MqlRates rCTF[];
ArraySetAsSeries(rCTF, true);
int countCTF = (InpFractalIndex * 2) + 1;
if(CopyRates(_Symbol, InpTimeframe, 0, countCTF, rCTF) < countCTF) return;
bool ctfFractalUp = false, ctfFractalDown = false;
IsFractal(rCTF, InpFractalIndex, InpFractalIndex, ctfFractalUp, ctfFractalDown);
// 4. Position Management (Runs every tick)
ManagePositions(InpTimeframe, InpFractalIndex, ctfFractalUp, ctfFractalDown, meanBuy, meanSell, totalBuys, totalSells);
// 5. Entry Timing Filter (Mid-Bar)
if(currentTime < barTime + (barDuration / 2))
{
long elapsed = currentTime - barTime;
Comment("Status: WAITING FOR MID-BAR",
"\nElapsed: ", elapsed, " / ", barDuration / 2, "s",
"\nBasket: Buy(", totalBuys, ") Avg:", DoubleToString(meanBuy, _Digits),
" | Sell(", totalSells, ") Avg:", DoubleToString(meanSell, _Digits));
return;
}
// 6. Final Entry Logic Prep
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
int currentSpread = (int)MathRound((ask - bid) / _Point);
bool spreadOK = (currentSpread <= InpMaxSpread);
// 7. MA Trend Filter
bool maTrendUp = true, maTrendDown = true;
double maVal[1] = {0};
if(InpUseMAFilter)
{
if(CopyBuffer(handleMA, 0, 0, 1, maVal) > 0)
{
maTrendUp = (ask > maVal[0]);
maTrendDown = (bid < maVal[0]);
}
}
// 8. HTF Confluence Filter
bool htfAlignedUp = true, htfAlignedDown = true;
if(InpUseHTFFilter)
{
MqlRates rHTF[];
ArraySetAsSeries(rHTF, true);
if(CopyRates(_Symbol, InpHTF, 0, countCTF, rHTF) < countCTF)
{
htfAlignedUp = false; htfAlignedDown = false;
}
else
{
bool htfUp = false, htfDown = false;
IsFractal(rHTF, InpFractalIndex, InpFractalIndex, htfUp, htfDown);
htfAlignedUp = htfUp; htfAlignedDown = htfDown;
}
}
// 9. Execute Trades
// BUY Signal: Fractal Down (CTF) + Aligned Down (HTF) + MA UP + Grid OK
if(ctfFractalDown && htfAlignedDown && buyCountOnBar < InpMaxPosPerSide && spreadOK && maTrendUp)
{
if(IsGridDistanceOK(POSITION_TYPE_BUY, ask))
{
double lot = CalculateNextLot(totalBuys);
if(trade.Buy(lot, _Symbol, ask, 0, 0, "Mid-Bar Buy")) { buyCountOnBar++; Print("Buy Entered: ", lot, " Lots"); }
}
}
// SELL Signal: Fractal Up (CTF) + Aligned Up (HTF) + MA DOWN + Grid OK
if(ctfFractalUp && htfAlignedUp && sellCountOnBar < InpMaxPosPerSide && spreadOK && maTrendDown)
{
if(IsGridDistanceOK(POSITION_TYPE_SELL, bid))
{
double lot = CalculateNextLot(totalSells);
if(trade.Sell(lot, _Symbol, bid, 0, 0, "Mid-Bar Sell")) { sellCountOnBar++; Print("Sell Entered: ", lot, " Lots"); }
}
}
// Diagnostic Output
string maLabel = !InpUseMAFilter ? "Off" : (maTrendUp && maTrendDown ? "Neutral" : (maTrendUp ? "UP" : "DOWN"));
string htfLabel = !InpUseHTFFilter ? "Off" : (htfAlignedUp && htfAlignedDown ? "Both" : (htfAlignedUp ? "UP Only" : (htfAlignedDown ? "DOWN Only" : "None")));
Comment("Status: EXECUTING MID-BAR",
"\n-----------------------------",
"\nMA Trend: ", maLabel, (InpUseMAFilter ? " (" + DoubleToString(maVal[0], _Digits) + ")" : ""),
"\nHTF Confluence: ", htfLabel,
"\nCTF Signal: UP(Sell):", ctfFractalUp, " | DOWN(Buy):", ctfFractalDown,
"\n-----------------------------",
"\nBuy Count/Limit: ", buyCountOnBar, " / ", InpMaxPosPerSide,
"\nSell Count/Limit: ", sellCountOnBar, " / ", InpMaxPosPerSide,
"\nSpread: ", currentSpread, " / ", InpMaxSpread, (spreadOK ? " [OK]" : " [OVER]"));
}
//+------------------------------------------------------------------+
//| Core Fractal Detection Logic |
//+------------------------------------------------------------------+
bool IsFractal(const MqlRates &rates[], int index, int strength, bool &isUp, bool &isDown)
{
int size = ArraySize(rates);
if(index < strength || index >= size - strength) return false;
isUp = true; isDown = true;
for(int i = 1; i <= strength; i++)
{
if(rates[index].high <= rates[index-i].high || rates[index].high <= rates[index+i].high) isUp = false;
if(rates[index].low >= rates[index-i].low || rates[index].low >= rates[index+i].low) isDown = false;
if(!isUp && !isDown) break;
}
return true;
}
//+------------------------------------------------------------------+
//| Efficient Historical Fractal Search |
//+------------------------------------------------------------------+
bool FindNearestFractal(ENUM_TIMEFRAMES tf, int start, int strength, bool lookForUp, double &price)
{
int lookback = 500;
MqlRates rates[];
ArraySetAsSeries(rates, true);
if(CopyRates(_Symbol, tf, 0, lookback, rates) < lookback) return false;
for(int i = start; i < lookback - strength; i++)
{
bool up = false, down = false;
if(IsFractal(rates, i, strength, up, down))
{
if(lookForUp && up) { price = rates[i].high; return true; }
if(!lookForUp && down) { price = rates[i].low; return true; }
}
}
return false;
}
//+------------------------------------------------------------------+
//| Basket Management - 3+ Pool Logic |
//+------------------------------------------------------------------+
void ManagePositions(ENUM_TIMEFRAMES tf, int strength, bool oppositeForBuy, bool oppositeForSell, double meanBuy, double meanSell, int totalBuys, int totalSells)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
// Manage BUYS: If 3+ trades, and identified FRACTAL is past mean, and opposite fractal (UP) appeared
if(totalBuys >= 3 && oppositeForBuy)
{
double slPrice = 0;
if(FindNearestFractal(tf, strength, strength, false, slPrice)) // Search for last support
{
// Update only if the fractal support level itself is past the mean entry (in profit)
if(slPrice > meanBuy)
{
UpdateCollectiveSL(POSITION_TYPE_BUY, slPrice - InpSLPadding * _Point);
}
}
}
// Manage SELLS: If 3+ trades, and identified FRACTAL is past mean, and opposite fractal (DOWN) appeared
if(totalSells >= 3 && oppositeForSell)
{
double slPrice = 0;
if(FindNearestFractal(tf, strength, strength, true, slPrice)) // Search for last resistance
{
// Update only if the fractal resistance level itself is past the mean entry (in profit)
if(slPrice < meanSell && meanSell > 0)
{
UpdateCollectiveSL(POSITION_TYPE_SELL, slPrice + InpSLPadding * _Point);
}
}
}
}
//+------------------------------------------------------------------+
//| Apply collective SL updates to the basket |
//+------------------------------------------------------------------+
void UpdateCollectiveSL(long posType, double sl)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagic || PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_TYPE) != posType) continue;
double curSL = PositionGetDouble(POSITION_SL);
bool shouldUpdate = false;
if(curSL == 0) shouldUpdate = true;
else if(posType == POSITION_TYPE_BUY && sl > curSL + 2 * _Point) shouldUpdate = true;
else if(posType == POSITION_TYPE_SELL && sl < curSL - 2 * _Point) shouldUpdate = true;
if(shouldUpdate)
{
// Price safety: Don't set SL to current price (causes error)
if(posType == POSITION_TYPE_BUY && sl >= SymbolInfoDouble(_Symbol, SYMBOL_BID)) continue;
if(posType == POSITION_TYPE_SELL && sl <= SymbolInfoDouble(_Symbol, SYMBOL_ASK)) continue;
trade.PositionModify(ticket, sl, 0);
}
}
}
//+------------------------------------------------------------------+
//| Calculate next lot based on progression factor |
//+------------------------------------------------------------------+
double CalculateNextLot(int currentCount)
{
double lot = InpLots * MathPow(InpLotMultiplier, currentCount);
// Normalize for broker constraints
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
lot = MathFloor(lot/lotStep + 0.000001) * lotStep;
if(lot < minLot) lot = minLot;
if(lot > maxLot) lot = maxLot;
return NormalizeDouble(lot, 2);
}
//+------------------------------------------------------------------+
//| Check if current price is far enough from existing orders |
//+------------------------------------------------------------------+
bool IsGridDistanceOK(long type, double price)
{
if(InpGridDistance <= 0) return true;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagic || PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_TYPE) != type) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
if(MathAbs(price - openPrice) < InpGridDistance * _Point) return false;
}
return true;
}