mql5/Experts/FractalOnly.mq5

266 lines
9.9 KiB
MQL5

2026-01-30 15:40:33 +03:00
//+------------------------------------------------------------------+
//| DualFractal.mq5 |
//| Copyright 2026, Google Gemini |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Google Gemini"
#property link "https://www.mql5.com"
#property version "2.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 int InpMinSLPips = 100; // Min SL distance from Price (Pips)
input double InpTPRatio = 2.0; // Reward-to-Risk Ratio
input int InpSLPaddingPoints = 100; // SL Padding Buffer (Points)
//--- Global Handles
int handleFractals;
//--- Trading Variable
CTrade trade;
//--- State Tracking for Avoidance of Duplicates
datetime lastSignalTime = 0;
//+------------------------------------------------------------------+
//| 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);
Comment(""); // Clear chart dashboard
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Manage Trailing Stop for existing positions
//CheckTrailingStop();
// 2. Dashboard Status
UpdateDashboard();
// 3. Only one position at a time for this EA
if(HasOpenPosition()) return;
int dir = 0;
datetime sigTime = 0;
int sigIndex = -1;
// Get last confirmed fractal on the selected timeframe
dir = GetLastFractal(_Symbol, InpTimeframe, handleFractals, sigTime, sigIndex);
// Trading Logic: Trigger Entry
if(dir != 0)
{
// Ensure we don't open multiple trades on the same fractal
if(sigTime == lastSignalTime) return;
MqlTick lastTick;
if(!SymbolInfoTick(_Symbol, lastTick)) return;
double sl = 0, tp = 0;
double riskPoints = 0;
double minSLPoints = 0;
// MQL5 Pips to points adjustment
minSLPoints = InpMinSLPips*_Point;
if(dir == 1) // Signal is BUY (triggered by Down Fractal/Support)
{
// Find a valid SL (trough) on the same timeframe with min distance
if(GetSafeFractalSL(InpTimeframe, handleFractals, 1, lastTick.ask, minSLPoints, InpSLPaddingPoints, sl))
{
riskPoints = lastTick.ask - sl;
tp = lastTick.ask + (riskPoints * InpTPRatio);
if(trade.Buy(InpLots, _Symbol, lastTick.ask, sl, tp, "Fractal Signal Entry"))
{
lastSignalTime = sigTime;
Print("Buy Order Placed SL:", sl, " TP:", tp, " Risk:", riskPoints/10.0, " pips");
}
}
else
{
Print("Entry Blocked: No safe BUY SL found within search depth");
}
}
else if(dir == -1) // Signal is SELL (triggered by Up Fractal/Resistance)
{
// Find a valid SL (peak) on the same timeframe with min distance
if(GetSafeFractalSL(InpTimeframe, handleFractals, -1, lastTick.bid, minSLPoints, InpSLPaddingPoints, sl))
{
riskPoints = sl - lastTick.bid;
tp = lastTick.bid - (riskPoints * InpTPRatio);
if(trade.Sell(InpLots, _Symbol, lastTick.bid, sl, tp, "Fractal Signal Entry"))
{
lastSignalTime = sigTime;
Print("Sell Order Placed SL:", sl, " TP:", tp, " Risk:", riskPoints/10.0, " pips");
}
}
else
{
Print("Entry Blocked: No safe SELL SL found within search depth");
}
}
}
}
//+------------------------------------------------------------------+
//| Get direction and time of last confirmed fractal |
//+------------------------------------------------------------------+
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++)
{
bool isUp = (upper[i] < EMPTY_VALUE);
bool isDown = (lower[i] < EMPTY_VALUE);
if(isUp || isDown)
{
outTime = iTime(sym, period, i);
outIndex = i;
return isDown ? 1 : -1; // Down Fractal (Support) = BUY (1), Up Fractal (Resistance) = SELL (-1)
}
}
return 0;
}
//+------------------------------------------------------------------+
//| Find a fractal SL that satisfies the minimum distance rule |
//+------------------------------------------------------------------+
bool GetSafeFractalSL(ENUM_TIMEFRAMES period, int handle, int direction, double priceNow, double minDistancePoints, int paddingPoints, double &outSL)
{
// direction: 1 for Buy (look for Trough/Low), -1 for Sell (look for Peak/High)
double buf[];
ArraySetAsSeries(buf, true);
int bufferIdx = (direction == 1) ? 1 : 0; // Buy needs Lower (1), Sell needs Upper (0)
if(CopyBuffer(handle, bufferIdx, 0, 1000, buf) <= 0) return false;
for(int i = 2; i < 1000; i++)
{
if(buf[i] < EMPTY_VALUE)
{
double extreme = (direction == 1) ? iLow(_Symbol, period, i) : iHigh(_Symbol, period, i);
double slCandidate = (direction == 1) ? (extreme - paddingPoints * _Point) : (extreme + paddingPoints * _Point);
double dist = (direction == 1) ? (priceNow - slCandidate) : (slCandidate - priceNow);
if(dist >= minDistancePoints || minDistancePoints == 0)
{
outSL = slCandidate;
return true;
}
}
}
return false;
}
//+------------------------------------------------------------------+
//| Manage Trailing Stop based on new confirming fractals |
//+------------------------------------------------------------------+
void CheckTrailingStop()
{
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 currentSL = PositionGetDouble(POSITION_SL);
datetime dummyTime;
int dummyIdx;
int dir = GetLastFractal(_Symbol, InpTimeframe, handleFractals, dummyTime, dummyIdx);
if(type == POSITION_TYPE_BUY && dir == -1) // Buy position, last fractal is Down (Support)
{
double newSL;
if(GetSafeFractalSL(InpTimeframe, handleFractals, 1, PositionGetDouble(POSITION_PRICE_OPEN), 0, InpSLPaddingPoints, newSL))
{
if(newSL > currentSL + _Point)
{
trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
Print("Trailing SL (Buy) moved higher to:", newSL);
}
}
}
else if(type == POSITION_TYPE_SELL && dir == 1) // Sell position, last fractal is Up (Resistance)
{
double newSL;
if(GetSafeFractalSL(InpTimeframe, handleFractals, -1, PositionGetDouble(POSITION_PRICE_OPEN), 0, InpSLPaddingPoints, newSL))
{
if(newSL < currentSL - _Point || currentSL == 0)
{
trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
Print("Trailing SL (Sell) moved lower to:", newSL);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Check for open positions for this EA |
//+------------------------------------------------------------------+
bool HasOpenPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetInteger(POSITION_MAGIC) == InpMagic && PositionGetString(POSITION_SYMBOL) == _Symbol)
return true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| Update the chart dashboard |
//+------------------------------------------------------------------+
void UpdateDashboard()
{
datetime time;
int idx;
int dir = GetLastFractal(_Symbol, InpTimeframe, handleFractals, time, idx);
string sigStr = (dir == 1) ? "BUY (Support)" : (dir == -1 ? "SELL (Resistance)" : "NONE");
Comment("Timeframe: ", EnumToString(InpTimeframe),
"\nLast Signal: ", sigStr,
"\nSignal Time: ", time);
}
//+------------------------------------------------------------------+