//
// "00-EA-Breakout_v107.mq4" -- Breakout Eagle like EA
//
//    Ver. 1.00  2009/05/02(Sat)  initial version
//    Ver. 1.01  2009/05/02(Sat)  adapt to Daylight Saving Time
//                                check spread just before order send also
//                                ServerGMT is added
//    Ver. 1.02  2009/05/07(Thu)  popup dialog and send mail on trade open/close
//    Ver. 1.03  2009/05/07(Thu)  limit max lots by global variable "MaxLots"
//    Ver. 1.04  2009/05/10(Sun)  no trail when TrailStop=0
//               2009/05/14(Thu)  support 2 trade time period
//                                digits() for fail safe
//    Ver. 1.05  2009/05/20(Wed)  added 121 spread info
//                                added bWaitBoxTocuh (wait box touch after order permission)
//                                added order time visualization
//    Ver. 1.06  2009/06/10(Wed)  bugfix: trade time wrong when SH > EH after ServerGMT/DST adjustment
//                                default for GMT+0/USDJPY/M5 (ODL Live)
//    Ver. 1.07  2009/06/16(Tue)  added DayRangeMin/MaxPP/StartHour, assumed daily limit range, don't order when range overs this value
//                                added day hi/lo line
//                                renamed BoxLimitPP to BoxLimitMaxPP, and added BoxLimitMinPP
//
//
#property  copyright "00"
#property  link      "http://www.mql4.com/"

//                ex. in DST
// 121    GMT+9  14:00  
// ODL    GMT+0   5:00  
// FXDD   GMT+3   8:00  
// LDN    GMT+1   6:00
// NY     GMT-5   1:00
// 
// 121   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24  0  1  2  3
// ODL  15 16 17 18 19 20 21 22 23  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19
// FXDD 18 19 20 21 22 23  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22
// LDN  16 17 18 19 20 21 22 23  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
// NY   11 12 13 14 15 16 17 18 19 20 21 22 23  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
//     --+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// 

//---- defines
#define SEC_PER_DAY   86400
#define SEC_PER_HOUR  3600
//
#define EV_FIRST      0
#define EV_OPEN_BUY   0
#define EV_OPEN_SELL  1
#define EV_CLOSE_BUY  2
#define EV_CLOSE_SELL 3
#define EV_LAST       3

//---- indicator settings
extern int     Magic               = 1000;         // order magic number
extern string  CommentString       = "";           // order comment string
extern int     ServerGMT           = 0;            // GMT time offset, 0: ODL, 9: 121, 3: FXDD,...
extern bool    bSpread121          = false;        // spread limit selection (ODL and 121 are supported)
extern double  Lots                = 0.1;          // order lots
extern int     StopLossMin         = 20;           // pips, S/L minimum
extern int     StopLossMax         = 0;            // pips, S/L maximum, 0: auto, limited by box range
extern int     TakeProfitMin       = 20;           // pips, T/P minimum
extern int     TakeProfitMax       = 0;            // pips, T/P maximum, 0: auto, limited by box range
extern int     TrailTake           = 23;           // pips, traling take
extern int     TrailStop           = 25;           // pips, trailing stop
extern double  BoxScaleSL          = 1.15;         // S/L multiplier to box height(Hi-Lo range)
extern double  BoxScaleTP          = 0.9;          // S/L multiplier to box height(Hi-Lo range)
extern double  BoxLimitMaxPP       = 80;           // pips, max box height in pips, 0: don't care
extern double  BoxLimitMinPP       = 15;           // pips, min box height in pips, 0: don't care
extern double  StartHour           = 23.3;         // GMT+0, box start time in hour
extern double  DurationHours       = 2.8;          // box duration
extern double  OrderGoSH1          = 15.1;         // GMT+0, order permission time 1, start hour
extern double  OrderGoDurH1        = 0.9;          // order permission time 1, end hour
extern double  OrderGoSH2          = 0.0;          // GMT+0, order permission time 2, start hour
extern double  OrderGoDurH2        = 0.0;          // order permission time 2, end hour
extern bool    bWaitBoxTouch       = false;        // wait box touch after order permission
extern int     MarginPips          = 4;            // pips, margin pips over/under box
extern int     LongMargin          = -4;           // pips, position long when (boxClose >= boxOpen + LongMargin)
extern int     ShortMargin         = -6;           // pips, position short when (boxClose <= boxOpen - ShortMargin)
extern double  DayRangeMaxPP       = 56 ;          // pips, max daily range in pips, 0: don't care
extern double  DayRangeMinPP       = 19;           // pips, min daily range in pips, 0: don't care
extern int     DayRangeStartHour   = 13;           // GMT+0, daily start hour to check range
extern int     BoxOpenPrice        = PRICE_OPEN;   // MA applied price for box open price, 0:CLOSE 1:OPEN 2:HIGH 3:LOW 4:MEDIAN 5:TYPICAL 6:WEIGHTED
extern int     BoxClosePrice       = PRICE_CLOSE;  // MA applied price for box close price
extern int     Slippage            = 2;            // pips, slippage
extern double  SpreadMargin        = 999;          // pips, spread margin between real and default (0..100: fixed, 999: limit by table)
extern int     StopDayOfWeek       = 5;            // GMT+0, weekly stop day, 0: Sun 1:Mon 2: Tue 3: Wed 4: Thu 5: Fri 6: Sat
extern int     CloseDayOfWeek      = 5;            // GMT+0, weekly close day, 0: Sun 1:Mon 2: Tue 3: Wed 4: Thu 5: Fri 6: Sat
extern double  WeeklyStopHours     = 18;           // GMT+0, weekly stop time in hour
extern double  WeeklyCloseHours    = 19.5;         // GMT+0, weekly close time in hour
/*extern*/ double  DstOffsetHours  = 1.0;          // offset hours between DST and non-DST
extern bool    bDailyClose         = true;         // close every day or not
extern bool    bSignalAlert        = true;         // popup dialog on trade
extern bool    bSignalMail         = false;        // send mail on trade
extern double  pointScale          = 0.0;          // scale factor of Point

