//
// EA Studio Portfolio Expert Advisor
//
// Created with: Expert Advisor Studio
// Website: https://forexsb.com/expert-advisor-studio-app
//
// Copyright 2020, Forex Software Ltd.
//
// This Portfolio Expert works in MetaTrader 4.
// It opens separate positions for each strategy.
// Every position has an unique magic number,
// which corresponds to the index of the strategy.
//
#property copyright "Forex Software Ltd."
#property version "2.4"
#property strict
enum ENUM_MM
{
Fixed,
Risk
};
input ENUM_MM useMM = Fixed;
static input double Fix_Lot = 0.01; // Entry lots
static input int Base_Magic_Number = 100; // Base Magic Number
input int risk = 1; // RIsk
static input int atrPeriod = 14;
static input double atrMultipleSL = 2.5; // Stop Loss ATR Multiple
static input double atrMultipleTP = 1; // Take Profit ATR Multiple
#define TRADE_RETRY_COUNT 4
#define TRADE_RETRY_WAIT 100
#define OP_FLAT -1
// Session time is set in seconds from 00:00
const int sessionSundayOpen = 0; // 00:00
const int sessionSundayClose = 86400; // 24:00
const int sessionMondayThursdayOpen = 0; // 00:00
const int sessionMondayThursdayClose = 86400; // 24:00
const int sessionFridayOpen = 0; // 00:00
const int sessionFridayClose = 86400; // 24:00
const bool sessionIgnoreSunday = true;
const bool sessionCloseAtSessionClose = false;
const bool sessionCloseAtFridayClose = false;
const int strategiesCount = 100;
const double sigma = 0.000001;
datetime barTime;
int digits;
double stopLevel;
double pip;
bool setProtectionSeparately = false;
//---
double Entry_Amount;
int SL;
int TP;
//---
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
enum OrderScope
{
ORDER_SCOPE_UNDEFINED,
ORDER_SCOPE_ENTRY,
ORDER_SCOPE_EXIT
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
enum OrderDirection
{
ORDER_DIRECTION_NONE,
ORDER_DIRECTION_BUY,
ORDER_DIRECTION_SELL,
ORDER_DIRECTION_BOTH
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
struct Position
{
int Type;
int Ticket;
int MagicNumber;
double Lots;
double Price;
double StopLoss;
double TakeProfit;
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
struct Signal
{
int MagicNumber;
OrderScope Scope;
OrderDirection Direction;
int StopLossPips;
int TakeProfitPips;
bool IsTrailingStop;
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
// Calculate Dynamic Lot Size //
double calculateLotSize(double StopLossPoints)
{
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double tickVal = MarketInfo(Symbol(), MODE_TICKVALUE);
double lotSize = AccountBalance() * risk / 100 / (StopLossPoints * tickVal);
return MathMin(
maxLot,
MathMax(
minLot,
NormalizeDouble(lotSize / lotStep, 0) * lotStep // This rounds the lotSize to the nearest lotstep interval
)
);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
int OnInit()
{
barTime = Time[0];
digits = (int) MarketInfo(_Symbol, MODE_DIGITS);
stopLevel = MarketInfo(_Symbol, MODE_STOPLEVEL);
pip = GetPipValue();
const ENUM_INIT_RETCODE initRetcode = ValidateInit();
return (initRetcode);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OnTick()
{
//---
double atr = iATR(NULL, 0, atrPeriod, 0);
SL = (int)(atr * atrMultipleSL / Point);
TP = (int)(atr * atrMultipleTP / Point);
//---
if(IsForceSessionClose())
{
CloseAllPositions();
return;
}
if(Time[0]>barTime)
{
barTime=Time[0];
OnBar();
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OnBar()
{
if(IsOutOfSession()) return;
Signal signalList[];
SetSignals(signalList);
int signalsCount=ArraySize(signalList);
for(int i=0;i<signalsCount;i++)
{
Signal signal=signalList[i];
ManageSignal(signal);
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void ManageSignal(Signal &signal)
{
Position position=CreatePosition(signal.MagicNumber);
if(position.Type!=OP_FLAT && signal.Scope==ORDER_SCOPE_EXIT)
{
if(signal.Direction==ORDER_DIRECTION_BOTH ||
(position.Type==OP_BUY && signal.Direction==ORDER_DIRECTION_SELL) ||
(position.Type==OP_SELL && signal.Direction==ORDER_DIRECTION_BUY))
{
ClosePosition(position);
}
}
if(position.Type!=OP_FLAT && signal.Scope==ORDER_SCOPE_EXIT && signal.IsTrailingStop)
{
double trailingStop=GetTrailingStop(position,signal.StopLossPips);
Print(trailingStop);
ManageTrailingStop(position,trailingStop);
}
if(position.Type==OP_FLAT && signal.Scope==ORDER_SCOPE_ENTRY)
{
if(signal.Direction==ORDER_DIRECTION_BUY || signal.Direction==ORDER_DIRECTION_SELL)
{
OpenPosition(signal);
}
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
Position CreatePosition(const int magicNumber)
{
Position position;
position.MagicNumber = magicNumber;
position.Type = OP_FLAT;
position.Ticket = 0;
position.Lots = 0;
position.Price = 0;
position.StopLoss = 0;
position.TakeProfit = 0;
int total=OrdersTotal();
for(int pos=total-1; pos>=0; pos--)
{
if(OrderSelect(pos,SELECT_BY_POS,MODE_TRADES) &&
OrderSymbol() == _Symbol &&
OrderMagicNumber() == magicNumber &&
OrderCloseTime() == 0)
{
position.Type = OrderType();
position.Lots = OrderLots();
position.Ticket = OrderTicket();
position.Price = OrderOpenPrice();
position.StopLoss = OrderStopLoss();
position.TakeProfit = OrderTakeProfit();
break;
}
}
return (position);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
Signal CreateEntrySignal(const int strategyIndex,const bool canOpenLong,const bool canOpenShort,const int stopLossPips,const int takeProfitPips, const bool isTrailingStop)
{
Signal signal;
signal.MagicNumber = GetMagicNumber(strategyIndex);
signal.Scope = ORDER_SCOPE_ENTRY;
signal.Direction = canOpenLong&&canOpenShort ? ORDER_DIRECTION_BOTH : canOpenLong ? ORDER_DIRECTION_BUY : canOpenShort ? ORDER_DIRECTION_SELL : ORDER_DIRECTION_NONE;
signal.StopLossPips = stopLossPips;
signal.TakeProfitPips = takeProfitPips;
signal.IsTrailingStop = isTrailingStop;
return (signal);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
Signal CreateExitSignal(const int strategyIndex,const bool canCloseLong,const bool canCloseShorts,const int stopLossPips,const int takeProfitPips, const bool isTrailingStop)
{
Signal signal;
signal.MagicNumber = GetMagicNumber(strategyIndex);
signal.Scope = ORDER_SCOPE_EXIT;
signal.Direction = canCloseLong&&canCloseShorts ? ORDER_DIRECTION_BOTH : canCloseLong ? ORDER_DIRECTION_SELL : canCloseShorts ? ORDER_DIRECTION_BUY : ORDER_DIRECTION_NONE;
signal.StopLossPips = stopLossPips;
signal.TakeProfitPips = takeProfitPips;
signal.IsTrailingStop = isTrailingStop;
return (signal);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OpenPosition(Signal &signal)
{
for(int attempt=0; attempt<TRADE_RETRY_COUNT; attempt++)
{
int ticket = 0;
int lastError = 0;
bool modified = false;
int command = OrderDirectionToCommand(signal.Direction);
double amount = Entry_Amount;
double stopLoss = signal.StopLossPips>0 ? GetStopLoss(command, signal.StopLossPips) : 0;
double takeProfit = signal.TakeProfitPips>0 ? GetTakeProfit(command, signal.TakeProfitPips) : 0;
string comment = IntegerToString(signal.MagicNumber);
color arrowColor = command==OP_BUY ? clrGreen : clrRed;
if(IsTradeContextFree())
{
double price=MarketInfo(_Symbol,command==OP_BUY ? MODE_ASK : MODE_BID);
if(useMM == Fixed)
{
Entry_Amount = Fix_Lot;
}
if(useMM == Risk)
{
Entry_Amount = calculateLotSize(stopLoss);
}
//---
Print("--------- LOT -- ",Entry_Amount," ------------ ");
if(setProtectionSeparately)
{
ticket=OrderSend(_Symbol,command,amount,price,10,0,0,comment,signal.MagicNumber,0,arrowColor);
if(ticket>0 && (signal.StopLossPips>0 || signal.TakeProfitPips>0))
modified=OrderModify(ticket,0,stopLoss,takeProfit,0,clrBlue);
}
else
{
ticket=OrderSend(_Symbol,command,amount,price,10,stopLoss,takeProfit,comment,signal.MagicNumber,0,arrowColor);
lastError=GetLastError();
if(ticket<=0 && lastError==130)
{
ticket=OrderSend(_Symbol,command,amount,price,10,0,0,comment,signal.MagicNumber,0,arrowColor);
if(ticket>0 && (signal.StopLossPips>0 || signal.TakeProfitPips>0))
modified=OrderModify(ticket,0,stopLoss,takeProfit,0,clrBlue);
if(ticket>0 && modified)
{
setProtectionSeparately=true;
Print("Detected ECN type position protection.");
}
}
}
}
if(ticket>0) break;
lastError=GetLastError();
if(lastError!=135 && lastError!=136 && lastError!=137 && lastError!=138) break;
Sleep(TRADE_RETRY_WAIT);
Print("Open Position retry no: "+IntegerToString(attempt+2));
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void ClosePosition(Position &position)
{
for(int attempt=0; attempt<TRADE_RETRY_COUNT; attempt++)
{
bool closed;
int lastError=0;
if(IsTradeContextFree())
{
double price=MarketInfo(_Symbol,position.Type==OP_BUY ? MODE_BID : MODE_ASK);
closed=OrderClose(position.Ticket,position.Lots,price,10,clrYellow);
lastError=GetLastError();
}
if(closed)
{
position.Type = OP_FLAT;
position.Lots = 0;
position.Price = 0;
position.StopLoss = 0;
position.TakeProfit = 0;
break;
}
if(lastError==4108) break;
Sleep(TRADE_RETRY_WAIT);
Print("Close Position retry no: "+IntegerToString(attempt+2));
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void ModifyPosition(Position &position)
{
for(int attempt=0; attempt<TRADE_RETRY_COUNT; attempt++)
{
bool modified;
int lastError=0;
if(IsTradeContextFree())
{
modified=OrderModify(position.Ticket,0,position.StopLoss,position.TakeProfit,0,clrBlue);
lastError=GetLastError();
}
if(modified)
{
const int magicNumber=position.MagicNumber;
position=CreatePosition(magicNumber);
break;
}
if(lastError==4108)
break;
Sleep(TRADE_RETRY_WAIT);
Print("Modify Position retry no: "+IntegerToString(attempt+2));
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
for(int i=0;i<strategiesCount;i++)
{
int magicNumber=GetMagicNumber(i);
Position position=CreatePosition(magicNumber);
if(position.Type==OP_BUY || position.Type==OP_SELL)
ClosePosition(position);
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double GetStopLoss(int command,int stopLossPips)
{
if(stopLossPips==0) return (0);
double deltaPips = MathMax(pip*stopLossPips, _Point*stopLevel);
double referencePrice = MarketInfo(_Symbol, command==OP_BUY ? MODE_BID : MODE_ASK);
double stopLossPoints = command==OP_BUY ? referencePrice-deltaPips : referencePrice+deltaPips;
double finalStopLoss = NormalizeDouble(stopLossPoints, digits);
return (finalStopLoss);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double GetTakeProfit(int command,int takeProfitPips)
{
if(takeProfitPips==0) return (0);
double deltaPips = MathMax(pip*takeProfitPips, _Point*stopLevel);
double referencePrice = MarketInfo(_Symbol, command==OP_BUY ? MODE_BID : MODE_ASK);
double takeProfitPoints = command==OP_BUY ? referencePrice+deltaPips : referencePrice-deltaPips;
double finalTakeProfit = NormalizeDouble(takeProfitPoints, digits);
return (finalTakeProfit);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double GetTrailingStop(Position &position, int stopLoss)
{
double bid=MarketInfo(_Symbol,MODE_BID);
double ask=MarketInfo(_Symbol,MODE_ASK);
double stopLevelPoints=_Point*stopLevel;
double stopLossPoints=pip*stopLoss;
if(position.Type==OP_BUY)
{
double stopLossPrice=High[1]-stopLossPoints;
if(position.StopLoss<stopLossPrice-pip)
{
if(stopLossPrice<bid)
{
if(stopLossPrice>=bid-stopLevelPoints)
return (bid - stopLevelPoints);
else
return (stopLossPrice);
}
else
{
return (bid);
}
}
}
else if(position.Type==OP_SELL)
{
double stopLossPrice=Low[1]+stopLossPoints;
if(position.StopLoss>stopLossPrice+pip)
{
if(stopLossPrice>ask)
{
if(stopLossPrice<=ask+stopLevelPoints)
return (ask + stopLevelPoints);
else
return (stopLossPrice);
}
else
{
return (ask);
}
}
}
return (position.StopLoss);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void ManageTrailingStop(Position &position, double trailingStop)
{
double bid=MarketInfo(_Symbol,MODE_BID);
double ask=MarketInfo(_Symbol,MODE_ASK);
if(position.Type==OP_BUY && MathAbs(trailingStop-bid)<_Point)
{
ClosePosition(position);
}
else if(position.Type==OP_SELL && MathAbs(trailingStop-ask)<_Point)
{
ClosePosition(position);
}
else if(MathAbs(trailingStop-position.StopLoss)>_Point)
{
position.StopLoss=NormalizeDouble(trailingStop,digits);
ModifyPosition(position);
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool IsTradeContextFree()
{
if(IsTradeAllowed()) return (true);
uint startWait=GetTickCount();
Print("Trade context is busy! Waiting...");
while(true)
{
if(IsStopped()) return (false);
uint diff=GetTickCount()-startWait;
if(diff>30*1000)
{
Print("The waiting limit exceeded!");
return (false);
}
if(IsTradeAllowed())
{
RefreshRates();
return (true);
}
Sleep(TRADE_RETRY_WAIT);
}
return (true);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool IsOutOfSession()
{
MqlDateTime time0; TimeToStruct(Time[0],time0);
int weekDay = time0.day_of_week;
long timeFromMidnight = Time[0]%86400;
int periodLength = PeriodSeconds(_Period);
bool skipTrade = false;
if(weekDay==0)
{
if(sessionIgnoreSunday) return true;
int lastBarFix=sessionCloseAtSessionClose ? periodLength : 0;
skipTrade=timeFromMidnight<sessionSundayOpen || timeFromMidnight+lastBarFix>sessionSundayClose;
}
else if(weekDay<5)
{
int lastBarFix=sessionCloseAtSessionClose ? periodLength : 0;
skipTrade=timeFromMidnight<sessionMondayThursdayOpen || timeFromMidnight+lastBarFix>sessionMondayThursdayClose;
}
else
{
int lastBarFix=sessionCloseAtFridayClose || sessionCloseAtSessionClose ? periodLength : 0;
skipTrade=timeFromMidnight<sessionFridayOpen || timeFromMidnight+lastBarFix>sessionFridayClose;
}
return (skipTrade);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool IsForceSessionClose()
{
if(!sessionCloseAtFridayClose && !sessionCloseAtSessionClose)
return (false);
MqlDateTime time0; TimeToStruct(Time[0],time0);
int weekDay = time0.day_of_week;
long timeFromMidnight = Time[0]%86400;
int periodLength = PeriodSeconds(_Period);
bool forceExit=false;
if(weekDay==0 && sessionCloseAtSessionClose)
{
forceExit=timeFromMidnight+periodLength>sessionSundayClose;
}
else if(weekDay<5 && sessionCloseAtSessionClose)
{
forceExit=timeFromMidnight+periodLength>sessionMondayThursdayClose;
}
else if(weekDay==5)
{
forceExit=timeFromMidnight+periodLength>sessionFridayClose;
}
return (forceExit);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
datetime Time(int bar)
{
return Time[bar];
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double Open(int bar)
{
return Open[bar];
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double High(int bar)
{
return High[bar];
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double Low(int bar)
{
return Low[bar];
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double Close(int bar)
{
return Close[bar];
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double GetPipValue()
{
switch((int) MarketInfo(_Symbol,MODE_DIGITS))
{
case 5: case 4: return (0.0001);
case 3: case 2: return (0.01);
case 1: return (0.1);
}
return (1);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
int GetMagicNumber(int strategyIndex)
{
int magic=1000*Base_Magic_Number+strategyIndex;
return (magic);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
int OrderDirectionToCommand(OrderDirection dir)
{
if(dir==ORDER_DIRECTION_BUY) return (OP_BUY);
if(dir==ORDER_DIRECTION_SELL) return (OP_SELL);
return (OP_FLAT);
}