//+------------------------------------------------------------------+
//|                                   Copyright © 2010, Bernd Kreuss |
//|                        PayPal donations go here -> 7bit@arcor.de |
//+------------------------------------------------------------------+
#property copyright "© Bernd Kreuss, Version 2010.6.11.1"
#property link      "http://sites.google.com/site/prof7bit/"
#include <WinUser32.mqh>
#include <stderror.mqh>
#include <stdlib.mqh>


//Stuff from Offline_charts.mqh and common_functions.mqh

#define OFFLINE_HEADER_SIZE 148 ///< LONG_SIZE + 64 + 12 + 4 * LONG_SIZE + 13 * LONG_SIZE 
#define OFFLINE_RECORD_SIZE 44  ///< 5 * DOUBLE_SIZE + LONG_SIZE 
#define SW_SHOWNORMAL 1

                       
#import "shell32.dll"
int ShellExecuteA(int hWnd, string Verb, string File, string Parameter, string Path, int ShowCommand);
#import "kernel32.dll"
void OutputDebugStringA(string msg);
#import


static color CLR_BUY_ARROW = Blue;
static color CLR_SELL_ARROW = Red;
static color CLR_CROSSLINE_ACTIVE = Magenta;
static color CLR_CROSSLINE_TRIGGERED = Gray;
static int STYLE_CROSSLINE_TRIGGERED = STYLE_SOLID;
static int STYLE_CROSSLINE_ACTIVE = STYLE_DASH;
static int WIDTH_CROSSLINE_TRIGGERED = 1;
static int WIDTH_CROSSLINE_ACTIVE = 1;
static bool IS_ECN_BROKER = false;
int __chart_file = 0; ///< the cache for the file handle (only used in backtesting mode) 


//#include <common_functions.mqh>
//#include <offline_charts.mqh> 
//#include <oanda.mqh> 

extern double LotsToTrade = 0.01; // LotsToTrade to use per trade
//extern double oanda_factor = 25000;
extern int stop_distance = 20;
extern int auto_tp = 2; // auto-takeprofit this many levels (roughly) above the BE point
extern bool is_ecn_broker = false; // different market order procedure when resuming after pause

extern color clr_breakeven_level = Lime;
extern color clr_buy = Blue;
extern color clr_sell = Red;
extern color clr_gridline = Lime;
extern color clr_stopline_active = Magenta;
extern color clr_stopline_triggered = Aqua;
extern string sound_grid_trail = "";
extern string sound_grid_step = "";
extern string sound_order_triggered = "";
extern string sound_stop_all = "";

string EAname = "snow";

double pip;
double points_per_pip;
string strComment;
int MagicNo;
bool running;
int direction;
double last_line;
int level; // current level, signed, minus=short, calculated in trade()
double Realized; // total Realized (all time) (calculated in info())
double cycle_total_profit; // total profit since cycle started (calculated in info())
double stop_value; // dollars (account) per single level (calculated in info())
double auto_tp_price; // the price where auto_tp should trigger, calculated during break even calc.
double auto_tp_profit; // rough estimation of auto_tp profit, calculated during break even calc.

#define SP "                                    "

// trading direction
#define BIDIR 0
#define LONG  1
#define SHORT 2


void defaults(){
   /*
   IS_ECN_BROKER = true;
   //auto_tp = 2;
   
   if (IsTesting()){
      return(0);
   }
   if (Symbol6() == "GBPUSD"){
      LotsToTrade = 0.1;
      oanda_factor = 900;
      stop_distance = 30;
   }
   if (Symbol6() == "EURUSD"){
      LotsToTrade = 0.1;
      oanda_factor = 1800;
      stop_distance = 30;
   }
   if (Symbol6() == "USDCHF"){
      LotsToTrade = 0.1;
      oanda_factor = 1800;
      stop_distance = 20;
   }
   if (Symbol6() == "USDJPY"){
      LotsToTrade = 0.1;
      oanda_factor = 1800;
      stop_distance = 30;
   }
   
   sound_grid_step = "expert.wav";
   sound_grid_trail = "alert2.wav";
   sound_stop_all = "alert.wav";
   sound_order_triggered = "alert.wav";
   */
}


void OnInit(){
   if (!IsDllsAllowed()){
      MessageBox("DLL imports must be allowed!", "Snowball");
      return;
   }
      
   IS_ECN_BROKER = is_ecn_broker;
   CLR_BUY_ARROW = clr_buy;
   CLR_SELL_ARROW = clr_sell;
   CLR_CROSSLINE_ACTIVE = clr_stopline_active;
   CLR_CROSSLINE_TRIGGERED = clr_stopline_triggered;
   
   defaults();

   points_per_pip = pointsPerPip();
   pip = Point * points_per_pip;
   
   strComment = EAname + "_" + Symbol6();
   MagicNo = makeMagicNumber(EAname + "_" + Symbol());
   
   if (last_line == 0){
      last_line = getLine();
   }
   
   if (IsTesting()){
      setGlobal("Realized", 0);
      setGlobal("running", 0);
   }
   
   readVariables();
   
   if (IsTesting() && !IsVisualMode()){
      Print("!!! This is not an automated strategy! Automated backtesting is nonsense! Starting in bidirectional mode!");
      running = true;
      direction = BIDIR;
      placeLine(Bid);
   }
      
   info();
}

void OnDeinit(const int reas){
   deleteStartButtons();
   deleteStopButtons();
   storeVariables();
   if (UninitializeReason() == REASON_PARAMETERS){
      Comment("Parameters changed, pending orders deleted, will be replaced with the next tick");
      closeOpenOrders(OP_SELLSTOP, MagicNo);
      closeOpenOrders(OP_BUYSTOP, MagicNo);
   }else{
      Comment("EA removed, open orders, trades and status untouched!");
   }
}

void onTick(){
   recordEquity(EAname+Symbol6(), PERIOD_H1, MagicNo);
   //checkOanda(MagicNo, oanda_factor);
   checkLines();
   checkButtons();
   trade();
   info();
   checkAutoTP();
   if(!IsTesting()){
      plotNewOpenTrades(MagicNo);
      plotNewClosedTrades(MagicNo);
   }
}

void onOpen(){
}

void storeVariables(){
   setGlobal("running", running);
   setGlobal("direction", direction);
}

void readVariables(){
   running = getGlobal("running");
   direction = getGlobal("direction");
}

void deleteStartButtons(){
   ObjectDelete("start_long");
   ObjectDelete("start_short");
   ObjectDelete("start_bidir");
}

void deleteStopButtons(){
   ObjectDelete("stop");
   ObjectDelete("pause");
}

/**
* mark the start (or resume) of the cycle in the chart 
*/
void startArrow(){
   string aname = "cycle_start_" + TimeToStr(TimeCurrent());
   ObjectCreate(aname, OBJ_ARROW, 0, TimeCurrent(), Close[0]);
   ObjectSet(aname, OBJPROP_ARROWCODE, 5);
   ObjectSet(aname, OBJPROP_COLOR, clr_gridline);
   ObjectSet(aname, OBJPROP_BACK, true);
}

/**
* mark the end (or pause) of the cycle in the chart 
*/
void endArrow(){
   string aname = "cycle_end_" + TimeToStr(Time[0]);
   ObjectCreate(aname, OBJ_ARROW, 0, TimeCurrent(), Close[0]);
   ObjectSet(aname, OBJPROP_ARROWCODE, 6);
   ObjectSet(aname, OBJPROP_COLOR, clr_gridline);
   ObjectSet(aname, OBJPROP_BACK, true);
}

void stop(){
   endArrow();
   deleteStopButtons();
   closeOpenOrders(-1, MagicNo);
   running = false;
   storeVariables();
   setGlobal("Realized", getProfitRealized(MagicNo)); // store this only on pyramid close
   //checkOanda(MagicNo, oanda_factor);
   if (sound_stop_all != ""){
      PlaySound(sound_stop_all);
   }
}

void go(int mode){
   startArrow();
   deleteStartButtons();
   running = true;
   direction = mode;
   storeVariables();
   resume();
}

void pause(){
   endArrow();
   deleteStopButtons();
   label("paused_level", 15, 100, 1, level, Yellow);
   closeOpenOrders(-1, MagicNo);
   running = false;
   storeVariables();
   //checkOanda(MagicNo, oanda_factor);
   if (sound_stop_all != ""){
      PlaySound(sound_stop_all);
   }
}

/**
* resume trading after we paused it.
* Find the text label containing the level where we hit pause
* and re-open the corresponding amounts of LotsToTrade, then delete the label.
*/ 
void resume(){
   int i;
   double sl;
   double line = getLine();
   level = StrToInteger(ObjectDescription("paused_level"));
   
   if (direction == LONG){
      level = MathAbs(level);
   }
   
   if (direction == SHORT){
      level = -MathAbs(level);
   }
   
   if (level > 0){
      for (i=1; i<=level; i++){
         sl = line - pip * i * stop_distance;
         buy(LotsToTrade, sl, 0, MagicNo, strComment);
      }
   }
   
   if (level < 0){
      for (i=1; i<=-level; i++){
         sl = line + pip * i * stop_distance;
         sell(LotsToTrade, sl, 0, MagicNo, strComment);
      }
   }
      
   ObjectDelete("paused_level");
}

void checkLines(){
   if (crossedLine("stop")){
      stop();
   }
   if (crossedLine("pause")){
      pause();
   }
   if (crossedLine("start long")){
      go(LONG);
   }
   if (crossedLine("start short")){
      go(SHORT);
   }
   if (crossedLine("start bidir")){
      go(BIDIR);
   }   
}

void checkButtons(){
   if(!running){
      deleteStopButtons();
      if (labelButton("start_long", 15, 15, 1, "start long", Lime)){
         go(LONG);
      }
      if (labelButton("start_short", 15, 30, 1, "start short", Lime)){
         go(SHORT);
      }
      if (labelButton("start_bidir", 15, 45, 1, "start bidirectional", Lime)){
         go(BIDIR);
      }
   }
   
   if (running){
      deleteStartButtons();
      if (labelButton("stop", 15, 15, 1, "stop", Red)){
         stop();
      }
      if (labelButton("pause", 15, 30, 1, "pause", Yellow)){
         pause();
      }
   }
}

void checkAutoTP(){
   if (auto_tp > 0 && auto_tp_price > 0){
      if (level > 0 && Close[0] >= auto_tp_price){
         stop();
      }
      if (level < 0 && Close[0] <= auto_tp_price){
         stop();
      }
   }
}

void placeLine(double price){
   horizLine("last_order", price, clr_gridline, SP + "grid position");
   last_line = price;
   WindowRedraw();
}

double getLine(){
   return(ObjectGet("last_order", OBJPROP_PRICE1));
}

bool lineMoved(){
   double line = getLine();
   if (line != last_line){
      // line has been moved by external forces (hello wb ;-)
      if (MathAbs(line - last_line) < stop_distance * pip){
         // minor adjustment by user
         last_line = line;
         return(true);
      }else{
         // something strange (gap? crash? line deleted?)
         if (MathAbs(Bid - last_line) < stop_distance * pip){
            // last_line variable still near price and thus is valid.
            placeLine(last_line); // simply replace line
            return(false); // no action needed
         }else{
            // line is far off or completely missing and last_line doesn't help also
            // make a completely new line at Bid
            placeLine(Bid);
            return(true);
         }
      }
      return(true);
   }else{
      return(false);
   }
}