datetime DstStart1 = D'2008/03/09 00:00:00';
datetime DstEnd1   = D'2008/11/02 00:00:00';
datetime DstStart2 = D'2009/03/08 00:00:00';
datetime DstEnd2   = D'2009/11/01 00:00:00';

//---- vars
string   sIndicatorName;
string   sPrefix;
string   sIndSelf     = "00-EA-Breakout_v107";
double   point        = 0.0;
int      nRetry       = 10;
int      retrySleep   = 800;
bool     bOrderReady  = false;
bool     bValidBox    = false;
double   boxHi;
double   boxLo;
double   boxOpen;
double   boxClose;
datetime tsBox;
datetime teBox;
bool     bLongPermit;
bool     bShortPermit;
int      nOrderLong;
int      nOrderShort;
int      infoFontSize  = 12;
string   infoFontName  = "Arial";
color    infoFontColor = Aqua;
color    colBoxRect    = Aqua;
color    colLong       = DodgerBlue;
color    colShort      = Crimson;
color    colGoLine1    = Aqua;
color    colGoLine2    = Aqua;
color    colBoxHi      = Gray;
color    colBoxLo      = Gray;
color    colDayRange   = Gold;
string   g_sValMaxLots = "MaxLots";
// date/time
double   dstOffsetHours;
double   weeklyStopHours;
double   weeklyCloseHours;
double   startHour;
double   orderGoSH1;
double   orderGoEH1;
double   orderGoSH2;
double   orderGoEH2;
double   dayRangeStartHour;

//----------------------------------------------------------------------
// ODL spread
string g_spreadOdl[20][4] = {
    // 2pips    spec   weekend   limit
    "EURCHF",   "2",   "8",      "5",  // 0
    "EURGBP",   "2",   "6",      "6",  // 1
    "EURJPY",   "2",   "7",      "6",  // 2
    "EURUSD",   "2",   "2",      "5",  // 3
    "GBPUSD",   "2",   "3",      "5",  // 4
    "USDCHF",   "2",   "6",      "5",  // 5
    "USDJPY",   "2",   "2",      "4",  // 6
    // 4pips
    "AUDJPY",   "4",   "8",      "7",  // 7
    "AUDUSD",   "4",   "3",      "7",  // 8
    "CHFJPY",   "4",   "10",     "6",  // 9
    "NZDUSD",   "4",   "9",      "7",  // 10
    "USDCAD",   "4",   "4",      "6",  // 11
    // 6pips
    "GBPCAD",   "6",   "33",     "8",  // 12
    "GBPCHF",   "6",   "23",     "8",  // 13
    "GBPJPY",   "6",   "6",      "8",  // 14
    // 7pips
    "AUDCAD",   "7",   "12",     "8",  // 15
    "CADJPY",   "7",   "38",     "8",  // 16
    "EURAUD",   "7",   "17",     "8",  // 17
    "NZDJPY",   "7",   "14",     "8",  // 18
    // 15pips
    "NZDCHF",   "15",   "19",    "8"   // 19
};

//----------------------------------------------------------------------
// 121 spread
string g_spread121[20][4] = {
    // 1pip     spec   weekend   limit
    "EURUSD.",  "1",   "5",      "5",  // 0
    "USDJPY.",  "1",   "4",      "5",  // 1
    // 2pips    
    "EURJPY.",  "2",   "5",      "5",  // 2
    "GBPUSD.",  "2",   "6",      "6",  // 3
    "USDCHF.",  "2",   "5",      "5",  // 4
    // 4pips    
    "AUDJPY.",  "4",   "4",      "4",  // 5
    "AUDUSD.",  "4",   "5",      "5",  // 6
    "CHFJPY.",  "4",   "5",      "5",  // 7
    "NZDUSD.",  "4",   "5",      "5",  // 8
    "USDCAD.",  "4",   "5",      "5",  // 9
    // 5pips    
    "GBPJPY.",  "5",   "6",      "6",  // 10
    // 6pips    
    "CADJPY.",  "6",   "6",      "6",  // 11
    "EURCHF.",  "6",   "6",      "6",  // 12
    "EURGBP.",  "6",   "6",      "6",  // 13
    // 7pips    
    "AUDCAD.",  "7",   "7",      "7",  // 14
    "EURAUD.",  "7",   "7",      "7",  // 15
    "NZDJPY.",  "7",   "7",      "7",  // 16
    // 10pips   
    "EURCAD.",  "10",  "10",     "10", // 17
    "GBPCHF.",  "10",  "10",     "10", // 18
    // 15pips   
    "GBPAUD.",  "15"   "15",     "15"  // 19
};

