Смотри, как бесплатно скачать роботов
Ищи нас в Twitter!
Ставь лайки и следи за новостями
Интересный скрипт?
Поставь на него ссылку - пусть другие тоже оценят
Понравился скрипт?
Оцени его работу в терминале MetaTrader 5
Просмотров:
22
Рейтинг:
(3)
Опубликован:
MQL5 Фриланс Нужен робот или индикатор на основе этого кода? Закажите его на бирже фрилансеров Перейти на биржу

Разрывы справедливой стоимости при инверсии: обнаруживает разрывы справедливой стоимости, отмечает их, когда тело свечи закрывается сквозь эту зону (инверсия), а затем сигнализирует об отскоке от инвертированной зоны. Логика закрытия бара, без перерисовки. Средняя линия, удаление заполненной зоны, настраиваемые цвета и оповещения (всплывающее окно, звук, push-уведомление, электронная почта, Telegram).


#property copyright   "Inversion Fair Value Gaps"
#property version     "1.00"
#property description "Inversion fair value gaps with signals. Bar-close, no repaint."
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
#property indicator_type1   DRAW_NONE
#property indicator_label1  "Bounce Up"
#property indicator_type2   DRAW_NONE
#property indicator_label2  "Bounce Down"

enum ENUM_IFVG_BOUNCE
{
   BOUNCE_CLOSE = 0,   // Close
   BOUNCE_WICK  = 1    // Wick
};

enum ENUM_IFVG_LABEL
{
   LABEL_SMALL  = 0,   // Small
   LABEL_MEDIUM = 1,   // Medium
   LABEL_LARGE  = 2    // Large
};

//---------------------------------------------------------------- inputs (engine first: the dump script passes this prefix; no "input group": it shifts iCustom)
// --- 1. Parameters
input int              InpBoxCount      = 5;            // Show Last
input ENUM_IFVG_BOUNCE InpBounce        = BOUNCE_CLOSE; // Detect Bounce
input int              InpHistoryBars   = 20000;        // History bars (loaded and computed)
// --- 2. Filter
input int              InpAtrLength     = 200;          // ATR Length
input double           InpAtrMultiplier = 0.25;         // ATR Multiplier
input bool             InpRemoveFilled  = true;         // Remove Filled iFVG
// --- 3. Parity
input bool             InpWriteParity   = false;        // Write parity CSV files (MQL5\Files)
// --- 4. Style
input bool             InpShowBull      = true;         // Show Bull IFVG
input bool             InpShowBear      = true;         // Show Bear IFVG
input bool             InpShowMidLine   = true;         // Show MidLine
input bool             InpShowSignals   = true;         // Show Bounce Signals
input int              InpExtend        = 50;           // Extend Active IFVG (bars, 0-100)
input ENUM_IFVG_LABEL  InpLabelSize     = LABEL_MEDIUM; // Signal Label Size
input color            InpBullColor     = clrDarkGreen; // Bull Colour
input color            InpBearColor     = clrMaroon;    // Bear Colour
input color            InpLineColor     = clrGray;      // Mid line colour
input int              InpOpacity       = 40;           // Opacity (5-100, blended with the chart background)
// --- 5. Alerts
input bool             InpAlertBounce   = true;         // Alert on bounce
input bool             InpAlertInversion = false;       // Alert on inversion
input bool             InpUsePopup      = true;         // Popup (Alert)
input bool             InpUseSound      = true;         // Sound
input string           InpSoundFile     = "alert.wav";  // Sound file (terminal Sounds folder)
input bool             InpUsePush       = false;        // Push notification (MetaQuotes ID)
input bool             InpUseMail       = false;        // E-mail (terminal mail settings)
input string           InpTelegramToken = "";           // Telegram: bot token (empty = off, allow api.telegram.org in WebRequest)
input string           InpTelegramChat  = "";           // Telegram: chat id
input bool             InpLogEvents     = true;         // Log every event (Experts tab)

//---------------------------------------------------------------- engine state (single instance per chart)
#define EV_CREATED  0
#define EV_INVERTED 1
#define EV_UP       2
#define EV_DOWN     3
#define EV_FILLED   4

