2026-03-04 17:09:32 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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 |
|
| | | //+------------------------------------------------------------------+
|
2026-03-15 02:46:38 +02:00 | | | 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];
|
| | |
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
2026-03-04 17:09:32 +02:00 | | | 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) |
|
| | | //+------------------------------------------------------------------+
|
| | |
|
| | | //+------------------------------------------------------------------+
|