//----------------------------------------------------------------------
double getSpreadTab(string sSym, string tab[][4], bool bSpec)
{
    double spread = 999;
    int n = ArrayRange(tab, 0);
    int idx;
    
    if (bSpec) {
	idx = 1;  // spec
    } else {
	idx = 3;  // limit
    }
    
    for (int i = 0; i < n; i++) {
	if (StringFind(tab[i][0], sSym) >= 0) {
	    spread = StrToDouble(tab[i][idx]);
	    break;
	}
    }
    
    return(spread);
}

//----------------------------------------------------------------------
double getSpread(string sSym, bool bSpec)
{
    double spread = 999;
    
    if (bSpread121) {
	spread = getSpreadTab(sSym, g_spread121, bSpec);
    } else {
	spread = getSpreadTab(sSym, g_spreadOdl, bSpec);
    }
    
    return(spread);
}

//----------------------------------------------------------------------
double checkPoint(string sym, int pointScale)
{
    if (pointScale != 0) {
	// point manually scaled
	return(Point * pointScale);
    }
    
    // auto scale
    string   s_point001[] = {  // point=0.01 pairs, for auto pointScale(0), *JPY are 0.01 as default
	"EURHUF", "USDHUF"
    };
    string   s_point0001[] = {  // point=0.001 pairs, for auto pointScale(0)
	"EURCZK", "EURHKD", "EURSKK", "EURZAR", "GBPNOK",
	"GBPSEK", "GBPZAR", "USDCZK", "USDMXN", "USDRUB",
	"USDSKK"
    };
    double point = 0;
    
    if (StringFind(sym, "JPY") == 3) {
	point = 0.01;
    } else {
	// check point=0.01 table
	int n = ArraySize(s_point001);
	for (int i = 0; i < n; i++) {
	    if (StringFind(sym, s_point001[i]) == 0) {
		point = 0.01;
		break;
	    }
	}
	if (point == 0) {
	    // check point=0.001 table
	    n = ArraySize(s_point0001);
	    for (i = 0; i < n; i++) {
		if (StringFind(sym, s_point0001[i]) == 0) {
		    point = 0.001;
		    break;
		}
	    }
	}
	if (point == 0) {
	    // others
	    point = 0.0001;
	}
    }
    
    return(point);
}

//----------------------------------------------------------------------
string TimeFrameToStr(int timeFrame)
{
    switch (timeFrame) {
    case 1:     return("M1");
    case 5:     return("M5");
    case 15:    return("M15");
    case 30:    return("M30");
    case 60:    return("H1");
    case 240:   return("H4");
    case 1440:  return("D1");
    case 10080: return("W1");
    case 43200: return("MN");
    }
    
    return("??");
}

//----------------------------------------------------------------------
void init()
{
    string tf = TimeFrameToStr(Period());
    sIndicatorName = sIndSelf + "(" + tf + ")";
    sPrefix = sIndicatorName;
    
    if (point == 0.0) {
	point = checkPoint(Symbol(), pointScale);
    }
    
    string sServer = AccountServer();
    double lotsize = MarketInfo(Symbol(),MODE_LOTSIZE);
    double maxlot = MarketInfo(Symbol(), MODE_MAXLOT);
    double minlot = MarketInfo(Symbol(), MODE_MINLOT);
    Print("server= ", sServer);
    Print("pair= ", Symbol());
    Print("LOTSIZE= ", lotsize, ", MAXLOT= ", maxlot, ", MINLOT= ", minlot);
    Print("Point= ", Point, ", point= ", point);
    
    // adjust time difference
    {
	// StartHour
	StartHour += ServerGMT;
	if (StartHour >= 24) {
	    StartHour -= 24;
	} else if (StartHour < 0) {
	    StartHour += 24;
	}
	
	// WeeklyStop/CloseHours
	WeeklyStopHours += ServerGMT;
	WeeklyCloseHours += ServerGMT;
	if (WeeklyStopHours >= 24) {
	    StopDayOfWeek++;
	    WeeklyStopHours -= 24;
	} else if (WeeklyStopHours < 0) {
	    StopDayOfWeek--;
	    WeeklyStopHours += 24;
	}
	if (WeeklyCloseHours >= 24) {
	    CloseDayOfWeek++;
	    WeeklyCloseHours -= 24;
	} else if (WeeklyCloseHours < 0) {
	    CloseDayOfWeek--;
	    WeeklyCloseHours += 24;
	}
	
	// OrderGoSH
	OrderGoSH1 += ServerGMT;
	if (OrderGoSH1 >= 24) {
	    OrderGoSH1 -= 24;
	} else if (OrderGoSH1 < 0) {
	    OrderGoSH1 += 24;
	}
	OrderGoSH2 += ServerGMT;
	if (OrderGoSH2 >= 24) {
	    OrderGoSH2 -= 24;
	} else if (OrderGoSH2 < 0) {
	    OrderGoSH2 += 24;
	}
	
	// DayRangeStartHour
	DayRangeStartHour += ServerGMT;
	if (DayRangeStartHour >= 24) {
	    DayRangeStartHour -= 24;
	} else if (DayRangeStartHour < 0) {
	    DayRangeStartHour += 24;
	}
	
	Print("ServerGMT= ", ServerGMT);
	Print("  StartHour= ", StartHour);
	Print("  StopDayOfWeek= ", StopDayOfWeek, ", WeeklyStopHours= ", WeeklyStopHours);
	Print("  CloseDayOfWeek= ", CloseDayOfWeek, ", WeeklyCloseHours= ", WeeklyCloseHours);
	Print("  OrderGoSH1= ", OrderGoSH1, ", OrderGoDurH1= ", OrderGoDurH1);
	Print("  OrderGoSH2= ", OrderGoSH2, ", OrderGoDurH2= ", OrderGoDurH2);
	Print("  DayRangeStartHour= ", DayRangeStartHour);
	
	double sec = ServerGMT * SEC_PER_HOUR;
	DstStart1 += sec;
	DstEnd1 += sec;
	DstStart2 += sec;
	DstEnd2 += sec;
    }
}

