#define BOT_NAME "ReticoloFX_Basket_USD"
#include <stdlib.mqh>

extern double lot_size = 0.01;
extern double target_profit = 10.0;

double initStep = 3.0;
bool production = TRUE;

extern int minimum_step = 20;
extern bool stop_after_close = FALSE;
extern bool trend_following = TRUE;
extern bool closeby_enabled = TRUE;
extern bool on_hold = FALSE;
extern bool show_next_trades = TRUE;
extern bool show_open_trades = TRUE;
extern bool show_alert = FALSE;
extern bool play_sound = FALSE;
extern string comment = BOT_NAME;

double atrMultiplier = 1.0;
double slippage = 5.0;
int minBars = 30;
int forcePeriod = PERIOD_H4;
int step = 30;
int numBuy, numSell, last, magic;
double nPoint, maxDD;
bool followBuy = FALSE;
bool followSell = FALSE;
string gClose, gMaxDD, oNextBuy, oNextSell;
double nextBuy = 0.0;
double nextSell = 0.0;
string pairs[];
bool stopped = FALSE;
int buyTickets[], sellTickets[];

int init () {
  magic = calcMagic (BOT_NAME);
  string s = magic;
  gClose = s + "_CLOSE";
  gMaxDD = s + "_MAXDD";

  if (! GlobalVariableCheck (gClose)) {
    GlobalVariableSet (gClose, 0);
  }

  if (! GlobalVariableCheck (gMaxDD)) {
    GlobalVariableSet (gMaxDD, 0);
  }

  if (Digits == 3 || Digits == 5) {
    nPoint = 10.0 * Point;
  } else {
    nPoint = Point;
  }

  s = StringSubstr (Symbol (), 6, 0);
  ArrayResize (pairs, 7);
  pairs[0] = "AUDUSD" + s;
  pairs[1] = "USDCAD" + s;
  pairs[2] = "USDCHF" + s;
  pairs[3] = "EURUSD" + s;
  pairs[4] = "GBPUSD" + s;
  pairs[5] = "USDJPY" + s;
  pairs[6] = "NZDUSD" + s;

  if (show_next_trades) {
    oNextBuy = BOT_NAME + "_" + Symbol () + "_NEXT_BUY";
    ObjectCreate (oNextBuy, OBJ_HLINE, 0, 0, 0);
    ObjectSet (oNextBuy, OBJPROP_COLOR, Blue);
    ObjectSet (oNextBuy, OBJPROP_STYLE, STYLE_DASH);

    oNextSell = BOT_NAME + "_" + Symbol () + "_NEXT_SELL";
    ObjectCreate (oNextSell, OBJ_HLINE, 0, 0, 0);
    ObjectSet (oNextSell, OBJPROP_COLOR, Red);
    ObjectSet (oNextSell, OBJPROP_STYLE, STYLE_DASH);
  }

  if (production == FALSE) {
    step = initStep;

    if (step <= 0) {
      step = minimum_step;
    }
  }

  return (0);
}

int deinit () {
  ObjectsDeleteAll ();
  return (0);
}