struct Zone
{
   int    left;
   double top;
   int    right;
   double bot;
   double mid;
   int    dir;     // +1 support, -1 resistance (flipped when the gap inverts)
   int    state;   // 0 = just inverted, 1 = active inversion, -1 = filled and kept
   int    xval;    // bar where the gap became an inversion
};

struct Lab { int zone; int x; double y; int dir; };

int      gBuffer = 500;       //caps each list at 500 entries
double   gO[], gH[], gL[], gC[], gCTop[], gCBot[];
datetime gT[];
int      gCount = 0;          // closed bars fed
int      gFirst = 0;          // chart index of engine bar 0
double   gAtr = 0, gPrevClose = 0;
bool     gLive = false;
int      gEvents = 0;

Zone gZ[];                    // every zone ever created; the lists below hold indexes into it
int  gZn = 0;
int  gBullFvg[], gBearFvg[];  // open gaps
int  gBullInv[], gBearInv[];  // inverted zones, by the list of their origin
Lab  gLabs[];

double gBounceSignal = 0, gIsIfvg = 0;

double gUpBuf[], gDnBuf[];
string gDrawn[];
int    gBarsFile = INVALID_HANDLE, gEvFile = INVALID_HANDLE;

//---------------------------------------------------------------- helpers
double O(int k) { return gO[gCount - 1 - k]; }
double H(int k) { return gH[gCount - 1 - k]; }
double L(int k) { return gL[gCount - 1 - k]; }
double C(int k) { return gC[gCount - 1 - k]; }
double CTop(int k) { return gCTop[gCount - 1 - k]; }
double CBot(int k) { return gCBot[gCount - 1 - k]; }

string TimeStr(datetime t)
{
   MqlDateTime dt; TimeToStruct(t, dt);
   return StringFormat("%04d-%02d-%02d %02d:%02d:%02d", dt.year, dt.mon, dt.day, dt.hour, dt.min, dt.sec);
}

string Px(double p) { return DoubleToString(p, _Digits); }

void ListInsert0(int &a[], int v)
{
   int n = ArraySize(a);
   ArrayResize(a, n + 1, 256);
   for(int i = n; i > 0; i--) a[i] = a[i - 1];
   a[0] = v;
}

void ListRemoveAt(int &a[], int i)
{
   int n = ArraySize(a);
   for(int k = i; k < n - 1; k++) a[k] = a[k + 1];
   ArrayResize(a, n - 1, 256);
}

void ResetEngine()
{
   gCount = 0; gAtr = 0; gPrevClose = 0; gEvents = 0; gZn = 0;
   gBounceSignal = 0; gIsIfvg = 0;
   ArrayResize(gO, 0); ArrayResize(gH, 0); ArrayResize(gL, 0); ArrayResize(gC, 0);
   ArrayResize(gCTop, 0); ArrayResize(gCBot, 0); ArrayResize(gT, 0);
   ArrayResize(gZ, 0); ArrayResize(gLabs, 0);
   ArrayResize(gBullFvg, 0); ArrayResize(gBearFvg, 0); ArrayResize(gBullInv, 0); ArrayResize(gBearInv, 0);
   ObjectsDeleteAll(0, "IFVG_");
   ArrayResize(gDrawn, 0);
}

int NewZone(int left, double bot, int right, double top, double mid, int dir)
{
   ArrayResize(gZ, gZn + 1, 1024);
   gZ[gZn].left = left; gZ[gZn].top = top; gZ[gZn].right = right; gZ[gZn].bot = bot; gZ[gZn].mid = mid;
   gZ[gZn].dir = dir; gZ[gZn].state = 0; gZ[gZn].xval = INT_MIN;
   return gZn++;
}

void AddLab(int zone, int x, double y, int dir)
{
   int k = ArraySize(gLabs);
   ArrayResize(gLabs, k + 1, 1024);
   gLabs[k].zone = zone; gLabs[k].x = x; gLabs[k].y = y; gLabs[k].dir = dir;
}

//---------------------------------------------------------------- events
string Token(int type)
{
   switch(type)
   {
      case EV_CREATED:  return "G";
      case EV_INVERTED: return "I";
      case EV_UP:       return "U";
      case EV_DOWN:     return "D";
      default:          return "F";
   }
}