/**
* manage all the entry order placement
*/
void trade(){
   double start;
   static int last_level;
   
   if (lineMoved()){
      closeOpenOrders(OP_SELLSTOP, MagicNo);
      closeOpenOrders(OP_BUYSTOP, MagicNo);
   }
   start = getLine();
   
   // calculate global variable level here // FIXME: global variable side-effect hell.
   level = getNumOpenOrders(OP_BUY, MagicNo) - getNumOpenOrders(OP_SELL, MagicNo);
   
   if (running){
      // are we flat?
      if (level == 0){
         if (direction == SHORT && Ask > start){
            if (getNumOpenOrders(OP_SELLSTOP, MagicNo) != 2){
               closeOpenOrders(OP_SELLSTOP, MagicNo);
            }else{
               moveOrders(Ask - start);
            }
            placeLine(Ask);
            start = Ask;
            plotBreakEven();
            if (sound_grid_trail != ""){
               PlaySound(sound_grid_trail);
            }
         }
         
         if (direction == LONG && Bid < start){
            if (getNumOpenOrders(OP_BUYSTOP, MagicNo) != 2){
               closeOpenOrders(OP_BUYSTOP, MagicNo);
            }else{
               moveOrders(Bid - start);
            }
            placeLine(Bid);
            start = Bid;
            plotBreakEven();
            if (sound_grid_trail != ""){
               PlaySound(sound_grid_trail);
            }
         }
         
         // make sure first long orders are in place
         if (direction == BIDIR || direction == LONG){
            longOrders(start);
         }
         
         // make sure first short orders are in place
         if (direction == BIDIR || direction == SHORT){
            shortOrders(start);
         }
      }
   
      // are we already long?
      if (level > 0){
         // make sure the next long orders are in place
         longOrders(start);
      }

      // are we short?
      if (level < 0){
         // make sure the next short orders are in place
         shortOrders(start);
      }
      
      // we have two different models how to move the grid line.
      // If we are *not* flat we can snap it to the nearest grid level,
      // ths is better for handling situations where the order is triggered 
      // by the exact pip and price is immediately reversing.
      // If we are currently flat we *must* move it only when we have reached 
      // it *exactly*, because otherwise this would badly interfere with 
      // the trailing of the grid in the unidirectional modes. Also in 
      // bidirectional mode this would have some unwanted effects.
      if (level != 0){
         // snap to grid
         if (Ask + (pip * stop_distance / 6) >= start + stop_distance*pip){
            jumpGrid(1);
         }
      
         // snap to grid
         if (Bid - (pip * stop_distance / 6) <= start - stop_distance*pip){
            jumpGrid(-1);
         }
      }else{   
         // grid reached exactly
         if (Ask  >= start + stop_distance*pip){
            jumpGrid(1);
         }
         
         // grid reached exactly
         if (Bid  <= start - stop_distance*pip){
            jumpGrid(-1);
         }
      }
      
      // alert on level change (order triggered, not line moved)
      if (level != last_level){
         if (sound_order_triggered != ""){
            PlaySound(sound_order_triggered);
         }
         last_level = level;
      }
      
   }else{ // not running
      placeLine(Bid);
   }
}

/**
* move the line 1 stop_didtance up or down.
* 1 means up, -1 means down.
*/
void jumpGrid(int dir){
   placeLine(getLine() + pip * stop_distance * dir);
   if (sound_grid_step != ""){
      PlaySound(sound_grid_step);
   }
}

/**
* do we need to place a new entry order at this price?
* This is done by looking for a stoploss below or above the price
* where=-1 searches for stoploss below, where=1 for stoploss above price
* return false if there is already an order (open or pending)
*/ 
bool needsOrder(double price, int where){
   //return(false);
   int x;
   int i;
   int total = OrdersTotal();
   int type;
   // search for a stoploss at exactly one grid distance away from price
   for (i=0; i<total; i++){
     x= OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
      type = OrderType();
      if (where < 0){ // look only for buy orders (stop below)
         if (OrderMagicNumber() == MagicNo && (type == OP_BUY || type == OP_BUYSTOP)){
            if (isEqualPrice(OrderStopLoss(), price + where * pip * stop_distance)){
               return(false);
            }
         }
      }
      if (where > 0){ // look only for sell orders (stop above)
         if (OrderMagicNumber() == MagicNo && (type == OP_SELL || type == OP_SELLSTOP)){
            if (isEqualPrice(OrderStopLoss(), price + where * pip * stop_distance)){
               return(false);
            }
         }
      }
   }
   return(true);
}

/**
* Make sure there are the next two long orders above start in place.
* If they are already there do nothing, else replace the missing ones.
*/
void longOrders(double start){
   double a = start + stop_distance * pip;
   double b = start + 2 * stop_distance * pip;
   if (needsOrder(a, -1)){
      buyStop(LotsToTrade, a, start, 0, MagicNo, strComment);
   }
   if (needsOrder(b, -1)){
      buyStop(LotsToTrade, b, a, 0, MagicNo, strComment);
   }
}

/**
* Make sure there are the next two short orders below start in place.
* If they are already there do nothing, else replace the missing ones.
*/
void shortOrders(double start){
   double a = start - stop_distance * pip;
   double b = start - 2 * stop_distance * pip;
   if (needsOrder(a, 1)){
      sellStop(LotsToTrade, a, start, 0, MagicNo, strComment);
   }
   if (needsOrder(b, 1)){
      sellStop(LotsToTrade, b, a, 0, MagicNo, strComment);
   }
}

/**
* move all entry orders by the amount of d
*/
void moveOrders(double d){
   int x;
   int i;
   for(i=0; i<OrdersTotal(); i++){
     x= OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
      if (OrderMagicNumber() == MagicNo){
         if (MathAbs(OrderOpenPrice() - getLine()) > 3 * stop_distance * pip){
            orderDeleteReliable(OrderTicket());
         }else{
            orderModifyReliable(
               OrderTicket(),
               OrderOpenPrice() + d,
               OrderStopLoss() + d,
               0,
               0,
               CLR_NONE
            );
         }
      }
   }
}

void info(){
   double floating;
   double pb, lp, tp;
   static int last_ticket;
   static datetime last_be_plot = 0; 
   int ticket;
   string dir;
   
   ticket = OrderSelect(OrdersHistoryTotal()-1, SELECT_BY_POS, MODE_HISTORY);
   ticket = OrderTicket();
   
   if (ticket != last_ticket){
      // history changed, need to recalculate Realized profit
      Realized = getProfitRealized(MagicNo);
      last_ticket = ticket;
      
      // enforce a new break-even arrow plot immediately
      last_be_plot = 0;
   }
   
   floating = getProfit(MagicNo);
   
   // the variable Realized is the total Realized of all time. 
   // the MT4-global variable _Realized is a snapshot of this value when 
   // the EA was reset the last time. The difference is what we made
   // during the current cycle. Add floating to it and we have the 
   // profit of the current cycle.
   cycle_total_profit = Realized - getGlobal("Realized") + floating;
   
   if (running == false){
      dir = "trading stopped";
   }else{
      switch(direction){
         case LONG: 
            dir = "trading long";
            break;
         case SHORT: 
            dir = "trading short";
            break;
         default: 
            dir = "trading both directions";
      }
   }
   
   int level_abs = MathAbs(getNumOpenOrders(OP_BUY, MagicNo) - getNumOpenOrders(OP_SELL, MagicNo));
   stop_value = MarketInfo(Symbol(), MODE_TICKVALUE) * LotsToTrade * stop_distance * points_per_pip;
   
   Comment("\n" + SP + EAname + MagicNo + ", " + dir +
           "\n" + SP + "1 pip is " + DoubleToStr(pip, Digits) + " " + Symbol6() +
           "\n" + SP + "stop distance: " + stop_distance + " pip, lot-size: " + DoubleToStr(LotsToTrade, 2) +
           "\n" + SP + "every stop equals " + DoubleToStr(stop_value, 2) + " " + AccountCurrency() +
           "\n" + SP + "Realized: " + DoubleToStr(Realized - getGlobal("Realized"), 2) + "  floating: " + DoubleToStr(floating, 2) +
           "\n" + SP + "profit: " + DoubleToStr(cycle_total_profit, 2) + " " + AccountCurrency() + "  current level: " + level_abs +
           "\n" + SP + "auto-tp: " + auto_tp + " levels (" + DoubleToStr(auto_tp_price, Digits) + ", " + DoubleToStr(auto_tp_profit, 2) + " " + AccountCurrency() + ")");

   if (last_be_plot == 0 || TimeCurrent() - last_be_plot > 300){ // every 5 minutes
      plotBreakEven();
      last_be_plot = TimeCurrent();
   }

   // If you put a text object (not a label!) with the name "profit",  
   // anywhere on the chart then this can be used as a profit calculator.
   // The following code will find the position of this text object 
   // and calculate your profit, should price reach this position
   // and then write this number into the text object. You can
   // move it around on the chart to get profit projections for
   // any price level you want. 
   if (ObjectFind("profit") != -1){
      pb = getPyramidBase();
      lp = ObjectGet("profit", OBJPROP_PRICE1);
      if (pb ==0){
         if (direction == SHORT){
            pb = getLine() - stop_distance * pip;
         }
         if (direction == LONG){
            pb = getLine() + stop_distance * pip;
         }
         if (direction == BIDIR){
            if (lp < getLine()){
               pb = getLine() - stop_distance * pip;
            }
            if (lp >= getLine()){
               pb = getLine() + stop_distance * pip;
            }
         }
      }
      tp = getTheoreticProfit(MathAbs(lp - pb));
      ObjectSetText("profit", "¯¯¯ " + DoubleToStr(MathRound(Realized - getGlobal("Realized") + tp), 0) + " " + AccountCurrency() + " profit projection ¯¯¯");
   }
   
}

/**
* Plot an arrow. Default is the price-exact dash symbol
* This function might be moved into common_functions soon
*/
string arrow(string name="", double price=0, datetime time=0, color clr=Red, int arrow_code=4){
   if (time == 0){
      time = TimeCurrent();
   }
   if (name == ""){
      name = "arrow_" + time;
   }
   if (price == 0){
      price = Bid;
   }
   if (ObjectFind(name) < 0){
      ObjectCreate(name, OBJ_ARROW, 0, time, price);
   }else{
      ObjectSet(name, OBJPROP_PRICE1, price);
      ObjectSet(name, OBJPROP_TIME1, time);
   }
   ObjectSet(name, OBJPROP_ARROWCODE, arrow_code);
   ObjectSet(name, OBJPROP_SCALE, 1);
   ObjectSet(name, OBJPROP_COLOR, clr);
   ObjectSet(name, OBJPROP_BACK, true);
   return(name);
}

/**
* plot the break even price into the chart
*/
void plotBreakEvenArrow(string arrow_name, double price){
   arrow(arrow_name + TimeCurrent(), price, 0, clr_breakeven_level);
}


