/*
10.06.2012 v2.4.002.01 Bugs fixed. Code optimized. Almost twice speed up.
10.06.2012 v2.4.002.02 Optimized default settings for 2011-2012 years.
*/

#property copyright "ForexHacked mod 2.4.002.02"
#property link      "http://forum.tradelikeapro.ru"

#include <stderror.mqh>

extern string MagicNumberInfo = "Magic Number Must be UNIQUE for each chart!";
extern int MagicNumber = 1002401;
extern string TradeChartInfo = "TradeChart = name of currencies pair used for trading";
extern string TradeChart = "GBPUSD";

extern double Lots = 0.02;
extern double TakeProfit = 49.0;
extern double Booster = 1.65;
extern double PipStarter = 37.2;

extern int MaxBuyOrders = 9;
extern int MaxSellOrders = 9;
extern int Slippage = 2;
extern bool AllowiStopLoss = true;
extern double iStopLoss = 295;

extern bool AllowExtraOrders = false;
extern bool Allow1Buy = true;
extern bool Allow1Sell = true;
extern int MAPeriod = 11;
extern double SARStep = 0.0636;
extern string EA_Name = "ForexHacked mod 2.4.002.02";

extern double StopLossPct = 100;
extern double TakeProfitPct = 100;

extern int StartHour = 0;
extern int StartMinute = 0;
extern int StopHour = 0;
extern int StopMinute = 0;
extern int StartingTradeDay = 0;
extern int EndingTradeDay = 7;

bool
gIsNotOptimization,
gIsNotReady = false,
gLowBalance = false,
gIsTesting,
gModifiedTP[2];

int
gMAShift = 0,
gMAMethod = MODE_LWMA,
gMAPrice = PRICE_WEIGHTED,
gLotStepDigits,
gFileHandler = 0,
gStartMinutes,
gStopMinutes,
gOrders[2],
gTicket[2];

double
gLots,
gSARMaximum = 0.2,
gLotMargin,
gPriceValue,
gTicksPerPrice,
gBaseLotMargin,
gBalanceNeeded,
gSlippage,
gPipStarterInv,
gStopLevel;

void Log(string text) {
  if (gFileHandler > 0) FileWrite(gFileHandler, TimeToStr(TimeCurrent(), TIME_DATE | TIME_SECONDS) + ": " + text);
}

int deinit() {
  if (gFileHandler > 0) FileClose(gFileHandler);
  return (0);
}

void AnalyzeOrders(int type) {
  gOrders[type] = 0;
  gTicket[type] = 0;
  for (int pos = OrdersTotal() - 1; pos >= 0; pos--) {
    if (!OrderSelect(pos, SELECT_BY_POS, MODE_TRADES) || OrderMagicNumber() != MagicNumber || OrderType() != type) continue;
    gOrders[type] = 1 + MathRound(MathLog(OrderLots() / Lots) / MathLog(Booster));
    gTicket[type] = OrderTicket();
    break;
  }
  gModifiedTP[type] = (gOrders[type] > 1);
}

int init() {
  if (TradeChart != Symbol()) {
    string msg = "EA is OFF! TradeChart differs from chart symbol: " + TradeChart + " != " + Symbol();
    Alert(msg);
    Log(msg);
    gIsNotReady = true;
    return (1);
  }

  if (Digits == 5 || Digits == 3) {
    TakeProfit *= 10.0;
    PipStarter *= 10.0;
    iStopLoss *= 10.0;
  }
  TakeProfit = NormalizeDouble(Point * TakeProfit, Digits);
  PipStarter = NormalizeDouble(Point * PipStarter, Digits);
  iStopLoss = NormalizeDouble(Point * iStopLoss, Digits);

  gIsTesting = IsTesting();
  gIsNotOptimization = !IsOptimization();
  gLotStepDigits = MathRound((-MathLog(MarketInfo(TradeChart, MODE_LOTSTEP))) / MathLog(10));
  if (gIsNotOptimization) gFileHandler = FileOpen(WindowExpertName() + "_" + Time[0] + "_" + TradeChart + "_" + MagicNumber + ".log", FILE_WRITE);

  // for "trade time" check
  gStartMinutes = 60 * StartHour + StartMinute;
  gStopMinutes = 60 * StopHour + StopMinute;
  if (gStartMinutes > gStopMinutes) {
    int tmp = gStartMinutes;
    gStartMinutes = gStopMinutes;
    gStopMinutes = tmp;
  }

  // for "close by percentage" check
  StopLossPct = 1.0 - 0.01 * StopLossPct;
  TakeProfitPct = 1.0 + 0.01 * TakeProfitPct;

  // for "no money" check
  gTicksPerPrice = 1.0 / MarketInfo(TradeChart, MODE_TICKSIZE);
  gPriceValue = MarketInfo(TradeChart, MODE_TICKVALUE) * gTicksPerPrice;
  gLotMargin = MarketInfo(TradeChart, MODE_MARGINREQUIRED);
  gBaseLotMargin = MarketInfo(TradeChart, MODE_LOTSIZE) / AccountLeverage();
  gStopLevel = MarketInfo(TradeChart, MODE_STOPLEVEL) * Point;

  // for lot correction during price gap
  gSlippage = Point * Slippage;
  gPipStarterInv = 1.0 / ((Booster - 1.0) * TakeProfit);

  AnalyzeOrders(OP_BUY);
  AnalyzeOrders(OP_SELL);
  if (gIsNotOptimization) Log("Initialization finished. TradeChart = " + TradeChart);
  else EA_Name = "";
  return (0);
}

