//+------------------------------------------------------------------+
//|                                        TheHolyMackerel_v1.7.mq4 |
//|              Copyright © 2009, Mark Johnson. All rights reserved |
//|              with some additions by charvo & mbkennel            |
//|                                        http://www.metaquotes.net |
//+------------------------------------------------------------------+


// this is a customized version of HG v1.6
// fixed lot size option
// cleaned up code
// changed RSI "active" vs "inactive" -- I like slowing things down to RSI=4 for H1, M15 in inactive hours
// more strict checking on open orders by both ticket and position,
//   immediately after opening an order OrderSelect() by MODE_TRADES can fail to
//   return recently added order, so multiple orders were placed.
//   This code requires both a check by ticket and by open trades.
// writes informational messages regarding trends to log every 5 minutes.
// uses LibGMT.mqh for time calcs
// uses LibOrderReliable.mqh for orders.  Supports ECN.
// corrected loop over open orders: was previously <= OrdersTotal(), should be < OrdersTotal()

/* updates for V1.7

*) Trend determination has new options
1) original 4 TF scooby
2) modification of #1 excluding dailiy
3) EMA(25) > EMA(50) > EMA(100) (1H) type of trend
        
*) Uses LaguerreRSI for actual entry instead of M5 RSI
    
*) TakeProfit/Stop Loss default set back to Scooby THG 1.6 Original (smaller take profit, higher win rate, larger stop), made
made configurable.
        
Still not yet implemented: news filter.       

*/


#property show_inputs
#include <LibOrderReliable.mqh>

#include <LibGMT.mqh>


//+------------------------------------------------------------------+
//| expert External variables                                        |
//+------------------------------------------------------------------+

extern int Magic = 1603;
extern string Key = "TheHolyMackerel v1.7";

extern int OrderLimit = 6;
extern bool LotFixed = false;
extern double fixedLot = 0.1;
//extern string msg1="Set the next param to TRUE for ECNs which require two step orders.";
extern bool UseTwoStepOrders = true; // true is default since it is usually safe to use on non ECN
extern int TrendMethod=2; // 1 == 4 TF 20/200 EMA, 2 is 3 TF 20/200 EMA, 3 is EMA(25)/EMA(50)/EMA(100) on H1
extern int TPSL_ATR_period= 14;
extern int TPSL_ATR_TF    = 15; 
extern double TP_multiplier = 2.0;
extern double SL_multiplier = 4.0; 
extern double LaguerreRSIgamma=0.5;
extern double LaguerreRSIentryCriterion =0.10;
extern double LaguerreRSIfireCriterion  = 0.15;

/*
  New 1.7 LaguerreRSI logic.

  To set up a buy, we must have the MTF RSI's in over-sold and 5M Laguerre RSI < LaguerreRSIentryCriterion.
  That will 'cock' a buy trigger.  As soon as 5M Laguerre RSI > LaguerreRSIexitCriterion, trigger pulled.
  and a buy order will be sent.  Of course reverse for sells.
   
  How to uncock a trade:  (a) enter a trade  (b) trend is no longer in right direction 
  (c) trading time is expired.   Future, once news filter is in place, news time?  
*/


/*
  deviation = iATR(Symbol(),TPSL_ATR_TF,TPSL_ATR_period)
  stop loss = SL_multiplier*deviation;
  take profit = TP_multiplier*deviation.
*/ 

//+------------------------------------------------------------------+
//| expert Internal variables                                        |
//+------------------------------------------------------------------+

//string Pairs[] = {"AUDJPY","AUDUSD","CHFJPY","EURGBP","EURJPY","EURUSD",
//                  "GBPJPY","GBPUSD","USDCAD","USDCHF","USDJPY"};

string Pairs[] = { "AUDJPY", "AUDUSD", "CADJPY", "CHFJPY", "EURAUD", "EURCAD", "EURGBP",
		   "EURJPY", "EURUSD", "GBPJPY", "GBPUSD", "USDCAD", "USDCHF", "USDJPY" };

/* tickets for actually open trades */ 
int PairTickets[] = { 0, 0, 0, 0, 0, 0, 0,
		      0, 0, 0, 0, 0, 0, 0 };

/* triggered to +1/-1 for buy/sell to set up trade
   once laguerreRSI on 5M is recovered */ 
   
int PairTriggers[] = { 0, 0, 0, 0, 0, 0, 0,
		       0, 0, 0, 0, 0, 0, 0 };


string Author = "Copyright © 2009, Mark Johnson. All rights reserved.";



int M05 = PERIOD_M5;
int M15 = PERIOD_M15;
int M30 = PERIOD_M30;
int H01 = PERIOD_H1;
int H04 = PERIOD_H4;
int D01 = PERIOD_D1;