//----------------------------------------------------------------------
void deinit()
{
    int i;
    int n = ObjectsTotal();
    
    for (i = n - 1; i >= 0; i--) {
	string sName = ObjectName(i);
	if (StringFind(sName, sPrefix) == 0) {
	    ObjectDelete(sName);
	}
    }
}

//----------------------------------------------------------------------
int orderCount()
{
    int i;
    int nTotal = OrdersTotal();
    int nOrder = 0;
    
    nOrderLong = 0;
    nOrderShort = 0;
    
    for (i = 0; i < nTotal; i++) {
	if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
	    continue;
	}
	if (OrderMagicNumber() != Magic) {
	    continue;
	}
	if (OrderSymbol() != Symbol()) {
	    continue;
	}
	if (OrderType() == OP_BUY) {
	    nOrderLong++;
	}
	if (OrderType() == OP_SELL) {
	    nOrderShort++;
	}
	
	nOrder++;
    }
    
    return(nOrder);
}

//----------------------------------------------------------------------
void notifyEvent(int event, int ticket = 0)
{
    static string sTab[] = { "Open Buy", "Open Sell", "Close Buy", "Close Sell" };
    string sEv = "";
    string sMsg = "";
    string sLots = "";
    string sPrice = "";
    string sProfit = "";
    string sOpen = "";
    
    if (event >= EV_FIRST && event <= EV_LAST) {
	sEv = sTab[event];
    } else {
	sEv = "unknown ev= " + event;
    }
    
    if (event == EV_OPEN_BUY || event == EV_OPEN_SELL) {
	if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES)) {
	    sLots = DoubleToStr(OrderLots(), 2);
	    sPrice = DoubleToStr(OrderOpenPrice(), Digits);
	    sMsg = "[" + Symbol() + "] " + sEv + ", " + sPrice + ", " + sLots + "(lots)";
	} else {
	    sMsg = "[" + Symbol() + "] " + sEv + ", OrderSelect() error, ticket= " + ticket;
	}
    } else if (event == EV_CLOSE_BUY || event == EV_CLOSE_SELL) {
	if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_HISTORY)) {
	    sLots = DoubleToStr(OrderLots(), 2);
	    sPrice = DoubleToStr(OrderClosePrice(), Digits);
	    sProfit = DoubleToStr(OrderProfit(), Digits);
	    sOpen = DoubleToStr(OrderOpenPrice(), Digits);
	    sMsg = "[" + Symbol() + "] " + sEv + ", " + sPrice + ", " + sLots + "(lots), open= " + sOpen + ", profit= " + sProfit + "(pp)";
	} else {
	    sMsg = "[" + Symbol() + "] " + sEv + ", OrderSelect() error, ticket= " + ticket;
	}
    }
    
    if (bSignalAlert) {
	string s = "[" + sIndSelf + "]" + sMsg;
	Alert(s);
    }
    if (bSignalMail) {
	s = "[" + sIndSelf + "]";
	SendMail(s, sMsg);
	Print("SendMail(), sub= ", s, ", body= ", sMsg);
    }
}

//----------------------------------------------------------------------
double digits(double v)
{
    v = NormalizeDouble(v, Digits);
    
    return(v);
}

