2026-03-04 17:09:32 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| AbstractChapter.mqh |
|
| | | //| Copyright 2025, MetaQuotes Ltd. |
|
| | | //| https://www.mql5.com |
|
| | | //+------------------------------------------------------------------+
|
| | | #property copyright "Copyright 2025, MetaQuotes Ltd."
|
| | | #property link "https://www.mql5.com"
|
| | | #property version "1.00"
|
2026-03-15 02:46:38 +02:00 | | | #property strict
|
2026-03-04 17:09:32 +02:00 | | |
|
| | | #include "Inducators.mqh"
|
| | |
|
| | | class CAbstractChapter {
|
| | | protected:
|
| | | ENUM_TIMEFRAMES timeframe;
|
| | | string symbol;
|
| | |
|
2026-03-15 02:46:38 +02:00 | | | CBarsbydate bar;
|
2026-03-04 17:09:32 +02:00 | | | Swing swing;
|
| | | CNewBar newBar;
|
| | | CChartDebug debug;
|
| | |
|
2026-03-15 02:46:38 +02:00 | | | protected:
|
| | | //========================
|
| | | // Life cycle
|
| | | //========================
|
2026-03-04 17:09:32 +02:00 | | | bool OnNewBar() {
|
| | | return newBar.CheckNewBar(symbol, timeframe);
|
| | | }
|
| | |
|
2026-03-15 02:46:38 +02:00 | | | void UpdateBars(int monthsBack = 3) {
|
| | | bar.UpdateFromMonthsBack(symbol, timeframe, monthsBack);
|
| | | }
|
| | |
|
| | | //========================
|
| | | // Bar info
|
| | | //========================
|
| | | int Bars() {
|
| | | return bar.Size();
|
| | | }
|
| | |
|
| | | bool HasBars(int count) {
|
| | | return bar.Size() >= count;
|
| | | }
|
| | |
|
| | | bool IsValidIndex(int index) {
|
| | | return (index >= 0 && index < bar.Size());
|
| | | }
|
| | |
|
| | | datetime TimeAt(int index) {
|
| | | return bar.Time(index);
|
| | | }
|
| | |
|
| | | double OpenAt(int index) {
|
| | | return bar.Open(index);
|
| | | }
|
| | |
|
| | | double HighAt(int index) {
|
| | | return bar.High(index);
|
| | | }
|
| | |
|
| | | double LowAt(int index) {
|
| | | return bar.Low(index);
|
| | | }
|
| | |
|
| | | double CloseAt(int index) {
|
| | | return bar.Close(index);
|
| | | }
|
| | |
|
| | | double RangeAt(int index) {
|
| | | return bar.High(index) - bar.Low(index);
|
| | | }
|
| | |
|
| | | double BodyTop(int index) {
|
| | | return MathMax(bar.Open(index), bar.Close(index));
|
| | | }
|
| | |
|
| | | double BodyBottom(int index) {
|
| | | return MathMin(bar.Open(index), bar.Close(index));
|
| | | }
|
| | |
|
| | | double BodySize(int index) {
|
| | | return MathAbs(bar.Close(index) - bar.Open(index));
|
| | | }
|
| | |
|
| | | double UpperWick(int index) {
|
| | | return bar.High(index) - BodyTop(index);
|
| | | }
|
| | |
|
| | | double LowerWick(int index) {
|
| | | return BodyBottom(index) - bar.Low(index);
|
| | | }
|
| | |
|
| | | double MidPrice(double a, double b) {
|
| | | return (a + b) * 0.5;
|
| | | }
|
| | |
|
| | | double CandleMid(int index) {
|
| | | return MidPrice(bar.High(index), bar.Low(index));
|
| | | }
|
| | |
|
| | | //========================
|
| | | // Price helpers
|
| | | //========================
|
| | | double PointValue() {
|
| | | return bar.Point();
|
| | | }
|
| | |
|
| | | double PointsToPrice(int points) {
|
| | | return points * PointValue();
|
| | | }
|
| | |
|
| | | bool NearPrice(double a, double b, int tolerancePoints) {
|
| | | return MathAbs(a - b) <= PointsToPrice(tolerancePoints);
|
| | | }
|
| | |
|
| | | //========================
|
| | | // Swing helpers
|
| | | //========================
|
| | | bool IsValidSwingValue(double value) {
|
| | | return swing.IsValidValue(value);
|
| | | }
|
| | |
|
| | | bool IsSwingHigh(int index) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | | return swing.IsValidValue(swing.HighAt(index));
|
| | | }
|
| | |
|
| | | bool IsSwingLow(int index) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | | return swing.IsValidValue(swing.LowAt(index));
|
| | | }
|
| | |
|
| | | int PrevSwingHighIndex(int anchor, int n = 1) {
|
| | | int found = -1;
|
| | |
|
| | | for(int i = anchor; i >= 0; i--) {
|
| | | if(IsSwingHigh(i)) {
|
| | | found++;
|
| | | if(found == n)
|
| | | return i;
|
| | | }
|
| | | }
|
| | |
|
| | | return -1;
|
| | | }
|
| | |
|
| | | int PrevSwingLowIndex(int anchor, int n = 1) {
|
| | | int found = -1;
|
| | |
|
| | | for(int i = anchor; i >= 0; i--) {
|
| | | if(IsSwingLow(i)) {
|
| | | found++;
|
| | | if(found == n)
|
| | | return i;
|
| | | }
|
| | | }
|
| | |
|
| | | return -1;
|
| | | }
|
| | |
|
| | | int NextSwingHighIndex(int anchor, int n = 1) {
|
| | | int found = 0;
|
| | |
|
| | | for(int i = anchor; i < bar.Size(); i++) {
|
| | | if(IsSwingHigh(i)) {
|
| | | found++;
|
| | | if(found == n)
|
| | | return i;
|
| | | }
|
| | | }
|
| | |
|
| | | return -1;
|
| | | }
|
| | |
|
| | | int NextSwingLowIndex(int anchor, int n = 1) {
|
| | | int found = 0;
|
| | |
|
| | | for(int i = anchor; i < bar.Size(); i++) {
|
| | | if(IsSwingLow(i)) {
|
| | | found++;
|
| | | if(found == n)
|
| | | return i;
|
| | | }
|
| | | }
|
| | |
|
| | | return -1;
|
| | | }
|
| | |
|
| | | //========================
|
| | | // Break helpers
|
| | | //========================
|
| | | bool BreakAboveHigh(int refIndex, int barIndex) {
|
| | | if(!IsValidIndex(refIndex) || !IsValidIndex(barIndex)) return false;
|
| | | return bar.Close(barIndex) > bar.High(refIndex);
|
| | | }
|
| | |
|
| | | bool BreakBelowLow(int refIndex, int barIndex) {
|
| | | if(!IsValidIndex(refIndex) || !IsValidIndex(barIndex)) return false;
|
| | | return bar.Close(barIndex) < bar.Low(refIndex);
|
| | | }
|
| | |
|
| | | bool WickAboveHigh(int refIndex, int barIndex) {
|
| | | if(!IsValidIndex(refIndex) || !IsValidIndex(barIndex)) return false;
|
| | | return bar.High(barIndex) > bar.High(refIndex);
|
| | | }
|
| | |
|
| | | bool WickBelowLow(int refIndex, int barIndex) {
|
| | | if(!IsValidIndex(refIndex) || !IsValidIndex(barIndex)) return false;
|
| | | return bar.Low(barIndex) < bar.Low(refIndex);
|
| | | }
|
| | |
|
| | | //========================
|
| | | // Candle state
|
| | | //========================
|
| | | bool IsBullishCandle(int index) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | | return bar.Close(index) > bar.Open(index);
|
| | | }
|
| | |
|
| | | bool IsBearishCandle(int index) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | | return bar.Close(index) < bar.Open(index);
|
| | | }
|
| | |
|
| | | bool IsDojiCandle(int index, double maxBodyToRange = 0.10) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | |
|
| | | double range = RangeAt(index);
|
| | | if(range <= 0.0) return false;
|
| | |
|
| | | return (BodySize(index) / range) <= maxBodyToRange;
|
| | | }
|
| | |
|
| | | bool IsSellToBuyCandle(int index, double wickToBodyRatio = 1.2) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | |
|
| | | double body = BodySize(index);
|
| | | if(body <= 0.0) body = _Point;
|
| | |
|
| | | return LowerWick(index) >= body * wickToBodyRatio;
|
| | | }
|
| | |
|
| | | bool IsBuyToSellCandle(int index, double wickToBodyRatio = 1.2) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | |
|
| | | double body = BodySize(index);
|
| | | if(body <= 0.0) body = _Point;
|
| | |
|
| | | return UpperWick(index) >= body * wickToBodyRatio;
|
| | | }
|
| | |
|
| | | //========================
|
| | | // Extremes in range
|
| | | //========================
|
| | | int FindLowestIndex(int fromIndex, int toIndex, bool inclusive = false) {
|
| | | if(fromIndex > toIndex) {
|
| | | int temp = fromIndex;
|
| | | fromIndex = toIndex;
|
| | | toIndex = temp;
|
| | | }
|
| | |
|
| | | int start = (inclusive ? fromIndex : fromIndex + 1);
|
| | | int end = (inclusive ? toIndex : toIndex - 1);
|
| | |
|
| | | if(start > end) return -1;
|
| | |
|
| | | int lowestIndex = -1;
|
| | |
|
| | | for(int i = start; i <= end; i++) {
|
| | | if(!IsValidIndex(i)) continue;
|
| | |
|
| | | if(lowestIndex == -1 || bar.Low(i) < bar.Low(lowestIndex))
|
| | | lowestIndex = i;
|
| | | }
|
| | |
|
| | | return lowestIndex;
|
| | | }
|
| | |
|
| | | int FindHighestIndex(int fromIndex, int toIndex, bool inclusive = false) {
|
| | | if(fromIndex > toIndex) {
|
| | | int temp = fromIndex;
|
| | | fromIndex = toIndex;
|
| | | toIndex = temp;
|
| | | }
|
| | |
|
| | | int start = (inclusive ? fromIndex : fromIndex + 1);
|
| | | int end = (inclusive ? toIndex : toIndex - 1);
|
| | |
|
| | | if(start > end) return -1;
|
| | |
|
| | | int highestIndex = -1;
|
| | |
|
| | | for(int i = start; i <= end; i++) {
|
| | | if(!IsValidIndex(i)) continue;
|
| | |
|
| | | if(highestIndex == -1 || bar.High(i) > bar.High(highestIndex))
|
| | | highestIndex = i;
|
| | | }
|
| | |
|
| | | return highestIndex;
|
| | | }
|
| | |
|
| | | //========================
|
| | | // Zone helpers for Chapter 4
|
| | | //========================
|
| | | double ZoneMid(double top, double bottom) {
|
| | | return (top + bottom) * 0.5;
|
| | | }
|
| | |
|
| | | double ZoneHeight(double top, double bottom) {
|
| | | return MathAbs(top - bottom);
|
| | | }
|
| | |
|
| | | bool PriceTouchesZone(int index, double top, double bottom) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | |
|
| | | double zTop = MathMax(top, bottom);
|
| | | double zBot = MathMin(top, bottom);
|
| | |
|
| | | return bar.Low(index) <= zTop && bar.High(index) >= zBot;
|
| | | }
|
| | |
|
| | | bool PriceClosesInsideZone(int index, double top, double bottom) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | |
|
| | | double zTop = MathMax(top, bottom);
|
| | | double zBot = MathMin(top, bottom);
|
| | | double c = bar.Close(index);
|
| | |
|
| | | return c <= zTop && c >= zBot;
|
| | | }
|
| | |
|
| | | bool PriceClosesBeyondSupply(int index, double top) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | | return bar.Close(index) > top;
|
| | | }
|
| | |
|
| | | bool PriceClosesBeyondDemand(int index, double bottom) {
|
| | | if(!IsValidIndex(index)) return false;
|
| | | return bar.Close(index) < bottom;
|
| | | }
|
| | |
|
| | | //========================
|
| | | // Drawing helpers
|
| | | //========================
|
| | | void DrawLevel(string key,
|
| | | int fromIndex,
|
| | | int toIndex,
|
| | | double price,
|
| | | string text,
|
| | | color col = clrChartreuse,
|
| | | int style = 1) {
|
| | | if(!IsValidIndex(fromIndex) || !IsValidIndex(toIndex))
|
| | | return;
|
| | |
|
| | | debug.LineWithCenteredText(
|
| | | key,
|
| | | bar.Time(fromIndex), price,
|
| | | bar.Time(toIndex), price,
|
| | | text,
|
| | | style,
|
| | | col
|
| | | );
|
| | | }
|
| | |
|
| | | void DrawZone(string key,
|
| | | int leftIndex,
|
| | | int rightIndex,
|
| | | double top,
|
| | | double bottom,
|
| | | string text,
|
| | | color col,
|
| | | double insetFrac = 0.06,
|
| | | int fontSize = 6,
|
| | | bool drawOnTop = false) {
|
| | | if(!IsValidIndex(leftIndex) || !IsValidIndex(rightIndex))
|
| | | return;
|
| | |
|
| | | debug.RectWithCenteredText(
|
| | | symbol,
|
| | | key,
|
| | | bar.Time(leftIndex), top,
|
| | | bar.Time(rightIndex), bottom,
|
| | | text,
|
| | | col,
|
| | | insetFrac,
|
| | | fontSize,
|
| | | drawOnTop
|
| | | );
|
| | | }
|
| | |
|
2026-03-04 17:09:32 +02:00 | | | public:
|
2026-03-15 02:46:38 +02:00 | | | CAbstractChapter(void) {
|
| | | timeframe = PERIOD_CURRENT;
|
| | | symbol = _Symbol;
|
| | | }
|
| | |
|
| | | virtual ~CAbstractChapter() {}
|
2026-03-04 17:09:32 +02:00 | | |
|
2026-03-15 02:46:38 +02:00 | | | virtual int OnInit(string s, ENUM_TIMEFRAMES tf_, int lr = 3, int look = 1) {
|
2026-03-04 17:09:32 +02:00 | | | timeframe = tf_;
|
| | | symbol = s;
|
2026-03-15 02:46:38 +02:00 | | |
|
2026-03-04 17:09:32 +02:00 | | | debug.Init(0, __FILE__, true);
|
| | |
|
2026-03-15 02:46:38 +02:00 | | | if(swing.Init("Fractal", s, tf_, lr, look) == -1) {
|
2026-03-04 17:09:32 +02:00 | | | Print("Error creating Swing Indicator: ", GetLastError());
|
| | | return INIT_FAILED;
|
| | | }
|
| | |
|
| | | return INIT_SUCCEEDED;
|
| | | }
|
| | |
|
2026-03-15 02:46:38 +02:00 | | | virtual void OnCalculate() {}
|
2026-03-04 17:09:32 +02:00 | | | };
|
2026-03-15 02:46:38 +02:00 | | | //+------------------------------------------------------------------+
|