/**
* plot the break-even Point (only a rough estimate plusminus less than one stop_distance,
* it will be most inaccurate just before hitting a stoploss (last trade negative).
* and this will be more obvious at the beginning of a new cycle when losses are still small
* and break even steps increments are still be big.
*
* Side effects: This function will also calculate auto-tp price and profit.
*
* FIXME: This whole break even calculation sucks comets through drinking straws!
* FIXME: Isn't there a more elegant way to calculate break even?
*/
void plotBreakEven(){
   double base = getPyramidBase();
   double be = 0;
   
   // loss is roughly the amount of Realized stop hits. But I can't use this number
   // directly because after resuming a paused pyramid this number is wrong. So
   // I have to estimate it with the (always accurate) total profit and the current
   // distance from base. In mose cases the outcome of this calculation is equal
   // to the Realized losses as displayed on the screen, only when resuming a pyramid 
   // it will differ and have the value it would have if the pyramid never had been paused.
   double distance = MathAbs(Close[0] - base);
   if ((level > 0 && Close[0] < base) || (level < 0 && Close[0] > base) || level == 0){
      distance = 0;
   }
   double loss = -(cycle_total_profit - getTheoreticProfit(distance));

   // this value should always be positive 
   // or 0 (or slightly below (rounding error)) in case we have a fresh pyramid.
   // If it is not positive (no loss yet) then we dont need to plot break even.
   if (loss <= 0 || !running){
      auto_tp_price = 0;
      auto_tp_profit = 0;
      return;
   }
   
   if (direction == LONG){
      if (base==0){
         base = getLine() + stop_distance * pip;
      }
      be = base + getBreakEven(loss);
      plotBreakEvenArrow("breakeven_long", be);
      auto_tp_price = be + pip * stop_distance * auto_tp;
      auto_tp_profit = getTheoreticProfit(MathAbs(auto_tp_price - base)) - loss;
   }
   
   if (direction == SHORT){
      if (base==0){
         base = getLine() - stop_distance * pip;
      }
      be = base - getBreakEven(loss);
      plotBreakEvenArrow("breakeven_short", be);
      auto_tp_price = be - pip * stop_distance * auto_tp;
      auto_tp_profit = getTheoreticProfit(MathAbs(auto_tp_price - base)) - loss;
   }
   
   if (direction == BIDIR){
      if (base == 0){
         base = getLine() + stop_distance * pip;
         plotBreakEvenArrow("breakeven_long", base + getBreakEven(loss));
         base = getLine() - stop_distance * pip;
         plotBreakEvenArrow("breakeven_short", base - getBreakEven(loss));
         auto_tp_price = 0;
         auto_tp_profit = 0;
      }else{
         if (getLotsOnTableSigned(MagicNo) > 0){
            be = base + getBreakEven(loss);
            plotBreakEvenArrow("breakeven_long", be);
            auto_tp_price = be + pip * stop_distance * auto_tp;
            auto_tp_profit = getTheoreticProfit(MathAbs(auto_tp_price - base)) - loss;
         }else{
            be = base - getBreakEven(loss);
            plotBreakEvenArrow("breakeven_short", be);
            auto_tp_price = be - pip * stop_distance * auto_tp;
            auto_tp_profit = getTheoreticProfit(MathAbs(auto_tp_price - base)) - loss;
         }
      }
   }
   
   if (auto_tp < 1){
      auto_tp_price = 0;
      auto_tp_profit = 0;
   }
}


/**
* return the entry price of the first order of the pyramid.
* return 0 if we are flat.
*/
double getPyramidBase(){
   double d, max_d, sl;
   int i;
   int type=-1;
   int x;
   
   // find the stoploss that is farest away from current price
   // we cannot just use the order open price because we might
   // be in resume mode and then all trades would be opened at
   // the same price. the only thing that works reliable is 
   // looking at the stoplossses
   for (i=0; i<OrdersTotal(); i++){
     x =  OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
      if (OrderMagicNumber() == MagicNo && OrderType() < 2){
         d = MathAbs(Close[0] - OrderStopLoss());
         if (d > max_d){
            max_d = d;
            sl = OrderStopLoss();
            type = OrderType();
         }
      }
   }
   
   if (type == OP_BUY){
      return(sl + pip * stop_distance);
   }
   
   if (type == OP_SELL){
      return(sl - pip * stop_distance);
   }
   
   return(0);
}

double getPyramidBase1(){
   int i,x;
   double pmax = -999999;
   double base = 0;
   for (i=0; i<OrdersTotal(); i++){
      x=OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
      if (OrderMagicNumber() == MagicNo && OrderType() < 2){
         if (OrderProfit() > pmax){
            base = OrderOpenPrice();
            pmax = OrderProfit();
         }
      }
   }
   return(base);
}

/**
* return the floating profit that would result if
* price would be the specified distance away from
* the base of the pyramid
*/ 
double getTheoreticProfit(double distance){
   int n = MathFloor(distance / (stop_distance * pip));
   double remain = distance - n * stop_distance * pip;
   int mult = n * (n + 1) / 2;
   double profit = MarketInfo(Symbol(), MODE_TICKVALUE) * LotsToTrade * stop_distance * points_per_pip * mult;
   profit = profit + MarketInfo(Symbol(), MODE_TICKVALUE) * LotsToTrade * (remain/Point) * (n + 1);
   return(profit);
}

/**
* return the price move relative to base required to compensate Realized losses
* FIXME: This algorithm does not qualify as "elegant", not even remotely. 
*/
double getBreakEven(double loss){
   double i = 0;
   
   while(true){
      if (getTheoreticProfit(pip * i) > loss){
         break;
      }
      i += stop_distance;
   }
   
   i -= stop_distance;
   while(true){
      if (getTheoreticProfit(pip * i) > loss){
         break;
      }
      i += 0.1;
   }

   return(pip * i);
}

int start(){
   static int numbars;
   onTick();
   if (Bars == numbars){
      return(0);
   }
   numbars = Bars;
   onOpen();
   return(0);
}

void setGlobal(string key, double value){
   GlobalVariableSet(EAname + MagicNo + "_" + key, value);
}

double getGlobal(string key){
   return(GlobalVariableGet(EAname + MagicNo + "_" + key));
}

//----------------------------------------------------------------------
//----------------------------------------------------------------------
// Functions from offline_charts.mqh
//----------------------------------------------------------------------
//----------------------------------------------------------------------
/**
* Record the equity curve for the specified filter critera into an offline chart.
* Call this function on every tick. It will produce a chart that will 
* include every high and low of the total floating and realized profits.
* 
* You can filter by magic number and/or by the comment field.
* If magic is -1 then all magic numbers are allowed, if it is 0 then 
* only manually opened trades are counted, if it is any other
* value then only trades with this particular number are counted. 
* The second filter is comment, if it is "" then it is not filtered
* by comment, else only trades with this exact comment string are
* counted.
* 
* Offset is used to make sure the chart always is in positive terrotory.
* Since metatrader won't display charts with negative values and we are
* only summing up an individual strategie's profit (or loss!), not total 
* equity, we need some imaginary positive starting capital for each chart.
*/
void recordEquity(string name, int period, int magic=-1, string comment="", double offset=5000){
   double equity;
   
   // don't do anything during optimization runs
   if (IsOptimization()){
      return;
   }
   
   // This can happen shortly after a restart of metatrader. The order history
   // is still empty. We do nothing in this case and wait for the next tick.
   if (OrdersHistoryTotal() == 0){
      return;
   }

   if (magic == -1 && comment == ""){
      // If there is no filter we can simply use AccountEquity(), also we
      // don't need a virtual starting balance in this case. 
      equity = AccountEquity();
   }else{
      // Otherwise calculate the partial profits and add 'offset' 
      // as virtual starting balance.
      equity = getAllProfitFiltered(magic, comment) + offset;
   }
      
   // when run in the strategy tester we add a _ to the chart name
   // so it wont interfere with the same chart in live trading
   if (IsTesting()){
      name = "_" + name;
   }
   
   // write it into the chart.
   updateOfflineChart(name, period, 2, equity, 0); 
}

/**
* Update the offline chart with a new price. 
* This function will find the last bar in the offline chart file 
* (create the chart if necessary) update the last bar (adjust the close, 
* add to the volume and extend high or low if necessary) or start a new bar 
* if current time is beyond the lifetime of the last bar in the chart.
* Note: if you want to make Renko or other range based charts you will need to
* write your own function similar to this but with a different algorithm to
* detect when a new bar must be started. This one is strictly time based.
*/ 
void updateOfflineChart(string symbol, int period, int digits, double price, double volume){
   double o,h,l,c,v;
   int t;
   int time_current = iTime(NULL, period, 0); // FIXME! the starting time for the period's current bar
   
   // create the chart if it doesn't already exist
   // or just update the header
   writeOfflineHeader(symbol, period, digits);
   
   // read the last bar in the chart (if any)
   if (!readOfflineBar(symbol, period, 1, t, o, h, l, c, v)){
      // no bars in chart yet, so just make one
      writeOfflineBar(symbol, period, 0, time_current, price, price, price, price, volume);
      return;
   }
   
   if (t > time_current){
      // this is a very special case: the last bar in the chart is
      // NEWER that the bar we just want to record. This can only
      // happen if we backtest and there is already a chart left
      // from a previous backtest. In this case the only reasonable
      // thing we would want to do is completely empty the chart and
      // start again.
      if (IsTesting()){
         // ONLY empty the chart if we REALLY are in the baktester, 
         // else we simply IGNORE it completely instead of accidently 
         // destroying a whole and possibly months old chart just 
         // because of one bad timestamp.
         emptyOfflineChart(symbol, period, digits);   
         writeOfflineBar(symbol, period, 0, time_current, price, price, price, price, volume);
      }
      return;
   }
   
   if (t == time_current){
      // the bar has the current time, so update it
      if (price > h){
         h = price;
      }
      if (price < l){
         l = price;
      }
      c = price;
      v += volume;
      writeOfflineBar(symbol, period, 1, t, o, h, l, c, v);
   }else{
      // last bar is old, start a new one
      writeOfflineBar(symbol, period, 0, time_current, price, price, price, price, volume);
   }  
}

/**
* empty the chart, write a fresh header
*/
void emptyOfflineChart(string symbol, int period, int digits){
   // close it first (if we are in the backtester and the file is kept open)
   forceFileClose();
      
   // open (and immediately close) the file in write only mode, 
   // this will truncate it to zero length, after that we write a fresh header
   FileClose(FileOpenHistory(offlineFileName(symbol, period), FILE_WRITE | FILE_BIN));
   writeOfflineHeader(symbol, period, digits);
}

/**
* write or update the header of an offline chart file,
* if the file does not yet exist create the file.
*/
void writeOfflineHeader(string symbol, int period, int digits){
   int    version = 400;
   string c_copyright = "(C)opyright 2009-2010, Bernd Kreuss";
   int    i_unused[13];
   
   int F = fileOpenEx(offlineFileName(symbol, period), FILE_BIN | FILE_READ | FILE_WRITE);
   FileSeek(F, 0, SEEK_SET);
   FileWriteInteger(F, version, LONG_VALUE);
   FileWriteString(F, c_copyright, 64);
   FileWriteString(F, symbol, 12);
   FileWriteInteger(F, period, LONG_VALUE);
   FileWriteInteger(F, digits, LONG_VALUE);
   FileWriteInteger(F, 0, LONG_VALUE);       //timesign
   FileWriteInteger(F, 0, LONG_VALUE);       //last_sync
   FileWriteArray(F, i_unused, 0, 13);

   fileCloseEx(F);
}

