ShareEA/TimeRangeBreakOut.mq5
super.admin e990dc3166 convert
2025-05-30 16:24:11 +02:00

389 lines
32 KiB
MQL5

//+------------------------------------------------------------------+
//| 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 <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| 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();
}
//+------------------------------------------------------------------+