string Describe(int type, int n, int z)
{
   string when = TimeStr(gT[n]);
   string span = Px(gZ[z].bot) + " - " + Px(gZ[z].top);
   switch(type)
   {
      case EV_CREATED:  return when + " fair value gap " + span;
      case EV_INVERTED: return when + " INVERSION: the zone " + span + " becomes " + (gZ[z].dir == 1 ? "resistance" : "support");
      case EV_UP:       return when + " BULLISH BOUNCE off " + span + " (close " + Px(gC[n]) + ")";
      case EV_DOWN:     return when + " BEARISH BOUNCE off " + span + " (close " + Px(gC[n]) + ")";
      default:          return when + " zone " + span + " filled";
   }
}

bool Wanted(int type)
{
   if(type == EV_UP || type == EV_DOWN) return InpAlertBounce;
   if(type == EV_INVERTED) return InpAlertInversion;
   return false;
}

string JsonEscape(string s)
{
   string r = s;
   StringReplace(r, "\\", "\\\\");
   StringReplace(r, "\"", "\\\"");
   StringReplace(r, "\n", "\\n");
   StringReplace(r, "\r", "");
   StringReplace(r, "\t", "\\t");
   return r;
}

void SendTelegram(string text)
{
   string url = "https://api.telegram.org/bot" + InpTelegramToken + "/sendMessage";
   string body = "{\"chat_id\":\"" + JsonEscape(InpTelegramChat) + "\",\"text\":\"" + JsonEscape(text) + "\"}";
   char data[]; char result[]; string headers;
   int len = StringToCharArray(body, data, 0, WHOLE_ARRAY, CP_UTF8) - 1;
   if(len > 0) ArrayResize(data, len);
   int code = WebRequest("POST", url, "Content-Type: application/json\r\n", 5000, data, result, headers);
   if(code != 200) Print("[IFVG] Telegram KO : ", code, " ", GetLastError());
}

void Notify(int type, string line)
{
   if(InpLogEvents) Print("[IFVG] ", line);
   if(!Wanted(type) || MQLInfoInteger(MQL_TESTER) != 0) return;
   string title = "IFVG " + _Symbol + " : " + (type == EV_INVERTED ? "inversion" : type == EV_UP ? "BOUNCE ▲" : "BOUNCE ▼");
   if(InpUsePopup) Alert(title, " | ", line);
   if(InpUseSound) PlaySound(InpSoundFile);
   if(InpUsePush) SendNotification(title + " | " + line);
   if(InpUseMail) SendMail(title, line);
   if(StringLen(InpTelegramToken) > 0 && StringLen(InpTelegramChat) > 0) SendTelegram(title + "\n" + line);
}

void Emit(int type, int n, int z)
{
   gEvents++;
   if(gEvFile != INVALID_HANDLE)
      FileWrite(gEvFile, IntegerToString(n) + "," + TimeStr(gT[n]) + "," + Token(type) + "," + DoubleToString(gZ[z].top, 8) + "," + DoubleToString(gZ[z].bot, 8));
   if(gLive) Notify(type, Describe(type, n, z));
   else if(InpLogEvents && (type == EV_UP || type == EV_DOWN)) Print("[IFVG] ", Describe(type, n, z));
   int chart = gFirst + n;
   if(type == EV_UP && chart < ArraySize(gUpBuf)) gUpBuf[chart] = gZ[z].bot;
   if(type == EV_DOWN && chart < ArraySize(gDnBuf)) gDnBuf[chart] = gZ[z].top;
}

//---------------------------------------------------------------- engine (IfvgEngine.cs)
// NinjaTrader's ATR: seeded with the first range, then a running mean over min(bar+1, period)
void UpdateAtr(double high, double low, double close, int n)
{
   if(n == 0) { gAtr = high - low; gPrevClose = close; return; }
   double tr = MathMax(high - low, MathMax(MathAbs(high - gPrevClose), MathAbs(low - gPrevClose)));
   int p = MathMin(n + 1, MathMax(1, InpAtrLength));
   gAtr = ((p - 1) * gAtr + tr) / p;
   gPrevClose = close;
}