/**
* Write (or update) one bar in the offline chart file
* and refresh the chart window if it is currently open.
* The parameter bars_back is the offset counted from the end of
* the file: 0 means append a new bar, 1 means update the last bar
* 2 would be the second last bar and so on.
* The parameter time is the POSIX-Timestamp representing the 
* beginning of that bar. It is the same value that would be 
* returned from iTime() or Time[], namely the seconds that
* have passed since the UNIX-Epoch (00:00 a.m. of 1 January, 1970)
*/
void writeOfflineBar(string symbol, int period, int bars_back, int time, double open, double high, double low, double close, double volume){
   int F = fileOpenEx(offlineFileName(symbol, period), FILE_BIN | FILE_READ | FILE_WRITE);
   
   int position = bars_back * OFFLINE_RECORD_SIZE;   
   FileSeek(F, -position, SEEK_END);

   if (FileTell(F) >= OFFLINE_HEADER_SIZE){
      FileWriteInteger(F, time, LONG_VALUE); 
      FileWriteDouble(F, open, DOUBLE_VALUE);
      FileWriteDouble(F, low, DOUBLE_VALUE);
      FileWriteDouble(F, high, DOUBLE_VALUE);
      FileWriteDouble(F, close, DOUBLE_VALUE);
      FileWriteDouble(F, volume, DOUBLE_VALUE);
   
      // refresh the chart window
      // this won't work in backtesting mode
      // and also don't do it while deinitializing or the
      // WindowHandle() function will run into a deadlock
      // for unknown reasons. (this took me a while to debug)
      if (!IsStopped() && !IsTesting()){
         int hwnd=WindowHandle(symbol, period);
         if (hwnd != 0){
            PostMessageA(hwnd, WM_COMMAND, 33324, 0);
         }
      }
   }
   fileCloseEx(F);
}

/**
* Read one bar out of the offline chart file and fill the
* "by reference"-parameters that were passed to the function.
* The function returns True if successful or False otherwise.
* The parameter bars_back is the offset counting from the end
* of the file: 0 makes no sense since it would be past the end,
* 1 means read the last bar in the file, 2 the second last, etc.
* If bars_back would point outside the file (beginning or end)
* the function will return False and do nothing, otherwise
* the read values will be filled into the supplied parameters
* and the function will return True
*/ 
bool readOfflineBar(string symbol, int period, int bars_back, int& time, double& open, double& high, double& low, double& close, double& volume){
   int F = fileOpenEx(offlineFileName(symbol, period), FILE_BIN | FILE_READ | FILE_WRITE);
   
   int position = bars_back * OFFLINE_RECORD_SIZE;
   FileSeek(F, -position, SEEK_END);
   
   if (FileSize(F) - FileTell(F) >= OFFLINE_RECORD_SIZE && FileTell(F) >= OFFLINE_HEADER_SIZE){
      time = FileReadInteger(F, LONG_VALUE); 
      open = FileReadDouble(F, DOUBLE_VALUE);
      low = FileReadDouble(F, DOUBLE_VALUE);
      high = FileReadDouble(F, DOUBLE_VALUE);
      close = FileReadDouble(F, DOUBLE_VALUE);
      volume = FileReadDouble(F, DOUBLE_VALUE);
      fileCloseEx(F);
      return(True);
   }else{
      fileCloseEx(F);
      return(False);
   }
}

/**
* construct the file name for the chart file, truncate the 
* symbol name to the maximum of 12 allowed characters.
*/
string offlineFileName(string symbol, int period){
   return(StringSubstr(symbol, 0, 12) + period + ".hst");
}

/**
* loop through all trades (historic and currently open) and
* sum up all profits (including swap and commission).
*/
double getAllProfitFiltered(int magic=-1, string comment=""){
   int cnt, total,x;
   double floating = 0;
   double realized = 0;
   static int last_closed_ticket = 0;
   string cache_name; 
   
   // we need a unique name under which to cache the realized P&L later
   if (IsTesting()){
      cache_name = "profit_cache@@" + magic + "@" + comment;
   }else{
      cache_name = "profit_cache@" + magic + "@" + comment;
   }
   
   // sum up the floating
   total=OrdersTotal();
   for(cnt=0; cnt<total; cnt++){
     x= OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if ((magic == -1 || OrderMagicNumber() == magic) 
       && (comment == "" || StringFind(OrderComment(), comment, 0) != -1)
      ){
         floating += OrderProfit() + OrderSwap() + OrderCommission();
      }
   }
   
   // Now calculate the total realized profit.
   // We first check if the order history has changed since 
   // the last tick by looking at the newest ticket number.
   // If there was no change then we can assume that no trade
   // has been closed and we can simply use the cached value
   // of the previously calculated realized profit.
   total=OrdersHistoryTotal();
   x=OrderSelect(total-1, SELECT_BY_POS, MODE_HISTORY);
   if (last_closed_ticket != OrderTicket()){
   
      // history is different from last time, so we must do 
      // the expensive loop and sum up all realized profit
      last_closed_ticket = OrderTicket();
      realized = 0;
      for(cnt=0; cnt<total; cnt++){
        x= OrderSelect(cnt, SELECT_BY_POS, MODE_HISTORY);
         if ((magic == -1 || OrderMagicNumber() == magic) 
          && (comment == "" ||  StringFind(OrderComment(), comment, 0) != -1)
         ){
            realized += OrderProfit() + OrderSwap() + OrderCommission();
         }
      }
      // remember it for the next call.
      // We need to store it separately for every possible filter.
      // We dont have hash tables in mql4, so we must abuse 
      // the global variables function to store name-value pairs.
      GlobalVariableSet(cache_name, realized);      
   }else{
   
      // history not changed. retrieve the cached value.
      realized = GlobalVariableGet(cache_name);
   }
   
   return (floating + realized);
}

/**
* Open the chart file. In testing mode this will open the file
* only when called for the first time and cache the file handle
* for subsequent calls to speed up things. The corresponding
* fileCloseEx() function will do nothing when in testing mode.
*/
int fileOpenEx(string name, int mode){
   if (IsTesting()){
      if (__chart_file == 0){
         __chart_file = FileOpenHistory(name, mode);
      }
      return(__chart_file);
   }else{
      return(FileOpenHistory(name, mode));
   }
}

/**
* close the file. Keep the file open when in teting mode.
*/
void fileCloseEx(int file){
   if (!IsTesting()){
      FileClose(file);
   }else{
      //FileFlush(file);
   }
}

/**
* enforce closing of the file (in backtesting mode when it is held open)
*/ 
void forceFileClose(){
   if(__chart_file != 0){
      FileClose(__chart_file);
      __chart_file = 0;
   }
}

//----------------------------------------------------------------------
//----------------------------------------------------------------------
// Functions from common_functions.mqh
//----------------------------------------------------------------------
//----------------------------------------------------------------------
/**
* start an external program but DON'T wait for it to finish
*/
void shell(string file, string parameters = "") {
   ShellExecuteA(0, "open", file, parameters, NULL, SW_SHOWNORMAL);
}

/**
* Replacement for the built-in Print(), output to the debug monitor.
* Send information to OutputDebugString() to be viewed and logged
* by SysInternals DebugView (free download from microsoft)
* This is ideal for debugging as an alternative to Print().
* The function will take up to 8 string (or numeric) arguments
* to be concatenated into one debug message.
*/
void log(
   string s1,
   string s2 = "",
   string s3 = "",
   string s4 = "",
   string s5 = "",
   string s6 = "",
   string s7 = "",
   string s8 = ""
) {
   string out = StringTrimRight(StringConcatenate(
                                   WindowExpertName(), ".mq4 ", Symbol(),
                                   " ", s1,
                                   " ", s2,
                                   " ", s3,
                                   " ", s4,
                                   " ", s5,
                                   " ", s6,
                                   " ", s7,
                                   " ", s8
                                ));
   OutputDebugStringA(out);
}

/**
* Replacement for the built-in Print(), output to the chart window.
* use the Comments() display to simulate the behaviour of
* the good old print command, useful for debugging.
* text will be appended as a new line on every call
* and if it has reached 20 lines it will start to scroll.
* if clear is set to True the buffer will be cleared.
*/
void print(string text, bool clear = False) {
   static string print_lines[20];
   static int print_line_position = 0;
   if (IsOptimization()) {
      return;
   }
   string output = "\n";
   string space = "                        ";
   int max_lines = 20;
   int i;
   if (clear) {
      for (i = 0; i < max_lines; i++) {
         print_lines[i] = "";
         print_line_position = 0;
      }
   }

   if (print_line_position == max_lines) {
      for (i = 0; i < max_lines; i++) {
         print_lines[i] = print_lines[i+1];
      }
      print_line_position--;
   }

   print_lines[print_line_position] = text;
   print_line_position++;

   for (i = 0; i < print_line_position; i++) {
      output = output + print_lines[i] + "\n";
   }

   output = stringReplace(output, "\n", "\n" + space);
   Comment(output);
}

/**
* search for the string needle in the string haystack and replace all
* occurrecnes with replace.
*/
string stringReplace(string haystack, string needle, string replace = "") {
   string left, right;
   int start = 0;
   int rlen = StringLen(replace);
   int nlen = StringLen(needle);
   while (start > -1) {
      start = StringFind(haystack, needle, start);
      if (start > -1) {
         if (start > 0) {
            left = StringSubstr(haystack, 0, start);
         } else {
            left = "";
         }
         right = StringSubstr(haystack, start + nlen);
         haystack = left + replace + right;
         start = start + rlen;
      }
   }
   return (haystack);
}


/**
* parse a string containing delimiters into an array of strings
* and return the number of elements in the resulting array.
*
* if compact is set to true (default) then it will trim all
* substrigs and only include non-empty strings in the result.
*
* if max is non-zero then this is the maximum number of results
* that should be returned. If for example max = 2 and str is
* "42 foo bar" and delimiter = " " then it will stop after the
* first delimiter and return only two elements: "42" and "foo bar"
* max = 0 (default) will search until the end.
*/
int stringExplode(string str, string delimiter, string &result[], bool compact = true, int max = 0) {
   int count = 0;
   int pos = 0;
   int pos_next = 0;
   int l_sub;
   string sub;
   bool end = false;
   int l_d = StringLen(delimiter);
   while (!end) {
      ArrayResize(result, count + 1);
      pos_next = StringFind(str, delimiter, pos);
      if (max > 0 && count == max - 1) {
         pos_next = -1;
      }
      if (pos_next > -1) {
         // found delimiter
         l_sub = pos_next - pos;
         if (l_sub == 0) {
            sub = "";
         } else {
            sub = StringSubstr(str, pos, l_sub);
         }
         pos = pos_next + l_d;
      } else {
         sub = StringSubstr(str, pos, 0); // until the end
         end = true;
      }
      if (compact) {
         sub = StringTrimLeft(StringTrimRight(sub));
      }
      if (sub != "" || !compact) {
         result[count] = sub;
         count++;
      }
   }
   return(count);
}


/**
* convert a 32 bit integer into its hexadecimal representation
*/
string intToHex(int x, int digits = 8) {
   int i, mask, nibble;
   string n, hex;
   mask = 15;
   for (i = 0; i < digits; i++) {
      nibble = (x & mask) >> (i << 2);
      if (nibble < 10) n = CharToStr(48 + nibble);
      if (nibble >  9) n = CharToStr(55 + nibble);
      mask = mask << 4;
      hex = n + hex;
   }
   return(hex);
}


