353 lines
No EOL
13 KiB
MQL5
353 lines
No EOL
13 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| HighLowBreakOut.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>
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Input |
|
|
//+------------------------------------------------------------------+
|
|
|
|
static input long InpMagicnumber = 54321; //magicnumber
|
|
static input double InpLots = 0.01; //lot size
|
|
input int InpBars = 20; // bars for high/low
|
|
input int InpIndexFilter = 0; // index filter in % (0=off)
|
|
input int InpSizeFilter = 0; //channel size filter in points(0 = off)
|
|
input int InpStopLoss = 200; // stop loss in points
|
|
input bool InpTrailingSL = true; // trailing stop loss?
|
|
input int InpTakeProfit = 0; //take profit in points (0 = off)
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Global variables |
|
|
//+------------------------------------------------------------------+
|
|
|
|
double high = 0; //highest price of the last N bars
|
|
double low = 0; // lowest price of the last N bars
|
|
int highIdx=0; //index of highest bar
|
|
int lowIdx=0; //index of lowest bar
|
|
MqlTick currentTick, previousTick;
|
|
CTrade trade;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
|
|
int OnInit(){
|
|
|
|
//check for user input
|
|
if(!CheckInputs()){return INIT_PARAMETERS_INCORRECT;}
|
|
|
|
//set the magic number
|
|
trade.SetExpertMagicNumber(InpMagicnumber);
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason){
|
|
|
|
ObjectDelete(NULL,"high");
|
|
ObjectDelete(NULL,"low");
|
|
ObjectDelete(NULL,"text");
|
|
ObjectDelete(NULL,"indexFilter");
|
|
|
|
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick(){
|
|
|
|
//check for the new bar open tick
|
|
if(!IsNewBar()){return;}
|
|
|
|
|
|
//get tick
|
|
previousTick = currentTick; //previousTick become last tick
|
|
if(!SymbolInfoTick(_Symbol,currentTick)){Print("Failed to get current tick"); return;} //ymbolinfotick() will renew the current tick
|
|
|
|
// count open positions
|
|
int cntBuy, cntSell;
|
|
if(!CountOpenPositions(cntBuy,cntSell)){return;}
|
|
|
|
// check for buy position
|
|
if(cntBuy==0 & high!=0 && previousTick.ask<high && currentTick.ask>=high && CheckIndexFilter(highIdx) && CheckSizeFilter()){
|
|
|
|
//calculate the stoploss/takeprofit
|
|
double sl = InpStopLoss==0 ? 0 : currentTick.bid - InpStopLoss *_Point;
|
|
double tp = InpTakeProfit == 0 ? 0 : currentTick.ask + InpTakeProfit *_Point;
|
|
if(!NormalizePrice(sl)){return;}
|
|
if(!NormalizePrice(tp)){return;}
|
|
|
|
trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,InpLots,currentTick.ask,sl,tp,"highlowBreakoutEA");
|
|
}
|
|
|
|
// check for sell position
|
|
if(cntSell==0 & low!=0 && previousTick.bid>low && currentTick.bid<=low && CheckIndexFilter(lowIdx) && CheckSizeFilter()){
|
|
|
|
//calculate the stoploss/takeprofit
|
|
double sl = InpStopLoss==0 ? 0 : currentTick.ask + InpStopLoss *_Point;
|
|
double tp = InpTakeProfit == 0 ? 0 : currentTick.bid - InpTakeProfit *_Point;
|
|
if(!NormalizePrice(sl)){return;}
|
|
if(!NormalizePrice(tp)){return;}
|
|
|
|
trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,InpLots,currentTick.bid,sl,tp,"highlowBreakoutEA");
|
|
}
|
|
|
|
//update stop loss (trailing stop)
|
|
if(InpStopLoss>0 && InpTrailingSL){
|
|
UpdateStopLoss(InpStopLoss*_Point);
|
|
}
|
|
|
|
// calculate high/low
|
|
highIdx = iHighest(_Symbol,PERIOD_CURRENT,MODE_HIGH,InpBars,1); //ihighest = highest bar in a period
|
|
lowIdx = iLowest(_Symbol,PERIOD_CURRENT,MODE_LOW,InpBars,1);
|
|
high = iHigh(_Symbol,PERIOD_CURRENT,highIdx); //ihigh = the high price of 1 bar
|
|
low = iLow(_Symbol,PERIOD_CURRENT,lowIdx);
|
|
|
|
DrawObjects();
|
|
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function |
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//check User input
|
|
bool CheckInputs(){
|
|
|
|
if(InpMagicnumber<=0){
|
|
Alert("Wrong inputs : Magicnumber <=0");
|
|
return false;
|
|
}
|
|
if(InpLots<=0){
|
|
Alert("Wrong inputs : lotsize <= 0");
|
|
}
|
|
if(InpBars<=0){
|
|
Alert("Wrong inputs : bars <= 0");
|
|
}
|
|
if(InpSizeFilter<0){
|
|
Alert("Wrong inputs : InpSizeFilter < 0");
|
|
}
|
|
if(InpIndexFilter<0 || InpIndexFilter>50){
|
|
Alert("Wrong inpuits : InpIndexFilter ");
|
|
}
|
|
if(InpStopLoss<=0){
|
|
Alert("Wrong input : InpStopLoss <= 0 , if no stoploss the trade will last forever!");
|
|
}
|
|
if(InpTakeProfit<0){
|
|
Alert("Wrong inpuits : InpTakeProfit < 0");
|
|
}
|
|
if(InpTrailingSL==0 && InpTakeProfit==0){
|
|
Alert("Trailing SL and Takeprofit set to 0!! the deal won't make profit!!");
|
|
}
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if high/low is inside valid index range
|
|
bool CheckIndexFilter(int index){
|
|
|
|
if(InpIndexFilter>0 && (index<=round(InpBars*InpIndexFilter*0.01) || index>InpBars-round(InpBars*InpIndexFilter*0.01))){
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// check channel size
|
|
bool CheckSizeFilter(){
|
|
|
|
if(InpSizeFilter>0 && (high-low)>InpSizeFilter*_Point){
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void DrawObjects(){
|
|
|
|
datetime time1 = iTime(_Symbol,PERIOD_CURRENT,InpBars);
|
|
datetime time2 = iTime(_Symbol,PERIOD_CURRENT,1);
|
|
//high
|
|
ObjectDelete(NULL,"high");
|
|
ObjectCreate(NULL,"high",OBJ_TREND,0,time1,high,time2,high);
|
|
ObjectSetInteger(NULL,"high",OBJPROP_WIDTH,3);
|
|
ObjectSetInteger(NULL,"high",OBJPROP_COLOR,CheckIndexFilter(highIdx) && CheckSizeFilter() ? clrLime : clrRed);
|
|
|
|
//low
|
|
ObjectDelete(NULL,"low");
|
|
ObjectCreate(NULL,"low",OBJ_TREND,0,time1,low,time2,low);
|
|
ObjectSetInteger(NULL,"low",OBJPROP_WIDTH,3);
|
|
ObjectSetInteger(NULL,"low",OBJPROP_COLOR,CheckIndexFilter(lowIdx) && CheckSizeFilter() ? clrLime : clrRed);
|
|
|
|
// index filter
|
|
ObjectDelete(NULL,"indexFilter");
|
|
if(InpIndexFilter>0){
|
|
datetime timeIF1 = iTime(_Symbol,PERIOD_CURRENT,(int)InpBars-round(InpBars*InpIndexFilter*0.01));
|
|
datetime timeIF2 = iTime(_Symbol,PERIOD_CURRENT,(int)round(InpBars*InpIndexFilter*0.01));
|
|
ObjectCreate(NULL,"indexFilter",OBJ_RECTANGLE,0,timeIF1,low,timeIF2,high);
|
|
ObjectSetInteger(NULL,"indexFilter",OBJPROP_BACK,false); //background
|
|
ObjectSetInteger(NULL,"indexFilter",OBJPROP_FILL,true); //background color switch
|
|
ObjectSetInteger(NULL,"indexFilter",OBJPROP_COLOR,clrLime); //select background color
|
|
}
|
|
|
|
// text
|
|
ObjectDelete(NULL,"text");
|
|
ObjectCreate(NULL,"text",OBJ_TEXT,0,time2,low);
|
|
ObjectSetInteger(NULL,"text",OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
|
|
ObjectSetInteger(NULL,"text",OBJPROP_COLOR,clrLime);
|
|
ObjectSetString(NULL,"text",OBJPROP_TEXT,"Bars:"+(string)InpBars+
|
|
" index filter"+DoubleToString(round(InpBars*InpIndexFilter*0.01),0)+
|
|
" high index"+(string)highIdx+
|
|
" low index"+(string)lowIdx)+
|
|
" size:"+DoubleToString((high-low)/_Point,0);
|
|
}
|
|
|
|
// check if we have a bar open tick
|
|
bool IsNewBar(){
|
|
|
|
static datetime previousTime = 0;
|
|
datetime currentTime = iTime(_Symbol,PERIOD_CURRENT,0);
|
|
if(previousTime!=currentTime){
|
|
previousTime=currentTime;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
// count open positions
|
|
bool CountOpenPositions(int &cntBuy, int &cntSell) {
|
|
|
|
cntBuy = 0;
|
|
cntSell = 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 false;}
|
|
if(!PositionSelectByTicket(ticket)){Print("Failed to select position"); return false;}
|
|
long magic;
|
|
if(!PositionGetInteger(POSITION_MAGIC,magic)) {Print("Failed to get position magicnumber"); return false;}
|
|
if(magic==InpMagicnumber){
|
|
long type;
|
|
if(!PositionGetInteger(POSITION_TYPE,type)) {Print("Failed to get position type"); return false;}
|
|
if(type==POSITION_TYPE_BUY) {cntBuy++ ;}
|
|
if(type==POSITION_TYPE_SELL) {cntSell++ ;}
|
|
}
|
|
}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// round up the digit for the trade
|
|
bool NormalizePrice(double &price){
|
|
|
|
double tickSize = 0;
|
|
if(!SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE,tickSize)){
|
|
Print("Failed to get tick size");
|
|
return false;
|
|
}
|
|
price = NormalizeDouble(MathRound(price/tickSize)*tickSize,_Digits);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* no close position needed in this EA
|
|
// close positions
|
|
bool ClosePositions(int all_buy_sell){
|
|
|
|
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 false;}
|
|
if(!PositionSelectByTicket(ticket)){Print("Failed to select position"); return false;}
|
|
long magic;
|
|
if(!PositionGetInteger(POSITION_MAGIC,magic)) {Print("Failed to get position magicnumber"); return false;}
|
|
if(magic==InpMagicnumber){
|
|
long type;
|
|
if(!PositionGetInteger(POSITION_TYPE,type)){Print("Failed to get position type"); return false;}
|
|
if(all_buy_sell==1 && type==POSITION_TYPE_SELL){continue;} //1 = buy position
|
|
if(all_buy_sell==2 && type==POSITION_TYPE_BUY){continue;} //2 = sell position
|
|
trade.PositionClose(ticket);
|
|
if(trade.ResultRetcode()!=TRADE_RETCODE_DONE){
|
|
Print("Failed to close position. Ticket:",
|
|
(string)ticket," result:",(string)trade.ResultRetcode(),":",trade.CheckResultRetcodeDescription());
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
*/
|
|
|
|
void UpdateStopLoss(double slDistance){
|
|
|
|
//loop through open positions
|
|
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;}
|
|
if(!PositionSelectByTicket(ticket)){Print("Failed to select position by ticket"); return;}
|
|
ulong magicnumber;
|
|
if(!PositionGetInteger(POSITION_MAGIC,magicnumber)) {Print("Failed to get position magicnumber ");return;}
|
|
if(InpMagicnumber==magicnumber){
|
|
|
|
//get type
|
|
long type;
|
|
if(!PositionGetInteger(POSITION_TYPE,type)){Print("Failed to get position type"); return;}
|
|
//get current sl and tp
|
|
double currSL, currTP;
|
|
if(!PositionGetDouble(POSITION_SL,currSL)){Print("Failed to get position stop loss"); return;}
|
|
if(!PositionGetDouble(POSITION_TP,currTP)){Print("Failed to get position stop loss"); return;}
|
|
|
|
// calculate stop loss
|
|
double currPrice = type==POSITION_TYPE_BUY ? currentTick.bid : currentTick.ask;
|
|
int n = type==POSITION_TYPE_BUY ? 1 : -1;
|
|
double newSL = currPrice - slDistance * n;
|
|
if(!NormalizePrice(newSL)){return;}
|
|
|
|
// check if new stop loss is closer to current price than existing stop loss
|
|
if((newSL*n) < (currSL*n) || NormalizeDouble(MathAbs(newSL-currPrice),_Digits)<_Point) {
|
|
//Print("No new stop loss needed");
|
|
continue;
|
|
}
|
|
|
|
//check for stop level
|
|
long level = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
|
|
if(level!=0 && MathAbs(currPrice-newSL)<=level*_Point){
|
|
Print("New stop loss inside stop level");
|
|
continue;
|
|
}
|
|
|
|
// modify position with new stop loss
|
|
if(!trade.PositionModify(ticket,newSL,currTP)){
|
|
Print("Failed to modify position, ticket:",(string)ticket,"currSL:",(string)currSL,
|
|
"newSL:",(string)newSL," currTP:", (string)currTP);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
} |