ShareEA/TimeRangeEADynamic.mq5

471 lines
19 KiB
MQL5
Raw Permalink Normal View History

2025-05-30 16:24:11 +02:00
//+------------------------------------------------------------------+
//| 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; // 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();
}
//+------------------------------------------------------------------+