/**
* create a positive integer for the use as a magic number.
*
* The function takes a string as argument and calculates
* an 31 bit hash value from it. The hash does certainly not
* have the strength of a real cryptographic hash function
* but it should be more than sufficient for generating a
* unique ID from a string and collissions should not occur.
*
* use it in your init() function like this:
*    magic = makeMagicNumber(WindowExpertName() + Symbol() + Period());
*
* where name would be the name of your EA. Your EA will then
* get a unique magic number for each instrument and timeframe
* and this number will always be the same, whenever you put
* the same EA onto the same chart.
*
* Numbers generated during testing mode will differ from those
* numbers generated on a live chart.
*/
int makeMagicNumber(string key) {
   int i, k;
   int h = 0;

   if (IsTesting()) {
      key = "_" + key;
   }

   for (i = 0; i < StringLen(key); i++) {
      k = StringGetChar(key, i);
      h = h + k;
      h = bitRotate(h, 5); // rotate 5 bits
   }

   for (i = 0; i < StringLen(key); i++) {
      k = StringGetChar(key, i);
      h = h + k;
      // rotate depending on character value
      h = bitRotate(h, k & 0x0000000F);
   }

   // now we go backwards in our string
   for (i = StringLen(key); i > 0; i--) {
      k = StringGetChar(key, i - 1);
      h = h + k;
      // rotate depending on the last 4 bits of h
      h = bitRotate(h, h & 0x0000000F);
   }

   return(h & 0x7fffffff);
}

/**
* Rotate a 32 bit integer value bit-wise
* the specified number of bits to the right.
* This function is needed for calculations
* in the hash function makeMacicNumber()
*/
int bitRotate(int value, int count) {
   int tmp, mask;
   mask = (0x00000001 << count) - 1;
   tmp = value & mask;
   value = value >> count;
   value = value | (tmp << (32 - count));
   return(value);
}

/**
* place a market buy with stop loss, target, magic and Comment
* keeps trying in an infinite loop until the position is open.
*/
int buy(double lots, double sl, double tp, int magic = 42, string comment = "") {
   int ticket;
   if (!IS_ECN_BROKER) {
      return(orderSendReliable(Symbol(), OP_BUY, lots, Ask, 100, sl, tp, comment, magic, 0, CLR_BUY_ARROW));
   } else {
      ticket = orderSendReliable(Symbol(), OP_BUY, lots, Ask, 100, 0, 0, comment, magic, 0, CLR_BUY_ARROW);
      if (sl + tp > 0) {
         orderModifyReliable(ticket, 0, sl, tp, 0);
      }
      return(ticket);
   }
}

/**
* place a market sell with stop loss, target, magic and comment
* keeps trying in an infinite loop until the position is open.
*/
int sell(double lots, double sl, double tp, int magic = 42, string comment = "") {
   int ticket;
   if (!IS_ECN_BROKER) {
      return(orderSendReliable(Symbol(), OP_SELL, lots, Bid, 100, sl, tp, comment, magic, 0, CLR_SELL_ARROW));
   } else {
      ticket = orderSendReliable(Symbol(), OP_SELL, lots, Bid, 100, 0, 0, comment, magic, 0, CLR_SELL_ARROW);
      if (sl + tp > 0) {
         orderModifyReliable(ticket, 0, sl, tp, 0);
      }
      return(ticket);
   }
}

/**
* place a buy limit order
*/
int buyLimit(double lots, double price, double sl, double tp, int magic = 42, string comment = "") {
   return(orderSendReliable(Symbol(), OP_BUYLIMIT, lots, price, 1, sl, tp, comment, magic, 0, CLR_NONE));
}

/**
* place a sell limit order
*/
int sellLimit(double lots, double price, double sl, double tp, int magic = 42, string comment = "") {
   return(orderSendReliable(Symbol(), OP_SELLLIMIT, lots, price, 1, sl, tp, comment, magic, 0, CLR_NONE));
}

/**
* place a buy stop order
*/
int buyStop(double lots, double price, double sl, double tp, int magic = 42, string comment = "") {
   return(orderSendReliable(Symbol(), OP_BUYSTOP, lots, price, 1, sl, tp, comment, magic, 0, CLR_NONE));
}

/**
* place a sell stop order
*/
int sellStop(double lots, double price, double sl, double tp, int magic = 42, string comment = "") {
   return(orderSendReliable(Symbol(), OP_SELLSTOP, lots, price, 1, sl, tp, comment, magic, 0, CLR_NONE));
}


/**
* calculate unrealized P&L, belonging to all open trades with this magic number
*/
double getProfit(int magic) {
   int cnt,x;
   double profit = 0;
   int total = OrdersTotal();
   for (cnt = 0; cnt < total; cnt++) {
      x=OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if (OrderMagicNumber() == magic) {
         profit += OrderProfit() + OrderSwap() + OrderCommission();
      }
   }
   return (profit);
}

/**
* calculate realized P&L resulting from all closed trades with this magic number
*/
double getProfitRealized(int magic) {
   int cnt,x;
   double profit = 0;
   int total = OrdersHistoryTotal();
   for (cnt = 0; cnt < total; cnt++) {
     x= OrderSelect(cnt, SELECT_BY_POS, MODE_HISTORY);
      if (OrderMagicNumber() == magic) {
         profit += OrderProfit() + OrderSwap() + OrderCommission();
      }
   }
   return (profit);
}

/**
* get the number of currently open trades of specified type
*/
int getNumOpenOrders(int type, int magic) {
   int cnt,x;
   int num = 0;
   int total = OrdersTotal();
   for (cnt = 0; cnt < total; cnt++) {
     x= OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if ((magic == -1 || OrderMagicNumber() == magic) && (type == -1 || OrderType() == type)) {
         num++;
      }
   }
   return (num);
}


/**
* Trail the take-profit if price moves against the trade.
*
* Loop through all matching open positions and trail the target
* towards price if the price moves AGAINST the trade direction (away
* from the target). This may even trail the target into negative territory.
*
* If trailtarget is 0 then nothing will be done, a positive number
* means the maximum distance between price and target.
*
* If max_negative is set to -1 (default) the there is no limitation
* how far into negative territory the target can be trailed, if
* max_negative is set to 0 then it will only trail down to break even
* and then stop at this point, if it is set to a positive number
* then it will use this number as a limit of how far into loss the
* target may be trailed.
*/
void trailTargets(double trailtarget, int magic, double max_negative = -1) {
   int total, cnt, type,x;
   double op, tp, old_tp, dist;
   bool change;

   if (trailtarget != 0) {
      total = OrdersTotal();
      for (cnt = 0; cnt < total; cnt++) {
        x= OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
         if (OrderMagicNumber() == magic) {
            op = OrderOpenPrice();
            old_tp = OrderTakeProfit();
            type = OrderType();
            change = False;

            if (type == OP_BUY) {
               tp = MarketInfo(OrderSymbol(), MODE_BID) + trailtarget;
               dist = tp - op;
               if (max_negative >= 0 && -dist > max_negative) {
                  tp = op - max_negative;
               }
               if (tp < old_tp) {
                  change = True;
               }
            }

            if (type == OP_SELL) {
               tp = MarketInfo(OrderSymbol(), MODE_ASK) - trailtarget;
               dist = op - tp;
               if (max_negative >= 0 && -dist > max_negative) {
                  tp = op + max_negative;
               }
               if (tp > old_tp) {
                  change = True;
               }
            }

            if (change) {
               orderModifyReliable(
                  OrderTicket(),
                  op,
                  OrderStopLoss(),
                  tp,
                  OrderExpiration()
               );
            }
         }
      }
   }
}

/**
* Trail the stoploss.
*
* Loop through all matching open positions and trail their stops.
*
* If trailstops is 0 then nothing will be done, a positive number is
* the maximum distance between price and stop, the distance at which
* the stop is trailed behind price.
*
* trailstop_slow is a factor that will increase trailing distance
* when the profit grows. default 1 is normal trailing stop behaviour
*/
void trailStops(double trailstop, int magic, double trailstop_slow = 1) {
   int total, cnt, type,x;
   double op, sl, old_sl;
   bool change;

   if (trailstop != 0) {
      total = OrdersTotal();
      for (cnt = 0; cnt < total; cnt++) {
        x= OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
         if (OrderMagicNumber() == magic) {
            op = OrderOpenPrice();
            old_sl = OrderStopLoss();
            type = OrderType();
            change = False;

            if (type == OP_BUY) {
               sl = op + (MarketInfo(OrderSymbol(), MODE_BID) - trailstop - op) / trailstop_slow;
               if (sl > op && sl > old_sl) {
                  change = True;
               }
            }

            if (type == OP_SELL) {
               sl = op - (op - MarketInfo(OrderSymbol(), MODE_ASK) - trailstop) / trailstop_slow;
               if (sl < op && (sl < old_sl || old_sl == 0)) {
                  change = True;
               }
            }

            if (change) {
               orderModifyReliable(
                  OrderTicket(),
                  op,
                  sl,
                  OrderTakeProfit(),
                  OrderExpiration()
               );
            }
         }
      }
   }
}

/**
* Lock in pofit after a crtain amount of profit is reached.
*
* This will place (or move) a stoploss min_profit away from
* the open price as soon as price has moved distance away from
* this point. If distance is 0 or smaller than the minimum
* allowed stop distance then it will still behave as if you had
* specified the minimum allowed stop distance.
*/
void lockProfit(double min_profit, int magic, double distance = 0) {
   int total, cnt, type, x;
   double op, sl, old_sl;
   bool change;

   total = OrdersTotal();
   for (cnt = 0; cnt < total; cnt++) {
      x=OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if (OrderMagicNumber() == magic) {
         old_sl = OrderStopLoss();
         op = OrderOpenPrice();
         type = OrderType();
         change = False;

         if (type == OP_BUY) {
            sl = op + min_profit;
            if (MarketInfo(OrderSymbol(), MODE_BID) > sl + distance && sl > old_sl) {
               change = True;
            }
         }

         if (type == OP_SELL) {
            sl = op - min_profit;
            if (MarketInfo(OrderSymbol(), MODE_ASK) < sl - distance && (sl < old_sl || old_sl == 0)) {
               change = True;
            }
         }

         if (change) {
            orderModifyReliable(
               OrderTicket(),
               op,
               sl,
               OrderTakeProfit(),
               OrderExpiration()
            );
         }
      }
   }
}

/**
* Return true if there exists an order of this type at exactly this price.
*/
bool isOrder(int type, double price, int magic) {
   int cnt, num,x;
   int total = OrdersTotal();
   for (cnt = 0; cnt < total; cnt++) {
      x=OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if (OrderMagicNumber() == magic && (type == -1 || OrderType() == type)) {
         if (isEqualPrice(OrderOpenPrice(), price)) {
            num++;
         }
      }
   }
   return (num > 0);
}

/**
* Close all open orders or trades that match type and magic number.
* This function won't return until all positions are closed
* type = -1 means all types, magic = -1 means all magic numbers
*/
void closeOpenOrders(int type, int magic) {
   int total, cnt,x;
   color clr;
   int order_type;

   Print("closeOpenOrders(" + type + "," + magic + ")");

   while (getNumOpenOrders(type, magic) > 0) {
      while (IsTradeContextBusy()) {
         Print("closeOpenOrders(): waiting for trade context.");
         Sleep(MathRand() / 10);
      }
      total = OrdersTotal();
      RefreshRates();
      if (type == OP_BUY) {
         clr = CLR_SELL_ARROW;
      } else {
         clr = CLR_BUY_ARROW;
      }
      for (cnt = 0; cnt < total; cnt++) {
        x= OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
         if ((type == -1 || OrderType() == type) && (magic == -1 || OrderMagicNumber() == magic)) {
            if (IsTradeContextBusy()) {
               break; // something else is trading too, back to the while loop.
            }
            order_type = OrderType();
            if (order_type == OP_BUYSTOP || order_type == OP_SELLSTOP || order_type == OP_BUYLIMIT || order_type == OP_SELLLIMIT) {
               orderDeleteReliable(OrderTicket());
            } else {
               orderCloseReliable(OrderTicket(), OrderLots(), 0, 999, clr);
            }
            break; // restart the loop from 0 (hello FIFO!)
         }
      }
   }
}