//----------------------------------------------------------------------
bool orderOpen(int type)
{
    int i;
    bool bOk = false;
    double sl, tp, stopLevel;
    int event;
    double lots = Lots;
    
    double boxSizeSL = (boxHi - boxLo) * BoxScaleSL;
    double boxSizeTP = (boxHi - boxLo) * BoxScaleTP;
    double stop = MathMax(boxSizeSL, StopLossMin * point);
    if (StopLossMax > 0) {
	stop = MathMin(stop, StopLossMax * point);
    }
    
    double take = MathMax(boxSizeTP, TakeProfitMin * point);
    if (TakeProfitMax > 0) {
	take = MathMin(take, TakeProfitMax * point);
    }
    
    // limit by global variable
    if (GlobalVariableCheck(g_sValMaxLots)) {
	double gMaxLots = GlobalVariableGet(g_sValMaxLots);
	if (lots > gMaxLots) {
	    lots = gMaxLots;
	}
    }
    
    // send order
    for (i = 0; i < nRetry; i++) {
	int ticket = -1;
	stopLevel = (MarketInfo(Symbol(), MODE_STOPLEVEL) + 1) * point;
	if (type == OP_BUY) {
	    event = EV_OPEN_BUY;
	    sl = Ask - MathMax(stop, stopLevel);
	    tp = Ask + MathMax(take, stopLevel);
	    ticket = OrderSend(Symbol(), type, lots, digits(Ask), Slippage, digits(sl), digits(tp), CommentString + " Long(" + Magic + ")", Magic, 0, DodgerBlue);
	} else if (type == OP_SELL) {
	    event = EV_OPEN_SELL;
	    sl = Bid + MathMax(stop, stopLevel);
	    tp = Bid - MathMax(take, stopLevel);
	    ticket = OrderSend(Symbol(), type, lots, digits(Bid), Slippage, digits(sl), digits(tp), CommentString + " Short(" + Magic + ")", Magic, 0, Crimson);
	}
	if (ticket >= 0) {
	    bOk = true;
	    notifyEvent(event, ticket);
	    break;
	}
	Sleep(retrySleep);
	RefreshRates();
	if (!checkSpread()) {
	    break;
	}
    }
    
    return(bOk);
}

//----------------------------------------------------------------------
bool orderClose()
{
    int i;
    bool bOk = false;
    int n = OrdersTotal();
    
    for (i = 0; i < n; i++) {
	if (OrderSelect(0, SELECT_BY_POS, MODE_TRADES) == false) {
	    break;
	}
	if (OrderMagicNumber() != Magic) {
	    continue;
	}
	if (OrderSymbol() != Symbol()) {
	    continue;
	}
	
	int ticket = OrderTicket();
	
	for (int retry = 0; retry < nRetry; retry++) {
	    if (OrderType() == OP_BUY) {
		if (OrderClose(ticket, OrderLots(), digits(Bid), Slippage, Aqua)) {
		    bOk = true;
		    notifyEvent(EV_CLOSE_BUY, ticket);
		    break;
		}
	    }
	    if (OrderType() == OP_SELL) {
		if (OrderClose(ticket, OrderLots(), digits(Ask), Slippage, Magenta)) {
		    bOk = true;
		    notifyEvent(EV_CLOSE_SELL, ticket);
		    break;
		}
	    }
	    Sleep(retrySleep);
	    RefreshRates();
	}
    }
    
    return(bOk);
}

//----------------------------------------------------------------------
void orderTrail()
{
    if (TrailTake == 0 && TrailStop == 0) {
	// no trail
	return;
    }
    
    int i;
    int nTotal = OrdersTotal();
    
    for (i = 0; i < nTotal; i++) {
	if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
	    continue;
	}
	if (OrderMagicNumber() != Magic) {
	    continue;
	}
	if (OrderSymbol() != Symbol()) {
	    continue;
	}
	
	double stopLevel = (MarketInfo(Symbol(), MODE_STOPLEVEL) + 1) * point;
	
	if (OrderType() == OP_BUY) {
	    double profit = Bid - OrderOpenPrice();
	    if (profit >= TrailTake * point) {
		double sl = Bid - MathMax(TrailStop * point, stopLevel);
		if (sl > OrderStopLoss() + point) {
		    double op = OrderOpenPrice();
		    double tp = OrderTakeProfit();
		    Print("Long Modify, Bid= ", Bid, ", old sl= ", OrderStopLoss(), ", new sl= ", sl);
		    OrderModify(OrderTicket(), op, digits(sl), digits(tp), 0, Magenta);
		}
	    }
	}
	if (OrderType() == OP_SELL) {
	    profit = OrderOpenPrice() - Ask;
	    if (profit >= TrailTake * point) {
		sl = Ask + MathMax(TrailStop * point, stopLevel);
		if (sl < OrderStopLoss() - point) {
		    op = OrderOpenPrice();
		    tp = OrderTakeProfit();
		    Print("Short Modify, Ask= ", Ask, ", old sl= ", OrderStopLoss(), ", new sl= ", sl);
		    OrderModify(OrderTicket(), op, digits(sl), digits(tp), 0, Magenta);
		}
	    }
	}
    }
}

//----------------------------------------------------------------------
void objLine(string sName, datetime ts, double ps, datetime te, double pe, color col, int width = 1, int style = STYLE_SOLID, bool ray = false)
{
    sName = sPrefix + sName;
    
    ObjectCreate(sName, OBJ_TREND, 0, 0, 0);
    ObjectSet(sName, OBJPROP_TIME1, ts);
    ObjectSet(sName, OBJPROP_PRICE1, ps);
    ObjectSet(sName, OBJPROP_TIME2, te);
    ObjectSet(sName, OBJPROP_PRICE2, pe);
    ObjectSet(sName, OBJPROP_COLOR, col);
    ObjectSet(sName, OBJPROP_WIDTH, width);
    ObjectSet(sName, OBJPROP_STYLE, style);
    ObjectSet(sName, OBJPROP_RAY, ray);
}

