//+------------------------------------------------------------------+
//|                                   Keltner Breakout 1 FxFisherman |
//|                                                         Scorpion |
//|                                              www.fxfisherman.com |
//+------------------------------------------------------------------+
#property copyright "Scorpion"
#property link      "http://www.fxfisherman.com"
#include <stdlib.mqh>

#define TS_MODE_DISABLE 0
#define TS_MODE_FIXED_SL 1
#define TS_MODE_ATR 2
#define TS_MODE_HALF_VOLATILITY 3
#define TS_MODE_BREAKOUT 4
#define TS_MODE_BREAKEVEN 5

//---- input parameters
extern string    _______Position_______;
extern double    Lots=1;
extern double    Lots_PCT=10;
extern bool      Use_Lots_PCT=false;
extern int       TP=0;
extern int       SL=30;
extern int       SL_Mode=0;
extern int       Breakeven_Pips=0;

extern string    ______TrailingStop_____;
extern int       TS_Mode=0;       // 0 = disabled, 1 = Fixed SL, 2 = ATR, 3 = Half Volatility, 4 = Breakout Yesterday Hi/Lo
extern int       TS_Trigger=30;
extern int       TS_Sensitivity=3;
extern double    TS_DynamicFactor=0.5;   // applied only if TrailingStopMode = 2 or 3
extern int       Evaluate_Interval=1; // -1 chart, 0 tick, > 0 specified min

extern string    _______Indicators______;
extern int       Extra_Pips=1;
extern int       Keltner_Period = 10;

extern string    _______Session_______;
extern bool Filter_Session=false;
extern int From_Hour=13;
extern int From_Min=0;
extern int To_Hour=18;
extern int To_Min=0;

string expert_name = "Keltner Breakout";
int main_magic;
int open_slippage=5;
int close_slippage=10;

int orderType;
double orderPrice;
datetime timeNextEval;

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
   main_magic = 1120000 + GetTimeframeConstant(Period()) + GetSymbolConstant(Symbol());
   return(0);
  }
  
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
   return(0);
  }

//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
{
  // set breakeven and trailing stops 
  int ticket = OrderTicketByMagicNum(main_magic);
  if (ticket > 0 && Breakeven_Pips > 0)
    ControlTrailingStop(ticket, SL, TS_MODE_BREAKEVEN, Breakeven_Pips, TS_DynamicFactor, TS_Sensitivity);
  if (ticket > 0 && TS_Mode > 0)
    ControlTrailingStop(ticket, SL, TS_Mode, TS_Trigger, TS_DynamicFactor, TS_Sensitivity);
  
  // analyse now?
  bool isAnalyseNow=false;
  datetime timeNow = CurTime();
  int intervalEval = Evaluate_Interval;
  if (Evaluate_Interval == -1) intervalEval = Period();
  if (!(intervalEval > 0 && timeNow < timeNextEval)) isAnalyseNow=true;

  // analyse chart
  static bool isBuy, isSell, isCloseBought, isCloseSold;
  if (isAnalyseNow)
  {
    timeNextEval = timeNow - (timeNow % (intervalEval*60)) + (intervalEval*60);
    ticket = OrderTicketByMagicNum(main_magic);
    double r0 = iCustom(Symbol(), Period(), "Keltner_Channels", Keltner_Period, 300, 0, 0);
    double s0 = iCustom(Symbol(), Period(), "Keltner_Channels", Keltner_Period, 300, 2, 0);

    double pipsExtra = Extra_Pips * Point;
  
    isBuy  = (Bid > r0 + pipsExtra);
    isSell = (Bid < s0 - pipsExtra);
    isCloseBought = (Bid < s0 - pipsExtra);
    isCloseSold = (Bid > r0 + pipsExtra);
    
    // filter out entries if not in trading session
    if (Filter_Session)
    {
      double From_Time = From_Hour + (From_Min/60);
      double To_Time = To_Hour + (To_Min/60);
      double Cur_Time = Hour() + (Minute()/60);
      if (!(From_Time <= Cur_Time && Cur_Time <= To_Time))
      {
        isBuy = False;
        isSell = False;
      }
    }
  }

  // close orders
  ticket = OrderTicketByMagicNum(main_magic);
  if (ticket > 0 && (isCloseBought || isCloseSold)){
    if (OrderSelectEx(ticket,SELECT_BY_TICKET,MODE_TRADES)==false) return(0);  
    if (OrderType() == OP_BUY && isCloseBought){
      CloseNow(ticket);
    }else if (OrderType() == OP_SELL && isCloseSold){
      CloseNow(ticket);
    }
  }
  
  // enter orders
  ticket = OrderTicketByMagicNum(main_magic);
  if (ticket==0){
    static int lastType;
    static int lastDay;
    if (isBuy && !isSell && !(lastType == 1 && lastDay == Today())){
      if (BuyNow()>0) {
        isBuy = false;
        lastType = 1;
        lastDay = Today();
      }
    }else if(isSell && !isBuy && !(lastType == 2 && lastDay == Today())){
      if (SellNow()>0) {
        isSell = false;
        lastType = 2;
        lastDay = Today();
      }
    }else if(isSell && isBuy){
      Print("Error: Buy and sell signals are issued at the same time!");
    }
  }
  
  return(0);
}


