//+------------------------------------------------------------------+ //| 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; // magicNumber enum LOT_MODE_ENUM{ LOT_MODE_FIXED, // fixed lots LOT_MODE_MONEY, // lots based on money LOT_MODE_PCT_ACCOUNT // lots based on % of account }; input LOT_MODE_ENUM InpLotMode = LOT_MODE_FIXED; // lot mode input double InpLots = 0.01; // lots / money / % percent 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(InpLotMode == LOT_MODE_FIXED && (InpLots <= 0 || InpLots > 10)){ Alert("Lots <= 0 or > 10"); return false; } if(InpLotMode == LOT_MODE_MONEY && (InpLots <= 0 || InpLots > 1000)){ Alert("Lots by money <= 0 or > 1000 USD"); return false; } if(InpLotMode == LOT_MODE_PCT_ACCOUNT && (InpLots <= 0 || InpLots > 9)){ Alert("Lots by account % < 0% or < 5%"); return false; } if((InpLotMode == LOT_MODE_MONEY || InpLotMode==LOT_MODE_PCT_ACCOUNT) && (InpStopLoss==0)){ Alert("Selected lot mode needs a stop loss!! you forgot to set the amount in stoploss!!"); 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); // calculate lots double lots; if(!CalculateLots(lastTick.bid-sl,lots)){return;} // open buy position trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lots,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); // calculate lots double lots; if(!CalculateLots(sl-lastTick.ask,lots)){return;} // open sell position trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lots,lastTick.bid,sl,tp,"Time range EA"); } } } //calculate lots bool CalculateLots(double slDistance, double &lots){ lots = 0.0; if(InpLotMode==LOT_MODE_FIXED){ lots = InpLots; } else{ double tickSize = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE); double tickValue = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE); double volumeStep = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); double riskMoney = InpLotMode==LOT_MODE_MONEY ? InpLots : AccountInfoDouble(ACCOUNT_BALANCE) * InpLots * 0.01; double moneyVolumeStep = (slDistance / tickSize) * tickValue * volumeStep; lots = MathFloor(riskMoney/moneyVolumeStep) * volumeStep; } // check calculated lots if(!CheckLots(lots)){return false;} return true; } // check lots for min, max and step bool CheckLots(double &lots){ double min = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); double max = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); double step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); if(lots < min){ Print("Lots size will be set to the minimum allowable volume!!"); lots = min; return true; } if(lots>max){ Print("Lots size greater than the maximum allowable volume!! Lots: ", lots,"max : ", max); return false; } lots = (int)MathFloor(lots/step) * step; return true; } 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(); } //+------------------------------------------------------------------+