bool IsTradeTime() {
  int day = DayOfWeek();
  if (day < StartingTradeDay || day > EndingTradeDay) return (false);
  if (gStartMinutes == gStopMinutes) return (true);
  datetime now = TimeCurrent();
  int minuntes = 60 * TimeHour(now) + TimeMinute(now);
  return (gStartMinutes <= minuntes && minuntes < gStopMinutes);
}

double RequiredBalance(double lot, double sl) {
  return (lot * (gLotMargin + sl * gPriceValue));
}

bool OpenDeal(int type) {
  int new_ticket;
  double tp, sl, price;
  bool is_opened = true;
  color arrow_color;
  string msg;

  if (type == OP_BUY) {
    price = Ask;
    arrow_color = Green;
    msg = "BUY";
  } else {
    price = Bid;
    arrow_color = Pink;
    msg = "SELL";
  }

  // check for "no money"
  tp = RequiredBalance(gLots, PipStarter);
  if (AccountFreeMargin() < tp) {
    if (!gLowBalance) {
      gLowBalance = true;
      gBalanceNeeded = AccountMargin() + tp;
      if (gIsNotOptimization) Log("ERROR! " + msg + " order canceled! Balance < " + gBalanceNeeded);
    }
    return (false);
  }
  gLowBalance = false;

  new_ticket = OrderSend(TradeChart, type, gLots, price, Slippage, 0, 0, EA_Name, MagicNumber, 0, arrow_color);
  if (new_ticket < 0) {
    if (gIsNotOptimization) Log("FAILED " + msg + ", lots: " + gLots + ", sl: " + sl + ", tp: " + tp);
    is_opened = false;
  } else {
    gTicket[type] = new_ticket;
    gModifiedTP[type] = (gOrders[type] > 0);
    gOrders[type]++;
    if (OrderSelect(new_ticket, SELECT_BY_TICKET, MODE_TRADES)) {
      price = OrderOpenPrice();
      if (type == OP_BUY) {
        tp = price + TakeProfit;
        sl = price - iStopLoss;
      } else {
        tp = price - TakeProfit;
        sl = price + iStopLoss;
      }
      if (!AllowiStopLoss) sl = 0.0;
      OrderModify(new_ticket, price, sl, tp, 0, Black);
      if (gIsNotOptimization) Log(msg + " #" + new_ticket);
    }
  }
  return (is_opened);
}

int StrategySignal() {
  double sar = iSAR(TradeChart, PERIOD_H1, SARStep, gSARMaximum, 0);
  double ma = iMA(TradeChart, PERIOD_H1, MAPeriod, gMAShift, gMAMethod, gMAPrice, 0);
  if (sar > ma) return (-1);
  if (sar < ma) return (1);
  return (0);
}

bool SetOrderLimits(double takeprofit = 0, double stoploss = 0) {
  bool skip_modify = true;
  double tp = OrderTakeProfit(), sl = OrderStopLoss();
  if (takeprofit > 0 && tp != takeprofit) {
    skip_modify = false;
    tp = takeprofit;
  }
  if (stoploss > 0 && sl != stoploss) {
    skip_modify = false;
    sl = stoploss;
  }
  if (skip_modify) return (false);

  skip_modify = !OrderModify(OrderTicket(), OrderOpenPrice(), sl, tp, 0, Pink) && GetLastError() > ERR_NO_RESULT;
  if (skip_modify && gIsNotOptimization)
    Log("OrderModify error! Ticket #" + OrderTicket() +
        ", OpenPrice: " + DoubleToStr(OrderOpenPrice(), Digits) +
        ", TP: " + DoubleToStr(tp, Digits) +
        ", SL: " + DoubleToStr(sl, Digits));
  return (skip_modify);
}

