266 líneas
9,9 KiB
MQL5
266 líneas
9,9 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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);
|
|
}
|
|
//+------------------------------------------------------------------+
|