// a gap whose body is crossed becomes an inversion and changes list
void FvgManage(int &source[], int &inv[], int n)
{
   if(ArraySize(source) >= gBuffer) ListRemoveAt(source, ArraySize(source) - 1);
   for(int i = ArraySize(source) - 1; i >= 0; i--)
   {
      int g = source[i];
      if(gZ[g].dir == 1 && CBot(0) < gZ[g].bot)
      {
         gZ[g].xval = n;
         ListInsert0(inv, g); ListRemoveAt(source, i);
         gIsIfvg = -1;
         Emit(EV_INVERTED, n, g);
      }
      else if(gZ[g].dir == -1 && CTop(0) > gZ[g].top)
      {
         gZ[g].xval = n;
         ListInsert0(inv, g); ListRemoveAt(source, i);
         gIsIfvg = 1;
         Emit(EV_INVERTED, n, g);
      }
   }
}

// flip the polarity on the first pass, then look for bounces and for the fill that kills the zone
void InvManage(int &ary[], int n)
{
   if(ArraySize(ary) >= gBuffer) ListRemoveAt(ary, ArraySize(ary) - 1);
   for(int i = ArraySize(ary) - 1; i >= 0; i--)
   {
      int z = ary[i];
      double top = gZ[z].top, bot = gZ[z].bot;
      int dirBefore = gZ[z].dir, state = gZ[z].state;
      if(state == 0 && dirBefore == 1) { gZ[z].state = 1; gZ[z].dir = -1; }
      else if(state == 0 && dirBefore == -1) { gZ[z].state = 1; gZ[z].dir = 1; }
      int prevDir = dirBefore;                 // keeps the direction as it was before the flip
      int dir = gZ[z].dir; state = gZ[z].state; // and re-reads the current one
      if(state >= 1) gZ[z].right = n;

      double refPrice = InpBounce == BOUNCE_WICK ? H(0) : C(1);
      double refPriceLow = InpBounce == BOUNCE_WICK ? L(0) : C(1);
      if(dir == -1 && prevDir == -1 && state == 1 && C(0) < bot && refPrice >= bot && refPrice < top)
      {
         AddLab(z, n, top, -1);
         gBounceSignal = -1;
         Emit(EV_DOWN, n, z);
      }
      else if(dir == 1 && prevDir == 1 && state == 1 && C(0) > top && refPriceLow <= top && refPriceLow > bot)
      {
         AddLab(z, n, bot, 1);
         gBounceSignal = 1;
         Emit(EV_UP, n, z);
      }

      if(state >= 1 && ((gZ[z].dir == -1 && CTop(0) > top && C(0) > O(0)) || (gZ[z].dir == 1 && CBot(0) < bot && O(0) > C(0))))
      {
         Emit(EV_FILLED, n, z);
         if(!InpRemoveFilled) { gZ[z].state = -1; gZ[z].right = n; }
         else ListRemoveAt(ary, i);
      }
   }
}

void FeedBar(datetime t, double o, double h, double l, double c)
{
   int n = gCount;
   ArrayResize(gO, n + 1, 4096); ArrayResize(gH, n + 1, 4096); ArrayResize(gL, n + 1, 4096); ArrayResize(gC, n + 1, 4096);
   ArrayResize(gCTop, n + 1, 4096); ArrayResize(gCBot, n + 1, 4096); ArrayResize(gT, n + 1, 4096);
   gO[n] = o; gH[n] = h; gL[n] = l; gC[n] = c; gCTop[n] = MathMax(o, c); gCBot[n] = MathMin(o, c); gT[n] = t;
   gCount = n + 1;
   if(gBarsFile != INVALID_HANDLE)
      FileWrite(gBarsFile, IntegerToString(n) + "," + TimeStr(t) + "," + DoubleToString(o, _Digits) + "," + DoubleToString(h, _Digits) + "," + DoubleToString(l, _Digits) + "," + DoubleToString(c, _Digits));

   UpdateAtr(h, l, c, n);
   gBounceSignal = 0;
   if(n < 3) return;

   double filter = gAtr * InpAtrMultiplier;
   gIsIfvg = 0;

   bool bullGap = L(0) > H(2) && C(1) > H(2);
   bool bearGap = H(0) < L(2) && C(1) < L(2);

   if(bullGap && MathAbs(L(0) - H(2)) > filter)
   {
      int g = NewZone(n - 1, MathMin(L(0), H(2)), n, MathMax(L(0), H(2)), (L(0) + H(2)) / 2.0, 1);
      ListInsert0(gBullFvg, g);
      Emit(EV_CREATED, n, g);
   }
   if(bearGap && MathAbs(L(2) - H(0)) > filter)
   {
      int g = NewZone(n - 1, MathMin(L(2), H(0)), n, MathMax(L(2), H(0)), (H(0) + L(2)) / 2.0, -1);
      ListInsert0(gBearFvg, g);
      Emit(EV_CREATED, n, g);
   }

   FvgManage(gBullFvg, gBullInv, n);
   FvgManage(gBearFvg, gBearInv, n);
   InvManage(gBullInv, n);
   InvManage(gBearInv, n);
}