// return true in case of error
bool SetOrdersTakeProfit(int type, double takeprofit) {
  double price;
  if (type == OP_BUY) price = Bid;
  else price = Ask;
  if (MathAbs(takeprofit - price) < gStopLevel) return (true);
  bool has_error = false;
  for (int pos = OrdersTotal() - 1; pos >= 0; pos--) {
    if (!OrderSelect(pos, SELECT_BY_POS, MODE_TRADES) || OrderMagicNumber() != MagicNumber || OrderType() != type) continue;
    if (SetOrderLimits(takeprofit, 0)) has_error = true;
  }
  return (has_error);
}

void ManageDeals(int type) {
  bool allow_first_order;
  double dd, price = 0, max_lots, last_tp;
  int n_max, pos;
  if (OrderSelect(gTicket[type], SELECT_BY_TICKET) && OrderCloseTime() == 0) {
    price = OrderOpenPrice();
    max_lots = OrderLots();
    last_tp = OrderTakeProfit();
  }

  if (type == OP_BUY) {
    pos = 1;
    n_max = MaxBuyOrders;
    dd = price - Ask;
    allow_first_order = Allow1Buy;
  } else {
    pos = -1;
    n_max = MaxSellOrders;
    dd = Bid - price;
    allow_first_order = Allow1Sell;
  }

  if (price == 0) {
    if (StrategySignal() == pos && IsTradeTime() && allow_first_order) {
      gLots = NormalizeDouble(Lots, gLotStepDigits);
      gOrders[type] = 0;
      OpenDeal(type);
    }
  } else {
    dd -= PipStarter;
    if (dd > 0 && (gOrders[type] < n_max || AllowExtraOrders)) {
      gOrders[type] = 1 + MathRound(MathLog(max_lots / Lots) / MathLog(Booster));
      if (gOrders[type] < n_max || AllowExtraOrders) {
        gLots = Lots * MathPow(Booster, gOrders[type]);
        if (dd > gSlippage) {
          double old_lots = gLots;
          gLots *= (1.0 + dd * gPipStarterInv);
        }
        gLots = NormalizeDouble(gLots, gLotStepDigits);
        OpenDeal(type);
      }
    } else {
      if (gModifiedTP[type]) gModifiedTP[type] = SetOrdersTakeProfit(type, last_tp);
    }
  }
}

double BalanceDrawdown() {
  double dd = 100.0 * (1.0 - AccountEquity() / AccountBalance());
  if (dd < 0.01) dd = 0;
  return (dd);
}

void ChartComment() {
  string msg =
    EA_Name + " Loaded Successfully" +
    "\nAccount Leverage:  1 : " + AccountLeverage() +
    "\nAccount Type:  " + AccountServer() +
    "\nServer Time:  " + TimeToStr(TimeCurrent(), TIME_SECONDS) +
    "\nAccount Equity:  " + DoubleToStr(AccountEquity(), 2) +
    "\nFree Margin:  " + DoubleToStr(AccountFreeMargin(), 2) +
    "\nDrawdown:  " + DoubleToStr(BalanceDrawdown(), 2) + "%";
  if (gLowBalance) {
    msg = msg + "\n\nLOW BALANCE! Needed:  " + DoubleToStr(gBalanceNeeded, 2);
  }
  Comment(msg);
}

void CloseByPercentage() {
  bool need_close = false;
  if (AccountEquity() < StopLossPct * AccountBalance()) {
    if (gIsNotOptimization) Log("Percentage Stop Loss: Balance = " + AccountBalance() + ", Equity = " + AccountEquity());
    need_close = true;
  }
  if (AccountEquity() > TakeProfitPct * AccountBalance()) {
    if (gIsNotOptimization) Log("Percentage Take Profit: Balance = " + AccountBalance() + ", Equity = " + AccountEquity());
    need_close = true;
  }
  int slippage;
  if (need_close) slippage = MarketInfo(TradeChart, MODE_SPREAD);
  while (need_close) {
    need_close = false;
    for (int pos = OrdersTotal() - 1; pos >= 0; pos--) {
      if (!OrderSelect(pos, SELECT_BY_POS) || OrderMagicNumber() != MagicNumber) continue;
      if (gIsNotOptimization) Log("Close by percentage #" + OrderTicket());
      if (!OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), slippage, White)) {
        if (gIsNotOptimization) Log("Close error #" + OrderTicket());
        need_close = true;
      }
    }
  }
}

int start() {
  if (gIsNotReady) return (-1);
  if (gIsNotOptimization) {
    ChartComment();
    CloseByPercentage();
  }
  if (gIsTesting) {
    gLotMargin = gBaseLotMargin * Ask;
  } else {
    gPriceValue = MarketInfo(TradeChart, MODE_TICKVALUE) * gTicksPerPrice;
    gLotMargin = MarketInfo(TradeChart, MODE_MARGINREQUIRED);
  }
  ManageDeals(OP_BUY);
  ManageDeals(OP_SELL);
  return (0);
}