Przejdź do treści
MQL5 · MetaTrader 5 · po polsku
kod odczarowany

MQL5 / Kurs / Lekcja 6 z 7

Własny wskaźnik od zera

Kompletny działający wskaźnik z wyjaśnieniem każdej właściwości: czym różni się bufor od linii, po co PLOT_DRAW_BEGIN i skąd biorą się linie spadające do zera.

7 fragmentów kodu z tej strony przeszło przez kompilator MetaEditor build 6090, 2026-08-05.

Wskaźnik różni się od eksperta trzema rzeczami: nie handluje, uruchamia się przy każdym przeliczeniu wykresu i komunikuje się ze światem przez bufory, a nie przez zlecenia.

Bufor to nie to samo co linia

To pierwsze nieporozumienie. Wskaźnik ma dwie różne liczby w nagłówku:

Dwie właściwości, które łatwo pomylić
#property indicator_buffers 2   // ile tablic, łącznie z pomocniczymi
#property indicator_plots   1   // ile linii widzi użytkownik

indicator_buffers to liczba tablic, które wskaźnik rezerwuje. indicator_plots to liczba rysowanych serii. Bufor pomocniczy — na przykład wynik pośredni obliczeń — zajmuje miejsce w pierwszej liczbie, ale nie w drugiej.

Podanie zbyt małego indicator_buffers daje błąd przy SetIndexBuffer. Podanie zbyt dużego indicator_plots daje ostrzeżenie o linii bez danych.

Rejestracja buforów w OnInit
double BuforSredniej[];   // rysowany
double BuforZakresu[];    // tylko do obliczeń

SetIndexBuffer(0, BuforSredniej, INDICATOR_DATA);
SetIndexBuffer(1, BuforZakresu, INDICATOR_CALCULATIONS);

Kolejność wywołań SetIndexBuffer wyznacza numerację, którą później podaje się w CopyBuffer. Bufory pomocnicze też są numerowane — dlatego wskaźnik z jedną widoczną linią może mieć ją pod numerem 0, a jego wewnętrzne obliczenia pod numerem 1.

Kompletny wskaźnik

MQL5\Indicators\SredniZakres.mq5
//+------------------------------------------------------------------+
//|                                                 SredniZakres.mq5 |
//|  Wskaźnik pokazujący średni zakres świecy w punktach.            |
//|  Napisany jako przykład do lekcji o budowie własnego wskaźnika.  |
//|  https://finanseodczarowane.pl/mql5/kurs/06-wlasny-wskaznik/      |
//|  Licencja MIT                                                    |
//+------------------------------------------------------------------+
#property copyright "finanseodczarowane.pl/kod"
#property version   "1.00"

#property indicator_separate_window
#property indicator_buffers 2       // bufory rysowane + pomocnicze
#property indicator_plots   1       // ile linii widać na wykresie

#property indicator_label1  "Średni zakres"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_width1  2

input int Okres = 14;               // liczba świec do uśrednienia

double BuforSredniej[];             // to widać
double BuforZakresu[];              // to tylko liczymy

//+------------------------------------------------------------------+
int OnInit()
  {
   if(Okres < 1)
     {
      Print("Okres musi wynosić co najmniej 1.");
      return INIT_PARAMETERS_INCORRECT;
     }

   SetIndexBuffer(0, BuforSredniej, INDICATOR_DATA);
   SetIndexBuffer(1, BuforZakresu, INDICATOR_CALCULATIONS);

   //--- pierwsze Okres świec nie ma z czego policzyć średniej
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, Okres);

   //--- 0.0 znaczy "brak wartości", inaczej linia spadłaby do zera
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);

   IndicatorSetString(INDICATOR_SHORTNAME,
                      StringFormat("Średni zakres (%d)", Okres));
   IndicatorSetInteger(INDICATOR_DIGITS, 1);

   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
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 <= Okres)
      return 0;                     // za mało danych, spróbujemy później

   //--- liczymy od miejsca, w którym skończyliśmy poprzednio;
   //--- minus jeden, bo ostatnia świeca wciąż się zmienia
   int start = (prev_calculated > 0) ? prev_calculated - 1 : 0;

   //--- zakres każdej świecy w punktach
   for(int i = start; i < rates_total; i++)
      BuforZakresu[i] = (high[i] - low[i]) / _Point;

   //--- średnia krocząca z zakresu
   int poczatek = MathMax(start, Okres - 1);
   for(int i = poczatek; i < rates_total; i++)
     {
      double suma = 0.0;
      for(int j = 0; j < Okres; j++)
         suma += BuforZakresu[i - j];

      BuforSredniej[i] = suma / Okres;
     }

   return rates_total;
  }