//---------------------------------------------------------------- drawing 
// engine index -> time; past the last closed bar the time is extrapolated one period per bar, like a bar index
datetime XT(int x)
{
   int last = gCount - 1;
   if(x <= last) return gT[MathMax(0, x)];
   return (datetime)((long)gT[last] + (long)(x - last) * PeriodSeconds(_Period));
}

color Blend(color c, int alpha)
{
   long bg = ChartGetInteger(0, CHART_COLOR_BACKGROUND);
   int br = (int)(bg & 0xFF), bgg = (int)((bg >> 8) & 0xFF), bb = (int)((bg >> 16) & 0xFF);
   int cr = (int)(c & 0xFF), cg = (int)((c >> 8) & 0xFF), cb = (int)((c >> 16) & 0xFF);
   int r = br + (cr - br) * alpha / 255, g = bgg + (cg - bgg) * alpha / 255, b = bb + (cb - bb) * alpha / 255;
   return (color)((b << 16) | (g << 8) | r);
}

void Mark(string name, string &live[])
{
   int k = ArraySize(live);
   ArrayResize(live, k + 1, 64);
   live[k] = name;
   for(int i = 0; i < ArraySize(gDrawn); i++) if(gDrawn[i] == name) return;
   k = ArraySize(gDrawn);
   ArrayResize(gDrawn, k + 1, 64);
   gDrawn[k] = name;
}

void Rect(string name, int x1, double y1, int x2, double y2, color c, string &live[])
{
   if(y1 == 0 || y2 == 0) return;
   if(x2 <= x1) x2 = x1 + 1;
   datetime t1 = XT(x1), t2 = XT(x2);
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, y1, t2, y2);
      ObjectSetInteger(0, name, OBJPROP_FILL, true);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   }
   else
   {
      ObjectMove(0, name, 0, t1, y1);
      ObjectMove(0, name, 1, t2, y2);
   }
   ObjectSetInteger(0, name, OBJPROP_COLOR, Blend(c, MathMax(10, InpOpacity * 255 / 100)));
   Mark(name, live);
}

void Seg(string name, int x1, double y, int x2, color c, string &live[])
{
   if(y == 0) return;
   if(x2 <= x1) x2 = x1 + 1;
   datetime t1 = XT(x1), t2 = XT(x2);
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_TREND, 0, t1, y, t2, y);
      ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   }
   else
   {
      ObjectMove(0, name, 0, t1, y);
      ObjectMove(0, name, 1, t2, y);
   }
   ObjectSetInteger(0, name, OBJPROP_COLOR, Blend(c, 200));
   Mark(name, live);
}

void Arrow(string name, int x, double y, int dir, string &live[])
{
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_TEXT, 0, XT(x), y);
      ObjectSetString(0, name, OBJPROP_FONT, "Arial");
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   }
   else ObjectMove(0, name, 0, XT(x), y);
   ObjectSetString(0, name, OBJPROP_TEXT, dir == 1 ? "▲" : "▼");
   ObjectSetInteger(0, name, OBJPROP_COLOR, dir == 1 ? InpBullColor : InpBearColor);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, InpLabelSize == LABEL_LARGE ? 16 : InpLabelSize == LABEL_MEDIUM ? 12 : 9);
   // below the zone for a bullish bounce, above it for a bearish one
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, dir == 1 ? ANCHOR_UPPER : ANCHOR_LOWER);
   Mark(name, live);
}