//+------------------------------------------------------------------+
//| Buy                                                              |
//+------------------------------------------------------------------+
int BuyNow()
{
    double trueSL, trueTP, lotSize;
    lotSize = GetLots(Symbol(), Lots, Lots_PCT, Use_Lots_PCT);
    trueSL = Get_SL(OP_BUY, Ask, Ask, SL, SL_Mode, TS_DynamicFactor);
    if (TP > 0) trueTP = Bid+(TP*Point);
    int ticket = OrderSendEx(Symbol(), OP_BUY, lotSize, Ask, open_slippage*Point, trueSL, trueTP, expert_name + " " + Symbol() + Period(), main_magic, 0, Yellow);
    if (ticket > 0) orderType = OP_BUY; orderPrice = Bid;
    return(ticket);
}

//+------------------------------------------------------------------+
//| Sell                                                             |
//+------------------------------------------------------------------+

int SellNow()
{
  double trueSL, trueTP, lotSize;
  lotSize = GetLots(Symbol(), Lots, Lots_PCT, Use_Lots_PCT);
  trueSL = Get_SL(OP_SELL, Bid, Bid, SL, SL_Mode, TS_DynamicFactor);
  if (TP > 0) trueTP = Bid-(TP*Point);
  int ticket = OrderSendEx(Symbol(), OP_SELL, lotSize, Bid, open_slippage*Point, trueSL, trueTP, expert_name + " " + Symbol() + Period(), main_magic, 0, Yellow);
  if (ticket > 0) orderType = OP_SELL; orderPrice = Bid;
  return(ticket);
}

//+------------------------------------------------------------------+
//| Control trailing stop                                            |
//+------------------------------------------------------------------+
void ControlTrailingStop(int ticket, double SL, int TS_Mode, int TS_Trigger, double TS_DynamicFactor, int TS_Sensitivity)
{
  if (ticket == 0 || TS_Mode == 0) return;
  
  double ts;
  if (OrderSelectEx(ticket, SELECT_BY_TICKET, MODE_TRADES)==false) return;
  if (OrderType() == OP_BUY){
    ts = Get_SL(OP_BUY, OrderOpenPrice(), Bid, SL, TS_Mode, TS_DynamicFactor);
    if ((ts >= OrderStopLoss() + TS_Sensitivity*Point) && (ts > 0)  && (Bid >= OrderOpenPrice() + TS_Trigger*Point )) {
      if (Bid - ts >= 5 * Point)
      {
        OrderModifyEx(ticket, OrderOpenPrice(), ts, OrderTakeProfit(), 0, Red);
      }else if(Bid <= ts){
        CloseNow(ticket);
      }
    }
    
  }else if(OrderType() == OP_SELL){
    ts = Get_SL(OP_SELL, OrderOpenPrice(), Ask, SL, TS_Mode, TS_DynamicFactor);
    if ((ts <= OrderStopLoss() - TS_Sensitivity*Point) && (ts > 0) && (Ask <= OrderOpenPrice() - TS_Trigger*Point)){
      if (ts - Ask >= 5 * Point)
      {
        OrderModifyEx(ticket, OrderOpenPrice(), ts, OrderTakeProfit(), 0, Red);
      }else if(ts <= Ask){
        CloseNow(ticket);
      }
    }
  }
  
}


