//+------------------------------------------------------------------+ //| 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 #include #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) | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+