int OB = 80;
int OS = 20;

int Decimals;
int Npairs;

//+------------------------------------------------------------------+
//| expert Initialization function                                   |
//+------------------------------------------------------------------+

int init()
{
  string Suffix = StringSubstr(Symbol(), 6, StringLen(Symbol()) - 6);

  for (int a = 0; a < ArraySize(Pairs); a++) {
    Pairs[a] = Pairs[a] + Suffix;
  }
  for (a = 0; a < ArraySize(Pairs); a++) {
    int ticket = TicketOfExistingTrade(Pairs[a], Magic);
    PairTickets[a] = ticket;
    if (ticket > 0)
      Print("init(): found existing open position ", Pairs[a], "/", ticket);
  }
  double Step = MarketInfo(Symbol(), MODE_LOTSTEP);

  if (Step == 0.01)
    Decimals = 2;
  if (Step == 0.10)
    Decimals = 1;
  if (Step == 1.00)
    Decimals = 0;
  if (Magic == 0)
    Magic = AccountNumber();
  Comment("\nWaiting for tick update...");

  return (0);
}

//+------------------------------------------------------------------+
//| expert Deinitialization function                                 |
//+------------------------------------------------------------------+

int deinit()
{
  Comment("");
  return (0);
}

//+------------------------------------------------------------------+
//| expert Start function                                            |
//+------------------------------------------------------------------+

int start()
{
  Npairs = ArraySize(Pairs);
	
  if (LotFixed == false) {
    double Lots = NormalizeDouble(AccountBalance() / 60000.0 * 100000.0 / MarketInfo(Symbol(),MODE_LOTSIZE), Decimals);
    //		Print ("Raw lots wanted = ", Lots);
    double Min = MarketInfo(Symbol(), MODE_MINLOT);
    double Max = MarketInfo(Symbol(), MODE_MAXLOT);

    if (Lots < Min)
      Lots = Min;
    if (Lots > Max)
      Lots = Max;
  } else {
    Lots = fixedLot;
    //		Print("Lots fixed by user input to ",fixedLot);
  } 
	
  string Status = "Trading enabled...";

  int gmtHHMM, gmtday;
  GetGMTDayHHMM(gmtday, gmtHHMM);

  if ((gmtday == 5 && gmtHHMM >= 1200) ||
      (gmtday == 0 && gmtHHMM < 2300) || gmtday == 6) {
    // do not trade on Friday past GMT 12:00, do not trade sunday before AUD/JPY open, or Saturday ever,
    // if you want add news filter
    Status = "Trading disabled for time blockout...";
    for (int i=0; i < Npairs; i++) {
      PairTriggers[i] = 0;
    }
  }

  Comment("\n", Author, "\n\n", Status, "\n\n", "Lots = ", DoubleToStr(Lots, 2));

  if (StringFind(Status, "disabled", 0) > 0)
    return (0);
  string trendmsg, notrendmsg;
  int ntrending = 0;

  static int saveminute = -1;
  for (int b = 0; b < ArraySize(Pairs); b++) {
    if (PosCount(Magic) >= OrderLimit) {
      Print("Position count too high: ", PosCount(Magic));
      break;
    }
    string thissym = Pairs[b];
    bool priceOK = MarketInfo(thissym, MODE_BID) > 0 && MarketInfo(thissym, MODE_ASK) > 0;

    if (!priceOK)
      Print("PriceOK failed for: ", thissym);
    if (priceOK) {
      //           Print("Price OK succeeded for: ",thissym);
      int RsiH4 = 3;
      int RsiH1 = 3;
      int RsiM15 = 3;
      int RsiM05 = 3;
      bool IsActive = IsPairActive(thissym, gmtHHMM);
      if (!IsActive) {
	RsiH1 = 4;  /* slow down time during inactive hours */
	RsiM15 = 4;
      }

      double RSI_M15 = iRSI(thissym, M15, RsiM15, PRICE_CLOSE, 0);
      double RSI_H01 = iRSI(thissym, H01, RsiH1, PRICE_CLOSE, 0);
      double RSI_H04 = iRSI(thissym, H04, RsiH4, PRICE_CLOSE, 0);

      string trendstr="none"; 
      int trend = Trend(thissym,trendstr); 

      if ((PairTriggers[b] != 0) && (PairTriggers[b] != trend)) {
	string bs;
	if (PairTriggers[b] == +1) {
	  bs = "BUY ";
	} else {
	  bs = "SELL ";
	}
	Print(thissym, " ", bs, "TRIGGER CANCELLED FOR TREND CHANGE." );
	PairTriggers[b] = 0;
      }

      if (trend != 0) {
	ntrending++;
	trendmsg = trendmsg + thissym + ":" + StringSubstr(trendstr, 0, 1);
	if (IsActive)
	  trendmsg = trendmsg + "a ";
	else
	  trendmsg = trendmsg + "i ";
      } else {
	notrendmsg = notrendmsg + thissym + ":" + trendstr + " ";
      }

      double LagRsiNow = LaguerreRSI(thissym, M05, LaguerreRSIgamma, 0);
      if (PairTriggers[b] == 0) {
	/* check if trade should be prepared for potential trade */
	if ((trend == +1) && RSI_M15 < OS && RSI_H01 < OS && RSI_H04 < OS && (LagRsiNow < LaguerreRSIentryCriterion)) {
	  if (AbleToTrade(thissym, PairTickets[b])) {
	    Print(thissym, " long trigger, RSI H4:", RSI_H04, " RSI_H1:", RSI_H01, " RSI_M15:", RSI_M15, " LagRSI=",LagRsiNow);
	    PairTriggers[b] = +1; 
	  } 
	}
	if ((trend == -1) && RSI_M15 > OB && RSI_H01 > OB && RSI_H04 > OB && (LagRsiNow > (1.0-LaguerreRSIentryCriterion))) {
	  if (AbleToTrade(thissym, PairTickets[b])) {
	    Print(thissym, " short trigger, RSI H4:", RSI_H04, " RSI_H1:", RSI_H01, " RSI_M15:", RSI_M15, " LagRSI=",LagRsiNow);
	    PairTriggers[b] = -1;
	  }
	}
      } else {
	/* pair trigger is set! so check if we need to send and order  */ 
	if ((PairTriggers[b] == +1) && (LagRsiNow > LaguerreRSIfireCriterion) && AbleToTrade(thissym, PairTickets[b])) {
	  PairTickets[b] = 0;
	    PairTickets[b] = SendOrder(thissym, OP_BUY, Lots);
	    if (PairTickets[b] != 0) {
	      PairTriggers[b] = 0;
	    }
	}
	if ((PairTriggers[b] == -1) && (LagRsiNow < (1.0-LaguerreRSIfireCriterion)) && AbleToTrade(thissym, PairTickets[b])) {
	  PairTickets[b] = 0;
	  PairTickets[b] = SendOrder(thissym, OP_SELL, Lots);
	  if (PairTickets[b] != 0) {
	      PairTriggers[b] = 0;
	  }
	}
      } /* PairTriggers[b] == / != 0 */ 
    } /* end if PriceOK */
  }   /* master loop over array pairs */

  if (((Minute() - saveminute) > 5) || (Minute() < saveminute)) {
    Print("trend(",ntrending, "): ",trendmsg, "notrend(",(ArraySize(Pairs)-ntrending),"):",notrendmsg );
    saveminute = Minute();
  }
  
  return (0);
}

