Tartalom

UniPulse MQL4 Indikátor – MetaTrader 4 Forex Programozás a Gyakorlatban
Ezen az oldalon a UniPulse EA teljes, éles forráskódját találod — egy konkrét, végigkövethető példát arra, hogyan néz ki a gyakorlatban a kereskedőrobot fejlesztés és az Expert programozás, tehát egy forex robot készítés, attól a pillanattól, hogy egy indikátor jelez, addig, hogy a robot ténylegesen pozíciót nyit vagy zár. Ha az UniPulse EA bemutató oldalát már ismered, ez az oldal annak a mélyebb, kódszintű folytatása.
Az EA szándékosan nem tartalmaz saját trendfelismerő logikát: a döntéseket teljes egészében a UniPulse MQL4 indikátor nyitó- és zárójel-puffereire bízza, saját magát pedig a fegyelmezett pozíciókezelésre — nyitás, zárás, és opcionálisan BreakEven — korlátozza. Ez a felosztás (az indikátor dönt, az EA végrehajt) az egyik legfontosabb alapelve a jól felépített stratégia automatizálásnak: a jelgenerálás és a pozíciókezelés különválasztása nélkül egy stratégia se marad átlátható vagy karbantartható hosszú távon. Ha még nem ismered az indikátor belső működését (MA-keresztezés, ATR-szűrés, gyertyatest-megerősítés), érdemes onnan indulnod, mielőtt belevágnál ebbe az oldalba.
Ez az oldal a forex robot készítés gyakorlati oldalát mutatja be: nem a szintaxist magyarázom sorról sorra, hanem azt, hogyan gondolkodik egy forex programozó, amikor egy indikátor jelzéseiből fegyelmezett, önvédő kereskedési logikát épít — a bemeneti paraméterek validálásától a hibakategorizált riasztási rendszerig. Ez a folyamat a kereskedőrobot fejlesztés és az Expert programozás egyik legjobb gyakorlati bemutatója is egyben.
Ha átugornád a programozási részt és szeretnél egyből a letöltési résznél landolni, csak kattints ide!
UniPulse_EA.mq4 – A Teljes Forráskód Letölthető Formában
1. Az EA architektúrája áttekintésben
Mielőtt bármilyen kereskedési döntés megszületne, érdemes tisztázni, miből épül fel az EA váza: honnan jön az indikátor, milyen adatcsatornákon (puffereken) keresztül beszélget vele az EA, és milyen struktúrába csomagolja a rá vonatkozó beállításokat. Ez a három elem adja a teljes rendszer gerincét.
1.1. Az indikátor mint beágyazott erőforrás
A UniPulse EA nem külön .ex4 fájlként várja el az indikátort a felhasználó gépén, hanem a #resource direktívával fordítási időben beágyazza saját magába. Ez azt jelenti, hogy a kész, futtatható EA önmagában is elegendő — a UniPulse indikátornak csak a fordítás pillanatában kell léteznie a megadott elérési úton, a végfelhasználónak külön telepítenie már nem kell. Ez a megoldás jól mutatja, mennyivel egyszerűbb lehet a kereskedőrobot fejlesztés, ha a disztribúciót már a tervezés fázisában átgondoljuk.
// Resource UniPulse indicator EX4 file. // If the indicator is located in a subfolder, the path and link should look like this: // Indicators\\SubFolder\\UniPulse.ex4 #define UNIPULSE_PATH "\\Indicators\\UniPulse.ex4" #define UNIPULSE_LINK "::Indicators\\UniPulse.ex4" #resource UNIPULSE_PATH
A UNIPULSE_PATH a fordítóprogram számára jelöli ki, honnan csomagolja be az .ex4 fájlt, a UNIPULSE_LINK pedig az a belső hivatkozás, amivel az EA futás közben, az iCustom() hívásokban ténylegesen eléri a beágyazott indikátort.
1.2. Puffer-indexek és trendirány-enumok
A UniPulse indikátor tíz megjelenített pufferéből az EA-nak csak ötre van szüksége: a két nyitó (Long/Short), a két záró (Long/Short) és a trend-puffer értékeire. Ahelyett, hogy ezeket a számokat (6, 7, 8, 9, 10) átláthatatlanul szórná szét a kódban, az EA névvel látja el őket — ez önmagában is dokumentáció, és később bármikor könnyen visszakereshető, melyik szám mit jelent. Ez a fajta explicit névhasználat az Expert programozás egyik alapszabálya, és minden komolyabb algoritmikus kereskedés rendszerben megtérül.
Emellett itt kapnak helyet azok az enumok is, amelyek a hibakategóriákat (ENUM_ERROR_CATEGORY), az indikátor trendirányát (ENUM_INDICATOR_TREND_DIRECTION) és a minimális MA-távolság gyertyaszám-limitjét (ENUM_MA_SPREAD_CANDLE_LIMIT) írják le — utóbbi ugyanaz a típus, amit az indikátor oldalán már megismertél.
//+------- Defines -------+
#define BUFFER_OPEN_LONG 6
#define BUFFER_OPEN_SHORT 7
#define BUFFER_CLOSE_LONG 8
#define BUFFER_CLOSE_SHORT 9
#define BUFFER_TREND 10
#define INDICATOR_MIN_CANDLES 100
#define INDICATOR_MA_HISTORY_MULTIPLIER 3
//+------- Enums -------+
enum ENUM_ERROR_CATEGORY
{
NO_ERROR = 0, //No Error
COMMON_ERROR = 1, //Common Error
TRADE_OPENING_ERROR = 2, //Trade Opening Error
TRADE_MODIFICATION_ERROR = 3, //Trade Modification Error
TRADE_CLOSING_ERROR = 4 //Trade Closing Error
};
enum ENUM_INDICATOR_TREND_DIRECTION{
INDICATOR_TREND_DIRECTION_LONG = 0, //Long
INDICATOR_TREND_DIRECTION_SHORT = 1, //Short
INDICATOR_TREND_DIRECTION_NONE = 2, //None
INDICATOR_TREND_DIRECTION_BOTH = 3 //Both
};
enum ENUM_MA_SPREAD_CANDLE_LIMIT{
MA_SPREAD_CANDLE_LIMIT_1 = 1, //One Candle
MA_SPREAD_CANDLE_LIMIT_2 = 2, //Two Candles
MA_SPREAD_CANDLE_LIMIT_3 = 3 //Three Candles
};
1.3. Az indikátor-beállítások struktúrája (sIndicatorSettings)
A UniPulse indikátor iCustom() hívása közel húsz paramétert vár — ezeket egyesével kezelni két helyen (végrehajtási és megerősítési időkeret) hibalehetőségek melegágya lenne. Ehelyett az EA egyetlen sIndicatorSettings struktúrába zárja az összes MA-, ATR-, spread- és gyertyatest-beállítást, majd ebből a struktúrából tölti fel a tényleges iCustom() hívást egy dedikált segédfüggvényben (lásd getUniPulseValue()). Ez a fajta strukturált paraméterkezelés az egyik legjobb példa arra, hogyan néz ki a gyakorlatban egy jól felépített stratégia automatizálás — kevesebb hiba, átláthatóbb kód, könnyebben bővíthető kereskedőrobot fejlesztés.
Ennek köszönhetően két azonos szerkezetű változó — execution_settings és confirmation_settings — elég ahhoz, hogy az EA egyszerre két időkereten (a chart saját időkeretén és egy opcionális, magasabb megerősítő időkereten) is lekérdezze az indikátort, ugyanazzal a kóddal.
//+------- Structs -------+
struct sIndicatorSettings{
int candles;
string mas_settings;
uint fast_ma_period;
uint slow_ma_period;
ENUM_MA_METHOD ma_method;
ENUM_APPLIED_PRICE ma_price;
uint ma_distance_limit_point;
string atr_settings;
uint atr_period;
uint atr_smoothing_period;
ENUM_MA_METHOD atr_smoothing_method;
string min_ma_spread_settings;
ENUM_MA_SPREAD_CANDLE_LIMIT min_ma_spread_candle_limit;
uint min_ma_spread_points;
string candle_body_settings;
bool check_candle_body;
string alert_settings;
bool pop_up_alert;
bool push_alert;
bool mail_alert;
};
2. Beállítási mezők (Inputs) és globális állapot
Az EA bemeneti mezői hat logikai csoportba rendeződnek — Trade, Trading Periods, BreakEven, Confirmation Timeframe, Execution Timeframe és Alerts —, mindegyik saját elválasztó címkével a beállítások ablakában. A globális változók pedig azt az állapotot tárolják, ami a tick-ek között is emlékezetben marad: hány pozíció fut éppen, mikor jött az utolsó gyertya, milyenek a szimbólum tulajdonságai.
2.1. Kereskedési, időzítési és BreakEven paraméterek
A Trade csoport a lotméretet, a megengedett csúszást (slippage), a fix pontban megadott StopLoss/TakeProfit távolságot, a magic number-t és a megbízás-kommentet tartalmazza. A Trading Periods egyetlen kezdő- és záróórával definiál egy kereskedési ablakot — ha a kettő megegyezik, az EA megállapodás szerint nem korlátozza az időzítést. A BreakEven csoport engedélyező kapcsolót, egy aktiváló profit-küszöböt (pontban) és egy StopLoss-eltolási távolságot vár. Ez a réteges felépítés jól mutatja, milyen körültekintést igényel egy valódi kereskedőrobot fejlesztés projekt a felhasználói kényelem és az algoritmikus kereskedés megbízhatósága között.
A Confirmation Timeframe és Execution Timeframe csoportok gyakorlatilag egy-egy teljes UniPulse-beállítás-készletet ismételnek meg — az egyik a chart saját időkeretén fut (ez adja a tényleges nyitó/záró jeleket), a másik egy opcionális, magasabb időkereten (ez csak a trendirányt szűri). Ez a kettős megerősítési logika az Expert programozás és a forex robot készítés egyik bevált mintája, mert csökkenti a hamis jelzések számát. Végül az Alerts csoport a pop-up, push és e-mail csatornákat kapcsolja ki-be a kereskedési eseményekhez.
//+------- Input Fields -------+ input string Trade_Settings = "---- Trade ----"; //---- Trade ---- input double Trade_Lot = 0.01; //Trade Lot Size input uint Trade_Slippage = 100; //Slippage In Points input uint Trade_StopLossPoints = 0; //StopLoss Points (0:= Off) input uint Trade_TakeProfitPoints = 0; //TakeProfit Points (0:= Off) input uint Trade_MagicNumber = 625125; //Magic Number (Unique Trade Identifier) input string Trade_Comment = ""; //Comment input string Trading_Periods_Settings = "---- Trading Periods ----"; //---- Trading Periods (Start = End means no time restriction) ---- input uint Trading_StartHour = 0; //Start Hour (0 - 23) input uint Trading_EndHour = 0; //End Hour (0 - 23) input string BreakEven_Settings = "---- BreakEven ----"; //---- BreakEven ---- input bool Use_BreakEven = false; //Use BreakEven input int BreakEven_ActivationPoint = 1000; //BreakEven Activator Profit Point input int BreakEven_StopLossPoint = 10; //BreakEven StopLoss shifting point input string Confirmation_Timeframe_Settings = "---- Confirmation Timeframe ----";//---- Confirmation Timeframe (Indicator Settings) ---- input bool Use_Confirmation_Timeframe = false; //Use Confirmation Timeframe input ENUM_TIMEFRAMES Confirmation_Timeframe = PERIOD_D1; //Confirmation Timeframe input string Confirmation_MAs_Settings = "- Confirmation MAs -"; //- Confirmation MAs - input uint Confirmation_FastMaPeriod = 10; //Confirmation Fast MA Period input uint Confirmation_SlowMaPeriod = 30; //Confirmation Slow MA Period input ENUM_MA_METHOD Confirmation_MAMethod = MODE_EMA; //Confirmation MA Method input ENUM_APPLIED_PRICE Confirmation_MAPrice = PRICE_CLOSE; //Confirmation MA Price input string Confirmation_ATR_Settings = "- Confirmation ATR -"; //- Confirmation ATR - input uint Confirmation_ATRPeriod = 14; //Confirmation ATR Period input uint Confirmation_ATRSmoothingPeriod = 10; //Confirmation ATR Smoothing Period input ENUM_MA_METHOD Confirmation_ATRSmoothingMethod = MODE_EMA; //Confirmation ATR Smoothing Method input string Execution_Timeframe_Settings = "---- Execution Timeframe ----"; //---- Execution Timeframe ---- input string Execution_MAs_Settings = "- Execution MAs -"; //- Execution MAs - input uint Execution_FastMaPeriod = 10; //Execution Fast MA Period input uint Execution_SlowMaPeriod = 30; //Execution Slow MA Period input ENUM_MA_METHOD Execution_MAMethod = MODE_EMA; //Execution MA Method input ENUM_APPLIED_PRICE Execution_MAPrice = PRICE_CLOSE; //Execution MA Price input uint Execution_MADistanceLimitPoint = 0; //Execution Minimum MA Lines Distance Point input string Execution_ATR_Settings = "- Execution ATR -"; //- Execution ATR - input uint Execution_ATRPeriod = 14; //Execution ATR Period input uint Execution_ATRSmoothingPeriod = 10; //Execution ATR Smoothing Period input ENUM_MA_METHOD Execution_ATRSmoothingMethod = MODE_EMA; //Execution ATR Smoothing Method input string Execution_MinMASpread_Settings = "- Execution MA Spread -"; //- Execution MA Spread - input ENUM_MA_SPREAD_CANDLE_LIMIT Execution_MinMASpreadCandleLimit = MA_SPREAD_CANDLE_LIMIT_1; //Execution Minimum MA Spread Candle Limit input uint Execution_MinMASpreadPoints = 50; //Execution Minimum MA Spread (points) input string Execution_CandleBody_Settings = "- Execution Candle Body -"; //- Execution Candle Body - input bool Execution_CheckCandleBody = true; //Execution Check Candle Body Direction input string Alert_Settings = "---- Alerts ----"; //---- Alerts ---- input bool PopUpAlert = true; //Pop-up Alert input bool PushAlert = false; //Push Alert input bool MailAlert = false; //E-Mail
2.2. Globális állapotváltozók
A globális változók négy csoportra oszthatók: riasztás-állapot (mikor volt az utolsó alert, milyen kategóriájú), hiba-állapot (az utoljára történt hiba kódja, üzenete, kategóriája — ezeket referencia-paraméterként adják tovább a segédfüggvények), metrika-változók (hány Long/Short pozíció fut, mennyi a lebegő profitjuk, mik az indikátor aktuális puffer-értékei, mikor volt az előző gyertya) és végül a szimbólum-tulajdonságok gyorsítótára (point, tick-méret, lot-határok, stop-szint), amiket az EA egyszer, indításkor tölt be és utána minden ticken újrahasznál. Ez a fajta állapotkezelés minden komolyabb kereskedőrobot fejlesztés és stratégia automatizálás projekt gerince.
//+------- Global Variables -------+
//--- alert variables ---
datetime last_alert_time = (datetime)0; //last alert time
int alert_period = 60; //alert repeat interval in seconds
//--- error variables ---
int last_error_code;
string last_error_message;
ENUM_ERROR_CATEGORY last_error_category;
ENUM_ERROR_CATEGORY stored_error_category;
//--- metric variables ---
int confirmation_seconds
, execution_seconds;
uint long_count
, short_count;
double long_profit
, short_profit;
double buffer_open_long
, buffer_open_short
, buffer_close_long
, buffer_close_short
, buffer_confirmation_trend
;
datetime last_candle_time = (datetime)0
, candle_0_time
;
bool is_new_candle;
//--- Symbol Values ---
double point
, tick_size
, min_lot
, max_lot
, lot_step
;
int digits
, lot_digits
, stoplevel_point
;
ENUM_INDICATOR_TREND_DIRECTION trend_direction;
sIndicatorSettings execution_settings
, confirmation_settings;
3. OnInit() – Inicializálás és paraméterellenőrzés
Az OnInit() egyszer fut le, amikor az EA-t a chartra húzod, vagy bármelyik bemeneti paramétert módosítod, esetleg a charton instrumentet- vagy idősíkot váltasz.
Két dolgot kell garantálnia, mielőtt bármilyen tick-adat feldolgozása elkezdődhetne: hogy a paraméterek matematikailag értelmesek-e, és hogy a szimbólum tulajdonságai (amikre a teljes kereskedési logika épít) sikeresen betölthetők-e. Ez a szigorú belépési ellenőrzés az egyik legfontosabb lépés minden felelős kereskedőrobot fejlesztés folyamatban.
3.1. Végrehajtási és megerősítési időkeret validálása
Ha a megerősítő időkeret be van kapcsolva, az EA elsőként azt ellenőrzi, hogy az valóban nem rövidebb-e a chart saját időkereténél — értelmetlen lenne egy kisebb időkeretre „megerősítést” kérni. Ezután mindkét időkeretre (végrehajtási és megerősítési) lefut ugyanaz az öt ellenőrzés, amit már a UniPulse indikátor oldalán is megismertél: nulla periódus, és Fast MA ≥ Slow MA — bármelyik hiba azonnal, popup riasztással és INIT_PARAMETERS_INCORRECT visszatéréssel állítja meg az inicializálást. Ez a fajta ismétlődő, de következetes validálás jellemzi az igényes Expert programozás gyakorlatot.
//checking of the confirmation variables
if(Use_Confirmation_Timeframe){
confirmation_seconds = PeriodSeconds(Confirmation_Timeframe);
if(confirmation_seconds < execution_seconds){
sendCustomAlert("", StringFormat("Confirmation Timeframe(%s) must be higher (or equal) than Chart Timeframe(%s)", EnumToString(Confirmation_Timeframe), EnumToString((ENUM_TIMEFRAMES)_Period)), COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
if(Confirmation_FastMaPeriod == 0){
sendCustomAlert("", "Confirmation Fast MA Period is 0! The indicator stops!", COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
if(Confirmation_SlowMaPeriod == 0){
sendCustomAlert("", "Confirmation Slow MA Period is 0! The indicator stops!", COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
if(Confirmation_ATRPeriod == 0){
sendCustomAlert("", "Confirmation ATR Period is 0! The indicator stops!", COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
if(Confirmation_ATRSmoothingPeriod == 0){
sendCustomAlert("", "Confirmation ATR Smoothing Period is 0! The indicator stops!", COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
if(Confirmation_FastMaPeriod >= Confirmation_SlowMaPeriod){
sendCustomAlert("", StringFormat("Confirmation Fast MA Period(%d) is bigger (or equal) than Slow MA Period(%d)! The indicator stops!", Confirmation_FastMaPeriod, Confirmation_SlowMaPeriod), COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
}
//checking of the execution variables
if(Execution_FastMaPeriod == 0){
sendCustomAlert("", "Execution Fast MA Period is 0! The indicator stops!", COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
if(Execution_SlowMaPeriod == 0){
sendCustomAlert("", "Execution Slow MA Period is 0! The indicator stops!", COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
if(Execution_ATRPeriod == 0){
sendCustomAlert("", "Execution ATR Period is 0! The indicator stops!", COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
if(Execution_ATRSmoothingPeriod == 0){
sendCustomAlert("", "Execution ATR Smoothing Period is 0! The indicator stops!", COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
if(Execution_FastMaPeriod >= Execution_SlowMaPeriod){
sendCustomAlert("", StringFormat("Execution Fast MA Period(%d) is bigger (or equal) than Slow MA Period(%d)! The indicator stops!", Execution_FastMaPeriod, Execution_SlowMaPeriod), COMMON_ERROR, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
3.2. Szimbólum-tulajdonságok betöltése és a beállítás-struktúrák feltöltése
Ha minden paraméter rendben van, az EA meghívja a loadSymbolProperties() függvényt (lásd 7.1), majd a bemeneti mezőkből feltölti a két sIndicatorSettings struktúrát. Fontos részlet: a candles mező itt sem közvetlen bemenet, hanem levezetett érték — a Slow MA periódus ötszöröse és egy minimum gyertyaszám (INDICATOR_MIN_CANDLES) közül a nagyobb, pontosan ugyanazzal a logikával, mint amit az indikátor oldalán a CandlesToCalculate levezetésénél láttál. Ez a fajta konzisztencia indikátor és EA között a megbízható algoritmikus kereskedés és stratégia automatizálás alapfeltétele.
//load properties
if(!loadSymbolProperties(last_error_code, last_error_message, last_error_category)){
sendCustomAlert("", last_error_message, last_error_category, true, false, false, false);
return(INIT_PARAMETERS_INCORRECT);
}
//filling of the setting structures
execution_settings.candles = (int)MathMax(INDICATOR_MIN_CANDLES, INDICATOR_MA_HISTORY_MULTIPLIER * Execution_SlowMaPeriod);
execution_settings.mas_settings = Execution_MAs_Settings;
execution_settings.fast_ma_period = Execution_FastMaPeriod;
execution_settings.slow_ma_period = Execution_SlowMaPeriod;
execution_settings.ma_method = Execution_MAMethod;
execution_settings.ma_price = Execution_MAPrice;
execution_settings.ma_distance_limit_point = Execution_MADistanceLimitPoint;
execution_settings.atr_settings = Execution_ATR_Settings;
execution_settings.atr_period = Execution_ATRPeriod;
execution_settings.atr_smoothing_period = Execution_ATRSmoothingPeriod;
execution_settings.atr_smoothing_method = Execution_ATRSmoothingMethod;
execution_settings.min_ma_spread_settings = Execution_MinMASpread_Settings;
execution_settings.min_ma_spread_candle_limit = Execution_MinMASpreadCandleLimit;
execution_settings.min_ma_spread_points = Execution_MinMASpreadPoints;
execution_settings.candle_body_settings = Execution_CandleBody_Settings;
execution_settings.check_candle_body = Execution_CheckCandleBody;
execution_settings.alert_settings = "";
execution_settings.pop_up_alert = false;
execution_settings.push_alert = false;
execution_settings.mail_alert = false;
confirmation_settings.candles = (int)MathMax(INDICATOR_MIN_CANDLES, INDICATOR_MA_HISTORY_MULTIPLIER * Confirmation_SlowMaPeriod);
confirmation_settings.mas_settings = Confirmation_MAs_Settings;
confirmation_settings.fast_ma_period = Confirmation_FastMaPeriod;
confirmation_settings.slow_ma_period = Confirmation_SlowMaPeriod;
confirmation_settings.ma_method = Confirmation_MAMethod;
confirmation_settings.ma_price = Confirmation_MAPrice;
confirmation_settings.ma_distance_limit_point = 0;
confirmation_settings.atr_settings = Confirmation_ATR_Settings;
confirmation_settings.atr_period = Confirmation_ATRPeriod;
confirmation_settings.atr_smoothing_period = Confirmation_ATRSmoothingPeriod;
confirmation_settings.atr_smoothing_method = Confirmation_ATRSmoothingMethod;
confirmation_settings.min_ma_spread_settings = "";
confirmation_settings.min_ma_spread_candle_limit = MA_SPREAD_CANDLE_LIMIT_1;
confirmation_settings.min_ma_spread_points = 0;
confirmation_settings.candle_body_settings = "";
confirmation_settings.check_candle_body = false;
confirmation_settings.alert_settings = "";
confirmation_settings.pop_up_alert = false;
confirmation_settings.push_alert = false;
confirmation_settings.mail_alert = false;
4. OnTick() – A kereskedési ciklus szíve
Az OnTick() az EA szíve — ez a függvény fut le minden egyes árticknél, és ez dönti el, mikor kell zárni egy pozíciót, mikor szabad újat nyitni, és mikor kell csak a BreakEven-t ellenőrizni. A forex programozás szempontjából ez a rész mutatja meg igazán, miért nem elég csak „megírni” egy jelkövető logikát: fegyelmezett sorrendhez és állapotkezeléshez van szükség, hogy a rendszer soha ne nyisson és zárjon egyszerre ellentmondó módon. Ez a lépésről lépésre felépülő logika a valódi kereskedőrobot fejlesztés és algoritmikus kereskedés egyik legjobb tanulságos példája.
4.1. Élő pozíciók számlálása és új gyertya észlelése
Az EA minden tickben megszámolja a nyitott kereskedéseket és összegyűjti azok profitjait is, így folyamatosan napra kész adatokkal ellátva hozhatja meg a megfelelő döntéseket.
Szintén minden ticken ellenőrizni kell, hogy egy korábban még élő gyertya lezáródott-e, így az új gyertya első tickjében már reagálni tudunk az új jelekre, amelyeket az indikátortól gyűjtünk be.
if(
!getTradeCount(Trade_MagicNumber, long_count, short_count, long_profit, short_profit, last_error_code, last_error_message, last_error_category)
){
sendCustomAlert("Trade collect error!", "Error during trade collecting! " + last_error_message, last_error_category, true, false, false);
return;
}
is_new_candle = false;
candle_0_time = iTime(_Symbol, _Period, 0);
if(
candle_0_time > 0
&& last_candle_time != candle_0_time
){
is_new_candle = last_candle_time > 0;
last_candle_time = candle_0_time;
}
4.2. Nyitott pozíciók zárása jelek alapján
Gyertyazáráskor lekérdezzük a vételi- és eladási zárójeleket — így ha bármikor zárást jelezne az indikátor, rögtön tudunk reagálni a kapott jelre. Ha történt zárás, akkor újraszámoltatjuk a trade-eket, hogy a nyitó feltételhez már frissített kötéslistával érkezzünk, a sikeres zárás állapotát az is_closed_trade boolean változó tárolja. Ez a fajta jelvezérelt zárási logika a forex robot készítés és stratégia automatizálás egyik kulcseleme.
if((long_count + short_count) > 0){
//get closing signs
buffer_close_long = getUniPulseValue(UNIPULSE_LINK, _Symbol, _Period, execution_settings, BUFFER_CLOSE_LONG, 1);
buffer_close_short = getUniPulseValue(UNIPULSE_LINK, _Symbol, _Period, execution_settings, BUFFER_CLOSE_SHORT, 1);
bool is_closed_trade = false;
if(
NormalizeDouble(buffer_close_long, digits) != NormalizeDouble(EMPTY_VALUE, digits)
&& long_count > 0
){
if(!closeTrade(OP_BUY, Trade_Slippage, Trade_MagicNumber, last_error_code, last_error_message, last_error_category)){
sendCustomAlert("Buy trade closing error!", last_error_message, last_error_category, true, false, false);
return;
}
is_closed_trade = true;
}
if(
NormalizeDouble(buffer_close_short, digits) != NormalizeDouble(EMPTY_VALUE, digits)
&& short_count > 0
){
if(!closeTrade(OP_SELL, Trade_Slippage, Trade_MagicNumber, last_error_code, last_error_message, last_error_category)){
sendCustomAlert("Sell trade closing error!", last_error_message, last_error_category, true, false, false);
return;
}
is_closed_trade = true;
}
if(is_closed_trade){
if(
!getTradeCount(Trade_MagicNumber, long_count, short_count, long_profit, short_profit, last_error_code, last_error_message, last_error_category)
){
sendCustomAlert("Trade collecting error!", "Error during trade collecting! " + last_error_message, last_error_category, true, false, false);
return;
}
}
}
4.3. Új pozíció nyitása és a megerősítő időkeret szerepe
Az új gyertya első tickjében, amennyiben nincs élő kereskedés, a stratégia beállítja a megerősítő időkeret irányát — ha be lett kapcsolva, majd lekérdezi az indikátor nyitó jeleit és ha valamelyik kereskedési irányra érkezett kereskedő jel, beköti a megfelelő pozíciót. Ez a kétlépcsős megerősítés jó példa arra, hogyan lehet egy egyszerű jelkövető indikátort felelős Expert programozás segítségével fegyelmezett algoritmikus kereskedés rendszerré alakítani.
if((long_count + short_count) == 0){ //open checking
bool is_trading_time = isTradingTime(Trading_StartHour, Trading_EndHour, TimeHour(TimeCurrent()));
if(is_trading_time){
buffer_open_long = getUniPulseValue(UNIPULSE_LINK, _Symbol, _Period, execution_settings, BUFFER_OPEN_LONG, 1);
buffer_open_short = getUniPulseValue(UNIPULSE_LINK, _Symbol, _Period, execution_settings, BUFFER_OPEN_SHORT, 1);
trend_direction = INDICATOR_TREND_DIRECTION_BOTH;
if(Use_Confirmation_Timeframe){
buffer_confirmation_trend = getUniPulseValue(UNIPULSE_LINK, _Symbol, Confirmation_Timeframe, confirmation_settings, BUFFER_TREND, 1);
if(
NormalizeDouble(buffer_confirmation_trend, digits) != NormalizeDouble(EMPTY_VALUE, digits)
){
trend_direction = (ENUM_INDICATOR_TREND_DIRECTION)buffer_confirmation_trend;
}else{
trend_direction = INDICATOR_TREND_DIRECTION_NONE;
}
}
if(
NormalizeDouble(buffer_open_long, digits) != NormalizeDouble(EMPTY_VALUE, digits)
&& (
trend_direction == INDICATOR_TREND_DIRECTION_BOTH
|| trend_direction == INDICATOR_TREND_DIRECTION_LONG
)
){//Open Long
if(
!openTrade(OP_BUY, Trade_Lot, Trade_StopLossPoints, Trade_TakeProfitPoints, Trade_Slippage, Trade_MagicNumber, Trade_Comment, last_error_code, last_error_message, last_error_category)
){
sendCustomAlert("Buy trade opening error!", last_error_message, last_error_category, true, false, false);
return;
}else{
sendCustomAlert(
"Buy Trade has opened!"
, StringFormat("Buy trade opened! Requested lot size:%s Stop:%s Profit:%s"
, DoubleToStr(Trade_Lot, lot_digits)
, Trade_StopLossPoints == 0 ? "-" : IntegerToString(Trade_StopLossPoints)
, Trade_TakeProfitPoints == 0 ? "-" : IntegerToString(Trade_TakeProfitPoints)
)
, last_error_category
, PopUpAlert
, PushAlert
, MailAlert
, false
);
}
}
else if(
NormalizeDouble(buffer_open_short, digits) != NormalizeDouble(EMPTY_VALUE, digits)
&& (
trend_direction == INDICATOR_TREND_DIRECTION_BOTH
|| trend_direction == INDICATOR_TREND_DIRECTION_SHORT
)
){//Open Short
if(
!openTrade(OP_SELL, Trade_Lot, Trade_StopLossPoints, Trade_TakeProfitPoints, Trade_Slippage, Trade_MagicNumber, Trade_Comment, last_error_code, last_error_message, last_error_category)
){
sendCustomAlert("Sell trade opening error!", last_error_message, last_error_category, true, false, false);
return;
}else{
sendCustomAlert(
"Sell Trade has opened!"
, StringFormat("Sell trade opened! Requested lot size:%s Stop:%s Profit:%s"
, DoubleToStr(Trade_Lot, lot_digits)
, Trade_StopLossPoints == 0 ? "-" : IntegerToString(Trade_StopLossPoints)
, Trade_TakeProfitPoints == 0 ? "-" : IntegerToString(Trade_TakeProfitPoints)
)
, last_error_category
, PopUpAlert
, PushAlert
, MailAlert
, false
);
}
}
}
}
4.4. BreakEven meghívása minden ticken
Mivel a BreakEven folyamatos figyelést igényel, az EA minden tickre ellenőrzi az élő kereskedés elért profitját, és ha az elérte a BreakEven aktiváló szintjét, akkor a kereskedés StopLoss-át a nyitó árához képest a BreakEven_StopLossPoint-ban beállított távolsággal eltolva biztosítja be az elért profit egy részének védelmét.
Továbbá fontos megjegyezni, hogy a BreakEven csak akkor kísérli meg a kereskedés StopLossának a módosítását, ha azt új, jobb helyre tudja állítani. Ezzel megelőzve, hogy a brókert érvénytelen hívások tucatjaival árassza el a rendszer.
if(
Use_BreakEven
&& (long_count + short_count) > 0
){
if(
!checkBreakEven(Trade_MagicNumber, BreakEven_ActivationPoint, BreakEven_StopLossPoint, last_error_code, last_error_message, last_error_category)
){
sendCustomAlert("BreakEven error!", last_error_message, last_error_category, true, false, false);
return;
}
}
5. Segédfüggvények – Pozíciók nyitása és zárása
5.1. getTradeCount() – nyitott pozíciók számbavétele
Végigmegy a teljes nyitott-megbízás listán (MODE_TRADES), és a szimbólum + magic number párost figyelve külön-külön összegzi a Long és Short pozíciók darabszámát, valamint lebegő profitját (profit + jutalék + swap). A magic number szűrés kulcsfontosságú: ez biztosítja, hogy az EA kizárólag a saját maga által nyitott pozíciókat számolja, egy esetleges kézi vagy másik robot általi kereskedést ne. Ez a fajta védekező programozás minden hosszú távon stabil kereskedőrobot fejlesztési projekt velejárója.
Tipp: ha ugyanazon a terméken, de más-más idősíkon vagy eltérő beállításokkal futtatnád a stratégiát, akkor győződj meg róla, hogy az egyik példánynál átállítottad a Trade_MagicNumber értékét, így nem fognak összekeveredni az azonos terméken nyitott kereskedések pozíciói.
/**
* @brief Counts currently open Buy/Sell positions for a given magic number on the current symbol,
* and accumulates their floating profit (including commission and swap).
*
* Iterates the full open-trades pool (MODE_TRADES) and filters by symbol and magic number.
* Both long and short positions are counted and summed independently.
*
* @param pmagic_number Magic number identifying trades belonging to this EA instance.
* @param plong_count [out] Number of open Buy positions matching symbol and magic number.
* @param pshort_count [out] Number of open Sell positions matching symbol and magic number.
* @param plong_profit [out] Sum of profit + commission + swap across all matching Buy positions.
* @param pshort_profit [out] Sum of profit + commission + swap across all matching Sell positions.
* @param perror_code [out] Error code from GetLastError() if OrderSelect() fails; ERR_NO_ERROR otherwise.
* @param perror_message [out] Human-readable description of perror_code.
* @param perror_category [out] Error category classification for alert throttling (COMMON_ERROR on failure).
*
* @return true if all open orders were enumerated successfully; false if OrderSelect() failed on any index,
* in which case the output count/profit values reflect only the orders processed before the failure.
*/
bool getTradeCount(uint pmagic_number, uint &plong_count, uint &pshort_count, double &plong_profit, double &pshort_profit, int &perror_code, string &perror_message, ENUM_ERROR_CATEGORY &perror_category){
perror_code = ERR_NO_ERROR;
perror_message = "";
perror_category = NO_ERROR;
plong_count = 0;
pshort_count = 0;
plong_profit = 0.0;
pshort_profit = 0.0;
for(int order_index = OrdersTotal() - 1; order_index >= 0; order_index--){
ResetLastError();
if(OrderSelect(order_index, SELECT_BY_POS, MODE_TRADES)){
if(
OrderSymbol() == _Symbol
&& OrderMagicNumber() == pmagic_number
){
if(OrderType() == OP_BUY){
plong_count++;
plong_profit += OrderProfit() + OrderCommission() + OrderSwap();
}else if(OrderType() == OP_SELL){
pshort_count++;
pshort_profit += OrderProfit() + OrderCommission() + OrderSwap();
}
}
}else{
perror_code = GetLastError();
perror_message = ErrorDescription(perror_code);
perror_category = COMMON_ERROR;
return false;
}
}
return true;
}
5.2. openTrade() – új pozíció nyitása védőellenőrzésekkel
Mielőtt egyetlen OrderSend() hívás is megtörténne, három védőellenőrzés fut le: a terminál kapcsolódik-e a szerverhez (IsConnected()), engedélyezett-e a kereskedés (IsTradeAllowed()), és nem foglalt-e éppen a kereskedési kontextus (IsTradeContextBusy()). Ezután a kért lotméretet a szimbólum lot-lépésére kerekíti, és a bróker min/max lot-határai közé szorítja.
A StopLoss és TakeProfit árakat pontban kapja meg, amiket az EA a nyitási ártól számítva, a bróker minimális stop-szintjét (stoplevel_point) figyelembe véve, a tick-mérethez igazítva normalizál. Megjegyzendő tervezési döntés: mivel az EA egyszerre csak egyetlen pozíciót tart nyitva, szabad margin-ellenőrzés szándékosan nincs benne — ez a leegyszerűsítés kifejezetten bemutatási célt szolgál. Ez a fajta védőellenőrzés-lánc a felelős Expert programozás és kereskedőrobot fejlesztés egyik legfontosabb alapköve.
/**
* @brief Opens a new market position after validating trading preconditions, and computes its
* stop-loss/take-profit prices from point offsets.
*
* Verifies terminal connectivity, that trading is allowed, and that the trade context is not busy
* before requesting a fresh tick quote. The requested lot size is rounded to the symbol's lot step
* and clamped to [min_lot, max_lot]. Stop-loss and take-profit prices (when their respective point
* offsets are non-zero) are computed relative to the fill price, widened if necessary to respect the
* broker's minimum stop level (stoplevel_point), and normalized to the symbol's tick size.
*
* Note: as this EA is designed to hold only one position at a time, no free-margin check is
* performed before sending the order (by design, for educational purposes).
*
* @param ptype Position type to open; must be OP_BUY or OP_SELL.
* @param plots Requested lot size (pre-normalization).
* @param pstoploss_point Stop-loss offset from the fill price, in points (0 disables the stop-loss).
* @param ptakeprofit_point Take-profit offset from the fill price, in points (0 disables the take-profit).
* @param pslippage Maximum allowed slippage, in points.
* @param pmagic_number Magic number to tag the new order with.
* @param pcomment Order comment text.
* @param perror_code [out] Error code on failure; ERR_NO_ERROR on success.
* @param perror_message [out] Human-readable description of the failure.
* @param pcategory [out] Error category classification for alert throttling
* (always TRADE_OPENING_ERROR on failure).
*
* @return true if the order was sent successfully (OrderSend() returned a valid ticket);
* false if connectivity/trading-allowed/context-busy checks failed, the tick could not
* be retrieved, ptype was invalid, or OrderSend() itself failed.
*/
bool openTrade(int ptype, double plots, int pstoploss_point, int ptakeprofit_point, int pslippage, int pmagic_number, string pcomment, int &perror_code, string &perror_message, ENUM_ERROR_CATEGORY &pcategory){
perror_code = ERR_NO_ERROR;
perror_message = "";
pcategory = NO_ERROR;
if(!IsConnected()){
perror_code = ERR_TRADE_DISABLED;
perror_message = StringFormat("%s trade open error! Connection lost!", EnumToString((ENUM_ORDER_TYPE)ptype));
pcategory = TRADE_OPENING_ERROR;
return false;
}
if(!IsTradeAllowed()){
perror_code = ERR_TRADE_DISABLED;
perror_message = "Open error! Trade is disabled!";
pcategory = TRADE_OPENING_ERROR;
return false;
}
if(IsTradeContextBusy()){
perror_code = ERR_TRADE_CONTEXT_BUSY;
perror_message = "Open error! Trade context is busy!";
pcategory = TRADE_OPENING_ERROR;
return false;
}
double lot = NormalizeDouble(MathMin(max_lot, MathMax(min_lot, MathRound(plots / lot_step) * lot_step)), lot_digits);
MqlTick tick_struct;
ResetLastError();
if(!SymbolInfoTick(_Symbol, tick_struct)){
perror_code = GetLastError();
perror_message = StringFormat("Price asking error (%d)! Error: %s", perror_code, ErrorDescription(perror_code));
pcategory = TRADE_OPENING_ERROR;
return false;
}
double open_price = -1.0;
if(ptype == OP_BUY)
open_price = tick_struct.ask;
else if(ptype == OP_SELL)
open_price = tick_struct.bid;
else{
perror_code = ERR_COMMON_ERROR;
perror_message = StringFormat("Invalid trade type %s(%d)!", EnumToString((ENUM_ORDER_TYPE)ptype), ptype);
pcategory = TRADE_OPENING_ERROR;
return false;
}
if(
open_price > 0.0
){
double stoploss_price = 0.0;
if(pstoploss_point > 0){
if(ptype == OP_BUY)
stoploss_price = NormalizeDouble(MathRound((open_price - (int)MathMax(stoplevel_point, pstoploss_point) * point) / tick_size) * tick_size, digits);
else
stoploss_price = NormalizeDouble(MathRound((open_price + (int)MathMax(stoplevel_point, pstoploss_point) * point) / tick_size) * tick_size, digits);
stoploss_price = MathMax(point, stoploss_price);
}
double takeprofit_price = 0.0;
if(ptakeprofit_point > 0){
if(ptype == OP_BUY)
takeprofit_price = NormalizeDouble(MathRound((open_price + (int)MathMax(stoplevel_point, ptakeprofit_point) * point) / tick_size) * tick_size, digits);
else
takeprofit_price = NormalizeDouble(MathRound((open_price - (int)MathMax(stoplevel_point, ptakeprofit_point) * point) / tick_size) * tick_size, digits);
takeprofit_price = MathMax(point, takeprofit_price);
}
ResetLastError();
int ticket = OrderSend(_Symbol, ptype, lot, open_price, pslippage, stoploss_price, takeprofit_price, pcomment, pmagic_number, 0, ptype == OP_BUY ? clrGreen : clrRed);
if(ticket <= 0){
perror_code = GetLastError();
perror_message = StringFormat("%s open error! Lot:%f(%d) Open:%f Sl:%f Tp:%f! Error:%s", EnumToString((ENUM_ORDER_TYPE)ptype), lot, lot_digits, open_price, stoploss_price, takeprofit_price, ErrorDescription(perror_code));
pcategory = TRADE_OPENING_ERROR;
return false;
}
}else{
perror_code = ERR_COMMON_ERROR;
perror_message = StringFormat("Invalid open price %f!", open_price);
pcategory = TRADE_OPENING_ERROR;
return false;
}
return true;
}
5.3. closeTrade() – pozíciók zárása
Végigmegy a nyitott megbízásokon, és minden, a megadott típusnak (Buy vagy Sell), szimbólumnak és magic number-nek megfelelő pozíciót lezár a saját lotméretével és aktuális zárási árával. Ha egy adott OrderClose() hívás elbukik, a hiba rögzítésre kerül, de a ciklus nem áll le — a többi, még nyitva lévő, egyező pozíció lezárását is megkísérli a függvény, mielőtt hibával térne vissza. Ez a hibatűrő megközelítés a valós piaci forex robot készítés egyik gyakorlati tanulsága: egy hívás sikertelensége nem béníthatja meg a teljes rendszert.
/**
* @brief Closes all open positions of a given type matching a magic number on the current symbol.
*
* Iterates the open-trades pool (MODE_TRADES) and closes every matching position individually via
* OrderClose(), using each order's own lot size and current close price. As with checkBreakEven(),
* a single OrderClose() failure is recorded but does not stop the loop from attempting to close the
* remaining matching positions.
*
* @param ptype Position type to close; must be OP_BUY or OP_SELL.
* @param pslippage Maximum allowed slippage, in points.
* @param pmagic_number Magic number identifying trades belonging to this EA instance.
* @param perror_code [out] Error code from the last failed OrderClose() call, or ERR_COMMON_ERROR
* if ptype was invalid; ERR_NO_ERROR on full success.
* @param perror_message [out] Human-readable description of the last error encountered.
* @param perror_category [out] Error category classification for alert throttling
* (always TRADE_CLOSING_ERROR on failure).
*
* @return true if ptype was valid and every matching position was closed successfully;
* false if ptype was not OP_BUY/OP_SELL, or if at least one matching position failed to close
* (processing still continues for the remaining positions in the latter case).
*/
bool closeTrade(int ptype, uint pslippage, uint pmagic_number, int &perror_code, string &perror_message, ENUM_ERROR_CATEGORY &perror_category){
perror_code = ERR_NO_ERROR;
perror_message = "";
perror_category = NO_ERROR;
bool was_error = false;
//Invalid type
if(
ptype != OP_BUY
&& ptype != OP_SELL
){
perror_code = ERR_COMMON_ERROR;
perror_message = StringFormat("Invalid type for trade closing(%d)!", ptype);
perror_category = TRADE_CLOSING_ERROR;
return false;
}
for(int order_index = OrdersTotal() - 1; order_index >= 0; order_index--){
ResetLastError();
if(OrderSelect(order_index, SELECT_BY_POS, MODE_TRADES)){
if(
OrderSymbol() == _Symbol
&& OrderMagicNumber() == pmagic_number
&& OrderType() == ptype
){
ResetLastError();
if(!OrderClose(OrderTicket(), NormalizeDouble(OrderLots(), lot_digits), NormalizeDouble(OrderClosePrice(), digits), pslippage, ptype == OP_BUY ? clrGreen : clrRed)){
perror_code = GetLastError();
perror_message = StringFormat("Order closing error! Ticket:%d Error:%s (%d)", OrderTicket(), ErrorDescription(perror_code), perror_code);
perror_category = TRADE_CLOSING_ERROR;
was_error = true;
}
}
}
}
return !was_error;
}
6. Segédfüggvények – BreakEven és kereskedési idősáv
6.1. getProfitPointAndStopPrice() és checkBreakEven() – a nullázási logika
A getProfitPointAndStopPrice() egyetlen pozícióra számítja ki a jelenlegi lebegő profitot pontban, és egy jelölt StopLoss-árat, amit a nyitási ártól pstoploss_point pontnyira, a profitos irányba helyez. Ezt a jelölt árat a függvény a bróker minimális stop-szintjéhez igazítva szükség esetén távolabbra tolja, majd a szimbólum tick-méretéhez kerekíti.
/**
* @brief Computes the current floating profit (in points) and the candidate breakeven stop-loss price
* for a single position, given a desired stop-loss offset from the open price.
*
* The candidate stop-loss is first placed pstoploss_point points beyond the open price (in the
* profitable direction), then clamped so it never sits closer to the current market price than the
* broker's minimum stop level (stoplevel_point), and finally normalized to the symbol's tick size.
*
* @param ptype Position type; must be OP_BUY or OP_SELL.
* @param popen_price Position's open price.
* @param pstoploss_point Desired stop-loss offset from the open price, in points.
* @param pprofit_point [out] Current floating profit in points (0 if open price equals current price).
* @param pstoploss_price [out] Candidate stop-loss price, normalized to tick size and digits.
* @param perror_code [out] Error code; ERR_INVALID_FUNCTION_PARAMVALUE if ptype is not OP_BUY/OP_SELL.
* @param perror_message [out] Human-readable error description.
* @param perror_category [out] Error category classification for alert throttling.
*
* @return true if the profit point and stop-loss price were computed successfully; false if ptype
* is not a supported order type (in which case pprofit_point and pstoploss_price are undefined
* beyond their default sentinel values).
*/
bool getProfitPointAndStopPrice(int ptype, double popen_price, int pstoploss_point, int &pprofit_point, double &pstoploss_price, int &perror_code, string &perror_message, ENUM_ERROR_CATEGORY &perror_category){
perror_code = ERR_NO_ERROR;
perror_message = "";
perror_category = NO_ERROR;
int new_stoploss_in_point;
double close_price;
pprofit_point = 0;
pstoploss_price = -1.0;
if(ptype == OP_BUY){
close_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
pstoploss_price = popen_price + pstoploss_point * point;
new_stoploss_in_point = (int)((close_price - pstoploss_price) / point);
if(new_stoploss_in_point <= stoplevel_point){
pstoploss_price = close_price - stoplevel_point * point;
}
pstoploss_price = NormalizeDouble(MathRound(pstoploss_price / tick_size) * tick_size, digits);
if(
NormalizeDouble(popen_price, digits) != NormalizeDouble(close_price, digits)
){
pprofit_point = (int)MathRound((close_price - popen_price) / point);
}
}else if(ptype == OP_SELL){
close_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
pstoploss_price = popen_price - pstoploss_point * point;
new_stoploss_in_point = (int)((pstoploss_price - close_price) / point);
if(new_stoploss_in_point <= stoplevel_point){
pstoploss_price = close_price + stoplevel_point * point;
}
pstoploss_price = NormalizeDouble(MathRound(pstoploss_price / tick_size) * tick_size, digits);
if(
NormalizeDouble(popen_price, digits) != NormalizeDouble(close_price, digits)
){
pprofit_point = (int)MathRound((popen_price - close_price) / point);
}
}else{
perror_code = ERR_INVALID_FUNCTION_PARAMVALUE;
perror_message = StringFormat("Not Supported trade type(%s)! %s", EnumToString((ENUM_ORDER_TYPE)ptype), __FUNCTION__);
perror_category = TRADE_MODIFICATION_ERROR;
return false;
}
return true;
}
A checkBreakEven() minden, a magic number-nek megfelelő nyitott pozíción végigmegy, és csak akkor módosítja a StopLoss-t, ha a lebegő profit elérte az aktiváló küszöböt (BreakEven_ActivationPoint), és az új StopLoss valódi javulást jelent a régihez képest (vagy a pozíciónak még egyáltalán nincs StopLoss-a). Ez a kettős feltétel védi a pozíciót attól, hogy a BreakEven véletlenül rontson a meglévő StopLoss-on. Egyetlen OrderModify() hiba itt sem szakítja meg a ciklust — a többi pozíció feldolgozása folytatódik. Ez a fajta óvatos állapotkezelés minden éles stratégia automatizálás és algoritmikus kereskedés rendszer gerince.
/**
* @brief Applies breakeven stop-loss management to all open positions matching a magic number.
*
* For each matching Buy/Sell position, computes the current profit in points and a candidate
* stop-loss price via getProfitPointAndStopPrice(). If the profit has reached pactivator_point and
* the candidate stop-loss represents an improvement over the position's current stop-loss (or the
* position has no stop-loss yet), the stop-loss is shifted via OrderModify(). Take-profit and open
* price are left unchanged.
*
* Failures are aggregated: a single OrderModify() failure is recorded and reported, but does not
* stop processing of the remaining open positions in the loop.
*
* @param pmagic_number Magic number identifying trades belonging to this EA instance.
* @param pactivator_point Minimum floating profit (in points) required before breakeven engages.
* @param pstoploss_shift_point Offset from the open price (in points) used to compute the new stop-loss.
* @param perror_code [out] Error code from the last failed OrderSelect()/OrderModify() call.
* @param perror_message [out] Human-readable description of the last error encountered.
* @param perror_category [out] Error category classification for alert throttling.
*
* @return true if OrderSelect() succeeded for every open order and no OrderModify() call failed;
* false if OrderSelect() failed outright, or if at least one OrderModify() call failed
* (processing still continues for the remaining orders in the latter case).
*/
bool checkBreakEven(uint pmagic_number, int pactivator_point, int pstoploss_shift_point, int &perror_code, string &perror_message, ENUM_ERROR_CATEGORY &perror_category){
perror_code = ERR_NO_ERROR;
perror_message = "";
perror_category = NO_ERROR;
int profit_point;
double trade_stoploss_price
, new_stoploss_price
;
bool was_error = false;
for(int order_index = OrdersTotal() - 1; order_index >= 0; order_index--){
ResetLastError();
if(OrderSelect(order_index, SELECT_BY_POS, MODE_TRADES)){
if(
OrderSymbol() == _Symbol
&& OrderMagicNumber() == pmagic_number
&& (OrderType() == OP_BUY || OrderType() == OP_SELL)
){
trade_stoploss_price = OrderStopLoss();
if(
!getProfitPointAndStopPrice(OrderType(), OrderOpenPrice(), pstoploss_shift_point, profit_point, new_stoploss_price, perror_code, perror_message, perror_category)
){
return false;
}
if(
profit_point >= pactivator_point
&& new_stoploss_price > 0.0
){
if(
(
OrderType() == OP_BUY
&& (
NormalizeDouble(trade_stoploss_price, digits) == NormalizeDouble(0.0, digits)
|| (
NormalizeDouble(trade_stoploss_price, digits) > NormalizeDouble(0.0, digits)
&& NormalizeDouble(new_stoploss_price, digits) > NormalizeDouble(trade_stoploss_price, digits)
)
)
) || (
OrderType() == OP_SELL
&& (
NormalizeDouble(trade_stoploss_price, digits) == NormalizeDouble(0.0, digits)
|| (
NormalizeDouble(trade_stoploss_price, digits) > NormalizeDouble(0.0, digits)
&& NormalizeDouble(new_stoploss_price, digits) < NormalizeDouble(trade_stoploss_price, digits)
)
)
)
){
ResetLastError();
if(
!OrderModify(OrderTicket(), NormalizeDouble(OrderOpenPrice(), digits), NormalizeDouble(new_stoploss_price, digits), NormalizeDouble(OrderTakeProfit(), digits), OrderExpiration(), OrderType() == OP_BUY ? clrGreen : clrRed)
){
perror_code = GetLastError();
perror_message = StringFormat(
"BreakEven error! Type:%s original stop:%s new stop:%s Error:%s (%d)"
, OrderType() == OP_BUY ? "buy" : "sell"
, NormalizeDouble(OrderStopLoss(), digits) > NormalizeDouble(0.0, digits) ? DoubleToStr(OrderStopLoss(), digits) : "-"
, NormalizeDouble(new_stoploss_price, digits) > NormalizeDouble(0.0, digits) ? DoubleToStr(new_stoploss_price, digits) : "-"
, ErrorDescription(perror_code)
, perror_code
);
perror_category = TRADE_MODIFICATION_ERROR;
was_error = true;
}
}
}
}
}else{
perror_code = GetLastError();
perror_message = ErrorDescription(perror_code);
perror_category = TRADE_MODIFICATION_ERROR;
return false;
}
}
return !was_error;
}
6.2. isTradingTime() – kereskedési idősáv, éjszakán átnyúló tartománnyal
Egyetlen összehasonlítás dönti el, hogy egy adott óra a megengedett kereskedési ablakba esik-e — de a logika kezeli azt az esetet is, amikor a záró óra kisebb, mint a kezdő óra (pl. 22-től 6-ig), vagyis a sáv átnyúlik éjfélen. Ha a kezdő és záró óra megegyezik, az EA megállapodás szerint nem korlátoz semmit. Ez a fajta időzítési finomság gyakran hiányzik a kezdő szintű kereskedőrobot fejlesztés próbálkozásokból, pedig a valós piaci algoritmikus kereskedés stratégiák elengedhetetlen része.
/**
* @brief Determines whether a given chart hour falls within a configured trading window.
*
* Supports overnight (wrap-around) windows where pend_hour is smaller than pstart_hour
* (e.g. 22 to 6 covers 22:00-23:59 and 00:00-05:59). If pstart_hour equals pend_hour,
* trading is treated as unrestricted (always allowed) by convention.
*
* @param pstart_hour Trading window start hour (0-23, inclusive).
* @param pend_hour Trading window end hour (0-23, exclusive).
* @param pchart_hour Hour to test, typically TimeHour(TimeCurrent()).
*
* @return true if pchart_hour lies within the configured window (or if pstart_hour == pend_hour);
* false otherwise.
*/
bool isTradingTime(int pstart_hour, int pend_hour, int pchart_hour){
if(pstart_hour == pend_hour)
return true;
else if(pend_hour > pstart_hour)
return pchart_hour >= pstart_hour && pchart_hour < pend_hour;
else
return pchart_hour >= pstart_hour || pchart_hour < pend_hour;
}
7. Segédfüggvények – Szimbólum-tulajdonságok és biztonságos indikátor-hívás
7.1. loadSymbolProperties() – pont, tick-méret és lot-határok betöltése
Ez a függvény egyszer, az OnInit()-ből fut le, és a szimbólum azon tulajdonságait olvassa be és validálja, amik a chart teljes élettartama alatt stabilak maradnak: point-méret, tick-méret, minimum/maximum lot, lot-lépés és a bróker stop-szintje. Minden egyes érték beolvasása után azonnali, önálló ellenőrzés fut — ha bármelyik érvénytelen, a függvény azonnal, a hibát pontosan megnevezve tér vissza, ahelyett hogy csendben hibás eredményt produkálnánk. Ez a szigorú validálás minden felelős Expert programozás és forex robot készítés projekt előfeltétele.
/**
* @brief Loads and caches the current symbol's trading properties into global state.
*
* Reads point size, tick size, minimum/maximum lot size, lot step, and the broker's minimum stop
* level via SymbolInfoDouble()/SymbolInfoInteger(), validating that each value is sane (point,
* tick_size, min_lot, max_lot, and lot_step must all be strictly positive; stoplevel_point must be
* non-negative). Also derives and caches the symbol's price digits (_Digits) and the lot-size
* decimal-digit precision via getLotDigits(). Intended to be called once from OnInit(), since these
* properties are stable for the lifetime of the chart/symbol.
*
* @param perror_code [out] Error code from GetLastError() on the first failing property read;
* ERR_NO_ERROR if all properties were loaded successfully.
* @param perror_message [out] Human-readable description identifying which property was invalid.
* @param perror_category [out] Error category classification for alert throttling (COMMON_ERROR on failure).
*
* @return true if all symbol properties were read and validated successfully; false on the first
* invalid/unreadable property encountered (remaining properties are left unset).
*/
bool loadSymbolProperties(int &perror_code, string &perror_message, ENUM_ERROR_CATEGORY &perror_category){
perror_code = ERR_NO_ERROR;
perror_message = "";
perror_category = NO_ERROR;
ResetLastError();
point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(point <= 0){
perror_code = GetLastError();
perror_message = StringFormat("Invalid point value(%f)! Error:%s", point, ErrorDescription(perror_code));
perror_category = COMMON_ERROR;
return false;
}
ResetLastError();
tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tick_size <= 0){
perror_code = GetLastError();
perror_message = StringFormat("Invalid tick size value(%f)! Error:%s", tick_size, ErrorDescription(perror_code));
perror_category = COMMON_ERROR;
return false;
}
ResetLastError();
min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
if(min_lot <= 0){
perror_code = GetLastError();
perror_message = StringFormat("Invalid minimal lot value(%f)!", min_lot);
perror_category = COMMON_ERROR;
return false;
}
ResetLastError();
max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
if(max_lot <= 0){
perror_code = GetLastError();
perror_message = StringFormat("Invalid maximal lot value(%f)!", max_lot);
perror_category = COMMON_ERROR;
return false;
}
ResetLastError();
lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(lot_step <= 0){
perror_code = GetLastError();
perror_message = StringFormat("Invalid lot step value(%f)!", lot_step);
perror_category = COMMON_ERROR;
return false;
}
ResetLastError();
stoplevel_point = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
if(stoplevel_point < 0){
perror_code = GetLastError();
perror_message = StringFormat("Invalid stoplevel value(%d)!", stoplevel_point);
perror_category = COMMON_ERROR;
return false;
}
digits = _Digits;
lot_digits = getLotDigits(min_lot, max_lot, lot_step);
return true;
}
7.2. getDecimalDigits() és getLotDigits() – lot-méret tizedesjegy-pontosság
A bróker lot-határai (minimum, maximum, lépésköz) nem feltétlenül igazodnak a szimbólum árfolyam-tizedesjegyeihez (_Digits) — ezért az EA külön számolja ki, hány tizedesjegy kell a lot-értékek pontos, kerekítés nélküli megjelenítéséhez és normalizálásához. A getDecimalDigits() egyetlen értékre próbál egyre több tizedesjegyet, amíg a kerekített érték meg nem egyezik az eredetivel; a getLotDigits() ezt mindhárom lot-határértékre lefuttatja, és a legnagyobb szükséges pontosságot adja vissza. Ez a fajta aprólékos részletkezelés különbözteti meg az amatőr scriptelést a valódi kereskedőrobot fejlesztés és algoritmikus kereskedés szakértelemtől.
/**
* @brief Determines the number of significant decimal digits required to represent a value exactly.
*
* Incrementally tests rounding pvalue to 0, 1, 2, ... decimal digits (up to a maximum of 8) and
* returns the first digit count at which the rounded value matches the original within a small
* epsilon tolerance (1e-12). Used to derive display/normalization precision for broker-supplied
* lot-size values (min/max/step), which are not guaranteed to align with the symbol's price digits.
*
* @param pvalue Value to test (e.g. a lot size boundary such as SYMBOL_VOLUME_MIN).
*
* @return Number of decimal digits (0-8) needed to represent pvalue exactly within tolerance.
* Returns 8 if no exact match is found within that range.
*/
int getDecimalDigits(double pvalue){
int dec_digits = 0;
while(dec_digits < 8){
if(MathAbs(pvalue - NormalizeDouble(pvalue, dec_digits)) < 1e-12)
return dec_digits;
dec_digits++;
}
return 8;
}
/**
* @brief Determines the number of decimal digits needed to represent a symbol's lot-size grid exactly.
*
* Computes the decimal-digit requirement (via getDecimalDigits()) for each of the minimum lot,
* maximum lot, and lot step values individually, and returns the largest of the three. This ensures
* lot sizes normalized with the resulting digit count are never truncated for any of the three
* boundary values.
*
* @param pmin Symbol's minimum tradable lot size (SYMBOL_VOLUME_MIN).
* @param pmax Symbol's maximum tradable lot size (SYMBOL_VOLUME_MAX).
* @param pstep Symbol's lot step increment (SYMBOL_VOLUME_STEP).
*
* @return Number of decimal digits required to represent pmin, pmax, and pstep exactly (0-8).
*/
int getLotDigits(double pmin, double pmax, double pstep){
return MathMax(MathMax(getDecimalDigits(pmin), getDecimalDigits(pmax)), getDecimalDigits(pstep));
}
7.3. getUniPulseValue() – biztonságos iCustom() hívás
Ahelyett, hogy a hosszú, húsz paraméteres iCustom() hívás minden előfordulási helyén megismétlődne, ez az egyetlen segédfüggvény csomagolja ki az sIndicatorSettings struktúrát a teljes paraméterlistává, és egyetlen helyen végzi el a határellenőrzést: ha a szimbólumon/időkereten nincs elég gyertya, vagy a kért index a rendelkezésre álló tartományon kívül esik, a függvény EMPTY_VALUE-t ad vissza ahelyett, hogy hibás vagy véletlenszerű adatot olvasna ki.
/**
* @brief Retrieves a single buffer value from the UniPulse custom indicator, with bounds checking.
*
* Wraps iCustom(), unpacking the sIndicatorSettings structure into the full ordered parameter list
* expected by the UniPulse indicator. Guards against requesting an index beyond the available bar
* count on the target symbol/timeframe.
*
* @param pname Indicator resource path/link (typically UNIPULSE_LINK, the compiled resource).
* @param psymbol Symbol to evaluate the indicator on.
* @param ptimeframe Timeframe to evaluate the indicator on.
* @param pset Indicator settings (MA/ATR/spread/candle-body/alert configuration) to apply.
* @param pmode Indicator buffer index to read (e.g. BUFFER_OPEN_LONG, BUFFER_CLOSE_SHORT, BUFFER_TREND).
* @param pindex Bar index to read, following the indicator's series convention (0 = current bar).
*
* @return The requested buffer value at pindex, as returned by iCustom(). Returns EMPTY_VALUE if
* no bars are available on psymbol/ptimeframe, or if pindex is out of the available range.
*/
double getUniPulseValue(string pname, string psymbol, int ptimeframe, const sIndicatorSettings &pset, int pmode, int pindex){
int bars = iBars(psymbol, ptimeframe);
if(
bars <= 0
|| pindex < 0
|| pindex >= bars
){
return EMPTY_VALUE;
}
return iCustom(
psymbol
, ptimeframe
, pname
, pset.candles
, pset.mas_settings
, pset.fast_ma_period
, pset.slow_ma_period
, pset.ma_method
, pset.ma_price
, pset.ma_distance_limit_point
, pset.atr_settings
, pset.atr_period
, pset.atr_smoothing_period
, pset.atr_smoothing_method
, pset.min_ma_spread_settings
, pset.min_ma_spread_candle_limit
, pset.min_ma_spread_points
, pset.candle_body_settings
, pset.check_candle_body
, pset.alert_settings
, pset.pop_up_alert
, pset.push_alert
, pset.mail_alert
, pmode
, pindex
);
}
8. Riasztási rendszer – sendCustomAlert()
Egy éles kereskedési robotnál a hibakezelés ugyanolyan fontos, mint maga a kereskedési logika — de egy riasztás-spam ugyanolyan veszélyes, mint a riasztás hiánya, mert elfedi a valóban fontos üzeneteket. A sendCustomAlert() ezért nem egyszerűen kiírja az üzenetet, hanem kategorizáltan (Common, Trade Opening, Trade Modification, Trade Closing) figyeli, mikor riasztott utoljára.
Ha egy adott hibakategória 60 másodpercen belül ismét bekövetkezik, a riasztás elnémul minden csatornán (popup, print, push, e-mail) — de amint egy másik kategóriájú hiba jelentkezik, az azonnal, a cooldown-tól függetlenül átüti magát. Így egy folyamatosan ismétlődő, ártalmatlan hiba nem tudja elfedni egy újonnan felbukkanó, súlyosabb probléma jelzését. Teszterben (Strategy Tester / optimalizáció) a push és e-mail csatornák automatikusan kikapcsolnak, és a Time[0] szimulált idő szolgál időbélyegként a valós TimeCurrent() helyett.
/**
* @brief Sends a de-duplicated alert to the terminal via Print(), Alert(), SendNotification()
* and/or SendMail(), depending on which channels are requested.
*
* Behavior differs by execution context:
* - In Strategy Tester / optimization mode, push and mail are always suppressed; the message is
* printed to the Experts log when popup is off, and popped up (Alert()) otherwise.
* - On a live/demo chart, the message is printed to the log only when none of popup/push/mail
* are requested; otherwise it is delivered exclusively through the requested channel(s).
*
* When pcheck_period is true, repeated alerts of the same pcategory within alert_period (60s by
* default, tracked via last_alert_time/stored_error_category) are suppressed entirely across all
* channels. This throttling is bypassed (i.e. the alert always fires) whenever pcategory differs
* from the previously stored category, so a newly occurring error type is never silenced by an
* unrelated error's cooldown. Passing pcheck_period=false disables throttling for that call
* (e.g. for one-off initialization failures or trade-open confirmations that should never be
* suppressed).
*
* @param psubject Subject line, used only for SendMail(); prefixed internally with the EA
* name and symbol/timeframe.
* @param pmessage Body text delivered to whichever channel(s) are active; prefixed internally
* with the EA name and symbol/timeframe.
* @param pcategory Error category used for throttling comparisons against stored_error_category.
* Defaults to COMMON_ERROR.
* @param ppopup Whether to raise a popup Alert(). Default: true.
* @param ppush Whether to send a push notification via SendNotification(). Default: false.
* @param pmail Whether to send an e-mail via SendMail(). Default: false.
* @param pcheck_period Whether to apply the 60-second same-category throttling. Default: true.
*/
void sendCustomAlert(const string psubject, const string pmessage, const ENUM_ERROR_CATEGORY pcategory = COMMON_ERROR, bool ppopup = true, bool ppush = false, bool pmail = false, bool pcheck_period = true){
bool is_testing = IsTesting() || IsOptimization();
bool print = false;
bool popup = ppopup;
bool push = ppush;
bool mail = pmail;
datetime current_time = TimeCurrent();
string message_pre = MQLInfoString(MQL_PROGRAM_NAME) + " " + _Symbol + "_" + EnumToString((ENUM_TIMEFRAMES)_Period) + " ";
//testing mode
if(is_testing){
current_time = Time[0]; //set Time[0]
push = false; //push off
mail = false; //mail off
print = !popup; //print or popup
if(
pcheck_period
&& pcategory == stored_error_category
&& last_alert_time >= (current_time - alert_period)
){
print = false; //print off
popup = false; //popup off
}else{
if(pcheck_period){
last_alert_time = current_time;
stored_error_category = pcategory;
}
}
}else{//real chart mode
print = !popup && !push && !mail;
if(
pcheck_period
&& pcategory == stored_error_category
&& last_alert_time >= (current_time - alert_period)
){
print = false;
popup = false;
push = false;
mail = false;
}else{
if(pcheck_period){
last_alert_time = current_time;
stored_error_category = pcategory;
}
}
}
string subject = message_pre + psubject;
string message = message_pre + pmessage;
if(print) Print(message);
if(popup) Alert(message);
if(push){
ResetLastError();
if(!SendNotification(message)){
last_error_code = GetLastError();
last_error_message = ErrorDescription(last_error_code);
Alert(message_pre + " Push Alert Error! Message:" + message + " Error:" + last_error_message + "(" + IntegerToString(last_error_code) + ")");
}
}
if(mail){
ResetLastError();
if(!SendMail(subject, message)){
last_error_code = GetLastError();
last_error_message = ErrorDescription(last_error_code);
Alert(message_pre + " E-Mail Sending Error! Message:" + message + " Error:" + last_error_message + "(" + IntegerToString(last_error_code) + ")");
}
}
}
9. Összefoglalás és Letöltés – Az UniPulse EA Telepítése
Ezzel a teljes UniPulse EA forráskódját és a mögötte húzódó logikát végigjártuk — a beágyazott indikátor-erőforrástól és a puffer-architektúrától kezdve, az OnInit() és OnTick() működésén át, egészen a pozíciókezelő, BreakEven- és riasztási segédfüggvényekig. Mivel az EA a UniPulse indikátorra épül, érdemes mindkét oldalt együtt kezelni: ha egy jelzés nem úgy viselkedik, ahogy vártad, gyakran az indikátor oldalán található a magyarázat, nem az EA oldalán.
9.1. A csomag tartalma
- UniPulse_EA.mq4 – a teljes, kommentezett forráskód, amit ezen az oldalon is végigjártunk
- UniPulse_EA.ex4 – előre lefordított, futtatható verzió, a UniPulse indikátorral együtt beágyazva (nem szükséges hozzá külön indikátor-fájl)
9.2. Telepítés lépésről lépésre
9.2.1. Ha a kész .ex4 fájlt használnád:
Másold az UniPulse_EA.ex4 fájlt a MetaTrader 4 adatmappájának MQL4/Experts almappájába. Az adatmappát a terminálon belül a Fájl → Rendszermappa megnyitása menüponttal találod meg legegyszerűbben.
9.2.2. Ha a forráskódot szeretnéd magad lefordítani:
Ehhez a UniPulse indikátor .ex4 fájljának is jelen kell lennie a MQL4/Indicators mappában, pontosan a fájl elején található UNIPULSE_PATH define által megadott elérési úton — enélkül a fordítás a #resource direktívánál hibával leáll. Ha ez megvan
- Másold a
UniPulse_EA.mq4fájlt aMQL4/Expertsmappába. - Nyisd meg a MetaEditor-t (MT4-ben az Eszközök → MQL4 Szerkesztő, vagy egyszerűen F4).
- Nyisd meg benne a
UniPulse_EA.mq4fájlt, és nyomj F7-et a fordításhoz. - Ha a fordítás „0 error(s), 0 warning(s)” eredménnyel zárul, az EA készen áll a használatra.
9.2.3. Az EA aktiválása a charton:
Indítsd újra a MetaTrader 4-et (vagy frissítsd a Navigátor ablakot), majd húzd rá a UniPulse EA-t a kívánt szimbólum és időkeret charjára a Navigátor ablakból. A beállítások fülön ellenőrizd, hogy az Algo Trading (automatikus kereskedés) engedélyezve van-e a terminálban.