void DrawList(int &ary[], string prefix, bool showBox, string &live[], int now)
{
   int extend = MathMax(0, MathMin(100, InpExtend));
   for(int i = 0; i < ArraySize(ary) && i < InpBoxCount; i++)
   {
      int id = ary[i];
      if(gZ[id].xval == INT_MIN || gZ[id].left == INT_MIN) continue;
      color first = gZ[id].dir == -1 ? InpBullColor : InpBearColor;
      color second = gZ[id].dir == -1 ? InpBearColor : InpBullColor;
      string p = "IFVG_" + prefix + IntegerToString(gZ[id].left) + "_";
      int xEnd = gZ[id].state < 1 ? gZ[id].right : now;
      if(showBox)
      {
         Rect(p + "M", gZ[id].left, gZ[id].top, gZ[id].xval, gZ[id].bot, first, live);
         Rect(p + "O", gZ[id].xval, gZ[id].top, xEnd, gZ[id].bot, second, live);
         if(gZ[id].state >= 1 && extend > 0) Rect(p + "E", now, gZ[id].top, now + extend, gZ[id].bot, second, live);
      }
      if(InpShowMidLine)
      {
         Seg(p + "L1", gZ[id].left, gZ[id].mid, xEnd, InpLineColor, live);
         if(gZ[id].state >= 1 && extend > 0) Seg(p + "L2", now, gZ[id].mid, now + extend, InpLineColor, live);
      }
      if(!InpShowSignals) continue;
      for(int k = 0; k < ArraySize(gLabs); k++)
      {
         if(gLabs[k].zone != id) continue;
         Arrow("IFVG_lab" + IntegerToString(gLabs[k].x) + "_" + IntegerToString(gLabs[k].dir), gLabs[k].x, gLabs[k].y, gLabs[k].dir, live);
      }
   }
}

void Redraw()
{
   if(gCount < 1) return;
   string live[];
   int now = gCount - 1;
   DrawList(gBullInv, "FVGP", InpShowBear, live, now);   // zones born bullish, now resistance
   DrawList(gBearInv, "FVGN", InpShowBull, live, now);
   for(int i = ArraySize(gDrawn) - 1; i >= 0; i--)
   {
      bool keep = false;
      for(int k = 0; k < ArraySize(live); k++) if(live[k] == gDrawn[i]) { keep = true; break; }
      if(keep) continue;
      ObjectDelete(0, gDrawn[i]);
      int n = ArraySize(gDrawn);
      gDrawn[i] = gDrawn[n - 1];
      ArrayResize(gDrawn, n - 1, 64);
   }
   ChartRedraw(0);
}

//---------------------------------------------------------------- parity files
void OpenParityFiles(int total)
{
   string stem = "ifvg_" + _Symbol + "_" + EnumToString(_Period);
   StringReplace(stem, "PERIOD_", "");
   gBarsFile = FileOpen(stem + "_bars.csv", FILE_WRITE | FILE_TXT | FILE_ANSI);
   gEvFile = FileOpen(stem + "_events.csv", FILE_WRITE | FILE_TXT | FILE_ANSI);
   if(gBarsFile == INVALID_HANDLE || gEvFile == INVALID_HANDLE) { Print("[IFVG] parity files KO : ", GetLastError()); return; }
   FileWrite(gBarsFile, "# count=" + IntegerToString(total) + " atrlen=" + IntegerToString(InpAtrLength) + " atrmult=" + DoubleToString(InpAtrMultiplier, 4) +
             " bounce=" + (InpBounce == BOUNCE_WICK ? "wick" : "close") + " removefilled=" + (InpRemoveFilled ? "1" : "0") + " history=" + IntegerToString(InpHistoryBars));
   FileWrite(gBarsFile, "index,time,open,high,low,close");
   FileWrite(gEvFile, "index,time,token,top,bot");
   Print("[IFVG] parity files: MQL5\\Files\\", stem, "_bars.csv / _events.csv");
}

void CloseParityFiles()
{
   if(gBarsFile != INVALID_HANDLE) { FileClose(gBarsFile); gBarsFile = INVALID_HANDLE; }
   if(gEvFile != INVALID_HANDLE) { FileClose(gEvFile); gEvFile = INVALID_HANDLE; }
}