bool AbleToTrade(string symbol, int ticker)
{
  /* this is a computationally expensive function as it has to hit the server.
     use short-circuit if starting with less expensive (check by ticker) first. */
  if ( TradeExistTicker(ticker) || TradeExistsInOpenOrders(symbol,Magic) || !IsTradeAllowed() ) {
    return(false); 
  } else {
    return(true);
  }
}


//+------------------------------------------------------------------+
//| expert positions counter function                                            |
//+------------------------------------------------------------------+

int PosCount(int magic)
{
  int poscnt = 0;

  for (int c = 0; c < OrdersTotal(); c++) {
    if (OrderSelect(c, SELECT_BY_POS, MODE_TRADES) == true) {
      if (OrderMagicNumber() == magic)
	poscnt++;
    }
  }
  return (poscnt);
}

//+------------------------------------------------------------------+
//| expert TradeExists function                                      |
//+------------------------------------------------------------------+
bool TradeExistsInOpenOrders(string symbol, int magic)
{
  bool Found = false;

  for (int c = 0; c < OrdersTotal(); c++) {
    if (OrderSelect(c, SELECT_BY_POS, MODE_TRADES) == true) {
      if (OrderSymbol() == symbol && OrderMagicNumber() == magic) {
	Found = true;
	break;
      }
    }
  }
  return (Found);
}

/* returns true if there is an open position with the given ticket number */ 
bool TradeExistTicker(int ticket)
{
  GetLastError(); /* clear it */ 
  if (ticket <= 0)
    return (false);
  if (OrderSelect(ticket, SELECT_BY_TICKET)) {
    if (OrderCloseTime() == 0)
      return (true);   /* an open order */
    else
      return (false);  /* historical order */
  } else {
    Print("TradeExistTicker: OrderSelect by ticket failed for: ", ticket, " err=", GetLastError());
    return (true);  /* NOTE RETURN TRUE FOR THIS EA BECAUSE TRUE WILL AVOID MULTIPLE TRADES IN ERR CONDITIONS */
  }
}