int start () {
  bool pairok = FALSE;
  int prevCloseNow, n, closeNow, nClosed;
  double profit;
  string s, cbars, status;

  for (int i = 0; i < ArraySize (pairs); i ++) {
    if (pairs[i] == Symbol ()) {
      pairok = TRUE;
      break;
    }
  }

  if (pairok == FALSE) {
    Comment ("\nATTENTION PLEASE!" + "\n "
    + BOT_NAME + " is not made to work on " + Symbol ());
    return (0);
  }

  if (production && Period () != forcePeriod) {
    Comment ("\nATTENTION PLEASE!" + "\n "
    + BOT_NAME + " is made to run on 4H timeframe.");
    return (0);
  }

  if (production && Bars < minBars) {
    Comment ("\nATTENTION PLEASE!" + "\n "
    + Symbol () + " has not enough historical data to run.");
    return (0);
  }

  prevCloseNow = GlobalVariableGet (gClose);
  numBuy = numSymType (Symbol (), OP_BUY, magic);
  numSell = numSymType (Symbol (), OP_SELL, magic);

  if (production) {
    step = iATR (Symbol (), forcePeriod, minBars, 0) * atrMultiplier / nPoint;

    if (step < minimum_step) {
      step = minimum_step;
    }
  }

  if (numBuy == 0 && numSell == 0) {
    stopped = stop_after_close;

    if (nextBuy == 0.0 || nextSell == 0.0 || stopped) {
      setNextBuy (Ask + step * nPoint);
      setNextSell (Bid - step * nPoint);
    }

    if (prevCloseNow == 0 && stopped == FALSE && on_hold == FALSE) {
      if (followBuy
      && orderSendReliable (Symbol (), OP_BUY, lot_size, Ask, slippage, 0, 0,
        calcComment (numBuy), magic, 0, Green) > 0) {
        followBuy = FALSE;
      } else
      if (followSell
      && orderSendReliable (Symbol (), OP_SELL, lot_size, Bid, slippage, 0, 0,
        calcComment (numSell), magic, 0, Red) > 0) {
        followSell = FALSE;
      }
    }
  } else
  if (numBuy > 0 && numSell == 0) {
    setNextBuy (maxBuyPrice (magic) + step * nPoint);
    setNextSell (minBuyPrice (magic) - (Ask - Bid) - step * 2 * nPoint);
  } else
  if (numBuy == 0 && numSell > 0) {
    setNextSell (minSellPrice (magic) - step * nPoint);
    setNextBuy (maxSellPrice (magic) + (Ask - Bid) + step * 2 * nPoint);
  } else
  if (numBuy > 0 && numSell > 0) {
    setNextBuy (maxBuyPrice (magic) + step * nPoint);
    setNextSell (minSellPrice (magic) - step * nPoint);
  }

  if (prevCloseNow == 0 && stopped == FALSE && on_hold == FALSE) {
    if (Ask >= nextBuy) {
      orderSendReliable (Symbol (), OP_BUY, lot_size, Ask, slippage, 0, 0,
        calcComment (numBuy), magic, 0, Green);
    } else
    if (Bid <= nextSell) {
      orderSendReliable (Symbol (), OP_SELL, lot_size, Bid, slippage, 0, 0,
        calcComment (numSell), magic, 0, Red);
    }
  }

  n = numType (OP_BUY, magic) + numType (OP_SELL, magic);

  if (n == 0) {
    GlobalVariableSet (gClose, 0);
  }

  profit = ordersProfit (magic);
  closeNow = GlobalVariableGet (gClose);

  if (closeNow == 0) {
    last = lastType (Symbol (), magic);
  }

  if (profit >= target_profit || closeNow > 0) {
    if (closeby_enabled) {
      nClosed = OrdersCloseBy (Symbol (), magic);
    } else {
      nClosed = ordersClose (Symbol (), OP_BUY, magic)
      + ordersClose (Symbol (), OP_SELL, magic);
    }

    if (closeNow == 0) {
      GlobalVariableSet (gClose, 1);
    }

    if (play_sound) {
      PlaySound ("alert.wav");
    }

    if (show_alert && nClosed > 0) {
      Alert (BOT_NAME + ": " + nClosed + " orders closed on "
      + Symbol () + "!");
    }

    if (closeNow == 2) { // wtf?
      stop_after_close = TRUE;
    }

    if (stop_after_close) {
      stopped = TRUE;
    }

    setNextBuy (0);
    setNextSell (0);
    followBuy = FALSE;
    followSell = FALSE;

    if (trend_following) {
      if (last == OP_BUY) {
        followBuy = TRUE;
      } else
      if (last == OP_SELL) {
        followSell = TRUE;
      }
    }

    for (i = 0; i < 100; i ++) {
      s = BOT_NAME + "_" + Symbol () + "_TRADE_" + i;
      ObjectDelete (s);
    }
  }

  maxDD = GlobalVariableGet (gMaxDD);

  if (profit < maxDD) {
    maxDD = profit;
    GlobalVariableSet (gMaxDD, maxDD);
    Print ("Max Floating DD: $", DoubleToStr (maxDD, 2));
  }

  for (i = 0; i < Seconds () % 40; i ++) {
    cbars = cbars + "|";
  }

  if (stopped) {
    status = "STOPPED";
  } else
  if (on_hold) {
    status = "RUNNING (on hold)";
  } else
  if (stop_after_close) {
    status = "RUNNING (stop after close)";
  } else {
    status = "RUNNING";
  }

  Comment ("\n " + BOT_NAME + " " + "v1.4\n"
  + " by PimpMyEA.com\n"
  + " ======================\n"
  + " ", cbars, "\n"
  + " ----------------------------------------------------\n"
  + " Status: ", status, "\n"
  + " ----------------------------------------------------\n"
  + " " + Symbol () + " Spread: ", DoubleToStr ((Ask - Bid) / nPoint, 2), "\n"
  + " " + Symbol () + " Step: ", DoubleToStr (step, 2), "\n"
  + " " + Symbol () + " Next BUY @ ", DoubleToStr (nextBuy, Digits), "\n"
  + " " + Symbol () + " Next Sell @ ", DoubleToStr (nextSell, Digits), "\n"
  + " ----------------------------------------------------\n"
  + " " + Symbol () + " Orders: BUY ", numSymType (Symbol (), OP_BUY, magic),
    "  \\  SELL ", numSymType (Symbol (), OP_SELL, magic), "\n"
  + " " + Symbol () + " Orders Value: $",
    DoubleToStr (ordersSymProfit (Symbol (), magic), 2), "\n"
  + " " + Symbol () + " Closed Profit: $",
    DoubleToStr (closedSymProfit (Symbol (), magic), 2), "\n"
  + " ----------------------------------------------------\n"
  + " TOTAL Orders: BUY ", numType (OP_BUY, magic), "  \\  SELL ",
    numType (OP_SELL, magic), "\n"
  + " TOTAL Orders Value: $", DoubleToStr (profit, 2), "\n"
  + " TOTAL Closed Profit: $", DoubleToStr (closedProfit (magic), 2), "\n"
  + " TOTAL Max Floating DD: $", DoubleToStr (maxDD, 2), "\n"
  + " ----------------------------------------------------\n"
  + " Magic Number: ", magic, "\n"
  + " ----------------------------------------------------");

  if (Seconds () % 5 == 0) {
    for (i = 0; i < 8; i ++) {
      for (int j = 0; j < 12; j ++) {
        ObjectDelete ("background" + i + j);
        ObjectDelete ("background" + i + j + 1);
        ObjectDelete ("background" + i + j + 2);
        ObjectCreate ("background" + i + j, OBJ_LABEL, 0, 0, 0);
        ObjectSetText ("background" + i + j, "n", 30, "Wingdings", SteelBlue);
        ObjectSet ("background" + i + j, OBJPROP_XDISTANCE, 20 * i);
        ObjectSet ("background" + i + j, OBJPROP_YDISTANCE, 23 * j + 11);
      }
    }

    setNextBuy (nextBuy);
    setNextSell (nextSell);

    if (show_open_trades) {
      drawOrders (Symbol (), magic);
    }
  }

  return (0);
}

