//+------------------------------------------------------------------+ //| ZoneScanner.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" // INPUT VARIABLES input ENUM_TIMEFRAMES inputTimeFrameFractals ; // FRACTAL VARIABLES INITIALIZATION input int leftPeriod; input int rightPeriod; input int weekPeriodToInitialize; input int initFractalsPeriod ; int lowFractalIndex ; int highFractalIndex ; // GUI CLASSES INCLUDES #include "ZoneContainer.mqh" #include "GraphicalObjectsManager.mqh" #include "ObjectConfirmationUnit.mqh" #include "TempObjectAttributes.mqh" // DATA STRUCTURE CLASSES INCLUDES #include "Zone.mqh" #include "LowFractal.mqh" #include "HighFractal.mqh" #include "LowFractalContainer.mqh" #include "HighFractalContainer.mqh" // ALGORITHM CLASSES INCLUDES #include "NewCandleDetector.mqh" // LIBRARIES INCLUDES #include // GUI CLASSES INITIALIZATION GraphicalObjectsManager* objectsManager = new GraphicalObjectsManager(); ObjectConfirmationUnit* objectConfUnit = new ObjectConfirmationUnit(); TempObjectAttributes* tempObjectAttr = new TempObjectAttributes(); // CONTAINERS INITIALIZATION ZoneContainer zoneContainer ; LowFractalContainer* lowsContainer = new LowFractalContainer(inputTimeFrameFractals) ; HighFractalContainer* highsContainer = new HighFractalContainer(inputTimeFrameFractals) ; // GENERAL CHART VARIABLES INITIALIZATION bool initialized = false ; bool firstCandleAfterStart = true ; // NEW CANDLE CLASSES INITIALIZATION //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ /*NewCandleDetector* newCandleDetectorM1 = new NewCandleDetector("PERIOD_M1"); NewCandleDetector* newCandleDetectorM5 = new NewCandleDetector("PERIOD_M5"); NewCandleDetector* newCandleDetectorM15 = new NewCandleDetector("PERIOD_M15"); NewCandleDetector* newCandleDetectorM30 = new NewCandleDetector("PERIOD_M30"); NewCandleDetector* newCandleDetectorH1 = new NewCandleDetector("PERIOD_H1"); NewCandleDetector* newCandleDetectorH4 = new NewCandleDetector("PERIOD_H4"); NewCandleDetector* newCandleDetectorD1 = new NewCandleDetector("PERIOD_D1"); */ // LIVE FORMING FRACTALS HANDLER CLASS NewCandleDetector* newCandleDetectorLive ; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //Print("the ask is: " + SymbolInfoDouble(_Symbol, SYMBOL_ASK)); // THIS IS FOR SETTING THE NEW CANDLE DETECTOR TIMEFRAME FOR THE LIVE FRACTALS . (IN ORDER TO KNOW WHICH TIMEFRAME OF THE LIVE FRACTALS TO TAKE) if(inputTimeFrameFractals == PERIOD_M1) { newCandleDetectorLive = new NewCandleDetector("PERIOD_M1"); } else if(inputTimeFrameFractals == PERIOD_M5) { newCandleDetectorLive = new NewCandleDetector("PERIOD_M5"); } else if(inputTimeFrameFractals == PERIOD_M15) { newCandleDetectorLive = new NewCandleDetector("PERIOD_M15"); } else if(inputTimeFrameFractals == PERIOD_M30) { newCandleDetectorLive = new NewCandleDetector("PERIOD_M30"); } else if(inputTimeFrameFractals == PERIOD_H1) { newCandleDetectorLive = new NewCandleDetector("PERIOD_H1"); } else if(inputTimeFrameFractals == PERIOD_H4) { newCandleDetectorLive = new NewCandleDetector("PERIOD_H4"); } else if(inputTimeFrameFractals == PERIOD_D1) { newCandleDetectorLive = new NewCandleDetector("PERIOD_D1"); } if(initialized == false) { Print("Shark Initialized successfully on " + Symbol()); // ADD THE BUTTONS TO THE SCREEN objectsManager.addZoneButton(); objectsManager.addConfirmButton(); objectsManager.addDeleteButton(); objectsManager.addExitExperAdvisorButton(); objectsManager.addPrintFractalsButton(); objectsManager.addPrintZonesButton(); // INIT FRACTAL VARIABLES lowFractalIndex = rightPeriod+1 ; // this is used for the fractals highFractalIndex = rightPeriod+1; // this is used for the fractals initLowFractalsFromIndex(initFractalsPeriod,inputTimeFrameFractals); initHighFractalsFromIndex(initFractalsPeriod,inputTimeFrameFractals); // INITIALIZATION FLAG initialized = true ; } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer /*Print("Low Fractals:"); lowsContainer.printLowFractals(); Print(" "); Print("High Fractals:"); highsContainer.printhighFractals(); Print(" "); */ EventKillTimer(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- if(newCandleDetectorLive.isNewCandle()) { handleLowFractals(inputTimeFrameFractals); // scans low fractals that are forming and adds them to the container handleHighFractals(inputTimeFrameFractals); // scans high fractals that are forming and adds them to the container } } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- } //+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade() { //--- } //+------------------------------------------------------------------+ //| TradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { //--- } //+------------------------------------------------------------------+ //| Tester function | //+------------------------------------------------------------------+ double OnTester() { //--- double ret=0.0; //--- //--- return(ret); } //+------------------------------------------------------------------+ //| TesterInit function | //+------------------------------------------------------------------+ void OnTesterInit() { //--- } //+------------------------------------------------------------------+ //| TesterPass function | //+------------------------------------------------------------------+ void OnTesterPass() { //--- } //+------------------------------------------------------------------+ //| TesterDeinit function | //+------------------------------------------------------------------+ void OnTesterDeinit() { //--- } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- //--- left-clicking on a chart switch(id) { case CHARTEVENT_CLICK: { break; } //--- clicking on a graphical object case CHARTEVENT_OBJECT_CLICK: { if(sparam == "ZONE_ADD_BTN") { if(objectConfUnit.getWaitingStatus() == false) // THIS IS TO MAKE SURE THERE IS NO OTHER OBJECT SELECTED { // create a rectangle with a unique name int currentIdCounter = zoneContainer.getZonesIdCounter(); objectsManager.drawRectangle(currentIdCounter); // create a rectangle . set its name to be the current zonesIdCounter (and that would be the id of this current zone ) objectConfUnit.setWaitingStatus(true); objectConfUnit.setObjectType("OBJ_RECTANGLE"); objectConfUnit.setConfirmationObjectId(currentIdCounter); zoneContainer.incrementZonesIdCounter(); } else { Print("Failed to Add a new Object, Make sure to confirm the previous object first! ."); } } else if(sparam == "CONFIRM_BTN") { if(objectConfUnit.getWaitingStatus() == true) // THIS IS TO MAKE SURE THAT THERE IS SOMETHING SELECTED TO BE CONFIRMED { if(objectConfUnit.getObjectType() == "OBJ_RECTANGLE") // CASE OF ZONE { if(zoneContainer.getZoneIndex(objectConfUnit.getConfirmationObjectId()) != -1) // means the zone already in the data structure, in this case we just need to update it { // update the zone attributes // zoneContainer.UpdateZone(int id); Zone* tempZone = new Zone(); tempZone.setHigherEdgePrice(tempObjectAttr.getHigherEdgePrice()) ; tempZone.setLowerEdgePrice(tempObjectAttr.getLowerEdgePrice()); tempZone.setLeftEdge(tempObjectAttr.getLeftEdgeTime()); tempZone.setRightEdge(tempObjectAttr.getRightEdgeTime()); tempZone.setTimeFrame(tempObjectAttr.getTimeFrame()); string idOfOriginalZone = objectConfUnit.getConfirmationObjectId(); // no need to set the ID here because the ID belongs to the original zone and not this new temp one (this is just a temporary zone to check if the adjustment is valid or no) if(zoneContainer.updateZone(tempZone, idOfOriginalZone) ==1) { // in this case we successfully updated the zone Print("Updated the object:" + "\n ID: " + objectConfUnit.getConfirmationObjectId() + "\n Type: " + objectConfUnit.getObjectType() + "\n Asset: " + tempObjectAttr.getSymbol() + "\n Time Frame:" + tempObjectAttr.getTimeFrame() + "\n Higher Edge Price: " + tempObjectAttr.getHigherEdgePrice() + "\n Lower Edge Price: " + tempObjectAttr.getLowerEdgePrice() + "\n Left Edge Date: " + tempObjectAttr.getLeftEdgeTime() + "\n Right Edge Date: " + tempObjectAttr.getRightEdgeTime() ); delete tempZone; if(ObjectSetInteger(_Symbol,objectConfUnit.getConfirmationObjectId(),OBJPROP_SELECTED,false) == true) // this is to unselect the object once we finish using it { } else { Print("Failed to unselect the object. Error: " + GetLastError()); } // THIS IS TO CLEAN THE objectConfUnit objectConfUnit.setWaitingStatus(false); ChartRedraw(); } else { Print("Failed to update the zone, please reposition it"); } } else // means the zone is new so we need to add it { Zone* newZone = new Zone() ; newZone.setHigherEdgePrice(tempObjectAttr.getHigherEdgePrice()); newZone.setLowerEdgePrice(tempObjectAttr.getLowerEdgePrice()); newZone.setLeftEdge(tempObjectAttr.getLeftEdgeTime()); newZone.setRightEdge(tempObjectAttr.getRightEdgeTime()); newZone.setId(objectConfUnit.getConfirmationObjectId()); newZone.setTimeFrame(tempObjectAttr.getTimeFrame()); if(tempObjectAttr.getHigherEdgePrice() < SymbolInfoDouble(_Symbol, SYMBOL_ASK) ){ // means its a support zone newZone.setType("TYPE_SUPPORT"); } if(tempObjectAttr.getLowerEdgePrice() > SymbolInfoDouble(_Symbol, SYMBOL_ASK)){ // means its a resistance zone newZone.setType("TYPE_RESISTANCE"); } // add the zone to the data structure if(zoneContainer.addZone(newZone) == 1) { zoneContainer.findOrUpdatePivotEdges(); // update the pivot edges Print("Added the object:" + "\n ID: " + objectConfUnit.getConfirmationObjectId() + "\n Type: " + objectConfUnit.getObjectType() + "\n Asset: " + tempObjectAttr.getSymbol() + "\n Time Frame:" + tempObjectAttr.getTimeFrame() + "\n Higher Edge Price: " + tempObjectAttr.getHigherEdgePrice() + "\n Lower Edge Price: " + tempObjectAttr.getLowerEdgePrice() + "\n Left Edge Date: " + tempObjectAttr.getLeftEdgeTime() + "\n Right Edge Date: " + tempObjectAttr.getRightEdgeTime() ); if(ObjectSetInteger(_Symbol,objectConfUnit.getConfirmationObjectId(),OBJPROP_SELECTED,false) == true) // this is to unselect the object once we finish using it { } else { Print("Failed to unselect the object. Error: " + GetLastError()); } // THIS IS TO CLEAN THE objectConfUnit objectConfUnit.setWaitingStatus(false); } else { Print("Failed to add the zone, Error: 2 or more zones are crossing"); } ChartRedraw(); } } else if(objectConfUnit.getObjectType() == "OBJ_TRENDLINE") // CASE OF TRENDLINE { } } else { Print("Cannot confirm, Please select an object first !"); } } else if(sparam == "DELETE_BTN") { if(objectConfUnit.getWaitingStatus() == true) // THIS IS TO MAKE SURE THAT THERE IS SOMETHING SELECTED TO BE DELETED { if(ObjectDelete(_Symbol,objectConfUnit.getConfirmationObjectId())) { if(zoneContainer.deleteZone(objectConfUnit.getConfirmationObjectId()) == 1) { // delete from data structure zoneContainer.findOrUpdatePivotEdges(); Print("Deleted the object: " + "\n Type: " + objectConfUnit.getObjectType() + "\n ID: " + objectConfUnit.getConfirmationObjectId()); objectConfUnit.setWaitingStatus(false); objectConfUnit.setObjectType("NO_OBJECT"); objectConfUnit.setConfirmationObjectId(-2); ChartRedraw(); } else{ objectConfUnit.setWaitingStatus(false); objectConfUnit.setObjectType("NO_OBJECT"); objectConfUnit.setConfirmationObjectId(-2); } } } else { Print("Cannot delete, Please select an object first !"); } } else if(sparam == "EXIT_BTN") { delete objectsManager ; delete objectConfUnit; delete tempObjectAttr; delete lowsContainer; delete highsContainer; delete newCandleDetectorLive; zoneContainer.freeZonesSortedArray(); /*delete newCandleDetectorD1; delete newCandleDetectorM5; delete newCandleDetectorM30; delete newCandleDetectorM15; delete newCandleDetectorM1; delete newCandleDetectorH4; delete newCandleDetectorH1; */ ExpertRemove(); } else if(sparam == "PRINT_FRACTAL_BTN") { Print("Low Fractals:"); lowsContainer.printLowFractals(); Print("High Fractals:"); highsContainer.printHighFractals(); } else if(sparam == "PRINT_ZONES_BTN") { zoneContainer.printZonesSortedArray(); } else // THIS is the case were we click on any object except for the 'Add Zone' and 'Confirm Object' Buttons { if(objectConfUnit.getWaitingStatus() == true) // THIS IS TO MAKE SURE THERE IS NO OTHER OBJECT SELECTED { Print("Cannot select the object clicked, Make sure to confirm a previous selected object first !"); } else { ObjectSetInteger(_Symbol,sparam,OBJPROP_SELECTED,true); if(ObjectGetInteger(_Symbol,sparam,OBJPROP_TYPE,0) == OBJ_RECTANGLE) { Print("Selected the object:" + "\n Type: OBJ_RECTANGLE" + "\n ID: " + sparam); objectConfUnit.setWaitingStatus(true) ; objectConfUnit.setConfirmationObjectId(StringToInteger(sparam)); objectConfUnit.setObjectType("OBJ_RECTANGLE"); ChartRedraw(); } } } break; } //--- object moved or anchor point coordinates changed case CHARTEVENT_OBJECT_DRAG: { double _higherEdgePrice = ObjectGetDouble(_Symbol,sparam,OBJPROP_PRICE,1); double _lowerEdgePrice = ObjectGetDouble(_Symbol,sparam,OBJPROP_PRICE,0); datetime _leftEdgeTime = (datetime)ObjectGetInteger(_Symbol,sparam,OBJPROP_TIME,0); datetime _rightEdgeTime = (datetime)ObjectGetInteger(_Symbol,sparam,OBJPROP_TIME,1); if(_higherEdgePrice < _lowerEdgePrice) // this is the case were the user flips the rectangle while adjusting it. { tempObjectAttr.setHigherEdgePrice(_lowerEdgePrice); tempObjectAttr.setLowerEdgePrice(_higherEdgePrice); } else { tempObjectAttr.setHigherEdgePrice(_higherEdgePrice); tempObjectAttr.setLowerEdgePrice(_lowerEdgePrice); } tempObjectAttr.setLeftEdgeTime(_leftEdgeTime); tempObjectAttr.setRightEdgeTime(_rightEdgeTime); tempObjectAttr.setSymbol(); tempObjectAttr.setTimeFrame(getChartTimeFrameInString()); break; } } } //+------------------------------------------------------------------+ //| BookEvent function | //+------------------------------------------------------------------+ void OnBookEvent(const string &symbol) { //--- } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void initLowFractalsFromIndex(int _index,ENUM_TIMEFRAMES timeFramePeriod) { for(int i=_index ; i> rightPeriod ; i--) { bool accessDeniedLows = false ; // variable for dealing with equal consecutive fractals if(isLowFractalByIndex(i,leftPeriod,rightPeriod,timeFramePeriod) == true) { if(lowsContainer.getFreeIndex() > 0) { if(iLow(Symbol(),timeFramePeriod,i) == lowsContainer.getLowFractal(lowsContainer.getFreeIndex()-1).getPrice()) // if low fractal is equal to the previous low fractal { if(lowsContainer.getLowFractal(lowsContainer.getFreeIndex()-1).getDistance() - i < 2*rightPeriod) // if the 2 lows are inside the range of 2 times of right period { accessDeniedLows = true ; } } } if(accessDeniedLows == false) { LowFractal* currentFractal = new LowFractal(iLow(Symbol(),timeFramePeriod,i),leftPeriod,rightPeriod,TimeCurrent(),timeFramePeriod); currentFractal.setDate(iTime(Symbol(),timeFramePeriod,i)); currentFractal.setDistance(i); int NumberOfCandle = i-1; string NumberOfCandleText = IntegerToString(NumberOfCandle); currentFractal.setArrowObjName(NumberOfCandleText); lowsContainer.addLowFractal(currentFractal); } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void initHighFractalsFromIndex(int _index,ENUM_TIMEFRAMES timeFramePeriod) { for(int i=_index ; i> rightPeriod ; i--) { bool accessDeniedHighs = false ; // variable for dealing with equal consecutive fractals if(isHighFractalByIndex(i,leftPeriod,rightPeriod,timeFramePeriod) == true) { if(highsContainer.getFreeIndex() > 0) { if(iHigh(Symbol(),timeFramePeriod,i) == highsContainer.getHighFractal(highsContainer.getFreeIndex()-1).getPrice()) // if high fractal is equal to the previous high fractal { if(highsContainer.getHighFractal(highsContainer.getFreeIndex()-1).getDistance() - i < 2*rightPeriod) // if the 2 highs are inside the range of 2 times of right period { accessDeniedHighs = true ; } } } if(accessDeniedHighs == false) { HighFractal* currentFractal = new HighFractal(iHigh(Symbol(),timeFramePeriod,i),leftPeriod,rightPeriod,TimeCurrent(),timeFramePeriod); currentFractal.setDate(iTime(Symbol(),timeFramePeriod,i)); currentFractal.setDistance(i); int NumberOfCandle = i-1; string NumberOfCandleText = IntegerToString(NumberOfCandle); currentFractal.setArrowObjName(NumberOfCandleText); highsContainer.addHighFractal(currentFractal); } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void handleLowFractals(ENUM_TIMEFRAMES tf) { bool accessDeniedLows = false ; // variable for dealing with equal consecutive fractals if its true, then the current fractal will not be added for(int x=0 ; x 0) { if(iLow(Symbol(),tf,lowFractalIndex) == lowsContainer.getLowFractal(lowsContainer.getFreeIndex()-1).getPrice()) // if low fractal is equal to the previous low fractal { if(lowsContainer.getLowFractal(lowsContainer.getFreeIndex()-1).getDistance() <= 2*rightPeriod) // if the 2 lows are inside the range of 2 times of right period { accessDeniedLows = true ; } } } if(accessDeniedLows == false) { LowFractal* currentFractal = new LowFractal(iLow(Symbol(),tf,lowFractalIndex),leftPeriod,rightPeriod,TimeCurrent(),tf); int NumberOfCandle = Bars(Symbol(),tf); string NumberOfCandleText = IntegerToString(NumberOfCandle); currentFractal.setArrowObjName(NumberOfCandleText); lowsContainer.addLowFractal(currentFractal); Print("added a new low fractal !"); } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void handleHighFractals(ENUM_TIMEFRAMES tf) { bool accessDeniedHighs = false ; // variable for dealing with equal consecutive fractals for(int x=0 ; x 0) { if(iHigh(Symbol(),tf,highFractalIndex) == highsContainer.getHighFractal(highsContainer.getFreeIndex()-1).getPrice()) // if high fractal is equal to the previous high fractal { if(highsContainer.getHighFractal(highsContainer.getFreeIndex()-1).getDistance() <= 2*rightPeriod) // if the 2 highs are inside the range of 2 times of right period { accessDeniedHighs = true ; } } } if(accessDeniedHighs == false) { HighFractal* currentFractal = new HighFractal(iHigh(Symbol(),tf,highFractalIndex),leftPeriod,rightPeriod,TimeCurrent(),tf); int NumberOfCandle = Bars(Symbol(),tf); string NumberOfCandleText = IntegerToString(NumberOfCandle); currentFractal.setArrowObjName(NumberOfCandleText); highsContainer.addHighFractal(currentFractal); Print("added a new high fractal !"); } } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+