double Get_SL(int order_type, double order_price, double price, double sl, int sl_mode, double sl_dynamicfactor)
{
  if (sl_mode == 0) return(0);
  
  double ts;
  double ma_0, hh, ll;
  if (order_type == OP_BUY){
    switch (sl_mode){
      case TS_MODE_FIXED_SL: if(sl > 0) ts = price-(Point*sl); break;
      case TS_MODE_ATR: ts = Low[0] - (sl_dynamicfactor * iATR(NULL,0,14,0)); break;
      case TS_MODE_HALF_VOLATILITY: ts = Low[0] - (sl_dynamicfactor *(High[0]-Low[0])); break;
      case TS_MODE_BREAKOUT: ts = Low[1] - Point; break;
      case TS_MODE_BREAKEVEN: ts = order_price; break;
    }
  }else if(order_type == OP_SELL){
    switch (sl_mode){
      case TS_MODE_FIXED_SL: if(sl > 0) ts = price+(Point*sl); break;
      case TS_MODE_ATR: ts = High[0] + (sl_dynamicfactor * iATR(NULL,0,14,0)); break;
      case TS_MODE_HALF_VOLATILITY: ts = High[0] + (sl_dynamicfactor *(High[0]-Low[0])); break;
      case TS_MODE_BREAKOUT: ts = High[1] + Point; break;
      case TS_MODE_BREAKEVEN: ts = order_price; break;
    }
  }
  return(ts);
}


//+------------------------------------------------------------------+
//| Close at market price                                            |
//+------------------------------------------------------------------+
bool CloseNow(int ticket)
{
  if (OrderSelectEx(ticket, SELECT_BY_TICKET))
  {
    if (OrderType() == OP_BUY)
    {
      OrderCloseEx(ticket, OrderLots(), Bid, close_slippage);
    }else if (OrderType() == OP_SELL){
      OrderCloseEx(ticket, OrderLots(), Ask, close_slippage);
    }
  }
}


//+------------------------------------------------------------------+
//| Lots size functions (fixed lot, compound lot, etc)               |
//+------------------------------------------------------------------+
double GetLots(string symbol, double lots, double lots_pct, bool use_lots_pct)
{
  if (!use_lots_pct)
  {
    return(lots);
  }else{
    double lotStep = MarketInfo(symbol, MODE_LOTSTEP);
    double lotSize = MarketInfo(symbol, MODE_LOTSIZE) / AccountLeverage();
    double lot = (AccountBalance() * (lots_pct/100)) / lotSize;
    double leftover = MathMod(lot, lotStep);
    if (MathMod(lot/lotStep, 1) >= 0.00001) lot = lot - leftover;
    return(lot);
  }
}


//+------------------------------------------------------------------+
//| Extended order execution functions for used in multiple pairs    |
//| with automatic retry attempts.                                   |
//+------------------------------------------------------------------+
int OrderSendEx(string symbol, int cmd, double volume, double price, int slippage, double stoploss, double takeprofit, string comment, int magic, datetime expiration=0, color arrow_color=CLR_NONE) {
  if(!WaitWhileBusy())
  {
    Print("Error in OrderSendEx(): Timeout encountered");
    return(-1);
  }
  SetBusyState();
  int ticket = OrderSend(symbol, cmd, volume, price, slippage, stoploss, takeprofit, comment, magic, expiration, arrow_color);
  Sleep(6000);
  ReleaseBusyState();  
  return(ticket);
}


bool OrderCloseEx(int ticket, double lots, double price, int slippage, color Color=CLR_NONE)
{
  if(!WaitWhileBusy())
  {
    Print("Error in OrderCloseEx(): Timeout encountered");
    return(false);
  }
  SetBusyState();
  bool ret = OrderClose(ticket, lots, price, slippage, Color);
  Sleep(6000);
  ReleaseBusyState();  
  return(ret);
}