/**
* Scale out of a position the specified amounts of lots and return
* the number of lots that could *not* be closed.
*
* This function will try to close out the needed lots out of any
* combination of open orders, using partial closes or complete closes
* until there is nothing left to close or the needed lot size has been
* closed. It will start closing the oldest orders first (FIFO).
* The return value will be the amount that could not be closed, this
* can then be directly used to place a buy or sell into the other
* direction. If the requested amount could be closed it will return 0.
*
* This function is not yet fully tested and debugged. It may change
* in future releases, it may not work at all. Dont use it (yet)!
*/
double reducePosition(int type, double lots, int magic) {
   int i,x;
   int clr;
   bool loop_again = true;

   Print("reducePosition()");

   while (loop_again) {
      RefreshRates();
      if (type == OP_BUY) {
         clr = CLR_SELL_ARROW;
      }
      if (type == OP_SELL) {
         clr = CLR_BUY_ARROW;
      }
      int total_orders = OrdersTotal();
      loop_again = false;
      for (i = 0; i < total_orders; i++) {
        x= OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
         if (OrderMagicNumber() == magic) {
            if (OrderType() == type) {
               if (NormalizeDouble(OrderLots() - lots, 2) >= 0) {
                  // found big enough order to do it in one step
                  Print("reducePosition(): now trying to close order");
                  if (orderCloseReliable(OrderTicket(), lots, 0, 100, clr) == true) {
                     Print("reducePosition(): success!");
                     return(0);
                  } else {
                     Print("reducePosition(): order found but failed to close: " + GetLastError());
                     // permanent error occured
                     return(lots);
                  }
               } else {
                  // order is smaller. close it comppetely
                  if (orderCloseReliable(OrderTicket(), OrderLots(), 0, 100, clr) == true) {
                     lots -= OrderLots();
                     Print("reducePosition(): closed " + OrderLots() + " remaining: " + lots);
                     loop_again = True;
                     break; // number of orders has now changed, restart the for loop
                  } else {
                     Print("reducePosition(): order found but failed to close: " + GetLastError());
                     // permanent error occured
                     return(lots);
                  }
               }

               // are we already done?
               if (NormalizeDouble(lots, 2) == 0) {
                  return(0);
               }
            }
         }
      }//for (all orders)

      // whenever the for loop has completed
      // loop_again would indicate that an order has been closed
      // and we need to start the for loop again from 0

   }// while (loop_again)
   Print("reducePosition(): nothing more to close. " + lots + " could not be closed");
   return(lots);
}

/**
* move the stop (stops) to the specified price.
*/
void moveStop(int type, int magic, double stoploss = -1) {
   int total, cnt,x;
   total = OrdersTotal();
   for (cnt = 0; cnt < total; cnt++) {
     x= OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if (OrderType() == type && OrderMagicNumber() == magic) {
         if (stoploss == -1) {
            stoploss = OrderOpenPrice();
         }
         if (!isEqualPrice(stoploss, OrderStopLoss())) {
            orderModifyReliable(
               OrderTicket(),
               OrderOpenPrice(),
               stoploss,
               OrderTakeProfit(),
               OrderExpiration()
            );
         }
      }
   }
}

/**
* Move the pending order to the specified price.
* This will also take care of moving associated
* stop and target the same amount.
*/
void moveOrder(int type, int magic, double price) {
   int total, cnt,x;
   double d, sl, tp;
   total = OrdersTotal();
   for (cnt = 0; cnt < total; cnt++) {
     x= OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if (OrderType() == type && OrderMagicNumber() == magic) {
         if (!isEqualPrice(d, OrderOpenPrice())) {
            d = price - OrderOpenPrice();
            if (OrderStopLoss() == 0) {
               sl = 0;
            } else {
               sl = OrderStopLoss() + d;
            }
            if (OrderTakeProfit() == 0) {
               tp = 0;
            } else {
               tp = OrderTakeProfit() + d;
            }
            orderModifyReliable(
               OrderTicket(),
               price,
               sl,
               tp,
               OrderExpiration()
            );
         }
      }
   }
}

/**
* Move the take-profit to the specified price.
*/
void moveTarget(int type, int magic, double target) {
   int total, cnt, x;
   total = OrdersTotal();
   for (cnt = 0; cnt < total; cnt++) {
     x= OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if (OrderType() == type && OrderMagicNumber() == magic) {
         if (!isEqualPrice(target, OrderTakeProfit())) {
            orderModifyReliable(
               OrderTicket(),
               OrderOpenPrice(),
               OrderStopLoss(),
               target,
               OrderExpiration()
            );
         }
      }
   }
}

/**
* Get the number of (effective) lots that are curretly open.
* This will return the effective exposure. Offsetting trades
* will be subtracted. The return value will always be positive.
* See also getLotsOnTableSigned()
*/
double getLotsOnTable(int magic) {
   double total_lots = 0;
   int i,x;
   int total_orders = OrdersTotal();
   for (i = 0; i < total_orders; i++) {
    x=  OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
      if (OrderMagicNumber() == magic) {
         if (OrderType() == OP_BUY) {
            total_lots += OrderLots();
         }
         if (OrderType() == OP_SELL) {
            total_lots -= OrderLots();
         }
      }
   }
   total_lots = MathAbs(total_lots);
   return(total_lots);
}

/**
* Get the number of (effective) lots that are curretly open.
* This will return the effective exposure. Offsetting trades
* will be subtracted. Positive means long, negative is short.
* See also getLotsOnTable()
*/
double getLotsOnTableSigned(int magic) {
   double total_lots = 0;
   int i,x;
   int total_orders = OrdersTotal();
   for (i = 0; i < total_orders; i++) {
      x=OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
      if (OrderMagicNumber() == magic) {
         if (OrderType() == OP_BUY) {
            total_lots += OrderLots();
         }
         if (OrderType() == OP_SELL) {
            total_lots -= OrderLots();
         }
      }
   }
   return(total_lots);
}

/**
* Get the open price of the average position.
*/
double getAveragePositionPrice(int magic) {
   double total_lots = getLotsOnTable(magic);
   double average_price = 0;
   int i,x;
   int total_orders = OrdersTotal();
   for (i = 0; i < total_orders; i++) {
      x=OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
      if (OrderMagicNumber() == magic) {
         average_price += OrderOpenPrice() * OrderLots() / total_lots;
      }
   }
   return(average_price);
}

/**
* Get the profit of the last closed order (sl, tp or manually closed).
*/
double getLastProfit(int magic) {
   selectLastClosedTrade(magic);
   return(OrderProfit());
}

/**
* Select the last closed order (sl, tp or manually closed).
*/
void selectLastClosedTrade(int magic) {
   int total, cnt, type,x;
   total = OrdersHistoryTotal();
   for (cnt = total - 1; cnt >= 0; cnt--) {
     x= OrderSelect(cnt, SELECT_BY_POS, MODE_HISTORY);
      type = OrderType();
      if (OrderMagicNumber() == magic && (type == OP_BUY || type == OP_SELL)) {
         return;
      }
   }
}

/**
* Plot the opening trade arrow.
* This is part of a re-implementation of what metatrader does when dragging
* a trade from the history to the chart. Metatrader won't do this automatically
* for manual trading and for pending order fills so we have to do it ourselves.
* See also plotNewOpenTrades() and plotNewClosedTrades() defined below.
*/
void plotOpenedTradeArrow(int ticket, bool remove = false) {
   string name, name_wrong;
   color clr;
   int x;
   if (IsOptimization()) {
      return;
   }
  x= OrderSelect(ticket, SELECT_BY_TICKET);

   name = "#" + ticket + " ";
   if (OrderType() == OP_BUY) {
      name = name + "buy ";
      clr = CLR_BUY_ARROW;
   }
   if (OrderType() == OP_SELL) {
      name = name + "sell ";
      clr = CLR_SELL_ARROW;
   }
   name = name + DoubleToStr(OrderLots(), 2) + " ";
   name = name + OrderSymbol() + " ";

   // sometimes mt4 will have created an arrow with open price = 0.
   // this is wrong, we will delete it.
   name_wrong = name + "at " + DoubleToStr(0, MarketInfo(OrderSymbol(), MODE_DIGITS));

   name = name + "at " + DoubleToStr(OrderOpenPrice(), MarketInfo(OrderSymbol(), MODE_DIGITS));

   ObjectDelete(name_wrong);
   if (remove) {
      ObjectDelete(name);
   } else {
      ObjectCreate(name, OBJ_ARROW, 0, OrderOpenTime(), OrderOpenPrice());
      ObjectSet(name, OBJPROP_ARROWCODE, 1);
      ObjectSet(name, OBJPROP_COLOR, clr);
      ObjectSetText(name, formatOrderArrowInfo());
   }
}

/**
* Plot the closing trade arrow.
* This is part of a re-implementation of what metatrader does when dragging
* a trade from the history to the chart. Metatrader won't do this automatically
* for manual trading and for pending order fills so we have to do it ourselves.
* See also plotNewOpenTrades() and plotNewClosedTrades() defined below.
*/
void plotClosedTradeArrow(int ticket, bool remove = false) {
   string name;
   color clr;
   int x;
   if (IsOptimization()) {
      return;
   }
   x=OrderSelect(ticket, SELECT_BY_TICKET);
   name = "#" + ticket + " ";
   if (OrderType() == OP_BUY) {
      name = name + "buy ";
      clr = CLR_SELL_ARROW; // closing a buy is a sell, so make it red
   }
   if (OrderType() == OP_SELL) {
      name = name + "sell ";
      clr = CLR_BUY_ARROW; // closing a sell is a buy, so make it blue
   }
   name = name + DoubleToStr(OrderLots(), 2) + " ";
   name = name + OrderSymbol() + " ";
   name = name + "at " + DoubleToStr(OrderOpenPrice(), MarketInfo(OrderSymbol(), MODE_DIGITS)) + " ";
   name = name + "close at " + DoubleToStr(OrderClosePrice(), MarketInfo(OrderSymbol(), MODE_DIGITS));

   if (remove) {
      ObjectDelete(name);
   } else {
      ObjectCreate(name, OBJ_ARROW, 0, OrderCloseTime(), OrderClosePrice());
      ObjectSet(name, OBJPROP_ARROWCODE, 3);
      ObjectSet(name, OBJPROP_COLOR, clr);
      ObjectSetText(name, formatOrderArrowInfo());
   }
}

/**
* Plot the line connecting open and close of a history trade.
* This is part of a re-implementation of what metatrader does when dragging
* a trade from the history to the chart. Metatrader won't do this automatically
* for manual trading and for pending order fills so we have to do it ourselves.
* See also plotNewOpenTrades() and plotNewClosedTrades() defined below.
*/
void plotClosedTradeLine(int ticket, bool remove = false) {
   string name;
   color clr;
   int x;
   if (IsOptimization()) {
      return;
   }
  x= OrderSelect(ticket, SELECT_BY_TICKET);
   name = "#" + ticket + " ";
   if (OrderType() == OP_BUY) {
      clr = CLR_BUY_ARROW;
   }
   if (OrderType() == OP_SELL) {
      clr = CLR_SELL_ARROW;
   }
   name = name + DoubleToStr(OrderOpenPrice(), MarketInfo(OrderSymbol(), MODE_DIGITS));
   name = name + " -> ";
   name = name + DoubleToStr(OrderClosePrice(), MarketInfo(OrderSymbol(), MODE_DIGITS));

   if (remove) {
      ObjectDelete(name);
   } else {
      ObjectCreate(name, OBJ_TREND, 0, OrderOpenTime(), OrderOpenPrice(), OrderCloseTime(), OrderClosePrice());
      ObjectSet(name, OBJPROP_RAY, false);
      ObjectSet(name, OBJPROP_STYLE, STYLE_DOT);
      ObjectSet(name, OBJPROP_COLOR, clr);
   }
}