void setNextBuy (double p) {
  nextBuy = p;

  if (show_next_trades) {
    ObjectSet (oNextBuy, OBJPROP_PRICE1, p);
  }
}

void setNextSell (double p) {
  nextSell = p;

  if (show_next_trades) {
    ObjectSet (oNextSell, OBJPROP_PRICE1, p);
  }
}

string calcComment (int n) {
  if (StringLen (comment) > 0) {
    return (comment + "_" + (n + 1));
  }

  return ("");
}

int getArray (int &a[], string sym, int type, int magic) {
  int ret = 0;
  int n = OrdersTotal ();

  ArrayResize (a, 100);

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderSymbol () == sym && OrderType () == type
    && OrderMagicNumber () == magic) {
      a[ret] = OrderTicket ();
      ret ++;
    }
  }

  ArrayResize (a, ret);
  return (ret);
}

int numSymType (string sym, int type, int magic) {
  int ret = 0;
  int n = OrdersTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderSymbol () == sym && OrderType () == type
    && OrderMagicNumber () == magic) {
      ret ++;
    }
  }

  return (ret);
}

void drawOrders (string sym, int magic) {
  string s;
  int n = OrdersTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderSymbol () == sym && OrderMagicNumber () == magic) {
      s = BOT_NAME + "_" + Symbol () + "_TRADE_" + i;

      if (ObjectFind (s) == - 1) {
        ObjectCreate (s, OBJ_HLINE, 0, 0, 0);
        ObjectSet (s, OBJPROP_COLOR, Yellow);
        ObjectSet (s, OBJPROP_STYLE, STYLE_DASHDOT);
      }

      ObjectSet (s, OBJPROP_PRICE1, OrderOpenPrice ());
    }
  }

  return;
}

