//+------------------------------------------------------------------+ //| ExpGRI_AMA_TEMA.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include #include //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ //--- Состояния GRI enum ENUM_GRI_STATE { GRI_STATE_UNKNOWN, // Нет данных GRI_STATE_FLAT, // Низкая волатильность GRI_STATE_VOLATILITY_HIGH, // Высокая волатильность GRI_STATE_GROWING, // Волатильность растёт GRI_STATE_FALLING, // Волатильность падает GRI_STATE_TURN_TO_GROW, // Переход к росту волатильности GRI_STATE_TURN_TO_FALL // Переход к снижению волатильности }; //--- Типы сигналов скользящих средних enum ENUM_SIGNAL_TYPE { SIGNAL_TYPE_NONE, // Нет сигнала SIGNAL_TYPE_LONG, // Сигнал на покупку SIGNAL_TYPE_SHORT, // Сигнал на продажу SIGNAL_TYPE_CLOSE, // Сигнал на закрытие позиций }; //--- Структура позиций struct SData { CArrayLong list_tickets; // Список тикетов открытых позиций double total_volume; // Общий объём открытых позиций }; //--- Структура данных позиций по типам struct SDataPositions { SData Buy; // Данные позиций Buy SData Sell; // Данные позиций Sell } Data; //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define DATA_COUNT 3 // Количество получаемых данных от индикаторов (3 и более) #define ENV_ATTEMPTS 3 // Количество попыток ожидания получения окружения #define ENV_WAIT_ATTEMPT 1000 // Количество миллисекунд ожидания обновления окружения #define SPREAD_MLTP 3 // Множитель спреда для дистанции стоп-приказов //+------------------------------------------------------------------+ //| Входные параметры | //+------------------------------------------------------------------+ //--- GRI input int InpPeriodGRI = 9; /* GRI calculation period */ // Период расчёта GRI input bool InpUseGRI = true; /* Use GRI filtering */ // Использовать фильтрацию по GRI input double InpThresholdGRI = 50.0; /* GRI Volatility threshold */ // Порог волатильности GRI //--- TEMA - указывает направление торговли input int InpPeriodTEMA = 14; /* TEMA calculation period */ // Период расчёта TEMA input ENUM_APPLIED_PRICE InpPriceTEMA = PRICE_CLOSE; /* TEMA applied price */ // Цена расчёта TEMA input int InpShiftTEMA = 0; /* TEMA shift */ // Сдвиг линии TEMA //--- AMA - фильтрует сигналы по положению цены выше/ниже input int InpPeriodAMA = 9; /* AMA calculation period */ // Период расчёта AMA input int InpFastEmaAMA = 2; /* AMA fast EMA period */ // Период быстрого EMA AMA input int InpSlowEmaAMA = 30; /* AMA slow EMA period */ // Период медленного EMA AMA input ENUM_APPLIED_PRICE InpPriceAMA = PRICE_CLOSE; /* AMA applied price */ // Цена расчёта AMA input int InpShiftAMA = 0; /* AMA shift */ // Сдвиг линии AMA //--- BB - установка стопов по значениям input int InpPeriodBB = 58; /* BB calculation period */ // Период расчёта BB input double InpDeviationBB = 2.0; /* BB deviations */ // Отклонения BB input int InpShiftBB = 0; /* BB shift */ // Сдвиг BB input ENUM_APPLIED_PRICE InpPriceBB = PRICE_CLOSE; /* BB applied price */ // Цена расчёта BB //--- ATR - расчёт величины тейк-профит по значению input int InpPeriodATR = 64; /* ATR calculation period */ // Период расчёта ATR //--- Торговля input double InpVolume = 0.1; /* Position volume */ // Объем позиции sinput ulong InpDeviation = 10; /* Slippage (in points) */ // Проскальзывание (в пунктах) sinput ulong InpMagic = 123456; /* Magic number */ // Магик input int InpStopLoss = -1; /* Stop loss (in points), 0 - none, -1 - half of BB */ // Stop loss (в пунктах), 0 - отсутствует, -1 - половина BB input int InpTakeProfit = -1; /* Take profit (in points), 0 - none, -1 - ATR value */ // Take profit (в пунктах), 0 - отсутствует, -1 - значение ATR input double InpSLMltp = 2.6; /* Stop loss size multiplier, if SL==-1 */ // Множитель размера Stop loss, если Stop loss==-1 input double InpTPMltp = 1.3; /* Take profit size multiplier, if TP==-1 */ // Множитель размера Take profit, если Take profit==-1 //+------------------------------------------------------------------+ //| Глобальные переменные | //+------------------------------------------------------------------+ CTrade trade; // Объект торгового класса int handle_gri; // Хэндл индикатора GRI int handle_tema; // Хэндл индикатора TEMA int handle_ama; // Хэндл индикатора AMA int handle_bb; // Хэндл индикатора BB int handle_atr; // Хэндл индикатора ATR double gri[DATA_COUNT]={}; // Массив значений GRI double tema[DATA_COUNT]={}; // Массив значений TEMA double ama[DATA_COUNT]={}; // Массив значений AMA double bb0[DATA_COUNT]={}; // Массив значений BB, буфер 0 (Upper) double bb1[DATA_COUNT]={}; // Массив значений BB, буфер 1 (Lower) double bb2[DATA_COUNT]={}; // Массив значений BB, буфер 2 (Middle) double atr[DATA_COUNT]={}; // Массив значений ATR MqlRates prc[DATA_COUNT]={}; // Массив цен и времени //--- GRI int period_gri; // Период расчёта GRI //--- TEMA int period_tema; // Период расчёта TEMA //--- AMA int period_ama; // Период расчёта AMA int fast_ema_ama; // Период быстрого EMA AMA int slow_ema_ama; // Период медленного EMA AMA //--- BB int period_bb; // Период расчёта BB double deviation_bb; // Отклонения BB int shift_bb; // Сдвиг BB //--- ATR int period_atr; // Период расчёта ATR //--- double lot; // Объём позиции int prev_total; // Количество позиций на прошлой проверке string program_name; // Имя программы bool netto; // Признак нетто-счёта //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Если счёт не с типом хеджинг - ставим флаг и сообщаем о некорректной работе советника netto=false; if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { Print("The advisor is designed for use on a hedging account. Correct operation on a netting account is not guaranteed."); netto=true; } //--- Устанавливаем и корректируем входные переменные индикаторов //--- GRI period_gri=(InpPeriodGRI<1 ? 5 : InpPeriodGRI); //--- TEMA period_tema=(InpPeriodTEMA<1 ? 14 : InpPeriodTEMA); //--- AMA period_ama=(InpPeriodAMA<1 ? 9 : InpPeriodAMA); fast_ema_ama=(InpFastEmaAMA<1 ? 2 : InpFastEmaAMA); slow_ema_ama=(InpSlowEmaAMA<1 ? 30 : InpSlowEmaAMA); //--- BB period_bb=(InpPeriodBB<2 ? 20 : InpPeriodBB); deviation_bb=InpDeviationBB; shift_bb=InpShiftBB; //--- ATR period_atr=(InpPeriodATR<1 ? 14 : InpPeriodATR); //--- Инициализируем массивы значений индикаторов ArrayInitialize(gri,EMPTY_VALUE); ArrayInitialize(tema,EMPTY_VALUE); ArrayInitialize(bb0,EMPTY_VALUE); ArrayInitialize(bb1,EMPTY_VALUE); ArrayInitialize(bb2,EMPTY_VALUE); ArrayInitialize(atr,EMPTY_VALUE); ZeroMemory(prc); //--- Создаём хэндлы индикаторов //--- GRI (должен находиться в папке репозитория) handle_gri=iCustom(Symbol(),PERIOD_CURRENT,"Shared Projects\\Article-20795-MQL5-GRI-Indicator-Volatility-Math\\GRI",period_gri); if(handle_gri==INVALID_HANDLE) { PrintFormat("%s: Failed to create handle for custom GRI(%d) indicator",__FUNCTION__,period_gri); return INIT_FAILED; } //--- TEMA handle_tema=iTEMA(Symbol(),PERIOD_CURRENT,period_tema,InpShiftTEMA,InpPriceTEMA); if(handle_tema==INVALID_HANDLE) { PrintFormat("%s: Failed to create iTEMA(%d) handle",__FUNCTION__,period_tema); return INIT_FAILED; } //--- AMA handle_ama=iAMA(Symbol(),PERIOD_CURRENT,period_ama,fast_ema_ama,slow_ema_ama,InpShiftAMA,InpPriceAMA); if(handle_ama==INVALID_HANDLE) { PrintFormat("%s: Failed to create iAMA(%d,%d,%d) handle",__FUNCTION__,period_ama,fast_ema_ama,slow_ema_ama); return INIT_FAILED; } //--- BB handle_bb=iBands(Symbol(),PERIOD_CURRENT,period_bb,InpShiftBB,InpDeviationBB,InpPriceBB); if(handle_bb==INVALID_HANDLE) { PrintFormat("%s: Failed to create iBands(%d,%d,%.3f,%s) handle",__FUNCTION__,period_bb,shift_bb,deviation_bb,EnumToString(InpPriceBB)); return INIT_FAILED; } //--- ATR handle_atr=iATR(Symbol(),PERIOD_CURRENT,period_atr); if(handle_atr==INVALID_HANDLE) { PrintFormat("%s: Failed to create iATR(%d) handle",__FUNCTION__,period_atr); return INIT_FAILED; } //--- Имя программы и количество позиций на прошлой проверке program_name=MQLInfoString(MQL_PROGRAM_NAME); prev_total=0; //--- Автоматическая установка типа заполнения trade.SetTypeFilling(GetTypeFilling()); //--- Установка магика trade.SetExpertMagicNumber(InpMagic); //--- Установка проскальзывания trade.SetDeviationInPoints(InpDeviation); //--- Установка лота с корректировкой введённого значения lot=CorrectLots(InpVolume); //--- Всё успешно PrintFormat("%s::%s: Initialization was successful",program_name,__FUNCTION__); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Получаем в массивы данные трёх баров индикаторов и цен if(!CopyIndicatorsData() || !CopyPricesData()) return; //--- Заполняем списки тикетов позиций int positions_total=PositionsTotal(); if(prev_total!=positions_total) { if(!FillingListTickets(Symbol(),InpMagic)) return; prev_total=positions_total; } //--- Получаем сигналы от индикаторов ENUM_SIGNAL_TYPE signal_tema=SignalTEMA(); // Направление вверх/вниз (SIGNAL_TYPE_LONG/SIGNAL_TYPE_SHORT) ENUM_SIGNAL_TYPE signal_ama =SignalAMA(); // Цена выше/ниже (SIGNAL_TYPE_LONG/SIGNAL_TYPE_SHORT) //--- Сигналы должны совпадать у TEMA и AMA (TEMA - направление, AMA - положение цены (выше/ниже)) ENUM_SIGNAL_TYPE signal=(signal_tema==signal_ama ? signal_tema : SIGNAL_TYPE_NONE); //--- Если включена фильтрация по GRI if(InpUseGRI) { //--- Корректируем сигнал по состоянию GRI signal=SignalCorrectionByGRI(signal); //--- Корректируем сигнал по порогу волатильности GRI if(!IsHighVolatility(InpThresholdGRI) && signal!=SIGNAL_TYPE_CLOSE) signal=SIGNAL_TYPE_NONE; } //--- Торгуем по сигналам TradeProcess(signal); } //+------------------------------------------------------------------+ //| Получает значения OHLCTV для трёх баров | //+------------------------------------------------------------------+ bool CopyPricesData(void) { ResetLastError(); if(CopyRates(Symbol(),PERIOD_CURRENT,0,DATA_COUNT,prc)!=DATA_COUNT) { PrintFormat("%s: Failed to get price Open data. Error %d",__FUNCTION__,GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Получает значения GRI для трёх баров | //+------------------------------------------------------------------+ bool CopyGRIData(void) { ResetLastError(); if(CopyBuffer(handle_gri,0,0,DATA_COUNT,gri)!=DATA_COUNT) { PrintFormat("%s: Failed to get GRI data. Error %d",__FUNCTION__,GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Получает значения TEMA для трёх баров | //+------------------------------------------------------------------+ bool CopyTEMAData(void) { ResetLastError(); if(CopyBuffer(handle_tema,0,0,DATA_COUNT,tema)!=DATA_COUNT) { PrintFormat("%s: Failed to get TEMA data. Error %d", __FUNCTION__, GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Получает значения TEMA для трёх баров | //+------------------------------------------------------------------+ bool CopyAMAData(void) { ResetLastError(); if(CopyBuffer(handle_ama,0,0,DATA_COUNT,ama)!=DATA_COUNT) { PrintFormat("%s: Failed to get AMA data. Error %d", __FUNCTION__, GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Получает значения BB для трёх баров | //+------------------------------------------------------------------+ bool CopyBBData(void) { ResetLastError(); if(CopyBuffer(handle_bb,UPPER_BAND,0,DATA_COUNT,bb0)!=DATA_COUNT) { PrintFormat("%s: Failed to get BB Upper Line data. Error %d",__FUNCTION__,GetLastError()); return false; } if(CopyBuffer(handle_bb,LOWER_BAND,0,DATA_COUNT,bb1)!=DATA_COUNT) { PrintFormat("%s: Failed to get BB Lower Line data. Error %d",__FUNCTION__,GetLastError()); return false; } if(CopyBuffer(handle_bb,BASE_LINE,0,DATA_COUNT,bb2)!=DATA_COUNT) { PrintFormat("%s: Failed to get BB Base Line data. Error %d",__FUNCTION__,GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Получает значения ATR для трёх баров | //+------------------------------------------------------------------+ bool CopyATRData(void) { ResetLastError(); if(CopyBuffer(handle_atr,0,0,DATA_COUNT,atr)!=DATA_COUNT) { PrintFormat("%s: Failed to get ATR data. Error %d",__FUNCTION__,GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Получает значения индикаторов для трёх баров | //+------------------------------------------------------------------+ bool CopyIndicatorsData(void) { bool res=CopyTEMAData(); // Результат получения данных TEMA res &=CopyAMAData(); // Результат получения данных AMA res &=CopyGRIData(); // Результат получения данных GRI res &=CopyBBData(); // Результат получения данных BB res &=CopyATRData(); // Результат получения данных ATR return res; } //+------------------------------------------------------------------+ //| Возвращает из массива цену Open по индексу таймсерии (0 - 2) | //+------------------------------------------------------------------+ double PriceOpen(const int index) { return(index<0 || index>DATA_COUNT-1 ? 0 : prc[DATA_COUNT-index-1].open); } //+------------------------------------------------------------------+ //|Возвращает из массива цену High по индексу таймсерии (0 - 2)| //+------------------------------------------------------------------+ double PriceHigh(const int index) { return(index<0 || index>DATA_COUNT-1 ? 0 : prc[DATA_COUNT-index-1].high); } //+------------------------------------------------------------------+ //| Возвращает из массива цену Low по индексу таймсерии (0 - 2) | //+------------------------------------------------------------------+ double PriceLow(const int index) { return(index<0 || index>DATA_COUNT-1 ? 0 : prc[DATA_COUNT-index-1].low); } //+------------------------------------------------------------------+ //| Возвращает из массива цену Close по индексу таймсерии (0 - 2) | //+------------------------------------------------------------------+ double PriceClose(const int index) { return(index<0 || index>DATA_COUNT-1 ? 0 : prc[DATA_COUNT-index-1].close); } //+------------------------------------------------------------------+ //| Возвращает из массива время бара по индексу таймсерии (0 - 2) | //+------------------------------------------------------------------+ datetime Time(const int index) { return(index<0 || index>DATA_COUNT-1 ? 0 : prc[DATA_COUNT-index-1].time); } //+------------------------------------------------------------------+ //| Возвращает из массива данные GRI по индексу таймсерии (0 - 2) | //+------------------------------------------------------------------+ double GRI(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : gri[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| Возвращает из массива данные TEMA по индексу таймсерии (0 - 2) | //+------------------------------------------------------------------+ double TEMA(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : tema[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| Возвращает из массива данные AMA по индексу таймсерии (0 - 2) | //+------------------------------------------------------------------+ double AMA(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : ama[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //|Возвращает из массива данные BB Upper по индексу таймсерии (0 - 2)| //+------------------------------------------------------------------+ double BBUpper(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : bb0[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //|Возвращает из массива данные BB Lower по индексу таймсерии (0 - 2)| //+------------------------------------------------------------------+ double BBLower(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : bb1[DATA_COUNT-index-1]); } //+-------------------------------------------------------------------+ //|Возвращает из массива данные BB Middle по индексу таймсерии (0 - 2)| //+-------------------------------------------------------------------+ double BBMiddle(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : bb2[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| Возвращает половину ширины BB в пунктах | //+------------------------------------------------------------------+ int HalfSizeBB(const int index) { double up=BBUpper(index); double dn=BBLower(index); if(up==EMPTY_VALUE || dn==EMPTY_VALUE) return 0; return (int)round(((up-dn)*0.5)/Point()); } //+------------------------------------------------------------------+ //| Возвращает из массива данные ATR по индексу таймсерии (0 - 2) | //+------------------------------------------------------------------+ double ATR(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : atr[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| Состояние GRI | //+------------------------------------------------------------------+ ENUM_GRI_STATE GRIState(void) { //--- Получаем данные GRI double gri0=GRI(0); double gri1=GRI(1); double gri2=GRI(2); //--- Ошибка получения данных - нет сигнала if(gri0==EMPTY_VALUE || gri1==EMPTY_VALUE || gri2==EMPTY_VALUE) return GRI_STATE_UNKNOWN; //--- Волатильность растёт if(gri0>gri1 && gri1>gri2) return GRI_STATE_GROWING; //--- Волатильность падает if(gri0gri1 && gri1<=gri2) return GRI_STATE_TURN_TO_GROW; //--- Переход к падению (был рост/флэт, теперь спад) if(gri0=gri2) return GRI_STATE_TURN_TO_FALL; //--- Флэт return GRI_STATE_FLAT; } //+------------------------------------------------------------------+ //| Корректирует сигнал по состоянию GRI | //+------------------------------------------------------------------+ ENUM_SIGNAL_TYPE SignalCorrectionByGRI(const ENUM_SIGNAL_TYPE signal_type) { //--- Текущий сигнал ENUM_SIGNAL_TYPE signal=signal_type; //--- В зависимости от состояния GRI корректируем текущий сигнал ENUM_GRI_STATE state=GRIState(); switch(state) { //--- Переход к росту волатильности case GRI_STATE_TURN_TO_GROW : // Здесь сигнал без изменений, // но можно как-то ещё обрабатывать начало роста break; //--- Волатильность растёт case GRI_STATE_GROWING : // Здесь сигнал без изменений, // но можно как-то ещё обрабатывать постоянный рост break; //--- Высокая волатильность case GRI_STATE_VOLATILITY_HIGH : // Здесь сигнал без изменений, // но можно как-то ещё обрабатывать высокую волатильность break; //--- Переход к снижению волатильности case GRI_STATE_TURN_TO_FALL : signal=SIGNAL_TYPE_NONE; // Нет сигнала // Здесь просто нет сигнала, // но можно как-то иначе обрабатывать это состояние, // например, здесь можно закрыть часть позиции или выставить стоп в безубыток, // либо сделать и то, и то break; //--- Волатильность падает case GRI_STATE_FALLING : signal=SIGNAL_TYPE_NONE; // Нет сигнала // Здесь просто нет сигнала, // но можно как-то иначе обрабатывать это состояние, // например, включить трейлинг-стоп или закрыть позиции: //signal=SIGNAL_TYPE_CLOSE; // Сигнал на закрытие всех позиций (три бара подряд снижается волатильность) break; //--- Низкая волатильность case GRI_STATE_FLAT : signal=SIGNAL_TYPE_NONE; // Нет сигнала // Здесь просто нет сигнала, // но можно как-то иначе обрабатывать это состояние, // например, здесь можно выставлять отложенные ордера break; //--- GRI_STATE_UNKNOWN Ошибка получения данных данных default: signal=SIGNAL_TYPE_NONE; // Нет сигнала break; } return signal; } //+------------------------------------------------------------------+ //| Превышение порога волатильности GRI | //+------------------------------------------------------------------+ bool IsHighVolatility(double threshold) { //--- Получаем данные GRI double gri0=GRI(0); //--- Ошибка получения данных - false if(gri0==EMPTY_VALUE) return false; return(gri0>=threshold); } //+------------------------------------------------------------------+ //| Сигнал TEMA | //+------------------------------------------------------------------+ ENUM_SIGNAL_TYPE SignalTEMA(void) { double tema0=TEMA(0); double tema1=TEMA(1); //--- Ошибка - нет сигнала if(tema0==EMPTY_VALUE || tema1==EMPTY_VALUE) return SIGNAL_TYPE_NONE; //--- Сигнал на покупку: TEMA растёт if(tema0>tema1) return SIGNAL_TYPE_LONG; //--- Сигнал на продажу: TEMA падает if(tema0ama0 && ama0>ama1) return SIGNAL_TYPE_LONG; //--- Сигнал на продажу: цена ниже AMA, AMA падает if(price=ORDER_FILLING_RETURN) || ((filling_mode &(type+1))!=type+1)) ? (((exe_mode==SYMBOL_TRADE_EXECUTION_EXCHANGE) || (exe_mode==SYMBOL_TRADE_EXECUTION_INSTANT)) ? ORDER_FILLING_RETURN :((filling_mode==SYMBOL_FILLING_IOC) ? ORDER_FILLING_IOC : ORDER_FILLING_FOK)) : type); } //+------------------------------------------------------------------+ //| Возвращает корректный лот | //+------------------------------------------------------------------+ double CorrectLots(const double lots,const bool to_min_correct=true) { double min=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); double max=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX); double step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP); return(to_min_correct ? VolumeRoundToSmaller(lots,min,max,step) : VolumeRoundToCorrect(lots,min,max,step)); } //+------------------------------------------------------------------+ //| Возвращает ближайший корректный лот | //+------------------------------------------------------------------+ double VolumeRoundToCorrect(const double volume,const double min,const double max,const double step) { return(step==0 ? min : fmin(fmax(round(volume/step)*step,min),max)); } //+------------------------------------------------------------------+ //| Возвращает ближайший в меньшую сторону корректный лот | //+------------------------------------------------------------------+ double VolumeRoundToSmaller(const double volume,const double min,const double max,const double step) { return(step==0 ? min : fmin(fmax(floor(volume/step)*step,min),max)); } //+------------------------------------------------------------------+ //| Возвращает флаг не превышения общего объёма на счёте | //+------------------------------------------------------------------+ bool CheckLotForLimitAccount(const ENUM_POSITION_TYPE position_type,const double volume) { double lots_limit=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT); if(lots_limit==0) return true; double total_volume=(position_type==POSITION_TYPE_BUY ? Data.Buy.total_volume : Data.Sell.total_volume); return(total_volume+volume<=lots_limit); } //+------------------------------------------------------------------+ //| Возвращает корректный StopLoss относительно StopLevel | //+------------------------------------------------------------------+ double CorrectStopLoss(const ENUM_POSITION_TYPE position_type,const int stop_loss) { if(stop_loss==0) return 0; double pt=Point(); double price=(position_type==POSITION_TYPE_BUY ? SymbolInfoDouble(Symbol(),SYMBOL_ASK) : SymbolInfoDouble(Symbol(),SYMBOL_BID)); int lv=StopLevel(), dg=Digits(); return(position_type==POSITION_TYPE_BUY ? NormalizeDouble(fmin(price-lv*pt,price-stop_loss*pt),dg) : NormalizeDouble(fmax(price+lv*pt,price+stop_loss*pt),dg)); } //+------------------------------------------------------------------+ //| Возвращает корректный TakeProfit относительно StopLevel | //+------------------------------------------------------------------+ double CorrectTakeProfit(const ENUM_POSITION_TYPE position_type,const int take_profit) { if(take_profit==0) return 0; double pt=Point(); double price=(position_type==POSITION_TYPE_BUY ? SymbolInfoDouble(Symbol(),SYMBOL_ASK) : SymbolInfoDouble(Symbol(),SYMBOL_BID)); int lv=StopLevel(), dg=Digits(); return(position_type==POSITION_TYPE_BUY ? NormalizeDouble(fmax(price+lv*pt,price+take_profit*pt),dg) : NormalizeDouble(fmin(price-lv*pt,price-take_profit*pt),dg)); } //+------------------------------------------------------------------+ //| Возвращает рассчитанный StopLevel | //+------------------------------------------------------------------+ int StopLevel(void) { int sp=(int)SymbolInfoInteger(Symbol(),SYMBOL_SPREAD); int lv=(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL); return(lv==0 ? sp*SPREAD_MLTP : lv); } //+------------------------------------------------------------------+ //| Возвращает "неопределённое" состояние торгового окружения | //+------------------------------------------------------------------+ bool IsUncertainStateEnv(const string symbol_name,const ulong magic_number) { //--- В тестере сосотояние окружения всегда корректно if(MQLInfoInteger(MQL_TESTER)) return false; //--- В цикле по количеству ордеров int total=OrdersTotal(); for(int i=total-1; i>=0; i--) { //--- выбираем ордер для получения его свойств if(OrderGetTicket(i)==0) continue; //--- если магик ордера не соответствует искомому - пропускаем if(OrderGetInteger(ORDER_MAGIC)!=magic_number) continue; //--- если тип ордера не Buy и не Sell - пропускаем ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); if(type!=ORDER_TYPE_BUY && type!=ORDER_TYPE_SELL) continue; //--- если символ ордера соответствует искомому, но в ордере нет записи об идентификаторе позиции, //--- значит данные об открывающейся позиции ещё не добавлены в историю ордеров. //--- Это неопределённое состояние окружения - возвращаем true if(!OrderGetInteger(ORDER_POSITION_ID) && OrderGetString(ORDER_SYMBOL)==symbol_name) return true; } //--- Окружение в порядке return false; } //+------------------------------------------------------------------+ //| Проверка состояния окружения | //+------------------------------------------------------------------+ bool CheckUncertainStateEnv(const string symbol_name,const ulong magic_number,const int attempts,const int wait) { //--- Если окружение в порядке - возвращаем true if(IsUncertainStateEnv(symbol_name,magic_number)) return true; //--- Делаем цикле ENV_ATTEMPTS попыток получения корректного окружения с ожиданием ENV_WAIT_ATTEMPT между попытками int n=0; while(!IsStopped() && n=attempts && IsUncertainStateEnv(symbol_name,magic_number)) { PrintFormat("%s: Uncertain state of the environment. Please try again.",__FUNCTION__); return false; } //--- Окружение корректно return true; } //+------------------------------------------------------------------+ //| Заполняет массивы тикетов позиций | //+------------------------------------------------------------------+ bool FillingListTickets(const string symbol_name,const ulong magic_number) { //--- Если торговое окружение не корректно - возвращаем false if(!CheckUncertainStateEnv(symbol_name,magic_number,ENV_ATTEMPTS,ENV_WAIT_ATTEMPT)) return false; //--- Очищаем списки и инициализируем переменные Data.Buy.list_tickets.Clear(); Data.Sell.list_tickets.Clear(); Data.Buy.total_volume=0; Data.Sell.total_volume=0; //--- В цикле по открытым позициям int total=PositionsTotal(); for(int i=total-1; i>WRONG_VALUE; i--) { //--- выбираем позицию для получения свойств ulong ticket=PositionGetTicket(i); if(ticket==0) continue; //--- Если магик или символ не соответствуют переданным в функцию - идём дальше if(PositionGetInteger(POSITION_MAGIC)!=InpMagic || PositionGetString(POSITION_SYMBOL)!=symbol_name) continue; //--- Получаем тип и объём позиции ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double volume=PositionGetDouble(POSITION_VOLUME); //--- В зависимости от типа позиции добавляем тикет и объём в соответствующие списки if(type==POSITION_TYPE_BUY) { Data.Buy.list_tickets.Add(ticket); Data.Buy.total_volume+=volume; } //--- POSITION_TYPE_SELL else { Data.Sell.list_tickets.Add(ticket); Data.Sell.total_volume+=volume; } } //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| Возвращает количество позиций Buy | //+------------------------------------------------------------------+ int TotalBuy(void) { return Data.Buy.list_tickets.Total(); } //+------------------------------------------------------------------+ //| Возвращает количество позиций Sell | //+------------------------------------------------------------------+ int TotalSell(void) { return Data.Sell.list_tickets.Total(); } //+------------------------------------------------------------------+ //| Возвращает последний добавленный тикет позиции по типу | //+------------------------------------------------------------------+ ulong LastAddedTicket(const ENUM_POSITION_TYPE type) { return(type==POSITION_TYPE_BUY ? (TotalBuy()>0 ? Data.Buy.list_tickets.At(0) : 0) : (TotalSell()>0 ? Data.Sell.list_tickets.At(0) : 0)); } //+------------------------------------------------------------------+ //| Возвращает номер бара, на котором была открыта позиция | //+------------------------------------------------------------------+ int PositionBar(const ulong ticket) { //--- Выбираем позицию по тикету ResetLastError(); if(!PositionSelectByTicket(ticket)) { PrintFormat("%s: Failed to select position by ticket #%I64u. Error %d",__FUNCTION__,ticket,GetLastError()); return -1; } //--- Получаем время открытия и символ позиции datetime time=(datetime)PositionGetInteger(POSITION_TIME); string symbol=PositionGetString(POSITION_SYMBOL); //--- Возвращаем номер бара по времени открытия позиции return iBarShift(symbol,PERIOD_CURRENT,time); } //+------------------------------------------------------------------+ //| Возвращает наличие указанной позиции, открытой на текущем баре | //+------------------------------------------------------------------+ bool IsPresentPosOnCurrentBar(const ENUM_POSITION_TYPE type) { ulong ticket=LastAddedTicket(type); return(ticket>0 ? PositionBar(ticket)==0 : false); } //+------------------------------------------------------------------+ //| Возвращает цену открытия позиции по тикету | //+------------------------------------------------------------------+ double PositionPriceOpen(const ulong ticket) { //--- Проверяем тикет if(ticket==0) return 0; //--- Выбираем позицию по тикету ResetLastError(); if(!PositionSelectByTicket(ticket)) { PrintFormat("%s: Failed to select position by ticket #%I64u. Error %d",__FUNCTION__,ticket,GetLastError()); return 0; } //--- Возвращаем цену открытия позиции return PositionGetDouble(POSITION_PRICE_OPEN); } //+------------------------------------------------------------------+ //| Закрывает позиции Buy | //+------------------------------------------------------------------+ bool CloseBuy(void) { int total=TotalBuy(); bool res=true; for(int i=total-1; i>=0; i--) { ulong ticket=Data.Buy.list_tickets.At(i); if(ticket==NULL) continue; if(!trade.PositionClose(ticket,InpDeviation)) res=false; } return res; } //+------------------------------------------------------------------+ //| Закрывает позиции Sell | //+------------------------------------------------------------------+ bool CloseSell(void) { int total=TotalSell(); bool res=true; for(int i=total-1; i>=0; i--) { ulong ticket=Data.Sell.list_tickets.At(i); if(ticket==NULL) continue; if(!trade.PositionClose(ticket,InpDeviation)) res=false; } return res; } //+------------------------------------------------------------------+ //| Открытие позиции | //+------------------------------------------------------------------+ bool OpenPosition(const string symbol_name,const ENUM_POSITION_TYPE type,const double volume,const string comment) { //--- Рассчитываем значения для стоп-приказов int bb=int(HalfSizeBB(0)*InpSLMltp); double atrd=ATR(0); int atrp=(atrd!=EMPTY_VALUE ? int(round(atrd*InpTPMltp/Point())) : 0); double sl=(InpStopLoss==0 ? 0 : (InpStopLoss<0 ? (bb!=0 ? CorrectStopLoss(type,bb) : 0) : CorrectStopLoss(type,InpStopLoss))); double tp=(InpTakeProfit==0 ? 0 : (InpStopLoss<0 ? (atrp!=0 ? CorrectTakeProfit(type,atrp) : 0) : CorrectTakeProfit(type,InpTakeProfit))); //--- На неттинговом счёте убираем стоп-приказы if(netto) sl=tp=0; //--- Получаем цены MqlTick tick={}; if(!SymbolInfoTick(symbol_name,tick)) { PrintFormat("%s: Unable to get prices"); return false; } //--- Проверяем и получаем нормализованный лот открываемой позиции double ll=trade.CheckVolume(symbol_name,volume,(type==POSITION_TYPE_BUY ? tick.ask : tick.bid),(ENUM_ORDER_TYPE)type); if(ll==0) { PrintFormat("%s: Error. CheckVolume() returned a zero lot",__FUNCTION__); return false; } //--- Проверяем ограничение на максимальный объём открытых позиций на счёте if(!CheckLotForLimitAccount(type,ll)) { PrintFormat("%s: CheckLotForLimitAccount() returned an error",__FUNCTION__); return false; } //--- Может быть ситуация, когда торговый приказ уже был отправлен, но он ещё не полностью обработан, //--- что может привести к задвоению открываемой позиции. //--- Если торговое окружение не корректно - возвращаем false if(!CheckUncertainStateEnv(symbol_name,InpMagic,ENV_ATTEMPTS,ENV_WAIT_ATTEMPT)) return false; //--- Ожидание получения корректного торгового окружения может занять некоторое время //--- Ещё раз получим цены if(!SymbolInfoTick(symbol_name,tick)) { PrintFormat("%s: Unable to get prices"); return false; } //--- Возвращаем результат отправки торгового запроса на сервер return(type==POSITION_TYPE_BUY ? trade.Buy(ll,symbol_name,tick.ask,sl,tp,comment) : trade.Sell(ll,symbol_name,tick.bid,sl,tp,comment)); } //+------------------------------------------------------------------+ //| Процесс торговли | //+------------------------------------------------------------------+ void TradeProcess(const ENUM_SIGNAL_TYPE signal) { //--- Нет сигнала - уходим if(signal==SIGNAL_TYPE_NONE) return; //--- Сигнал на закрытие if(signal==SIGNAL_TYPE_CLOSE) { CloseBuy(); CloseSell(); } //--- Сигнал на покупку if(signal==SIGNAL_TYPE_LONG) { //--- Если нет открытой позиции Buy на этом баре if(!IsPresentPosOnCurrentBar(POSITION_TYPE_BUY)) { //--- Получаем цену последней открытой позиции Buy double price_last=PositionPriceOpen(LastAddedTicket(POSITION_TYPE_BUY)); //--- Если это самая первая позиция Buy, либо цена открытия лучше цены открытия прошлой позиции - //--- отсылаем запрос на открытие позиции Buy if(price_last==0 || price_last>SymbolInfoDouble(Symbol(),SYMBOL_ASK)) { //--- Если позиция открыта - обновляем списки тикетов открытых позиций if(OpenPosition(Symbol(),POSITION_TYPE_BUY,lot,"")) FillingListTickets(Symbol(),InpMagic); } } } //--- Сигнал на продажу if(signal==SIGNAL_TYPE_SHORT) { //--- Если нет открытой позиции Sell на этом баре if(!IsPresentPosOnCurrentBar(POSITION_TYPE_SELL)) { //--- Получаем цену последней открытой позиции Sell double price_last=PositionPriceOpen(LastAddedTicket(POSITION_TYPE_SELL)); //--- Если это самая первая позиция Sell, либо цена открытия лучше цены открытия прошлой позиции - //--- отсылаем запрос на открытие позиции Sell if(price_last