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

MQL5 / Funkcje

OnTradeTransaction

Jedno zlecenie rynkowe wywołuje tę funkcję cztery albo pięć razy. Wyjaśniamy, co znaczy każde wywołanie i jak zbudować na tym niezawodną obsługę zdarzeń.

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result);

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

To najtrudniejsza funkcja zdarzeniowa w MQL5 i jednocześnie jedyna, która pozwala wiarygodnie dowiedzieć się, że pozycję zamknął stop loss. Większość kodu, który ją wykorzystuje, jest napisana przy założeniu, że wywołuje się raz na transakcję. Nie wywołuje się raz.

Dlaczego nie wystarczy OnTrade

OnTrade mówi wyłącznie tyle, że coś się zmieniło. Nie ma argumentów, nie podaje, co i dlaczego. Kod na niej oparty musi za każdym razem przeskanować pozycje i historię, porównać ze stanem zapamiętanym wcześniej i wywnioskować, co się stało. To działa, ale jest wolne i zawodzi przy kilku zmianach w krótkim czasie.

OnTradeTransaction dostaje opis pojedynczej zmiany.

Ile wywołań daje jedno kupno

Zwykłe zlecenie rynkowe wykonane w całości wywołuje tę funkcję zazwyczaj cztery lub pięć razy, w takiej kolejności:

Kolejnośćtrans.typeCo się stało
1TRADE_TRANSACTION_ORDER_ADDzlecenie pojawiło się na liście aktywnych
2TRADE_TRANSACTION_DEAL_ADDzlecenie zostało wykonane, powstała transakcja
3TRADE_TRANSACTION_ORDER_DELETEzlecenie zniknęło z listy aktywnych
4TRADE_TRANSACTION_HISTORY_ADDzlecenie trafiło do historii
5TRADE_TRANSACTION_REQUESTpotwierdzenie obsługi twojego zapytania

Kod, który przy każdym wywołaniu zwiększa licznik pozycji albo wysyła powiadomienie, zrobi to cztery razy. To jest najczęstszy błąd w tej funkcji.

Pełna lista typów jest dłuższa: dochodzą ORDER_UPDATE, DEAL_UPDATE, DEAL_DELETE, HISTORY_UPDATE, HISTORY_DELETE i POSITION. Ostatni pojawia się przy zmianie pozycji poza normalnym trybem — na przykład przy korekcie po stronie brokera.

Zdarzenia po jednym zleceniu rynkowymJedno kupno wywołuje OnTradeTransaction cztery albo pięć razy, w kolejności od dodania zlecenia po potwierdzenie zapytania.zlecenie trafia nalistę aktywnychORDER_ADD1DEAL_ADDzlecenie wykonane,powstajetransakcja2zlecenie znika zlisty aktywnychORDER_DELETE3HISTORY_ADDzlecenie trafia dohistorii4potwierdzenieobsługi twojegozapytaniaREQUEST5Reaguj wyłącznie na DEAL_ADD. Kod zliczający cokolwiek przy każdym wywołaniu policzy to samozdarzenie pięć razy, a kolejność nie jest gwarantowana.

Reguła: reaguj tylko na DEAL_ADD

Praktycznie wszystko, czego potrzebujesz, jest w transakcji, a nie w zleceniu. Transakcja to fakt dokonany: coś zostało kupione albo sprzedane.

Szkielet obsługi, który nie liczy tego samego kilka razy
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
   //--- interesują nas wyłącznie nowe transakcje
   if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
      return;

   //--- dane transakcji czytamy z historii, nie ze struktury zdarzenia
   if(!HistoryDealSelect(trans.deal))
      return;

   long magic = HistoryDealGetInteger(trans.deal, DEAL_MAGIC);
   long wejscie = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
   long powod = HistoryDealGetInteger(trans.deal, DEAL_REASON);
   long pozycja = HistoryDealGetInteger(trans.deal, DEAL_POSITION_ID);
   double zysk = HistoryDealGetDouble(trans.deal, DEAL_PROFIT);

   if(magic != 12345)
      return;                       // nie nasza transakcja

   if(wejscie == DEAL_ENTRY_IN)
     {
      PrintFormat("Otwarto pozycję %I64d", pozycja);
      return;
     }

   if(wejscie == DEAL_ENTRY_OUT)
      PrintFormat("Zamknięto pozycję %I64d, wynik %.2f, powód: %s",
                  pozycja, zysk, OpisPowodu(powod));
  }

