Architektura eksperta
Dlaczego ekspert napisany w jednym OnTick nie da się rozwijać ani sprawdzić, i jak go rozdzielić na trzy niezależne części.
3 fragmenty kodu z tej strony przeszły przez kompilator MetaEditor build 6090, 2026-08-05.
Typowy ekspert z internetu ma jedną funkcję OnTick na trzysta linii. W środku, poprzeplatane ze sobą: odczyt wskaźników, warunek wejścia, wyliczenie wolumenu, wysłanie zlecenia, zarządzanie stopem i obsługa błędów.
To działa, dopóki nie chcesz niczego zmienić.
Co konkretnie się psuje
Nie da się zmienić jednej rzeczy. Chcesz sprawdzić inny sposób liczenia wolumenu — musisz grzebać w funkcji, która przy okazji wysyła zlecenia. Każda zmiana niesie ryzyko zepsucia czegoś obok.
Nie da się niczego sprawdzić osobno. Cała logika istnieje tylko w kontekście działającego terminala, otwartego rachunku i napływających ticków.
Nie da się użyć ponownie. Trailing stop napisany wewnątrz OnTick jednego eksperta trzeba przy drugim przepisać ręcznie, razem z jego błędami.
Nie da się czytać. Po trzech miesiącach nie odróżnisz warunku wejścia od obsługi przypadku brzegowego u brokera, który nie podaje poziomu stopów.
Trzy odpowiedzialności
Ekspert, niezależnie od strategii, robi trzy różne rzeczy:
| Część | Odpowiada na pytanie | Czego potrzebuje |
|---|---|---|
| Sygnał | czy i w którą stronę | danych rynkowych |
| Ryzyko | jak dużą pozycją i gdzie stop | stanu rachunku i parametrów instrumentu |
| Wykonanie | jak to wysłać na serwer | dostępu do funkcji handlowych |
Te trzy części nie muszą o sobie wiedzieć. Sygnał nie ma pojęcia, że istnieją zlecenia. Ryzyko nie wie, skąd wziął się sygnał. Wykonanie nie wie, dlaczego ma wysłać kupno.
Interfejsy
MQL5 ma słowo kluczowe interface i to jest najprostszy sposób na rozdzielenie tych warstw.
interface ISygnal
{
//--- 1 kupno, -1 sprzedaż, 0 brak sygnału
int Kierunek(void);
bool Przygotuj(void);
void Zwolnij(void);
};Konkretna strategia implementuje interfejs i nie robi nic poza swoim zadaniem:
class CPrzeciecieSrednich : public ISygnal
{
private:
int m_szybka;
int m_wolna;
int m_okresSzybkiej;
int m_okresWolnej;
public:
CPrzeciecieSrednich(const int szybka, const int wolna)
{
m_okresSzybkiej = szybka;
m_okresWolnej = wolna;
m_szybka = INVALID_HANDLE;
m_wolna = INVALID_HANDLE;
}
bool Przygotuj(void)
{
m_szybka = iMA(_Symbol, _Period, m_okresSzybkiej, 0, MODE_EMA, PRICE_CLOSE);
m_wolna = iMA(_Symbol, _Period, m_okresWolnej, 0, MODE_EMA, PRICE_CLOSE);
return (m_szybka != INVALID_HANDLE && m_wolna != INVALID_HANDLE);
}
void Zwolnij(void)
{
if(m_szybka != INVALID_HANDLE)
IndicatorRelease(m_szybka);
if(m_wolna != INVALID_HANDLE)
IndicatorRelease(m_wolna);
}
int Kierunek(void)
{
double szybka[], wolna[];
ArraySetAsSeries(szybka, true);
ArraySetAsSeries(wolna, true);
//--- świeca 1 i 2, nigdy 0
if(CopyBuffer(m_szybka, 0, 1, 2, szybka) < 2)
return 0;
if(CopyBuffer(m_wolna, 0, 1, 2, wolna) < 2)
return 0;
if(szybka[1] <= wolna[1] && szybka[0] > wolna[0])
return 1;
if(szybka[1] >= wolna[1] && szybka[0] < wolna[0])
return -1;
return 0;
}
};Co z tego wynika w OnTick
OnTick przestaje być miejscem, gdzie mieszka logika, i staje się tym, czym powinien być: dyspozytorem.
ISygnal *sygnal = NULL;
int OnInit()
{
sygnal = new CPrzeciecieSrednich(12, 26);
if(!sygnal.Przygotuj())
return INIT_FAILED;
return INIT_SUCCEEDED;
}
void OnDeinit(const int powod)
{
if(sygnal != NULL)
{
sygnal.Zwolnij();
delete sygnal;
sygnal = NULL;
}
}
void OnTick()
{
//--- decyzja raz na świecę, patrz lekcja o nowej świecy
static datetime ostatnia = 0;
datetime biezaca = iTime(_Symbol, _Period, 0);
if(biezaca == 0 || biezaca == ostatnia)
return;
ostatnia = biezaca;
int kierunek = sygnal.Kierunek();
if(kierunek == 0)
return;
//--- ryzyko i wykonanie mają własne funkcje
Print("Sygnał: ", kierunek);
}Wymiana strategii to teraz jedna linia w OnInit. Dodanie drugiego sygnału i wymóg, żeby oba się zgadzały, to kilka linii, bez dotykania czegokolwiek innego.
Gdzie postawić granicę
Nie każdy ekspert potrzebuje interfejsów. Przy stu liniach to narzut bez zysku. Sygnałem, że pora rozdzielić, są zwykle trzy rzeczy:
OnTickprzekracza ekran,- pojawia się druga strategia albo drugi instrument,
- zaczynasz kopiować fragmenty między plikami.
Zanim sięgniesz po klasy, często wystarczy rozbicie na funkcje w osobnych plikach .mqh — sygnał, ryzyko, wykonanie jako trzy pliki nagłówkowe. Zysk w czytelności jest podobny, a próg wejścia zerowy.