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

MQL5 / Inżynieria

Dziennik, który da się czytać

Print bez dyscypliny daje tysiąc linii, z których nic nie wynika. Poziomy, kontekst w jednej linii i zapis do pliku, który otworzysz w arkuszu.

Kod z tej strony skompilowany i uruchomiony: MetaEditor build 6090, sprawdzone 2026-08-02.

Dziennik eksperta to zwykle jedno z dwóch: pusty, bo autor usunął wszystkie Print po debugowaniu, albo zapchany, bo zostawił je wszystkie. Oba warianty są bezużyteczne dokładnie wtedy, gdy są potrzebne — po tygodniu pracy, kiedy próbujesz zrozumieć, dlaczego ekspert zrobił coś dziwnego w środę o 14:20.

Trzy zasady

Jedna linia to jedno zdarzenie z kompletem kontekstu. Wpis "Kupuję" nie mówi nic. Musi zawierać wszystko, co potrzebne, żeby zrozumieć sytuację bez zaglądania w sąsiednie linie: instrument, cenę, wolumen, powód.

Poziom ważności. Bez tego nie da się odsiać. Cztery wystarczą.

Format stały. Jeśli wszystkie wpisy mają ten sam układ, da się je przefiltrować, policzyć i wczytać do arkusza. Jeśli każdy jest inny, zostaje czytanie oczami.

Minimalny dziennik

Cztery poziomy i jeden format
enum ENUM_POZIOM
  {
   POZIOM_BLAD  = 0,
   POZIOM_UWAGA = 1,
   POZIOM_INFO  = 2,
   POZIOM_DEBUG = 3
  };

input ENUM_POZIOM Szczegolowosc = POZIOM_INFO;

void Zapisz(const ENUM_POZIOM poziom, const string zdarzenie, const string szczegoly)
  {
   if(poziom > Szczegolowosc)
      return;

   string etykieta = "";
   switch(poziom)
     {
      case POZIOM_BLAD:  etykieta = "BLAD ";  break;
      case POZIOM_UWAGA: etykieta = "UWAGA";  break;
      case POZIOM_INFO:  etykieta = "INFO ";  break;
      default:           etykieta = "DEBUG";  break;
     }

   PrintFormat("%s | %s | %s | %s",
               etykieta, _Symbol, zdarzenie, szczegoly);
  }

Użycie wygląda tak:

Wpis, z którego coś wynika
Zapisz(POZIOM_INFO, "wejscie",
       StringFormat("kierunek=%d wolumen=%.2f cena=%.5f sl=%.5f powod=przeciecie",
                    kierunek, wolumen, cena, sl));

Wpis w dzienniku:

INFO  | EURUSD | wejscie | kierunek=1 wolumen=0.03 cena=1.09325 sl=1.09125 powod=przeciecie

Po tygodniu da się z tego policzyć, ile było wejść, w jakich cenach i z jakiego powodu — bez czytania.

Zapis do pliku, który otworzysz w arkuszu

Zakładka Eksperci nadaje się do zerknięcia. Do analizy nadaje się plik CSV.

Wiersz CSV na każde zdarzenie handlowe
void ZapiszDoPliku(const string zdarzenie, const double cena, const double wolumen)
  {
   string nazwa = StringFormat("dziennik_%s.csv", _Symbol);

   //--- FILE_COMMON zapisuje w katalogu wspólnym wszystkich terminali
   int uchwyt = FileOpen(nazwa, FILE_READ | FILE_WRITE | FILE_CSV | FILE_ANSI, ';');
   if(uchwyt == INVALID_HANDLE)
     {
      PrintFormat("Nie mogę otworzyć %s, błąd %d", nazwa, GetLastError());
      return;
     }

   //--- dopisujemy na końcu, nie nadpisujemy
   FileSeek(uchwyt, 0, SEEK_END);

   FileWrite(uchwyt,
             TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS),
             _Symbol, zdarzenie,
             DoubleToString(cena, _Digits),
             DoubleToString(wolumen, 2));

   FileClose(uchwyt);
  }

Plik ląduje w MQL5\Files w katalogu danych terminala. Średnik jako separator i FILE_ANSI sprawiają, że polski arkusz kalkulacyjny otworzy go bez pytań o kodowanie i o to, czy przecinek jest separatorem dziesiętnym.

Czego nie zapisywać

Hasła i numeru rachunku. Dziennik bywa wysyłany do brokera albo wklejany na forum. Numer rachunku w każdej linii to kiepski pomysł.

Danych, które i tak masz. Historia transakcji jest w terminalu i da się ją odczytać przez HistorySelect. Dziennik ma zapisywać to, czego w niej nie ma: dlaczego ekspert podjął decyzję, jakie wartości miały wskaźniki i które warunki nie zostały spełnione.

Ta ostatnia rzecz jest najważniejsza i najczęściej pomijana. Wpis o zawartej transakcji da się odtworzyć z historii. Wpis o transakcji, której nie zawarłeś, bo spread był za szeroki — nie da się odtworzyć znikąd.

Zapisujemy też decyzje odmowne
if(!SpreadDopuszczalny(_Symbol, MaksymalnySpread))
  {
   Zapisz(POZIOM_UWAGA, "sygnal-odrzucony", "powod=spread");
   return;
  }

Po miesiącu takie wpisy odpowiadają na pytanie, czy filtr w ogóle miał sens — i to jest jedyne źródło tej odpowiedzi.

Powiązane