int TicketOfExistingTrade(string symbol, int magic)
{
  /* return the ticker of an existing open trade on the given symbol, or -1 if not present. 
     The loop through OrderSelect() will pick out only open trades */
  int ticket = -1;

  for (int c = 0; c < OrdersTotal(); c++) {
    if (OrderSelect(c, SELECT_BY_POS, MODE_TRADES) == true) {
      if (OrderSymbol() == symbol && OrderMagicNumber() == magic) {
	if (ticket != -1)
	  Print("ALERT: MORE THAN ONE OPEN TRADE FOR ", symbol, " FOUND!");
	ticket = OrderTicket();
      }
    }
  }
  return (ticket);
}


//+------------------------------------------------------------------+
//| expert SendOrder function                                        |
//+------------------------------------------------------------------+

int SendOrder(string symbol, int dir, double lots)
{
  double price;
  double point;
  double sl;
  double tp;

  int ticket = -1;
  int spreadmax = 10;
	
  double deviation=iATR(symbol, TPSL_ATR_TF, TPSL_ATR_period,0);
  double take = TP_multiplier*deviation;
  double stop = SL_multiplier*deviation;
	
  if (MarketInfo(symbol, MODE_DIGITS) == 3 || MarketInfo(symbol, MODE_DIGITS) == 5)
    spreadmax *= 10;

  if (MarketInfo(symbol, MODE_SPREAD) > spreadmax) {
    Print("Spread too high!");
    return (-1);
  }

  //  digit = MarketInfo(symbol, MODE_DIGITS);
  // do we still want this? take += MarketInfo(symbol, MODE_SPREAD) * MarketInfo(symbol, MODE_POINT);

  if (dir == OP_BUY) {
    price = MarketInfo(symbol, MODE_ASK);
    sl = price-stop; /* OrderReliable will handle the normalization */
    tp = price+take;
  }

  if (dir == OP_SELL) {
    price = MarketInfo(symbol, MODE_BID);
    sl = price+stop;
    tp = price-take;
  }
	
  if (UseTwoStepOrders) {
    ticket = OrderSendReliable2Step(symbol, dir, lots, price, 3, sl, tp, Key, Magic, 0, Green);
  } else {
    ticket = OrderSendReliable(symbol, dir, lots, price, 3, sl, tp, Key, Magic, 0, Green);
  }

  if (ticket > 0) {
    if (OrderSelect(ticket,SELECT_BY_TICKET)) {
      OrderPrint();
    }
  }
  return (ticket);
}


bool IsCurrencyActive(string sym, int gmtHHMM)
{
  /* returns true if a 3-letter currency is active during the given GMT HHMM */
  if ((sym == "AUD") || (sym == "NZD")) {
    if ((gmtHHMM >= 2200) || (gmtHHMM < 0600))
      return (true);
    else
      return (false);
  }


  if (sym == "JPY") {
    if ((gmtHHMM >= 0000) && (gmtHHMM < 0800))
      return (true);
    else
      return (false);
  }


  if ((sym == "CAD") || (sym == "USD")) {
    if ((gmtHHMM >= 1300) && (gmtHHMM < 2100))
      return (true);
    else
      return (false);
  }

  if ((sym == "EUR") || (sym == "CHF")) {
    if ((gmtHHMM >= 0700) && (gmtHHMM < 1500))
      return (true);
    else
      return (false);
  }

  if (sym == "GBP") {
    if ((gmtHHMM >= 0800) && (gmtHHMM < 1600))
      return (true);
    else
      return (false);
  }
}

bool IsPairActive(string pair, int gmthhmm)
{
  return (IsCurrencyActive(StringSubstr(pair, 0, 3), gmthhmm) || IsCurrencyActive(StringSubstr(pair, 3, 3), gmthhmm));
}