/**
* Create the info-string for opened and closed order arrows.
*/
string formatOrderArrowInfo() {
   // order is already selected
   return(StringConcatenate(
             OrderComment(),
             " (",
             OrderMagicNumber(),
             ")\nP/L: ",
             DoubleToStr(OrderProfit() + OrderSwap() + OrderCommission(), 2),
             " ",
             AccountCurrency(),
             " (",
             DoubleToStr(MathAbs(OrderOpenPrice() - OrderClosePrice()) / (MarketInfo(OrderSymbol(), MODE_POINT) * pointsPerPip()), 2),
             " pips)"
          ));
}

/**
* Plot all newly opened trades into the chart.
* Check if the open trade list has changed and plot
* arrows for opened trades into the chart.
* Metatrader won't do this automatically for manual trading.
* Use this function for scanning for new trades and plotting them.
*/
void plotNewOpenTrades(int magic = -1) {
   //static int last_ticket=0;
   int total, i,x;
   if (IsTesting()) {
      return;
   }

   total = OrdersTotal();
   //OrderSelect(total - 1, SELECT_BY_POS, MODE_TRADES);
   //if (OrderTicket() != last_ticket){
   //   last_ticket = OrderTicket();

   // FIXME! find something to detect changes as cheap as possible!

   // order list has changed, so plot all arrows
   for (i = 0; i < total; i++) {
    x=  OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
      if ((magic == -1 || OrderMagicNumber() == magic) && OrderSymbol() == Symbol()) {
         if (OrderType() == OP_BUY || OrderType() == OP_SELL) {
            plotOpenedTradeArrow(OrderTicket());
         }
      }
   }
   //}
}

/**
* Plot all newly closed trades into the chart.
* Check for changes in the trading history and plot the
* trades into the chart with arrows and lines connectimg them.
* Metatrader won't do this automatically for manual trading.
* Use this function for scanning for closed trades and plotting them.
*/
void plotNewClosedTrades(int magic = -1) {
   if (IsTesting()) {
      return;
   }

   static int last_change = 0;
   bool max_age_defined = GlobalVariableCheck("ARROW_MAX_AGE");
   int max_age = GlobalVariableGet("ARROW_MAX_AGE") * 24 * 60 * 60;
   datetime tc = TimeCurrent();
   bool remove;
   int total, i,x;

   total = OrdersHistoryTotal();
  x= OrderSelect(total - 1, SELECT_BY_POS, MODE_HISTORY);
   if (OrderTicket() + max_age + max_age_defined != last_change) {
      last_change = OrderTicket() + max_age + max_age_defined;

      // order list has changed, so plot all arrows
      for (i = 0; i < total; i++) {
        x= OrderSelect(i, SELECT_BY_POS, MODE_HISTORY);
         if ((magic == -1 || OrderMagicNumber() == magic) && OrderSymbol() == Symbol()) {
            if (OrderType() == OP_BUY || OrderType() == OP_SELL) {
               if (max_age_defined && tc - OrderCloseTime() > max_age) {
                  remove = true;
               } else {
                  remove = false;
               }
               plotOpenedTradeArrow(OrderTicket(), remove);
               plotClosedTradeArrow(OrderTicket(), remove);
               plotClosedTradeLine(OrderTicket(), remove);
            }
         }
      }
   }
}

/**
* Create a line connecting 2 points.
*/
string line(string name, datetime t1, double p1, datetime t2, double p2, color clr = Red, string label = "", bool ray = False) {
   if (!IsOptimization()) {
      if (name == "") {
         name = "line_" + Time[0];
      }
      if (ObjectFind(name) == -1) {
         ObjectCreate(name, OBJ_TREND, 0, t1, p1, t2, p2);
      }
      ObjectSet(name, OBJPROP_RAY, ray);
      ObjectSet(name, OBJPROP_COLOR, clr);
      ObjectSet(name, OBJPROP_TIME1, t1);
      ObjectSet(name, OBJPROP_TIME2, t2);
      ObjectSet(name, OBJPROP_PRICE1, p1);
      ObjectSet(name, OBJPROP_PRICE2, p2);
      ObjectSetText(name, label);
   }
   return(name);
}

/**
* Create a horizontal line.
*/
string horizLine(string name, double price, color clr = Red, string label = "") {
   if (!IsOptimization()) {
      if (name == "") {
         name = "line_" + Time[0];
      }
      if (ObjectFind(name) == -1) {
         ObjectCreate(name, OBJ_HLINE, 0, 0, price);
      } else {
         ObjectSet(name, OBJPROP_PRICE1, price);
      }
      ObjectSet(name, OBJPROP_COLOR, clr);
      ObjectSetText(name, label);
   }
   return(name);
}

/**
* Create a vertical line.
*/
string vertLine(string name, datetime time, color clr = Red, string label = "") {
   if (!IsOptimization()) {
      if (name == "") {
         name = "line_" + Time[0];
      }
      if (ObjectFind(name) == -1) {
         ObjectCreate(name, OBJ_VLINE, 0, time, 0);
      } else {
         ObjectSet(name, OBJPROP_TIME1, time);
      }
      ObjectSet(name, OBJPROP_COLOR, clr);
      ObjectSetText(name, label);
   }
   return(name);
}

/**
* Create a text object.
*/
string text(string name, string text, datetime time = 0, double price = 0, color clr = Red, int size = 8) {
   if (time == 0) {
      time = Time[0];
   }
   if (price == 0) {
      price = Close[iBarShift(NULL, 0, time)];
   }
   if (name == "") {
      name = "text_" + time;
   }
   if (ObjectFind(name) == -1) {
      ObjectCreate(name, OBJ_TEXT, 0, time, price);
   }
   ObjectSet(name, OBJPROP_TIME1, time);
   ObjectSet(name, OBJPROP_PRICE1, price);
   ObjectSet(name, OBJPROP_COLOR, clr);
   ObjectSet(name, OBJPROP_FONTSIZE, size);
   ObjectSetText(name, text);
   return(name);
}

/**
* Create a text label.
*/
string label(string name, int x, int y, int corner, string text, color clr = Gray) {
   if (!IsOptimization()) {
      if (name == "") {
         name = "label_" + Time[0];
      }
      if (ObjectFind(name) == -1) {
         ObjectCreate(name, OBJ_LABEL, 0, 0, 0);
      }
      ObjectSet(name, OBJPROP_COLOR, clr);
      ObjectSet(name, OBJPROP_CORNER, corner);
      ObjectSet(name, OBJPROP_XDISTANCE, x);
      ObjectSet(name, OBJPROP_YDISTANCE, y);
      ObjectSetText(name, text);
   }
   return(name);
}

/**
* Show a button and check if it has been actuated.
* Emulate a button with a label that must be moved by the user.
* Return true if the label has been moved and move it back.
* create it if it does not already exist.
*/
bool labelButton(string name, int x, int y, int corner, string text, color clr = Gray) {
   if (IsOptimization()) {
      return(false);
   }
   if (ObjectFind(name) != -1) {
      if (ObjectGet(name, OBJPROP_XDISTANCE) != x || ObjectGet(name, OBJPROP_YDISTANCE) != y) {
         ObjectDelete(name);
         return(true);
      }
   }
   label(name, x, y, corner, "[" + text + "]", clr);
   return(false);
}


/**
* Check if a line has been crossed, the line may contain a command string.
*
* return "1" or the the argument if price (Bid) just has crossed
* a line with a decription starting with this command or "" otherwise.
*
* for example if a line has the description
* buy 0.1
* then it will return the string "0.1" when price has just crossed it
* and it will return "" if this was not the case.
*
* If the parameter one_shot is true (default) it will add the word
* "triggered: " to the command so it can't be triggered a second time,
* the line description will then look like
* triggered: buy 0.1
* and it will not be active anymore.
*
* if there is bo argument behind the command then it will simply
* return the string "1" if it is triggered.
*
* This function is intentionally returning strings and not doubles
* to enable you to do such funny things like for example define a command
* "delete" and then have lines like "delete buy" or "delete sell" and have
* it delete other lines once price triggers this line or do even more
* complex "visual programming" in the chart.
*/
string crossedLineS(string command, bool one_shot = true, color clr_active = CLR_NONE, color clr_triggered = CLR_NONE) {
   double last_bid; // see below!
   int i;
   double price;
   string name;
   string command_line;
   string command_argument;
   int type;


   if (clr_active == CLR_NONE) {
      clr_active = CLR_CROSSLINE_ACTIVE;
   }
   if (clr_triggered == CLR_NONE) {
      clr_triggered = CLR_CROSSLINE_TRIGGERED;
   }

   for (i = 0; i < ObjectsTotal(); i++) {
      name = ObjectName(i);

      // is this an object without description (newly created by the user)?
      if (ObjectDescription(name) == "") {
         // Sometimes the user draws a new line and the default color is
         // accidentially the color and style of an active line. If we
         // simply reset all lines without decription but with the active
         // color and style we can almost completely eliminate this problem.
         // The color does not influence the functionality in any way but
         // we simply don't WANT to confuse the USER with lines that have
         // the active color and style that are not active lines.
         if (ObjectGet(name, OBJPROP_COLOR) == clr_active
               && ObjectGet(name, OBJPROP_WIDTH) == WIDTH_CROSSLINE_ACTIVE
               && ObjectGet(name, OBJPROP_STYLE) == STYLE_CROSSLINE_ACTIVE) {
            ObjectSet(name, OBJPROP_COLOR, clr_triggered);
            ObjectSet(name, OBJPROP_STYLE, STYLE_CROSSLINE_TRIGGERED);
            ObjectSet(name, OBJPROP_WIDTH, WIDTH_CROSSLINE_TRIGGERED);
            ObjectSet(name, OBJPROP_PRICE3, 0);
         }
      }

      // is this an object that contains our command?
      if (StringFind(ObjectDescription(name), command) == 0) {
         price = 0;
         type = ObjectType(name);

         // we only care about certain types of objects
         if (type == OBJ_HLINE) {
            price = ObjectGet(name, OBJPROP_PRICE1);
         }
         if (type == OBJ_TREND) {
            price = ObjectGetValueByShift(name, 0);
         }

         if (price > 0) { // we found a line

            // ATTENTION! DIRTY HACK! MAY BREAK IN FUTURE VERSIONS OF MT4
            // ==========================================================
            // We store the last bid price in the unused PRICE3 field
            // of every line, so we can call this function more than once
            // per tick for multiple lines. A static variable would not work here
            // since we could not call the functon a second time during the same tick
            last_bid = ObjectGet(name, OBJPROP_PRICE3);

            // visually mark the line as an active line
            ObjectSet(name, OBJPROP_COLOR, clr_active);
            ObjectSet(name, OBJPROP_STYLE, STYLE_CROSSLINE_ACTIVE);
            ObjectSet(name, OBJPROP_WIDTH, WIDTH_CROSSLINE_ACTIVE);

            // we have a last_bid value for this line
            if (last_bid > 0) {

               // did price cross this line since the last time we checked this line?
               if ((Close[0] >= price && last_bid <= price) || (Close[0] <= price && last_bid >= price)) {

                  // extract the argument
                  command_line = ObjectDescription(name);
                  command_argument = StringSubstr(command_line, StringLen(command) + 1);
                  if (command_argument == "") {
                     command_argument = "1"; // default argument is "1"
                  }

                  // make the line triggered if it is a one shot command
                  if (one_shot) {
                     ObjectSetText(name, "triggered: " + command_line);
                     ObjectSet(name, OBJPROP_COLOR, clr_triggered);
                     ObjectSet(name, OBJPROP_STYLE, STYLE_CROSSLINE_TRIGGERED);
                     ObjectSet(name, OBJPROP_WIDTH, WIDTH_CROSSLINE_TRIGGERED);
                     ObjectSet(name, OBJPROP_PRICE3, 0);
                  } else {
                     ObjectSet(name, OBJPROP_PRICE3, Close[0]);
                  }

                  return(command_argument);
               }
            }

            // store current price in the line itself
            ObjectSet(name, OBJPROP_PRICE3, Close[0]);
         }
      }

   }

   return(""); // command line not crossed, return empty string (false)
}