Pobierz plik

Pokazuje średni zakres świecy w punktach — ile średnio przechodzi instrument w jednym okresie. Prosta rzecz, za to dotyka wszystkich mechanizmów, które są w każdym wskaźniku.

Trzy właściwości, które robią różnicę

PLOT_DRAW_BEGIN mówi, od której świecy zaczyna się rysowanie. Bez tego wskaźnik o okresie 14 narysuje pierwsze trzynaście punktów z niekompletnych danych — i wykres zacznie się dziwnym garbem, który wygląda jak sygnał.

PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, Okres);

PLOT_EMPTY_VALUE to wartość oznaczająca „tu nie ma nic". Domyślnie jest nią EMPTY_VALUE, czyli ogromna liczba. Jeśli twój bufor zostawia miejsca niewypełnione, a wartością pustą jest zero, linia zjedzie do zera i utworzy pionowe kreski przez cały wykres.

PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);

To jest odpowiedź na najczęściej zadawane pytanie o własne wskaźniki: skąd te pionowe linie do dołu.

INDICATOR_SHORTNAME to nazwa w rogu okna wskaźnika i w oknie danych. Warto wstawić w nią parametry — inaczej przy trzech kopiach wskaźnika na wykresie nie odróżnisz, która ma jaki okres.

IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("Średni zakres (%d)", Okres));

OnCalculate: liczyć tylko to, co się zmieniło

Wskaźnik jest wywoływany przy każdym ticku. Przeliczanie całej historii za każdym razem działa, ale przy wykresie ze stu tysiącami świec zamula terminal i wydłuża każdy przebieg testera.

Punkt startu obliczeń
int start = (prev_calculated > 0) ? prev_calculated - 1 : 0;

Trzy przypadki, które ten jeden wiersz obsługuje:

  • pierwsze wywołanieprev_calculated wynosi zero, liczymy wszystko,
  • kolejny tick tej samej świecy — liczymy od ostatniej świecy, bo ona wciąż się zmienia,
  • nowa świeca — liczymy od poprzedniej i nowej.

Minus jeden jest tu kluczowy. Bez niego ostatnia świeca zostaje policzona raz, przy swoim pierwszym ticku, i nigdy nie aktualizuje się do końca okresu.

Wskaźnik w oknie wykresu czy osobnym

Dwie możliwości, wykluczające się
#property indicator_chart_window     // na wykresie, w cenach instrumentu
#property indicator_separate_window  // w osobnym oknie, własna skala

Reguła jest prosta: jeśli wartości wskaźnika są cenami — średnie, wstęgi, poziomy — idzie na wykres. Jeśli są czymkolwiek innym — punktami, procentami, oscylatorem — idzie do osobnego okna. Wskaźnik zwracający punkty umieszczony na wykresie cen po prostu nie będzie widoczny, bo jego wartości są o kilka rzędów wielkości mniejsze od ceny.

Jak to sprawdzić

Zadanie

Dołóż drugą linię pokazującą największy zakres z ostatnich Okres świec. Będzie to wymagało zwiększenia obu liczb w nagłówku, dodania właściwości indicator_label2 i indicator_type2 oraz trzeciego bufora. Sprawdź potem przez CopyBuffer z eksperta, czy nowa linia siedzi pod numerem, którego się spodziewasz.

Powiązane