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.type | Co się stało |
|---|---|---|
| 1 | TRADE_TRANSACTION_ORDER_ADD | zlecenie pojawiło się na liście aktywnych |
| 2 | TRADE_TRANSACTION_DEAL_ADD | zlecenie zostało wykonane, powstała transakcja |
| 3 | TRADE_TRANSACTION_ORDER_DELETE | zlecenie zniknęło z listy aktywnych |
| 4 | TRADE_TRANSACTION_HISTORY_ADD | zlecenie trafiło do historii |
| 5 | TRADE_TRANSACTION_REQUEST | potwierdzenie 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.
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.
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.
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.
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:
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.