417 lines
No EOL
10 KiB
MQL5
417 lines
No EOL
10 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| 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"
| |
#property strict
| |
| |
#include "Inducators.mqh"
| |
| |
class CAbstractChapter {
| |
protected:
| |
ENUM_TIMEFRAMES timeframe;
| |
string symbol;
| |
| |
CBarsbydate bar;
| |
Swing swing;
| |
CNewBar newBar;
| |
CChartDebug debug;
| |
| |
protected:
| |
//========================
| |
// Life cycle
| |
//========================
| |
bool OnNewBar() {
| |
return newBar.CheckNewBar(symbol, timeframe);
| |
}
| |
| |
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
| |
);
| |
}
| |
| |
public:
| |
CAbstractChapter(void) {
| |
timeframe = PERIOD_CURRENT;
| |
symbol = _Symbol;
| |
}
| |
| |
virtual ~CAbstractChapter() {}
| |
| |
virtual int OnInit(string s, ENUM_TIMEFRAMES tf_, int lr = 3, int look = 1) {
| |
timeframe = tf_;
| |
symbol = s;
| |
| |
debug.Init(0, __FILE__, true);
| |
| |
if(swing.Init("Fractal", s, tf_, lr, look) == -1) {
| |
Print("Error creating Swing Indicator: ", GetLastError());
| |
return INIT_FAILED;
| |
}
| |
| |
return INIT_SUCCEEDED;
| |
}
| |
| |
virtual void OnCalculate() {}
| |
};
| |
//+------------------------------------------------------------------+ |