//+------------------------------------------------------------------+
//| expert News function -- a contribution by mbkennel               |
//  http://www.forexfactory.com/showthread.php?p=3510269&highlight=newsexist#post3510269
//+------------------------------------------------------------------+
bool NewsExist()
{
  static int minuteSave = -1;
  static bool NewsSave = true;

  /* do not hammer server each tick */
  if (Minute() == minuteSave)
    return (NewsSave);
  minuteSave = Minute();
  bool News = false;

  for (int d = 0; d < ArraySize(Pairs); d++) {
    int MinutesSincePrevEvent = iCustom(NULL, 0, "Economic News", Pairs[d], true, false, false, true, true, 1, 0);
    int MinutesUntilNextEvent = iCustom(NULL, 0, "Economic News", Pairs[d], true, false, false, true, true, 1, 1);

    if (MinutesUntilNextEvent < 60 || MinutesSincePrevEvent < 15)
      News = true;
  }

  string xmlFileName = Month() + "-" + Day() + "-" + Year() + "-" + Symbol() + Period() + "-" + "FFCal.xml";
  int handle = FileOpen(xmlFileName, FILE_BIN | FILE_READ | FILE_WRITE);
  if (handle < 0)
    News = true;
  else
    FileClose(handle);
  NewsSave = News;
  return (News);
}

//+------------------------------------------------------------------+
//| expert Trend function                                            |
//+------------------------------------------------------------------+

int Trend(string sym, string& trendstr) {
  if (TrendMethod==1) {
    return(Trend_20_200_4TF(sym,trendstr));
  }
  if (TrendMethod==2) {
    return(Trend_20_200_3TF(sym,trendstr));
  }
  if (TrendMethod==3) {
    return(Trend_3_H1(sym,trendstr));
  }
}


int Trend_20_200_4TF(string sym, string& trendstr) {
    
  trendstr = Trend_2_EMA(sym, M15,20,200)+
    Trend_2_EMA(sym, H01,20,200)+
    Trend_2_EMA(sym, H04,20,200)+
    Trend_2_EMA(sym, D01,20,200);
    
  if (trendstr == "UUUU") {
    return(+1);
  }
  if (trendstr == "DDDD") {
    return(-1);
  }
  return(0);
}


int Trend_20_200_3TF(string sym, string& trendstr) {
    
  trendstr = Trend_2_EMA(sym, M15,20,200)+
    Trend_2_EMA(sym, H01,20,200)+
    Trend_2_EMA(sym, H04,20,200);    
  if (trendstr == "UUU") {
    return(+1);
  }
  if (trendstr == "DDD") {
    return(-1);
  }
  return(0);
}


int Trend_3_H1(string sym, string& trendstr) {
  trendstr = Trend_2_EMA(sym, H01,25,50)+
    Trend_2_EMA(sym, H01,50,100);    
  if (trendstr == "UU") {
    return(+1);
  }
  if (trendstr == "DD") {
    return(-1);
  }
  return(0);
}


string Trend_2_EMA(string symbol, int tf, int ema_small, int ema_large)
{
  string Pair_Trend = "F"; // Flat

  double EMAa = iMA(symbol, tf, ema_small, 0, MODE_EMA, PRICE_CLOSE, 0);
  double EMAb = iMA(symbol, tf, ema_large, 0, MODE_EMA, PRICE_CLOSE, 0);

  if (EMAa < EMAb)
    Pair_Trend = "D";  // Down
  if (EMAa > EMAb)
    Pair_Trend = "U";  // Up
  return (Pair_Trend);
}


//+------------------------------------------------------------------+
//| expert LaguerreRSI function |
//+------------------------------------------------------------------+ 
double LaguerreRSI(string strPair, int intTF, double dblGamma, int intShift)
{
  int intSize = 100;

  double dblRSI, dblUp, dblDown;
  double L[4], LA[4];

  ArrayInitialize( L, 0.0 );

  for(int inx = intSize; inx >= 0; inx--)
    {

      double Price = iMA(strPair,intTF,1,0,MODE_EMA,PRICE_CLOSE,inx+intShift);

      ArrayCopy(LA, L);
      dblUp = 0.0;
      dblDown = 0.0;

      L[0] = ( 1 - dblGamma ) * Price + ( dblGamma * LA[0] );
      L[1] = ( -dblGamma * L[0] ) + LA[0] + ( dblGamma * LA[1] );
      L[2] = ( -dblGamma * L[1] ) + LA[1] + ( dblGamma * LA[2] );
      L[3] = ( -dblGamma * L[2] ) + LA[2] + ( dblGamma * LA[3] );

      if(L[0] >= L[1]) dblUp = L[0] - L[1]; 
      else dblDown = L[1] - L[0];

      if(L[1] >= L[2]) dblUp = dblUp + L[1] - L[2]; 
      else dblDown = dblDown + L[2] - L[1];

      if(L[2] >= L[3]) dblUp = dblUp + L[2] - L[3]; 
      else dblDown = dblDown + L[3] - L[2];

      if(dblUp + dblDown != 0.0) dblRSI = dblUp / (dblUp + dblDown); 
    }

  return( dblRSI );

}