//----------------------------------------------------------------------
void objLabel(string sName, int corner, int x, int y, string text, color col, int size = 0, string font = "")
{
    sName = sPrefix + sName;
    
    if (size == 0) {
	size = infoFontSize;
    }
    if (font == "") {
	font = infoFontName;
    }
    
    ObjectCreate(sName, OBJ_LABEL, 0, 0, 0);
    ObjectSetText(sName, text, size, font, col);
    ObjectSet(sName, OBJPROP_CORNER, corner);
    ObjectSet(sName, OBJPROP_XDISTANCE, x);
    ObjectSet(sName, OBJPROP_YDISTANCE, y);
}

//----------------------------------------------------------------------
bool checkSpread()
{
    bool bOk = true;
    
    int spreadSpec = getSpread(Symbol(), true) / point;
    int spreadLimit = getSpread(Symbol(), false) / point;
    int spreadReal = (Ask - Bid) / point;
    
    if (SpreadMargin >= 0 && SpreadMargin <= 100) {
	if (spreadReal - spreadSpec > SpreadMargin) {
	    Print("too large spread, spreadReal= ", spreadReal, ", spreadSpec= ", spreadSpec, ", SpreadMargin= ", SpreadMargin);
	    bOk = false;
	}
    } else {
	// SpreadMargin == 999
	if (spreadReal > spreadLimit) {
	    Print("too large spread, spreadReal= ", spreadReal, ", spreadLimit= ", spreadLimit);
	    bOk = false;
	}
    }
    
    return(bOk);
}

//----------------------------------------------------------------------
datetime getDayRange(double &dayHi, double &dayLo, datetime tCur)
{
    datetime tStart = (tCur / SEC_PER_DAY) * SEC_PER_DAY + dayRangeStartHour * SEC_PER_HOUR;
    
    if (tCur < tStart) {
	tStart -= SEC_PER_DAY;
    }
    
    int xStart = iBarShift(NULL, PERIOD_M5, tStart);
    int xCur = iBarShift(NULL, PERIOD_M5, tCur);
    int n = xStart - xCur + 1;
    
    int ihi = iHighest(NULL, PERIOD_M5, MODE_HIGH, n, xCur);
    int ilo = iLowest(NULL, PERIOD_M5, MODE_LOW, n, xCur);
    
    dayHi = iHigh(NULL, PERIOD_M5, ihi);
    dayLo = iLow(NULL, PERIOD_M5, ilo);
    
    return(tStart);
}