int numType (int type, int magic) {
  int ret = 0;
  int n = OrdersTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderType () == type && OrderMagicNumber () == magic) {
      ret ++;
    }
  }

  return (ret);
}

int lastType (string sym, int magic) {
  int ret = - 1;
  int n = OrdersTotal ();

  for (int i = n - 1; i >= 0; i --) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderSymbol () == sym && OrderMagicNumber () == magic) {
      return (OrderType ());
    }
  }

  return (ret);
}

double ordersProfit (int magic) {
  double ret = 0;
  int n = OrdersTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderMagicNumber () == magic) {
      ret += OrderProfit () + OrderSwap () + OrderCommission ();
    }
  }

  return (ret);
}

double ordersSymProfit (string sym, int magic) {
  double ret = 0;
  int n = OrdersTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderSymbol () == sym && OrderMagicNumber () == magic) {
      ret += OrderProfit () + OrderSwap () + OrderCommission ();
    }
  }

  return (ret);
}

int closeArrays (int buy[], int sell[]) {
  int ret = 0;
  int nbuy = ArraySize (buy);
  int nsell = ArraySize (sell);

  for (int i = 0; i < MathMax (nbuy, nsell); i ++) {
    if (i < nbuy && i < nsell) {
      if (orderCloseByReliable (buy[i], sell[i], Yellow)) {
        ret += 2;
      }
    } else {
      if (i < nbuy) {
        OrderSelect (buy[i], SELECT_BY_TICKET, MODE_TRADES);

        if (orderCloseReliable (OrderTicket (), OrderLots (), Bid, slippage,
          Blue)) {
          ret ++;
        }
      }

      if (i < nsell) {
        OrderSelect (sell[i], SELECT_BY_TICKET, MODE_TRADES);

        if (orderCloseReliable (OrderTicket (), OrderLots (), Ask, slippage,
          Red)) {
          ret ++;
        }
      }
    }
  }

  return (ret);
}

int ordersCloseSymType (string sym, int type, int magic) {
  int ret = 0;
  int n = OrdersTotal ();

  for (int i = n; i >= 0; i --) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderSymbol () == sym && OrderType () == type
    && OrderMagicNumber () == magic) {
      if (((OrderType () == OP_BUY)
      && orderCloseReliable (OrderTicket (), OrderLots (), Bid, slippage, Blue))
      || ((OrderType () == OP_SELL)
      && orderCloseReliable (OrderTicket (), OrderLots (), Ask, slippage, Red))) {
        ret ++;
      }
    }
  }

  return (ret);
}