bool OrderModifyEx( int ticket, double price, double stoploss, double takeprofit, datetime expiration, color arrow_color=CLR_NONE)
{
  if(!WaitWhileBusy())
  {
    Print("Error in OrderModifyEx(): Timeout encountered");
    return(false);
  }
  SetBusyState();
  bool ret = OrderModify(ticket, price, stoploss, takeprofit, expiration, arrow_color);
  if(ret)
  {
    Sleep(6000);
  }else{
    Print("Error in OrderModifyEx(): ", LastErrorText());
  }
  ReleaseBusyState();  
  return(ret);
}


bool OrderSelectEx(int index, int select, int pool = MODE_TRADES)
{
  if (OrderSelect(index,select,pool)==true)
  {
    return(true);
  }else{
    Print("Error: Order #", index ," cannot be selected. ", LastErrorText());
  }
}


//+------------------------------------------------------------------+
//| Calling state functions                                          |
//+------------------------------------------------------------------+
bool WaitWhileBusy()
{
   datetime OldCurTime;
   int timeoutsec=6;
   
   OldCurTime=CurTime();
   while (GlobalVariableCheck("InTrade") || !IsTradeAllowed()) {
      if(OldCurTime + timeoutsec <= CurTime()) {
         return(false); 
      }
      Sleep(1000);
   }
   return(true);
}


void SetBusyState()
{
  GlobalVariableSet("InTrade", CurTime());  // set lock indicator
}

void ReleaseBusyState()
{
  GlobalVariableDel("InTrade");   // clear lock indicator
}

//+------------------------------------------------------------------+
//| Get order ticket by magic number                                 |
//+------------------------------------------------------------------+
int OrderTicketByMagicNum(int magic_number) {

  for(int i=0;i<OrdersTotal();i++)
  {
    if (OrderSelect(i, SELECT_BY_POS) == false) continue;
    if (OrderMagicNumber() == magic_number) return(OrderTicket());
  }   
      
}

//+------------------------------------------------------------------+
//| Time frame interval appropriation function                       |
//+------------------------------------------------------------------+
int GetTimeframeConstant(int chart_period) {
   switch(chart_period) {
      case 1:  // M1
         return(50);
      case 5:  // M5
         return(100);
      case 15:
         return(150);
      case 30:
         return(200);
      case 60:
         return(250);
      case 240:
         return(300);
      case 1440:
         return(350);
      case 10080:
         return(400);
      case 43200:
         return(450);
   }
}


//+------------------------------------------------------------------+
//| Symbol to index                                                  |
//+------------------------------------------------------------------+
int GetSymbolConstant(string symbol) {

	if(symbol=="EURUSD" || symbol=="mEURUSD" || symbol=="EURUSDm") {	return(1);
	} else if(symbol=="GBPUSD" || symbol=="GBPUSDm") { return(2);
	} else if(symbol=="USDCHF" || symbol=="USDCHFm") {	return(3);
	} else if(symbol=="USDJPY" || symbol=="USDJPYm") {	return(4);
	} else if(symbol=="USDCAD" || symbol=="USDCADm") {	return(5);
	} else if(symbol=="AUDUSD" || symbol=="AUDUSDm") {	return(6);
	} else if(symbol=="CHFJPY" || symbol=="CHFJPYm") {	return(7);
	} else if(symbol=="EURAUD" || symbol=="EURAUDm") {	return(8);
	} else if(symbol=="EURCAD" || symbol=="EURCADm") {	return(9);
	} else if(symbol=="EURCHF" || symbol=="EURCHFm") {	return(10);
	} else if(symbol=="EURGBP" || symbol=="EURGBPm") {	return(11);
	} else if(symbol=="EURJPY" || symbol=="EURJPYm") {	return(12);
  } else if(symbol=="GBPCHF" || symbol=="GBPCHFm") {	return(13);
	} else if(symbol=="GBPJPY" || symbol=="GBPJPYm") {	return(14);
	} else if(symbol=="GOLD"   || symbol=="GOLDm") {	return(15);
	} else {Print("Error: Unexpected symbol."); return(0);
	}
}

//+------------------------------------------------------------------+
//| Get last error description                                       |
//+------------------------------------------------------------------+
string LastErrorText()
{
  return(ErrorDescription(GetLastError()));
}


datetime StripTime(datetime dt)
{
  return (dt - (TimeHour(dt)*3600) - (TimeMinute(dt)*60) - TimeSeconds(dt));
}

datetime Today()
{
  return (StripTime(CurTime()));
}