/**
* Check if a line has been crossed, the line may contain a numeric value.
* Call crossedLineS() (see crossedLineS() for more documentation)
* and cast it into a number (will return 1 if the line has no argument)
*/
double crossedLineD(string command, bool one_shot = true, color clr_active = CLR_NONE, color clr_triggered = CLR_NONE) {
   string arg = crossedLineS(command, one_shot, clr_active, clr_triggered);
   return(StrToDouble(arg));
}


/**
* Check if a line has been crossed.
* Call crossedLineS() (see crossedLineS() for more documentation)
* and cast it into a bool (true if crossed, otherwise false)
*/
bool crossedLine(string command, bool one_shot = true, color clr_active = CLR_NONE, color clr_triggered = CLR_NONE) {
   string arg = crossedLineS(command, one_shot, clr_active, clr_triggered);
   return(arg != "");
}

/**
* Drop-in replacement for OrderModify().
* Try to handle all errors and locks and return only if successful
* or if the error can not be handled or waited for.
*/
bool orderModifyReliable(
   int ticket,
   double price,
   double stoploss,
   double takeprofit,
   datetime expiration,
   color arrow_color = CLR_NONE
) {
   bool success;
   int err;
   Print("OrderModifyReliable(" + ticket + "," + price + "," + stoploss + "," + takeprofit + "," + expiration + "," + arrow_color + ")");
   while (True) {
      while (IsTradeContextBusy()) {
         Print("OrderModifyReliable(): Waiting for trade context.");
         Sleep(MathRand() / 10);
      }
      success = OrderModify(
                   ticket,
                   NormalizeDouble(price, Digits),
                   NormalizeDouble(stoploss, Digits),
                   NormalizeDouble(takeprofit, Digits),
                   expiration,
                   arrow_color);

      if (success) {
         Print("OrderModifyReliable(): Success!");
         return(True);
      }

      err = GetLastError();
      if (isTemporaryError(err)) {
         Print("orderModifyReliable(): Temporary Error: " + err + " " + ErrorDescription(err) + ". waiting.");
      } else {
         Print("orderModifyReliable(): Permanent Error: " + err + " " + ErrorDescription(err) + ". giving up.");
         return(false);
      }
      Sleep(MathRand() / 10);
   }
   return(false);
}

/**
* Drop-in replacement for OrderSend().
* Try to handle all errors and locks and return only if successful
* or if the error can not be handled or waited for.
*/
int orderSendReliable(
   string symbol,
   int cmd,
   double volume,
   double price,
   int slippage,
   double stoploss,
   double takeprofit,
   string comment = "",
   int magic = 0,
   datetime expiration = 0,
   color arrow_color = CLR_NONE
) {
   int ticket;
   int err;
   Print("orderSendReliable("
         + symbol + ","
         + cmd + ","
         + volume + ","
         + price + ","
         + slippage + ","
         + stoploss + ","
         + takeprofit + ","
         + comment + ","
         + magic + ","
         + expiration + ","
         + arrow_color + ")");

   while (true) {
      if (IsStopped()) {
         Print("orderSendReliable(): Trading is stopped!");
         return(-1);
      }
      RefreshRates();
      if (cmd == OP_BUY) {
         price = MarketInfo(symbol, MODE_ASK);
      }
      if (cmd == OP_SELL) {
         price = MarketInfo(symbol, MODE_BID);
      }
      if (!IsTradeContextBusy()) {
         ticket = OrderSend(
                     symbol,
                     cmd,
                     volume,
                     NormalizeDouble(price, MarketInfo(symbol, MODE_DIGITS)),
                     slippage,
                     NormalizeDouble(stoploss, MarketInfo(symbol, MODE_DIGITS)),
                     NormalizeDouble(takeprofit, MarketInfo(symbol, MODE_DIGITS)),
                     comment,
                     magic,
                     expiration,
                     arrow_color
                  );
         if (ticket > 0) {
            Print("orderSendReliable(): Success! Ticket: " + ticket);
            return(ticket); // the normal exit
         }

         err = GetLastError();
         if (isTemporaryError(err)) {
            Print("orderSendReliable(): Temporary Error: " + err + " " + ErrorDescription(err) + ". waiting.");
         } else {
            Print("orderSendReliable(): Permanent Error: " + err + " " + ErrorDescription(err) + ". giving up.");
            return(-1);
         }
      } else {
         Print("orderSendReliable(): Must wait for trade context");
      }
      Sleep(MathRand() / 10);
   }
   return(false);
}

/**
* Drop-in replacement for OrderClose().
* Try to handle all errors and locks and return only if successful
* or if the error can not be handled or waited for.
*/
bool orderCloseReliable(
   int ticket,
   double lots,
   double price,
   int slippage,
   color arrow_color = CLR_NONE
) {
   bool success;
   int err,x;
   Print("orderCloseReliable()");
   x=OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES);
   while (true) {
      if (IsStopped()) {
         Print("orderCloseReliable(): Trading is stopped!");
         return(false);
      }
      RefreshRates();
      if (OrderType() == OP_BUY) {
         price = MarketInfo(OrderSymbol(), MODE_BID); // close long at bid
      }
      if (OrderType() == OP_SELL) {
         price = MarketInfo(OrderSymbol(), MODE_ASK); // close short at ask
      }
      if (!IsTradeContextBusy()) {
         success = OrderClose(
                      ticket,
                      lots,
                      NormalizeDouble(price, MarketInfo(OrderSymbol(), MODE_DIGITS)),
                      slippage,
                      arrow_color
                   );
         if (success) {
            Print("orderCloseReliable(): Success!");
            return(true); // the normal exit
         }

         err = GetLastError();
         if (isTemporaryError(err)) {
            Print("orderCloseReliable(): Temporary Error: " + err + " " + ErrorDescription(err) + ". waiting.");
         } else {
            Print("orderCloseReliable(): Permanent Error: " + err + " " + ErrorDescription(err) + ". giving up.");
            return(false);
         }
      } else {
         Print("orderCloseReliable(): Must wait for trade context");
      }
      Sleep(MathRand() / 10);
   }
   return(false);
}

/**
* Drop-in replacement for OrderDelete().
* Try to handle all errors and locks and return only if successful
* or if the error can not be handled or waited for.
*/
bool orderDeleteReliable(int ticket) {
   bool success;
   int err;
   Print("orderDeleteReliable(" + ticket + ")");
   while (true) {
      while (IsTradeContextBusy()) {
         Print("OrderDeleteReliable(): Waiting for trade context.");
         Sleep(MathRand() / 10);
      }

      success = OrderDelete(ticket);

      if (success) {
         Print("orderDeleteReliable(): success.");
         return(true);
      }

      err = GetLastError();
      if (isTemporaryError(err)) {
         Print("orderDeleteReliable(): Temporary Error: " + err + " " + ErrorDescription(err) + ". waiting.");
      } else {
         Print("orderDeleteReliable(): Permanent Error: " + err + " " + ErrorDescription(err) + ". giving up.");
         return(false);
      }
      Sleep(MathRand() / 10);
   }
  return(false);   
}

/**
* Compare two prices.
* The floating point precision of MT4 can make two seemingly identical values
* still differ from each other. This function will compare two prices after
* rounding them to the precision that is used for prices (value of Digits).
*/
bool isEqualPrice(double a, double b) {
   return(NormalizeDouble(a, Digits) == NormalizeDouble(b, Digits));
}

/**
* Is the error temporary (does it make sense to wait).
*/
bool isTemporaryError(int error) {
   return(
            error == ERR_NO_ERROR ||
            error == ERR_COMMON_ERROR ||
            error == ERR_SERVER_BUSY ||
            error == ERR_NO_CONNECTION ||
            error == ERR_MARKET_CLOSED ||
            error == ERR_PRICE_CHANGED ||
            error == ERR_INVALID_PRICE ||  //happens sometimes
            error == ERR_OFF_QUOTES ||
            error == ERR_BROKER_BUSY ||
            error == ERR_REQUOTE ||
            error == ERR_TRADE_TIMEOUT ||
            error == ERR_TRADE_CONTEXT_BUSY
         );
}


/**
* current symbol without broker initials.
* Return only the first 6 letters of Symbol()
* Some Brokers add their initials at the end
* of their symbol names and some of my EAs and
* indicators want a 6-character symbol name.
*/
string Symbol6() {
   return(StringSubstr(Symbol(), 0, 6));
}


/**
* Get the list of symbols available on this platform.
* The function will resize and fill the supplied array with
* the symbol names and return the number of symbols.
*/
int getSymbols(string &sym[]) {
   int i;
   string symbol;
   int f = FileOpenHistory("symbols.raw", FILE_BIN | FILE_READ);
   int count = FileSize(f) / 1936;
   ArrayResize(sym, count);
   for (i = 0; i < count; i++) {
      symbol = FileReadString(f, 12);
      sym[i] = symbol;
      FileSeek(f, 1924, SEEK_CUR);
   }
   FileClose(f);
   return(count);
}


/**
* Determine the pip multiplier.
* Determine the pip multiplier (1 or 10) depending on how many
* digits the EURUSD symbol has. This is done by first
* finding the exact name of this symbol in the symbols.raw
* file (it could be EURUSDm or EURUSDiam or any other stupid name
* the broker comes up with only to break other people's code)
* and then usig MarketInfo() for determining the digits.
*/
double pointsPerPip() {
   int i;
   int digits;
   double ppp = 1;
   string symbol;
   int f = FileOpenHistory("symbols.raw", FILE_BIN | FILE_READ);
   int count = FileSize(f) / 1936;
   for (i = 0; i < count; i++) {
      symbol = FileReadString(f, 12);
      if (StringFind(symbol, "EURUSD") != -1) {
         digits = MarketInfo(symbol, MODE_DIGITS);
         if (digits == 4) {
            ppp = 1;
         } else {
            ppp = 10;
         }
         break;
      }
      FileSeek(f, 1924, SEEK_CUR);
   }
   FileClose(f);
   return (ppp);
}


/**
* return false only if this is called the first 
* time after a new bar has opened, true otherwise.
* Common usage pattern:
* 
* int start(){
*   if (isOldBar()) return(0);
*   ...
*
* this will extit start() immediately if
* we are not at the opening of a new bar.
*/
bool isOldBar(){
   static int time;
   if (time == Time[0]){
      return(true);
   }else{
      time = Time[0];
      return(false);
   }
}