int ordersClose (string sym, int type, int magic) {
  for (int i = 0; numSymType (Symbol (), type, magic) > 0;
    i += ordersCloseSymType (Symbol (), type, magic)) {
    RefreshRates ();
  }

  return (i);
}

int OrdersCloseBy (string sym, int magic) {
  for (int ret = 0; getArray (buyTickets, Symbol (), OP_BUY, magic)
  + getArray (sellTickets, Symbol (), OP_SELL, magic) > 0;
    ret += closeArrays (buyTickets, sellTickets)) {
    RefreshRates ();
  }

  return (ret);
}

double closedProfit (int magic) {
  double ret = 0;
  int n = OrdersHistoryTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_HISTORY);

    if (OrderMagicNumber () == magic) {
      ret += OrderProfit () + OrderSwap () + OrderCommission ();
    }
  }

  return (ret);
}

double closedSymProfit (string sym, int magic) {
  double ret = 0;
  int n = OrdersHistoryTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_HISTORY);

    if (OrderSymbol () == sym && OrderMagicNumber () == magic) {
      ret += OrderProfit () + OrderSwap () + OrderCommission ();
    }
  }

  return (ret);
}

double maxBuyPrice (int magic) {
  double ret = 0;
  int n = OrdersTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderSymbol () == Symbol () && OrderMagicNumber () == magic
    && (OrderType () == OP_BUY || OrderType () == OP_BUYSTOP)) {
      ret = MathMax (ret, OrderOpenPrice ());
    }
  }

  return (ret);
}

double minSellPrice (int magic) {
  double ret = 999999999;
  int n = OrdersTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderSymbol () == Symbol () && OrderMagicNumber () == magic
    && (OrderType () == OP_SELL || OrderType () == OP_SELLSTOP)) {
      ret = MathMin (ret, OrderOpenPrice ());
    }
  }

  return (ret);
}

double minBuyPrice (int magic) {
  double ret = 999999999;
  int n = OrdersTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderSymbol () == Symbol () && OrderMagicNumber () == magic
    && (OrderType () == OP_BUY || OrderType () == OP_BUYSTOP)) {
      ret = MathMin (ret, OrderOpenPrice ());
    }
  }

  return (ret);
}

double maxSellPrice (int magic) {
  double ret = 0;
  int n = OrdersTotal ();

  for (int i = 0; i < n; i ++) {
    OrderSelect (i, SELECT_BY_POS, MODE_TRADES);

    if (OrderSymbol () == Symbol () && OrderMagicNumber () == magic
    && (OrderType () == OP_SELL || OrderType () == OP_SELLSTOP)) {
      ret = MathMax (ret, OrderOpenPrice ());
    }
  }

  return (ret);
}

int orderSendReliable (string sym, int type, double lot, double price,
  int slippage, double sl, double tp, string com = "", int magic = 0,
  int exp = 0, color col = - 1) {
  int r, e;

  Print ("orderSendReliable (" + sym + "," + type + "," + lot + "," + price
  + "," + slippage + "," + sl + "," + tp + "," + com + "," + magic + ","
  + exp + "," + col + ")");

  while (true) {
    if (IsStopped ()) {
      Print ("orderSendReliable (): Trading is stopped!");
      return (- 1);
    }

    RefreshRates ();

    if (type == OP_BUY) {
      price = Ask;
    } else
    if (type == OP_SELL) {
      price = Bid;
    }

    if (! IsTradeContextBusy ()) {
      r = OrderSend (sym, type, lot,
        NormalizeDouble (price, MarketInfo (sym, MODE_DIGITS)), slippage,
        NormalizeDouble (sl, MarketInfo (sym, MODE_DIGITS)),
        NormalizeDouble (tp, MarketInfo (sym, MODE_DIGITS)), com, magic,
        exp, col);

      if (r > 0) {
        Print ("orderSendReliable(): Success! Ticket: " + r);
        return (r);
      }

      e = GetLastError ();

      if (isTempError (e)) {
        Print ("orderSendReliable(): Temporary Error: " + e + " "
        + ErrorDescription (e) + ". waiting.");
      } else {
        Print ("orderSendReliable(): Permanent Error: " + e + " "
        + ErrorDescription (e) + ". giving up.");
        return (- 1);
      }
    } else {
      Print ("orderSendReliable(): Must wait for trade context");
    }

    Sleep (MathRand () / 10);
  }

  return (- 1);
}

