//+------------------------------------------------------------------+ //| TimeRangeBreakOut.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "===== General Inputs ======" input int InpMagicNumber = 1234; // input double InpLots = 0.01; // lot size input int InpStopLoss = 150; // stop loss in % of the range (0 = off) input int InpTakeProfit = 200; // take profit in % of the range (0 = off) input group "===== Range Inputs ======" input int InpRangeStart = 600; // range start time in minutes input int InpRangeDuration = 120; // range duration in minutes input int InpRangeClose = 1200; // range close time in minutes (-1 = off) enum BREAKOUT_MODE_ENUM { ONE_SIGNAL, // one breakout per range TWO_SIGNALS, // high and low breakout }; input BREAKOUT_MODE_ENUM InpBreakoutMode = ONE_SIGNAL; // breakout mode input group "===== Day of week filter ======" input bool InpMonday = true; // range on monday input bool InpTuesday = true; // range on Tuesday input bool InpWednesday = true; // range on Wednesday input bool InpThursday = true; // range on Thurday input bool InpFriday = true; // range on Friday input bool InpSaturday = true; // range on Saturday input bool InpSunday = true; // range on Sunday //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ struct RANGE_STRUCT{ datetime start_time; //start of the range datetime end_time; // end of the range datetime close_time; // close time double high; // high of the range double low; // low of the range bool f_entry; // flag if we are inside the range bool f_high_breakout; // flag if a high breakout occurred bool f_low_breakout; // flag if a low breakout occurred // MQL5 is a OOP language !! now is going to define it RANGE_STRUCT() : start_time(0), end_time(0),close_time(0),high(0),low(DBL_MAX),f_entry(false),f_high_breakout(false),f_low_breakout(false) {}; }; // make a object for Range_struct, then we can use range.xxxxx to use the function RANGE_STRUCT range; MqlTick prevTick, lastTick; CTrade trade; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ // make sure all the input are correct!! if incorrect it will break the function!! if(!CheckInputs()){return INIT_PARAMETERS_INCORRECT;} // set magic number for all the trade trade.SetExpertMagicNumber(InpMagicNumber); //calculated new range if inputs changed if(_UninitReason==REASON_PARAMETERS && countOpenPosition() == 0){ CalculateRange(); } //draw objects if the timeframe changed it will draw a new object !! DrawObjects(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ // delete objects ObjectsDeleteAll(NULL,"range"); // it will delete all object which start with "range" } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Get current tick prevTick = lastTick; SymbolInfoTick(_Symbol,lastTick); // range calculation if(lastTick.time >= range.start_time && lastTick.time < range.end_time){ // set flag range.f_entry = true; // new high if(lastTick.ask > range.high){ range.high = lastTick.ask; DrawObjects(); } // new low if(lastTick.bid < range.low){ range.low = lastTick.bid; DrawObjects(); } } // close positions if(InpRangeClose>=0 && lastTick.time>=range.close_time){ if(!ClosePostions()) {return;} } // calculate new range if ... if((( InpRangeClose>=0 && lastTick.time>=range.close_time) // close time reached || ( range.f_high_breakout && range.f_low_breakout) // both breakout flags are true || range.end_time == 0 // range not calculate yet || (range.end_time != 0 && lastTick.time>range.end_time && !range.f_entry)) // there was a range calculated, but no tick inside && countOpenPosition()== 0) { CalculateRange(); } // check for breakouts CheckBreakouts(); } //+------------------------------------------------------------------+ //| customer function below | //+------------------------------------------------------------------+ // check user inputs bool CheckInputs(){ if(InpMagicNumber <= 0){ Alert("Magicnumber <= 0"); return false; } if(InpLots <= 0 || InpLots > 1){ Alert("Lotsize error , Lotsize = " , InpLots); return false; } if(InpStopLoss<0 || InpStopLoss>1000){ Alert("stoploss error , stoploss = " , InpStopLoss); return false; } if(InpTakeProfit<0 || InpTakeProfit>1000){ Alert("InpTakeProfit error , TakeProfit = " , InpTakeProfit); return false; } if(InpRangeClose<0 && InpStopLoss==0 ){ Alert("Please be alert due to the stoploss or closing time is off!! the orders will never close!!"); return false; } if(InpRangeStart < 0 || InpRangeStart >= 1440){ Alert("RangeStart Error " , InpRangeStart); return false; } if(InpRangeDuration <= 0 || InpRangeDuration >= 1440){ Alert("InpRangeDuration Error " , InpRangeDuration); return false; } if( InpRangeClose >= 1440 || InpRangeStart + InpRangeDuration == InpRangeClose){ Alert("InpRangeClose Error " , InpRangeClose); return false; } if(InpMonday+InpTuesday+InpWednesday+InpThursday+InpFriday==0){ Alert("Range is prohibited on all days of the week!!"); return false; } // Calculated new range if inputs changed if(_UninitReason==REASON_PARAMETERS){ CalculateRange(); } return true; } // calculate a new range void CalculateRange(){ // reset range variable range.start_time = 0; range.end_time = 0; range.close_time = 0; range.high = 0.0; range.low = 99999; range.f_entry = false; range.f_high_breakout = false; range.f_low_breakout = false; // calculate range start time int time_cycle = 86400; //second for 1day range.start_time = (lastTick.time - (lastTick.time % time_cycle)) + InpRangeStart*60; //to make sure the start time is the first second of the day for( int i=0; i<8; i++){ MqlDateTime tmp; TimeToStruct(range.start_time,tmp); int dow = tmp.day_of_week; //day of the week //to calculate the start time depend on week of the day which turn on if(lastTick.time>=range.start_time || (dow==1 && !InpMonday) || (dow==2 && !InpTuesday) || (dow==3 && !InpWednesday)|| (dow==4 && !InpThursday) || (dow==5 && !InpFriday) || (dow==6 && !InpSaturday)|| (dow==0 && !InpSunday)) { range.start_time += time_cycle; } } // calculate range end time range.end_time = range.start_time + InpRangeDuration*60; for(int i=0; i<2; i++){ MqlDateTime tmp; TimeToStruct(range.end_time,tmp); int dow = tmp.day_of_week; if(dow==6 || dow==0){ range.end_time += time_cycle; } } // calculate range close if(InpRangeClose>=0){ range.close_time = (range.end_time - (range.end_time % time_cycle)) + InpRangeClose*60; //to make sure the start time is the first second of the day for( int i=0; i<3; i++){ MqlDateTime tmp; TimeToStruct(range.close_time,tmp); int dow = tmp.day_of_week; //day of the week if(range.close_time<=range.end_time || dow==6 || dow==0){ // dow==6(Sat) ==7(Sun) range.close_time += time_cycle; } } } // draw objects DrawObjects(); } int countOpenPosition(){ int counter = 0; int total = PositionsTotal(); for(int i =total-1 ; i >=0 ; i--){ ulong ticket = PositionGetTicket(i); if(ticket<=0){Print("Failed to get position ticket");return -1;} if(!PositionSelectByTicket(ticket)){Print("Failed to select position by ticket"); return-1;} ulong magicnumber; if(!PositionGetInteger(POSITION_MAGIC,magicnumber)){Print("Failed to get position magicnumber"); return-1;} if(InpMagicNumber==magicnumber){counter++;} } return counter; } void CheckBreakouts(){ // check if we are after the range end if(lastTick.time >= range.end_time && range.end_time>0 && range.f_entry){ // check for high breakout if(!range.f_high_breakout && lastTick.ask >= range.high){ range.f_high_breakout = true; if(InpBreakoutMode==ONE_SIGNAL){range.f_low_breakout = true;} // calculate stop loss and take profit for buy order double sl = (InpStopLoss == 0)?0 :NormalizeDouble((lastTick.bid - ((range.high - range.low) * InpStopLoss * 0.01)),_Digits); double tp = (InpTakeProfit == 0)?0 :NormalizeDouble((lastTick.ask + ((range.high - range.low) * InpTakeProfit * 0.01)),_Digits); // open buy position trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,InpLots,lastTick.ask,sl,tp,"Time range EA"); } // check for low breakout if(!range.f_low_breakout && lastTick.bid <= range.low){ range.f_low_breakout = true; if(InpBreakoutMode==ONE_SIGNAL){range.f_high_breakout = true;} // calculate stop loss and take profit for sell order double sl = (InpStopLoss == 0)? 0 :NormalizeDouble((lastTick.bid + ((range.high - range.low) * InpStopLoss * 0.01)),_Digits); double tp = (InpTakeProfit == 0)? 0 :NormalizeDouble((lastTick.ask - ((range.high - range.low) * InpTakeProfit * 0.01)),_Digits); // open sell position trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,InpLots,lastTick.bid,sl,tp,"Time range EA"); } } } bool ClosePostions(){ Print("Closing all the position"); int total = PositionsTotal(); for (int i = total-1; i >=0 ; i--){ if(total != PositionsTotal()){total=PositionsTotal(); i=total; continue;} ulong ticket = PositionGetTicket(i); //Select position if(ticket<=0){Print("Failed to get position ticket"); return false;} if(!PositionSelectByTicket(ticket)){Print("Failed to select position by ticket"); return false;} long magicnumber; if(!PositionGetInteger(POSITION_MAGIC,magicnumber)){Print("Failed to get position magicnumber"); return false;} if(magicnumber == InpMagicNumber){ trade.PositionClose(ticket); if(trade.ResultRetcode()!=TRADE_RETCODE_DONE){ Print("Failed to close position. Result: " + (string)trade.ResultRetcode() + ":" + trade.ResultRetcodeDescription() ); return false; } } } return true; } void DrawObjects(){ // start ObjectDelete(NULL,"range start"); // delete the old vline then make a new vline if(range.start_time>0){ ObjectCreate(NULL,"range start",OBJ_VLINE,0,range.start_time,0); ObjectSetString(NULL,"range start",OBJPROP_TOOLTIP,"start of the range \n"+TimeToString(range.start_time,TIME_DATE|TIME_MINUTES)); ObjectSetInteger(NULL,"range start",OBJPROP_COLOR,clrBlue); ObjectSetInteger(NULL,"range start",OBJPROP_WIDTH,2); ObjectSetInteger(NULL,"range start",OBJPROP_BACK,true); } //end time ObjectDelete(NULL,"range end"); // delete the old vline then make a new vline if(range.end_time>0){ ObjectCreate(NULL,"range end",OBJ_VLINE,0,range.end_time,0); ObjectSetString(NULL,"range end",OBJPROP_TOOLTIP,"end of the range \n"+TimeToString(range.end_time,TIME_DATE|TIME_MINUTES)); ObjectSetInteger(NULL,"range end",OBJPROP_COLOR,clrBlue); ObjectSetInteger(NULL,"range end",OBJPROP_WIDTH,2); ObjectSetInteger(NULL,"range end",OBJPROP_BACK,true); } //close time ObjectDelete(NULL,"range close"); // delete the old vline then make a new vline if(range.close_time>0){ ObjectCreate(NULL,"range close",OBJ_VLINE,0,range.close_time,0); ObjectSetString(NULL,"range close",OBJPROP_TOOLTIP,"close of the range \n"+TimeToString(range.close_time,TIME_DATE|TIME_MINUTES)); ObjectSetInteger(NULL,"range close",OBJPROP_COLOR,clrRed); ObjectSetInteger(NULL,"range close",OBJPROP_WIDTH,2); ObjectSetInteger(NULL,"range close",OBJPROP_BACK,true); } //high ObjectsDeleteAll(NULL,"range high"); if(range.high>0){ // Trend line is a line from 1 time to another 1 time ** not a line in the whole window ObjectCreate(NULL,"range high",OBJ_TREND,0,range.start_time,range.high,range.end_time,range.high); ObjectSetString(NULL,"range high",OBJPROP_TOOLTIP,"high of the range \n"+DoubleToString(range.high,_Digits)); ObjectSetInteger(NULL,"range high",OBJPROP_COLOR,clrRed); ObjectSetInteger(NULL,"range high",OBJPROP_WIDTH,2); ObjectSetInteger(NULL,"range high",OBJPROP_BACK,true); // another line from the end time to close time if rangeclose is off then draw the line all the way ObjectCreate(NULL,"range high ",OBJ_TREND,0,range.end_time,range.high,InpRangeClose>=0 ? range.close_time : INT_MAX ,range.high); ObjectSetString(NULL,"range high ",OBJPROP_TOOLTIP,"high of the range \n"+DoubleToString(range.high,_Digits)); ObjectSetInteger(NULL,"range high ",OBJPROP_COLOR,clrRed); ObjectSetInteger(NULL,"range high ",OBJPROP_BACK,true); ObjectSetInteger(NULL,"range high ",OBJPROP_STYLE,STYLE_DOT); } //low ObjectsDeleteAll(NULL,"range low"); if(range.low<999999){ // Trend line is a line from 1 time to another 1 time ** not a line in the whole window ObjectCreate(NULL,"range low",OBJ_TREND,0,range.start_time,range.low,range.end_time,range.low); ObjectSetString(NULL,"range low",OBJPROP_TOOLTIP,"low of the range \n"+DoubleToString(range.low,_Digits)); ObjectSetInteger(NULL,"range low",OBJPROP_COLOR,clrBlue); ObjectSetInteger(NULL,"range low",OBJPROP_WIDTH,2); ObjectSetInteger(NULL,"range low",OBJPROP_BACK,true); // another line from the end time to close time if rangeclose is off then draw the line all the way ObjectCreate(NULL,"range low ",OBJ_TREND,0,range.end_time,range.low,InpRangeClose>=0 ? range.close_time : INT_MAX ,range.low); ObjectSetString(NULL,"range low ",OBJPROP_TOOLTIP,"low of the range \n"+DoubleToString(range.low,_Digits)); ObjectSetInteger(NULL,"range low ",OBJPROP_COLOR,clrBlue); ObjectSetInteger(NULL,"range low ",OBJPROP_BACK,true); ObjectSetInteger(NULL,"range low ",OBJPROP_STYLE,STYLE_DOT); } //refresh chart ChartRedraw(); } //+------------------------------------------------------------------+