Files
mql-trading-bots/FadePivot2_v4.mq5
Garfield 74308b38e7 Initial commit: MQL Trading Bots
- MultiSignal Confluence EA v1.11 (stop loss bug fixed)
- Harmonic Pattern Finder v2 (optimized)
- Candlestick Pattern EA (fixed)
- Pivot Fade EA v4 (fixed)
- Bot series (10001, 10002, EnhancedEA)

Performance: ~19% return in 2 months on 00k account
2026-03-21 18:39:48 -04:00

640 lines
42 KiB
Plaintext
Executable File

//+------------------------------------------------------------------+
//| FadePivot2_v4.mq5 |
//| Garfield Heron / Abbey Road Tech |
//| https://abbeyroadtechnology.com |
//+------------------------------------------------------------------+
#property copyright "© 2024"
#property link "https://abbeyroadtechnology.com"
#property version "2.01"
#property strict
/***** User Inputs *****/
input double LotSize = 0.01; // Lot size for trades
input int Slippage = 3; // Max slippage in points
input int TP_OffsetPips = 0; // +/- offset from R1 or S1 (in pips)
input double StopLossFactor = 1.0; // Multiplier for (R2 - R1) or (S1 - S2)
input double TakeProfitFactor = 1.0; // Multiplier for take-profit
input bool OnlyOneSetPerDay = true; // If true, place only once per day
input bool AllowNewOrdersIfPreviousOpen = true; // Place new orders even if old remain
input bool CloseEndOfDay = true; // If true, do EoD cleanup
input bool CloseOnlyPendingEndOfDay = false; // If true => close only pending orders at EoD
input bool CloseOpenOrdersEndOfDay = false; // If true, close open positions at EoD
input long MagicNumber = 98765; // Magic number for this EA
/***** Trailing-Stop Inputs *****/
input double TrailingStartPips = 10.0; // in profit at least this many pips to start trailing
input double TrailingDistancePips = 5.0; // trailing distance behind best price
/***** Day-of-Week Logic *****/
input bool TradeMonday = true;
input bool TradeTuesday = true;
input bool TradeWednesday = true;
input bool TradeThursday = true;
input bool TradeFriday = true;
input bool TradeSaturday = false;
input bool TradeSunday = false;
/***** Global variables *****/
static datetime TradeDate = 0; // Tracks the current day
static bool OrdersPlaced = false;
/***** Pivots *****/
static double P, R1, R2, S1, S2;
/***** For multiple-trade trailing: track best price per position ticket *****/
/**
We'll keep two parallel static arrays:
- posTicketArray[] holds position tickets
- bestPriceArray[] holds the best price so far for that ticket
(highest for BUY, lowest for SELL).
We only store up to some arbitrary max positions (e.g. 100).
Each time we see a new position, we add if not found.
Each time position is closed, we remove it from arrays to prevent stale data.
*/
#define MAX_POSITIONS 100
static ulong posTicketArray[MAX_POSITIONS];
static double bestPriceArray[MAX_POSITIONS];
static int posCount=0; // how many valid entries we have
// Prototypes
void AddOrUpdateBestPrice(ulong ticket, long posType, double newPrice);
double GetBestPrice(ulong ticket, long posType);
void RemovePosition(ulong ticket);
/***** Additional prototypes *****/
void CalculateDailyPivots();
void PlaceFadeOrders();
bool HasOpenOrPendingWithMagic(long magic);
bool CloseAllByMagic(long magic);
void PlaceLimitOrder(ENUM_ORDER_TYPE orderType, double entryPrice, double slPrice, double tpPrice);
void ApplyTrailingStop();
bool IsTradingDay();
void ModifyPositionSL(long posType, double newSL, ulong ticket);
/*****=====================================================================
* OnInit / OnDeinit
*======================================================================*****/
int OnInit()
{
Print("FadePivot2_v4 EA initializing (multi-position trailing + day-of-week)...");
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
Print("FadePivot2_v4 EA deinitialized. reason=", reason);
// optionally clear arrays
posCount = 0;
}
/*****=====================================================================
* OnTick
*======================================================================*****/
void OnTick()
{
// 1) Check day-of-week
if(!IsTradingDay())
return;
// 2) Check for new D1 bar
datetime today = iTime(_Symbol, PERIOD_D1, 0);
if(today != TradeDate)
{
CalculateDailyPivots();
TradeDate = today;
OrdersPlaced = false;
Print("New daily bar. Pivots recalc. R2=", R2, " S2=", S2);
// Also, remove old positions from array if they are closed
// (optional check below or do in EoD logic):
CleanUpClosedPositions();
}
// 3) Place daily fade orders if needed
if(!OrdersPlaced)
{
if(!OnlyOneSetPerDay)
{
PlaceFadeOrders();
OrdersPlaced = true;
}
else
{
if(!HasOpenOrPendingWithMagic(MagicNumber) || AllowNewOrdersIfPreviousOpen)
{
PlaceFadeOrders();
OrdersPlaced = true;
}
else
{
Print("Skipping new orders (OneSetPerDay + existing trades).");
}
}
}
// 4) End-of-day close logic
if(CloseEndOfDay)
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.hour == 23 && dt.min == 59)
{
Print("FadePivot2_v4: EoD => cleanup...");
CloseAllByMagic(MagicNumber);
}
}
// 5) Trailing Stop for multiple trades
ApplyTrailingStop();
}
/*****=====================================================================
* IsTradingDay()
*======================================================================*****/
bool IsTradingDay()
{
MqlDateTime mt;
TimeToStruct(TimeCurrent(), mt);
int dow = mt.day_of_week; // 0=Sun,1=Mon,2=Tue,3=Wed,4=Thu,5=Fri,6=Sat
switch(dow)
{
case 0: return TradeSunday;
case 1: return TradeMonday;
case 2: return TradeTuesday;
case 3: return TradeWednesday;
case 4: return TradeThursday;
case 5: return TradeFriday;
case 6: return TradeSaturday;
}
return false;
}
/*****=====================================================================
* CalculateDailyPivots
*======================================================================*****/
void CalculateDailyPivots()
{
int shift = 1; // "yesterday"
double prevHigh = iHigh(_Symbol, PERIOD_D1, shift);
double prevLow = iLow(_Symbol, PERIOD_D1, shift);
double prevClose= iClose(_Symbol, PERIOD_D1, shift);
P = (prevHigh + prevLow + prevClose)/3.0;
R1 = 2.0*P - prevLow;
S1 = 2.0*P - prevHigh;
R2 = P + (prevHigh - prevLow);
S2 = P - (prevHigh - prevLow);
}
/*****=====================================================================
* PlaceFadeOrders
*======================================================================*****/
void PlaceFadeOrders()
{
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
// SELL LIMIT at R2
{
double entryPrice = NormalizeDouble(R2, digits);
double distanceR1R2 = MathAbs(R2 - R1);
double slPrice = R2 + (distanceR1R2 * StopLossFactor);
double tpPrice = (R1 * TakeProfitFactor) + (TP_OffsetPips * pt);
Print("Placing SELL LIMIT at R2= ", entryPrice,
", SL= ", slPrice, ", TP= ", tpPrice);
PlaceLimitOrder(ORDER_TYPE_SELL_LIMIT, entryPrice, slPrice, tpPrice);
}
// BUY LIMIT at S2
{
double entryPrice = NormalizeDouble(S2, digits);
double distanceS1S2 = MathAbs(S1 - S2);
double slPrice = S2 - (distanceS1S2 * StopLossFactor);
double tpPrice = (S1 * TakeProfitFactor) + (TP_OffsetPips * pt);
Print("Placing BUY LIMIT at S2= ", entryPrice,
", SL= ", slPrice, ", TP= ", tpPrice);
PlaceLimitOrder(ORDER_TYPE_BUY_LIMIT, entryPrice, slPrice, tpPrice);
}
}
/*****=====================================================================
* PlaceLimitOrder - helper
*======================================================================*****/
void PlaceLimitOrder(ENUM_ORDER_TYPE orderType,
double entryPrice,
double slPrice,
double tpPrice)
{
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_PENDING;
request.symbol = _Symbol;
request.magic = MagicNumber;
request.volume = LotSize;
request.deviation = Slippage;
request.type = orderType;
request.price = entryPrice;
request.sl = slPrice;
request.tp = tpPrice;
request.type_filling = ORDER_FILLING_FOK;
request.type_time = ORDER_TIME_GTC;
request.comment = "FadePivot2_v4";
if(!OrderSend(request, result))
{
Print(__FUNCTION__, ": OrderSend failed. LastErr=", GetLastError());
return;
}
if(result.retcode == TRADE_RETCODE_PLACED || result.retcode == TRADE_RETCODE_DONE)
{
Print("Limit order placed: type=",
(orderType==ORDER_TYPE_SELL_LIMIT ? "SellLimit":"BuyLimit"),
", ticket=", result.order);
}
else
{
Print("Limit order retcode=", result.retcode, ", lastErr=", GetLastError());
}
}
/*****=====================================================================
* ApplyTrailingStop - handles multiple trades
*======================================================================*****/
void ApplyTrailingStop()
{
// 1) Convert trailing inputs from pips -> price distance
double trailingStart = TrailingStartPips * _Point;
double trailingDist = TrailingDistancePips * _Point;
// 2) Loop all positions
uint totalPositions = PositionsTotal();
for(uint i=0; i < totalPositions; i++)
{
ulong posTicket = PositionGetTicket(i);
if(posTicket == 0)
continue; // skip invalid
if(!PositionSelectByTicket(posTicket))
continue;
// filter by symbol + magic
string sym = PositionGetString(POSITION_SYMBOL);
long mgc = PositionGetInteger(POSITION_MAGIC);
if(sym != _Symbol || mgc != MagicNumber)
continue;
// retrieve info
long posType = PositionGetInteger(POSITION_TYPE);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
// current market price
double cpx = (posType==POSITION_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_BID)
: SymbolInfoDouble(_Symbol, SYMBOL_ASK);
// pips in profit
double pipsInProfit=0.0;
if(posType==POSITION_TYPE_BUY)
pipsInProfit = (cpx - openPrice)/_Point;
else
pipsInProfit = (openPrice - cpx)/_Point;
// if not in enough profit, skip
if(pipsInProfit < TrailingStartPips)
continue;
// track the best price so far for this ticket
double oldBestPrice = GetBestPrice(posTicket, posType);
// if it's a BUY => best price is max
// if it's a SELL => best price is min
bool improved = false;
double newBest= oldBestPrice;
if(posType == POSITION_TYPE_BUY)
{
// if oldBestPrice =0 => not tracked yet => set to openPrice initially
if(oldBestPrice < 1e-8)
{
newBest= openPrice;
improved= true; // or set to cpx if you prefer
}
// if cpx is higher than old best => we have new best
if(cpx > oldBestPrice)
{
newBest = cpx;
improved= true;
}
}
else
{
// for SELL, oldBestPrice=0 => set to openPrice
if(oldBestPrice < 1e-8)
{
newBest= openPrice;
improved= true;
}
// if cpx < oldBest => we have new best
if(cpx < oldBestPrice)
{
newBest= cpx;
improved= true;
}
}
// update if improved
if(improved)
AddOrUpdateBestPrice(posTicket, posType, newBest);
// Now figure out the new trailing stop from best price
// For a BUY, the trailing stop is (bestPrice - trailingDist)
// For a SELL, the trailing stop is (bestPrice + trailingDist)
double potentialSL = 0.0;
if(posType==POSITION_TYPE_BUY)
{
potentialSL = newBest - trailingDist;
// only move SL up if potentialSL > currentSL
if(potentialSL > currentSL + (_Point*0.5)) // e.g. 0.5 pip buffer to reduce spam
{
ModifyPositionSL(posType, potentialSL, posTicket);
}
}
else // SELL
{
potentialSL = newBest + trailingDist;
// only move SL down if potentialSL < currentSL
if(currentSL < 1e-8 || potentialSL < currentSL - (_Point*0.5))
{
ModifyPositionSL(posType, potentialSL, posTicket);
}
}
}
}
/*****=====================================================================
* ModifyPositionSL
*======================================================================*****/
void ModifyPositionSL(long posType, double newSL, ulong ticket)
{
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_SLTP;
req.symbol = _Symbol;
req.magic = MagicNumber;
req.type = (posType==POSITION_TYPE_BUY)? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
// retrieve old TP, volume
if(!PositionSelectByTicket(ticket))
return; // just in case
double oldTP = PositionGetDouble(POSITION_TP);
double vol = PositionGetDouble(POSITION_VOLUME);
req.position = ticket;
req.volume = vol;
req.sl = newSL;
req.tp = oldTP;
req.comment = "TrailingStopUpdate";
if(!OrderSend(req, res))
{
Print("ModifyPositionSL: OrderSend fail. code=", GetLastError());
return;
}
if(res.retcode==TRADE_RETCODE_DONE || res.retcode==TRADE_RETCODE_PLACED)
Print("TrailingStopUpdate: new SL=", DoubleToString(newSL,_Digits), " for ticket=", ticket);
else
Print("TrailingStop retcode=", res.retcode, " err=", GetLastError());
}
/*****=====================================================================
* HasOpenOrPendingWithMagic
*======================================================================*****/
bool HasOpenOrPendingWithMagic(long magic)
{
// check open positions
if(PositionSelect(_Symbol))
{
if((long)PositionGetInteger(POSITION_MAGIC) == magic)
return true;
}
// check pending
int totalOrders = (int)OrdersTotal();
for(int i=0; i<totalOrders; i++)
{
ulong ticket = OrderGetTicket(i);
if(OrderSelect(ticket))
{
long ordMagic = OrderGetInteger(ORDER_MAGIC);
string ordSymbol = OrderGetString(ORDER_SYMBOL);
if(ordMagic == magic && ordSymbol == _Symbol)
return true;
}
}
return false;
}
/*****=====================================================================
* CloseAllByMagic - End-of-Day
*======================================================================*****/
bool CloseAllByMagic(long magic)
{
bool success = true;
if(!CloseOnlyPendingEndOfDay)
{
// close open position(s)
if(CloseOpenOrdersEndOfDay && PositionSelect(_Symbol))
{
if((long)PositionGetInteger(POSITION_MAGIC) == magic)
{
ulong ticket = PositionGetInteger(POSITION_TICKET);
double vol = PositionGetDouble(POSITION_VOLUME);
long pType = PositionGetInteger(POSITION_TYPE);
double cPx = (pType==POSITION_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_BID)
: SymbolInfoDouble(_Symbol, SYMBOL_ASK);
MqlTradeRequest clReq;
MqlTradeResult clRes;
ZeroMemory(clReq);
ZeroMemory(clRes);
clReq.action = TRADE_ACTION_DEAL;
clReq.symbol = _Symbol;
clReq.volume = vol;
clReq.magic = magic;
clReq.position = ticket;
clReq.deviation = Slippage;
clReq.type = (pType==POSITION_TYPE_BUY)? ORDER_TYPE_SELL: ORDER_TYPE_BUY;
clReq.price = cPx;
clReq.comment = "FadePivot2_v4 EoD Close";
if(!OrderSend(clReq, clRes) || clRes.retcode != TRADE_RETCODE_DONE)
{
Print("CloseAllByMagic: close pos retcode=", clRes.retcode,
", lastErr=", GetLastError());
success = false;
}
else
{
Print("Closed position #", ticket, " EoD.");
RemovePosition(ticket);
}
}
}
}
// remove pending
int totalOrders = (int)OrdersTotal();
for(int i=totalOrders-1; i>=0; i--)
{
ulong ticket = OrderGetTicket(i);
if(OrderSelect(ticket))
{
long omagic = OrderGetInteger(ORDER_MAGIC);
string sym = OrderGetString(ORDER_SYMBOL);
long otype = OrderGetInteger(ORDER_TYPE);
if(omagic == magic && sym == _Symbol)
{
if(otype == ORDER_TYPE_BUY_LIMIT || otype == ORDER_TYPE_SELL_LIMIT ||
otype == ORDER_TYPE_BUY_STOP || otype == ORDER_TYPE_SELL_STOP)
{
MqlTradeRequest odReq;
MqlTradeResult odRes;
ZeroMemory(odReq);
ZeroMemory(odRes);
odReq.action = TRADE_ACTION_REMOVE;
odReq.order = ticket;
odReq.symbol = _Symbol;
odReq.magic = magic;
odReq.comment= "FadePivot2_v4 EoD Remove";
if(!OrderSend(odReq, odRes) || odRes.retcode != TRADE_RETCODE_DONE)
{
Print("CloseAllByMagic: remove order retcode=", odRes.retcode,
", lastErr=", GetLastError());
success = false;
}
else
{
Print("Removed pending order #", ticket, " EoD.");
}
}
}
}
}
return success;
}
/*****=====================================================================
* Array-based "map" for best price handling
*======================================================================*****/
void AddOrUpdateBestPrice(ulong ticket, long posType, double newPrice)
{
// For BUY, newPrice is a new "max price so far"
// For SELL, newPrice is a new "min price so far"
// try to find the ticket in posTicketArray
for(int i=0; i<posCount; i++)
{
if(posTicketArray[i] == ticket)
{
// found
bestPriceArray[i] = newPrice;
return;
}
}
// not found => add (if we have space)
if(posCount < MAX_POSITIONS)
{
posTicketArray[posCount] = ticket;
bestPriceArray[posCount] = newPrice;
posCount++;
}
}
double GetBestPrice(ulong ticket, long posType)
{
// returns stored best price or 0 if not found
for(int i=0; i<posCount; i++)
{
if(posTicketArray[i] == ticket)
{
return bestPriceArray[i];
}
}
return 0.0;
}
void RemovePosition(ulong ticket)
{
// if a position is closed or forcibly removed
for(int i=0; i<posCount; i++)
{
if(posTicketArray[i] == ticket)
{
// shift arrays down by 1
for(int j=i; j<posCount-1; j++)
{
posTicketArray[j] = posTicketArray[j+1];
bestPriceArray[j] = bestPriceArray[j+1];
}
posCount--;
break;
}
}
}
/*****=====================================================================
* Cleanup any closed positions from the best price array
*======================================================================*****/
void CleanUpClosedPositions()
{
// We loop through our array of known tickets and see if they're still open
for(int i= posCount-1; i>=0; i--)
{
ulong storedTicket = posTicketArray[i];
// check if there's still a position with this ticket
bool found = false;
uint totalPos = PositionsTotal();
for(uint p=0; p<totalPos; p++)
{
if(PositionGetTicket(p) == storedTicket)
{
found = true;
break;
}
}
if(!found)
{
// remove from arrays
for(int j=i; j<posCount-1; j++)
{
posTicketArray[j] = posTicketArray[j+1];
bestPriceArray[j] = bestPriceArray[j+1];
}
posCount--;
}
}
}