//----------------------------------------------------------------------
void start()
{
    if (!IsTradeAllowed()) {
	//
	return;
    }
    
    // parameter check
    {
    }
    
    int i;
    bool bOrderStop = false;
    bool bOrderClose = false;
    
    datetime t0 = iTime(NULL, PERIOD_M5, 0);
    
    // DST adjustment
    {
	bool bDST = ((t0 >= DstStart1 && t0 < DstEnd1) || (t0 >= DstStart2 && t0 < DstEnd2));
	dstOffsetHours = 0;
	if (!bDST) {
	    dstOffsetHours = DstOffsetHours;
	}
	weeklyStopHours = WeeklyStopHours + dstOffsetHours;
	if (weeklyStopHours >= 24) {
	    StopDayOfWeek++;
	    weeklyStopHours -= 24;
	} else if (weeklyStopHours < 0) {
	    StopDayOfWeek--;
	    weeklyStopHours += 24;
	}
	weeklyCloseHours = WeeklyCloseHours + dstOffsetHours;
	if (weeklyCloseHours >= 24) {
	    CloseDayOfWeek++;
	    weeklyCloseHours -= 24;
	} else if (weeklyCloseHours < 0) {
	    CloseDayOfWeek--;
	    weeklyCloseHours += 24;
	}
	
	startHour = StartHour + dstOffsetHours;
	if (startHour >= 24) {
	    startHour -= 24;
	} else if (startHour < 0) {
	    startHour += 24;
	}
	
	orderGoSH1 = OrderGoSH1 + dstOffsetHours;
	if (orderGoSH1 >= 24) {
	    orderGoSH1 -= 24;
	} else if (orderGoSH1 < 0) {
	    orderGoSH1 += 24;
	}
	orderGoEH1 = (OrderGoSH1 + OrderGoDurH1) + dstOffsetHours;
	if (orderGoEH1 >= 24) {
	    orderGoEH1 -= 24;
	} else if (orderGoEH1 < 0) {
	    orderGoEH1 += 24;
	}
	
	orderGoSH2 = OrderGoSH2 + dstOffsetHours;
	if (orderGoSH2 >= 24) {
	    orderGoSH2 -= 24;
	} else if (orderGoSH2 < 0) {
	    orderGoSH2 += 24;
	}
	orderGoEH2 = (OrderGoSH2 + OrderGoDurH2) + dstOffsetHours;
	if (orderGoEH2 >= 24) {
	    orderGoEH2 -= 24;
	} else if (orderGoEH2 < 0) {
	    orderGoEH2 += 24;
	}
	
	dayRangeStartHour = DayRangeStartHour + dstOffsetHours;
	if (dayRangeStartHour >= 24) {
	    dayRangeStartHour -= 24;
	} else if (dayRangeStartHour < 0) {
	    dayRangeStartHour += 24;
	}
	
	//Print("orderGoSH1= ", orderGoSH1, ", orderGoEH1= ", orderGoEH1);
	//Print("orderGoSH2= ", orderGoSH2, ", orderGoEH2= ", orderGoEH2);
    }
    
    // check spread
    bOrderStop = !checkSpread();
    
    int dow = TimeDayOfWeek(t0);
    int hour = TimeHour(t0);
    bool bWeekEndStop = ((dow == StopDayOfWeek && hour >= weeklyStopHours) || dow > StopDayOfWeek || dow == 0);
    bool bWeekEndClose = ((dow == CloseDayOfWeek && hour >= weeklyCloseHours) || dow > CloseDayOfWeek || dow == 0);
    
    if (bWeekEndStop) {
	bOrderStop = true;
    }
    if (bWeekEndClose) {
	bOrderClose = true;
	bOrderStop = true;
    }
    
    // trade period 1
    bool bGo1 = false;
    bool bGo2 = false;
    double hour0 = TimeHour(t0) + TimeMinute(t0) / 60.0;
    if (orderGoSH1 < orderGoEH1 && (hour0 >= orderGoSH1 && hour0 < orderGoEH1)) {
	bGo1 = true;
    }
    if (orderGoSH1 > orderGoEH1 && (hour0 >= orderGoSH1 || hour0 < orderGoEH1)) {
	bGo1 = true;
    }
    // trade period 2
    if (orderGoSH2 < orderGoEH2 && (hour0 >= orderGoSH2 && hour0 < orderGoEH2)) {
	bGo2 = true;
    }
    if (orderGoSH2 > orderGoEH2 && (hour0 >= orderGoSH2 || hour0 < orderGoEH2)) {
	bGo2 = true;
    }
    if (!(bGo1 || bGo2)) {
	bOrderStop = true;
	if (bWaitBoxTouch) {
	    bOrderReady = false;
	}
    }
    
    // check time skip
    int skippedSec = t0 - iTime(NULL, PERIOD_M5, 1);
    if (skippedSec > 15 * 60) {
	bOrderStop = true;
	bOrderReady = false;
    }
    
    // calc box
    tsBox = (t0 / SEC_PER_DAY) * SEC_PER_DAY + startHour * SEC_PER_HOUR;
    teBox = tsBox + DurationHours * SEC_PER_HOUR;
    if (t0 < tsBox) {
	tsBox -= SEC_PER_DAY;
	teBox -= SEC_PER_DAY;
    }
    if (t0 <= teBox) {
	// check time
	if (orderCount() == 0) {
	    bOrderReady = true;
	}
	bValidBox = false;
	boxHi = 0.0;
	boxLo = 0.0;
	boxOpen = 0.0;
	boxClose = 0.0;
	if (bDailyClose) {
	    orderClose();
	}
    } else {
	if (boxHi == 0.0) {
	    int xs = iBarShift(NULL, PERIOD_M5, tsBox);
	    int xe = iBarShift(NULL, PERIOD_M5, teBox);
	    if (xs - xe > DurationHours * SEC_PER_HOUR / (PERIOD_M5 * 60) * 0.9) {
		int ihi = iHighest(NULL, PERIOD_M5, MODE_HIGH, xs - xe + 1, xe);
		int ilo = iLowest(NULL, PERIOD_M5, MODE_LOW, xs - xe + 1, xe);
		boxHi = iHigh(NULL, PERIOD_M5, ihi);
		boxLo = iLow(NULL, PERIOD_M5, ilo);
		boxOpen = iMA(NULL, PERIOD_M5, 1, 0, MODE_SMA, BoxOpenPrice, xs);
		boxClose = iMA(NULL, PERIOD_M5, 1, 0, MODE_SMA, BoxClosePrice, xe);
		bValidBox = true;
	    }
	}
	
	bLongPermit = (boxClose >= boxOpen + LongMargin * point);
	bShortPermit = (boxClose <= boxOpen - ShortMargin * point);
	
	double close1 = iClose(NULL, PERIOD_M5, 1);
	double close0 = iClose(NULL, PERIOD_M5, 0);
	double hi1 = iHigh(NULL, PERIOD_M5, 1);
	double lo1 = iLow(NULL, PERIOD_M5, 1);
	
	// check day range
	datetime tDayStart = 0;
	double dayHi = 0, dayLo = 0;
	tDayStart = getDayRange(dayHi, dayLo, t0);
	//Print("dayHi= ", dayHi, ", dayLo= ", dayLo, ", close0= ", close0, ", t0= ", TimeToStr(t0), ", tDayStart= ", TimeToStr(tDayStart));

	if (DayRangeMaxPP > 0) {
	    if (close0 - dayLo >= DayRangeMaxPP * point) {
		bLongPermit = false;
	    }
	    if (dayHi - close0 >= DayRangeMaxPP * point) {
		bShortPermit = false;
	    }
	}
	if (DayRangeMinPP > 0) {
	    if (close0 - dayLo < DayRangeMinPP * point) {
		bLongPermit = false;
	    }
	    if (dayHi - close0 < DayRangeMinPP * point) {
		bShortPermit = false;
	    }
	}
	
	orderCount();
	
	if (hi1 <= boxHi && lo1 >= boxLo) {
	    // in the box range
	    if (nOrderLong + nOrderShort == 0) {
		bOrderReady = true;
	    }
	}
	
	if (bValidBox && bOrderReady) {
	    bool bBoxNoLimit = true;
	    double boxRange = boxHi - boxLo;
	    if (BoxLimitMaxPP != 0) {
		if (boxRange >= BoxLimitMaxPP * point) {
		    bBoxNoLimit = false;
		}
	    }
	    if (BoxLimitMinPP != 0) {
		if (boxRange < BoxLimitMinPP * point) {
		    bBoxNoLimit = false;
		}
	    }

	    double m = MarginPips * point;
	    if (bLongPermit && close1 >= boxHi + m) {
		if (nOrderLong == 0) {
		    orderClose();
		    if (!bOrderStop && bBoxNoLimit) {
			orderOpen(OP_BUY);
			bOrderReady = false;
		    }
		}
	    }
	    if (bShortPermit && close1 <= boxLo - m) {
		if (nOrderShort == 0) {
		    orderClose();
		    if (!bOrderStop && bBoxNoLimit) {
			orderOpen(OP_SELL);
			bOrderReady = false;
		    }
		}
	    }
	}
    }
    
    if (bOrderClose) {
	orderClose();
    } else {
	orderTrail();
    }
    
    // visualize
    if (!IsOptimization()) {
	int y = 32;
	objLabel("label0", 0, 4, y, "Magic= " + Magic, infoFontColor, infoFontSize, infoFontName);
	y += 18;
	objLabel("label1", 0, 4, y, "bOrderStop= " + bOrderStop, infoFontColor, infoFontSize, infoFontName);
	y += 18;
	objLabel("label2", 0, 4, y, "bOrderReady= " + bOrderReady, infoFontColor, infoFontSize, infoFontName);
	y += 18;
	objLabel("label3", 0, 4, y, "bLongPermit= " + bLongPermit, infoFontColor, infoFontSize, infoFontName);
	y += 18;
	objLabel("label4", 0, 4, y, "bShortPermit= " + bShortPermit, infoFontColor, infoFontSize, infoFontName);
	y += 18;
	
	// box
	int ts = tsBox;
	int te = teBox;
	double ps = boxHi;
	double pe = boxLo;
	objLine("line0", ts, ps, te, ps, colBoxRect);
	objLine("line1", te, ps, te, pe, colBoxRect);
	objLine("line2", te, pe, ts, pe, colBoxRect);
	objLine("line3", ts, pe, ts, ps, colBoxRect);
	
	// long level
	if (bLongPermit) {
	    ts = teBox;
	    te = tsBox + SEC_PER_DAY;
	    ps = boxHi + MarginPips * point;
	    pe = ps;
	} else {
	    ps = 0;
	    pe = 0;
	}
	objLine("lineLong", ts, ps, te, pe, colLong, 1, STYLE_DOT);
	
	// short level
	if (bShortPermit) {
	    ts = teBox;
	    te = tsBox + SEC_PER_DAY;
	    ps = boxLo - MarginPips * point;
	    pe = ps;
	} else {
	    ps = 0;
	    pe = 0;
	}
	objLine("lineShort", ts, ps, te, pe, colShort, 1, STYLE_DOT);
	
	// box open to close line
	ts = tsBox;
	te = teBox;
	ps = boxOpen;
	pe = boxClose;
	objLine("lineOpenClose", ts, ps, te, pe, colBoxRect, 1);
	
	// box hi/lo line
	ts = teBox;
	te = tsBox + SEC_PER_DAY;
	ps = boxHi;
	pe = boxHi;
	objLine("lineHi", ts, ps, te, pe, colBoxHi, 1, STYLE_DOT);
	ps = boxLo;
	pe = boxLo;
	objLine("lineLo", ts, ps, te, pe, colBoxLo, 1, STYLE_DOT);
	
	// day hi/lo line
	ts = tDayStart;
	te = ts + SEC_PER_DAY;
	ps = dayHi;
	pe = dayHi;
	objLine("lineDayHi", ts, ps, te, pe, colDayRange, 1, STYLE_DOT);
	ps = dayLo;
	pe = dayLo;
	objLine("lineDayLo", ts, ps, te, pe, colDayRange, 1, STYLE_DOT);
	
	// OrderGoSH1
	int th = (t0 / SEC_PER_DAY) * SEC_PER_DAY;
	ts = th + orderGoSH1 * SEC_PER_HOUR;
	te = ts;
	ps = 0;
	if (orderGoSH1 == orderGoEH1 || !bValidBox) {
	    pe = 0;
	} else {
	    pe = 9999;
	}
	objLine("lineGoSH1", ts, ps, te, pe, colGoLine1, 1, STYLE_SOLID);
	ts = th + orderGoEH1 * SEC_PER_HOUR;
	te = ts;
	objLine("lineGoEH1", ts, ps, te, pe, colGoLine1, 1, STYLE_DOT);
	
	// OrderGoSH2
	ts = th + orderGoSH2 * SEC_PER_HOUR;
	te = ts;
	ps = 0;
	if (orderGoSH2 == orderGoEH2 || !bValidBox) {
	    pe = 0;
	} else {
	    pe = 9999;
	}
	objLine("lineGoSH2", ts, ps, te, pe, colGoLine2, 1, STYLE_SOLID);
	ts = th + orderGoEH2 * SEC_PER_HOUR;
	te = ts;
	objLine("lineGoEH2", ts, ps, te, pe, colGoLine2, 1, STYLE_DOT);
    }
}