Powód zamknięcia

To jest odpowiedź na pytanie, przez które większość ludzi tu trafia.

Rozpoznanie, co zamknęło pozycję
string OpisPowodu(const long powod)
  {
   switch((int)powod)
     {
      case DEAL_REASON_CLIENT:   return "ręcznie z terminala";
      case DEAL_REASON_MOBILE:   return "z aplikacji mobilnej";
      case DEAL_REASON_WEB:      return "z terminala webowego";
      case DEAL_REASON_EXPERT:   return "przez eksperta";
      case DEAL_REASON_SL:       return "stop loss";
      case DEAL_REASON_TP:       return "take profit";
      case DEAL_REASON_SO:       return "stop out";
      case DEAL_REASON_ROLLOVER: return "rolowanie";
      case DEAL_REASON_SPLIT:    return "split instrumentu";
      default:                   return "nieznany (" + IntegerToString(powod) + ")";
     }
  }

DEAL_REASON_SL i DEAL_REASON_TP to jedyny wiarygodny sposób, żeby odróżnić zamknięcie przez serwer od zamknięcia przez własny kod. Porównywanie ceny zamknięcia z zapamiętanym poziomem stopa nie działa — przy luce cenowej pozycja zamyka się w zupełnie innym miejscu.

Cztery rzeczy, o których dokumentacja nie mówi wprost

Kolejność nie jest gwarantowana. Zdarzenia trafiają do kolejki i przy dużym natężeniu potrafią przyjść w innej kolejności, niż się wydarzyły. Nie buduj automatu stanów zakładającego, że ORDER_ADD przyjdzie przed DEAL_ADD.

Zdarzenia mogą przepaść. Kolejka ma ograniczoną pojemność. Ekspert, który robi w tej funkcji coś czasochłonnego, potrafi ją przepełnić — wtedy część zdarzeń zwyczajnie nie dojdzie. Stąd druga reguła: w OnTradeTransaction tylko odczytujesz i zapisujesz, nigdy nie liczysz i nie handlujesz.

Nie wysyłaj stąd zleceń. Zlecenie wysłane w reakcji na transakcję generuje kolejne transakcje, które wywołają tę funkcję ponownie. Poprawny wzorzec to ustawienie flagi i obsłużenie jej w OnTick.

Flaga zamiast działania
bool trzebaZareagowac = false;

void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
      trzebaZareagowac = true;      // tylko notujemy
  }

void OnTick()
  {
   if(trzebaZareagowac)
     {
      trzebaZareagowac = false;
      //--- tutaj wolno liczyć i wysyłać zlecenia
     }
  }

W testerze zachowuje się inaczej. Tester wywołuje OnTradeTransaction synchronicznie, w przewidywalnej kolejności i bez opóźnień. Kod, który przechodzi test, może zawieść na rachunku właśnie dlatego, że tester nie odtwarza warunków, w których ta funkcja bywa kapryśna.

Zlecenia asynchroniczne

Przy OrderSendAsync funkcja OnTradeTransaction jest jedynym sposobem, żeby dowiedzieć się, co się stało z zapytaniem — bo samo wywołanie wraca natychmiast, przed odpowiedzią serwera. Powiązanie robi się przez identyfikator zapytania:

Dopasowanie odpowiedzi do wysłanego zapytania
uint mojeZapytanie = 0;

void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
   if(trans.type != TRADE_TRANSACTION_REQUEST)
      return;

   if(result.request_id != mojeZapytanie)
      return;                       // odpowiedź na cudze zapytanie

   PrintFormat("Nasze zapytanie zakończyło się kodem %d", result.retcode);
  }

Przy TRADE_TRANSACTION_REQUEST wypełnione są wyłącznie request i result — struktura trans poza polem type nie zawiera nic sensownego.

Powiązane