bool orderCloseReliable (int ticket, double lots, double price, int slippage,
  color col = - 1) {
  int e;

  Print ("orderCloseReliable()");
  OrderSelect (ticket, SELECT_BY_TICKET, MODE_TRADES);

  while (true) {
    if (IsStopped ()) {
      Print ("orderCloseReliable(): Trading is stopped!");
      return (FALSE);
    }

    RefreshRates ();

    if (OrderType () == OP_BUY) {
      price = Bid;
    } else
    if (OrderType () == OP_SELL) {
      price = Ask;
    }

    if (! IsTradeContextBusy ()) {
      if (OrderClose (ticket, lots,
        NormalizeDouble (price, MarketInfo (OrderSymbol (), MODE_DIGITS)),
        slippage, col)) {
        Print ("orderCloseReliable(): Success!");
        return (TRUE);
      }

      e = GetLastError ();

      if (isTempError (e)) {
        Print ("orderCloseReliable(): Temporary Error: " + e + " "
        + ErrorDescription (e) + ". waiting.");
      } else {
        Print ("orderCloseReliable(): Permanent Error: " + e + " "
        + ErrorDescription (e) + ". giving up.");
        return (FALSE);
      }
    } else {
      Print ("orderCloseReliable(): Must wait for trade context");
    }

    Sleep (MathRand () / 10);
  }

  return (FALSE);
}

bool orderCloseByReliable (int first, int second, color col = - 1) {
  int e;

  Print ("orderCloseByReliable()");

  while (true) {
    if (IsStopped ()) {
      Print ("orderCloseByReliable(): Trading is stopped!");
      return (FALSE);
    }

    if (! IsTradeContextBusy ()) {
      if (OrderCloseBy (first, second, col)) {
        Print ("orderCloseByReliable(): Success!");
        return (TRUE);
      }

      e = GetLastError ();

      if (isTempError (e)) {
        Print ("orderCloseByReliable(): Temporary Error: " + e + " "
        + ErrorDescription (e) + ". waiting.");
      } else {
        Print ("orderCloseByReliable(): Permanent Error: " + e + " "
        + ErrorDescription (e) + ". giving up.");
        return (FALSE);
      }
    } else {
      Print ("orderCloseByReliable(): Must wait for trade context");
    }

    Sleep (MathRand () / 10);
  }

  return (FALSE);
}

bool isTempError (int i) {
  return (i == 0 || i == 2 || i == 4 || i == 6 || i == 132 || i == 135
  || i == 129 || i == 136 || i == 137 || i == 138 || i == 128 || i == 146);
}

int calcMagic (string s) {
  int c;
  int ret = 0;

  if (IsTesting ()) {
    s = "_" + s;
  }

  for (int i = 0; i < StringLen (s); i ++) {
    c = StringGetChar (s, i);
    ret = ror (ret + c, 5);
  }

  for (i = 0; i < StringLen (s); i ++) {
    c = StringGetChar (s, i);
    ret = ror (ret + c, c & 15);
  }

  for (i = StringLen (s); i > 0; i --) {
    c = StringGetChar (s, i - 1);
    ret = ror (ret + c, ret & 15);
  }

  return (ret & EMPTY_VALUE);
}

int ror (int i, int s) {
  int t = i & (1 << s - 1);
  i >>= s;
  i |= t << (32 - s);
  return (i);
}