//---------------------------------------------------------------- MQL5 entry points
int OnInit()
{
   SetIndexBuffer(0, gUpBuf, INDICATOR_DATA);
   SetIndexBuffer(1, gDnBuf, INDICATOR_DATA);
   ArraySetAsSeries(gUpBuf, false);
   ArraySetAsSeries(gDnBuf, false);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   IndicatorSetString(INDICATOR_SHORTNAME, "IFVG");
   ResetEngine();
   Print("[IFVG] Inversion Fair Value Gaps, show last ", InpBoxCount, ", bounce on ", (InpBounce == BOUNCE_WICK ? "Wick" : "Close"),
         ", ATR ", InpAtrLength, " x ", DoubleToString(InpAtrMultiplier, 2), ", remove filled ", InpRemoveFilled,
         ", history ", InpHistoryBars, " bars, parity ", InpWriteParity);
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   CloseParityFiles();
   ObjectsDeleteAll(0, "IFVG_");
   ChartRedraw(0);
}

void FullPass(const int rates_total, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[])
{
   CloseParityFiles();
   ResetEngine();
   gLive = false;
   ArrayInitialize(gUpBuf, EMPTY_VALUE);
   ArrayInitialize(gDnBuf, EMPTY_VALUE);
   gFirst = MathMax(0, rates_total - InpHistoryBars);
   if(InpWriteParity) OpenParityFiles(rates_total - 1 - gFirst);
   for(int i = gFirst; i < rates_total - 1; i++) FeedBar(time[i], open[i], high[i], low[i], close[i]);
   CloseParityFiles();
   Print("[IFVG] history loaded: ", gCount, " bars (from chart index ", gFirst, "), ", gEvents, " events, ",
         ArraySize(gBullInv), " active zones born bullish, ", ArraySize(gBearInv), " born bearish.");
}

int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[],
                const double &open[], const double &high[], const double &low[], const double &close[],
                const long &tick_volume[], const long &volume[], const int &spread[])
{
   if(rates_total < 2) return 0;
   // the engine maps bar gFirst + k to its bar k
   bool shifted = gCount > 0 && (gFirst + gCount - 1 >= rates_total || time[gFirst + gCount - 1] != gT[gCount - 1]);
   if(prev_calculated == 0 || shifted)
   {
      FullPass(rates_total, time, open, high, low, close);
      Redraw();
      return rates_total;
   }
   gLive = true;
   for(int i = MathMax(0, prev_calculated - 1); i < rates_total; i++) { gUpBuf[i] = EMPTY_VALUE; gDnBuf[i] = EMPTY_VALUE; }
   int before = gCount;
   for(int i = gFirst + gCount; i < rates_total - 1; i++) FeedBar(time[i], open[i], high[i], low[i], close[i]);
   if(gCount != before) Redraw();
   return rates_total;
}
//+------------------------------------------------------------------+

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная публикация: https://www.mql5.com/en/code/77726

ZoneUS30 — Разворот и положительный своп на вашей стороне в US30 ZoneUS30 — Разворот и положительный своп на вашей стороне в US30

ZoneUS30 — это простой советник для индекса US30, ориентированный на поиск возможностей для продажи после восходящих ценовых разворотов. Стратегия сочетает в себе концепцию возврата к среднему значению с идеей кэрри-трейда: когда короткие позиции приносят положительный своп за ночь, советник может извлечь выгоду как из коррекции цены, так и из удержания позиции в течение определенного времени. Разработан для индекса US30 и предназначен для экспериментального и образовательного использования. Торговля против тренда сопряжена со значительным риском, особенно при использовании больших размеров лотов.

EMA Cross Demo EA with ATR Stops EMA Cross Demo EA with ATR Stops

Небольшой демонстрационный советник: вход по пересечению быстрой и медленной EMA (оценка производится только по закрытым свечам, без перерисовки), стоп-лосс и тейк-профит на основе ATR, фильтр спреда, а также управление позициями, подходящее как для хеджирующих, так и для нетто-счетов. Включает эталонную реализацию на Python для тестирования на паритет. Это пример для демонстрации, а не торговая рекомендация — прилагаемая кривая капитала построена на основе синтетических данных и иллюстрирует метод, а не преимущество.

YURAZ_RSAXEL Скрипт рисует уровни Рудолфа Акселя YURAZ_RSAXEL Скрипт рисует уровни Рудолфа Акселя

Скрипт рисует уровни Рудолфа Акселя

YURAZ_MCCH YURAZ_MCCH

Индикатор рассчитывает % роста или падения относительно CLOSE, написан с применением ООП, и легко интегрируется в любой советник или иной индикатор.