402 lines
14 KiB
MQL5
402 lines
14 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| Arjo_Indicators.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 <Mql5Book\Timer.mqh>
| |
#include <Mql5Book\Indicators.mqh>
| |
| |
#include "Chapter1_ChartDebug.mqh"
| |
#include "Barsbydate.mqh"
| |
| |
string location = "DexterrFX - SMC Bible/";
| |
//+------------------------------------------------------------------+
| |
//| Swing wrapper for iCustom "External" |
| |
//| NOTE: We keep arrays NON-series: index 0 = oldest |
| |
//+------------------------------------------------------------------+
| |
datetime FirstOfPreviousMonths(datetime currentTime, int monthsBack) {
| |
MqlDateTime dt;
| |
TimeToStruct(currentTime, dt);
| |
| |
dt.mon -= monthsBack;
| |
while(dt.mon <= 0) {
| |
dt.mon += 12;
| |
dt.year--;
| |
}
| |
| |
dt.day = 1;
| |
dt.hour = 0;
| |
dt.min = 0;
| |
dt.sec = 0;
| |
| |
return StructToTime(dt);
| |
}
| |
| |
// Copies buffer values by DATE RANGE (not count)
| |
// Arrays remain NON-series: high[0] oldest within range, high[copied-1] newest
| |
int CopyBufRange(long indicator_handle, int buffer_index, datetime start_time, datetime stop_time, double &dst[]) {
| |
ArrayResize(dst, 0);
| |
| |
if(indicator_handle == INVALID_HANDLE) return 0;
| |
if(start_time <= 0 || stop_time <= 0 || stop_time <= start_time) return 0;
| |
| |
int copied = CopyBuffer(indicator_handle, buffer_index, start_time, stop_time, dst);
| |
if(copied <= 0) {
| |
Print("CopyBufRange failed. buf=", buffer_index,
| |
" err=", GetLastError(),
| |
" from=", TimeToString(start_time),
| |
" to=", TimeToString(stop_time));
| |
return 0;
| |
}
| |
| |
// DO NOT ArraySetAsSeries(dst,true); (your rule)
| |
return copied;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
struct Fvg {
| |
double top, bottom;
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
class FairValue: public CIndicator {
| |
private:
| |
double bullTop[], bullBottom[], bearTop[], bearBottom[];
| |
datetime fromTime;
| |
datetime toTime;
| |
| |
public:
| |
FairValue();
| |
int Init(string indName, string pSymbol, ENUM_TIMEFRAMES pTimeframe, int InpLeftRightBars = 3, int InpSwingWindow = 1);
| |
| |
bool IsValidValue(Fvg &fvg) {
| |
return ((fvg.top != 0.0 && fvg.top != EMPTY_VALUE) && (fvg.bottom != 0.0 && fvg.bottom != EMPTY_VALUE));
| |
}
| |
| |
void getBullFvg(Fvg &fvg, int barIndex = 0);
| |
void getBearFvg(Fvg &fvg, int barIndex = 0);
| |
};
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
FairValue::FairValue(void) {
| |
fromTime = 0;
| |
toTime = 0;
| |
}
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
int FairValue::Init(string indName, string pSymbol, ENUM_TIMEFRAMES pTimeframe, int InpLeftRightBars = 3, int InpSwingWindow = 1) {
| |
handle = iCustom(pSymbol, pTimeframe, location + indName, InpLeftRightBars, InpSwingWindow);
| |
| |
if(handle == INVALID_HANDLE) {
| |
Print("Error creating Swing Indicator: ", GetLastError());
| |
return -1;
| |
}
| |
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3); // <<--- ONLY 3 MONTHS
| |
| |
return handle;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
void FairValue::getBullFvg(Fvg &fvg, int barIndex = 0) {
| |
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
| |
int copied = CopyBufRange(handle, 1, fromTime, toTime, bullTop);
| |
int copied0 = CopyBufRange(handle, 2, fromTime, toTime, bullBottom);
| |
| |
if(copied <= 0 && copied0 <= 0) return ;
| |
if(barIndex < 0 || (barIndex >= copied && barIndex >= copied0)) return ;
| |
| |
fvg.top = bullTop[barIndex];
| |
fvg.bottom = bullBottom[barIndex];
| |
| |
}
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
| |
void FairValue::getBearFvg(Fvg &fvg, int barIndex = 0) {
| |
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
| |
int copied = CopyBufRange(handle, 5, fromTime, toTime, bearTop);
| |
int copied0 = CopyBufRange(handle, 6, fromTime, toTime, bearBottom);
| |
| |
if(copied <= 0 && copied0 <= 0) return ;
| |
if(barIndex < 0 || (barIndex >= copied && barIndex >= copied0)) return ;
| |
| |
fvg.top = bearTop[barIndex];
| |
fvg.bottom = bearBottom[barIndex];
| |
| |
}
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
class Swing: public CIndicator {
| |
private:
| |
double high[], low[], swings[];
| |
CIntegerArrayList hs;
| |
CIntegerArrayList ls;
| |
CIntegerArrayList zz;
| |
| |
// we only copy last 3 months
| |
datetime fromTime;
| |
datetime toTime;
| |
| |
public:
| |
Swing();
| |
| |
int Init(string indName, string pSymbol, ENUM_TIMEFRAMES pTimeframe, int InpLeftRightBars = 3, int InpSwingWindow = 1);
| |
bool IsValidValue(double v) {
| |
return (v != 0.0 && v != EMPTY_VALUE);
| |
}
| |
// ---------------------------
| |
// SET A: FILTERED (non-empty) — returns the BAR INDEX (within the copied range)
| |
// pShift = 0 -> most recent non-empty swing in range
| |
// ---------------------------
| |
int High(int pShift = 0);
| |
int Low(int pShift = 0);
| |
int ZZ(int pShift = 0);
| |
| |
double HighPrice(int pShift = 0);
| |
double LowPrice(int pShift = 0);
| |
double ZZPrice(int pShift = 0);
| |
| |
// ---------------------------
| |
// SET B: EXACT INDEX — returns raw buffer VALUE at an EXACT bar index (within the copied range)
| |
// barIndex: 0 oldest in range, BarCount-1 newest in range
| |
// ---------------------------
| |
double HighAt(int barIndex);
| |
double LowAt(int barIndex);
| |
double ZZAt(int barIndex);
| |
| |
// Range info (optional but handy)
| |
int BarsCountHigh();
| |
int BarsCountLow();
| |
int BarsCountZZ();
| |
};
| |
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
Swing::Swing(void) {
| |
// IMPORTANT: no series
| |
ArrayResize(high, 0);
| |
ArrayResize(low, 0);
| |
ArrayResize(swings, 0);
| |
| |
fromTime = 0;
| |
toTime = 0;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
int Swing::Init(string indName, string pSymbol, ENUM_TIMEFRAMES pTimeframe, int InpLeftRightBars, int InpSwingWindow) {
| |
handle = iCustom(pSymbol, pTimeframe, location + indName, InpLeftRightBars, InpSwingWindow);
| |
| |
if(handle == INVALID_HANDLE) {
| |
Print("Error creating Swing Indicator: ", GetLastError());
| |
return -1;
| |
}
| |
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3); // <<--- ONLY 3 MONTHS
| |
| |
return handle;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Exact value at exact bar index (within range) |
| |
//+------------------------------------------------------------------+
| |
double Swing::HighAt(int barIndex) {
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
| |
int copied = CopyBufRange(handle, 0, fromTime, toTime, high);
| |
if(copied <= 0) return EMPTY_VALUE;
| |
if(barIndex < 0 || barIndex >= copied) return EMPTY_VALUE;
| |
| |
return high[barIndex];
| |
}
| |
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
double Swing::LowAt(int barIndex) {
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
| |
int copied = CopyBufRange(handle, 1, fromTime, toTime, low);
| |
if(copied <= 0) return EMPTY_VALUE;
| |
if(barIndex < 0 || barIndex >= copied) return EMPTY_VALUE;
| |
| |
return low[barIndex];
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
double Swing::ZZAt(int barIndex) {
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
| |
int copied = CopyBufRange(handle, 4, fromTime, toTime, swings);
| |
if(copied <= 0) return EMPTY_VALUE;
| |
if(barIndex < 0 || barIndex >= copied) return EMPTY_VALUE;
| |
| |
return swings[barIndex];
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
int Swing::BarsCountHigh() {
| |
return ArraySize(high);
| |
}
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
int Swing::BarsCountLow() {
| |
return ArraySize(low);
| |
}
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
int Swing::BarsCountZZ() {
| |
return ArraySize(swings);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| FILTERED (non-empty): return BAR INDEX of pShift-th swing HIGH |
| |
//| pShift=0 -> most recent non-empty in the 3-month window |
| |
//+------------------------------------------------------------------+
| |
int Swing::High(int pShift) {
| |
hs.Clear();
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
int copied = CopyBufRange(handle, 0, fromTime, toTime, high);
| |
if(copied <= 0) return -1;
| |
| |
// scan oldest->newest but we want "most recent" first,
| |
// so collect indexes then read from the end.
| |
for(int i = 0; i < copied; i++)
| |
if(IsValidValue(high[i]))
| |
hs.Add(i);
| |
| |
int count = hs.Size();
| |
if(count == 0) return -1;
| |
| |
int idxFromEnd = count - 1 - pShift;
| |
if(idxFromEnd < 0) idxFromEnd = 0; // fallback to oldest swing in range
| |
return hs.Get(idxFromEnd);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| FILTERED (non-empty): return BAR INDEX of pShift-th swing LOW |
| |
//+------------------------------------------------------------------+
| |
int Swing::Low(int pShift) {
| |
ls.Clear();
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
int copied = CopyBufRange(handle, 1, fromTime, toTime, low);
| |
if(copied <= 0) return -1;
| |
| |
for(int i = 0; i < copied; i++)
| |
if(IsValidValue(low[i]))
| |
ls.Add(i);
| |
| |
int count = ls.Size();
| |
if(count == 0) return -1;
| |
| |
int idxFromEnd = count - 1 - pShift;
| |
if(idxFromEnd < 0) idxFromEnd = 0;
| |
return ls.Get(idxFromEnd);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| FILTERED (non-empty): return BAR INDEX of pShift-th ZZ swing |
| |
//+------------------------------------------------------------------+
| |
int Swing::ZZ(int pShift) {
| |
zz.Clear();
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
int copied = CopyBufRange(handle, 4, fromTime, toTime, swings);
| |
if(copied <= 0) return -1;
| |
| |
for(int i = 0; i < copied; i++)
| |
if(IsValidValue(swings[i]))
| |
zz.Add(i);
| |
| |
int count = zz.Size();
| |
if(count == 0) return -1;
| |
| |
int idxFromEnd = count - 1 - pShift;
| |
if(idxFromEnd < 0) idxFromEnd = 0;
| |
return zz.Get(idxFromEnd);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| FILTERED price by swing order |
| |
//+------------------------------------------------------------------+
| |
double Swing::HighPrice(int pShift) {
| |
int barIndex = High(pShift);
| |
if(barIndex < 0) return EMPTY_VALUE;
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
if(barIndex >= ArraySize(high)) CopyBufRange(handle, 0, fromTime, toTime, high);
| |
if(barIndex < 0 || barIndex >= ArraySize(high)) return EMPTY_VALUE;
| |
return high[barIndex];
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
double Swing::LowPrice(int pShift) {
| |
int barIndex = Low(pShift);
| |
if(barIndex < 0) return EMPTY_VALUE;
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
if(barIndex >= ArraySize(low)) CopyBufRange(handle, 1, fromTime, toTime, low);
| |
if(barIndex < 0 || barIndex >= ArraySize(low)) return EMPTY_VALUE;
| |
return low[barIndex];
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
double Swing::ZZPrice(int pShift) {
| |
int barIndex = ZZ(pShift);
| |
if(barIndex < 0) return EMPTY_VALUE;
| |
toTime = TimeCurrent();
| |
fromTime = FirstOfPreviousMonths(toTime, 3);
| |
if(barIndex >= ArraySize(swings)) CopyBufRange(handle, 4, fromTime, toTime, swings);
| |
if(barIndex < 0 || barIndex >= ArraySize(swings)) return EMPTY_VALUE;
| |
return swings[barIndex];
| |
}
| |
| |
| |
//+------------------------------------------------------------------+
| |
//| Base chapter (fixed constructor name + uses CBarsbydate) |
| |
//+------------------------------------------------------------------+
| |
| |
//+------------------------------------------------------------------+
|