//+------------------------------------------------------------------+ //| SignalMA.mqh | //| Copyright 2000-2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "..\Expert\ExpertSignalCustom.mqh" // wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=Signals of indicator 'Moving Average' | //| Type=SignalAdvanced | //| Name=Moving Average | //| ShortName=MA | //| Class=CSignalMA | //| Page=signal_ma | //| Parameter=PeriodMA,int,10,Period of averaging | //| Parameter=Shift,int,0,Time shift | //| Parameter=Method,ENUM_MA_METHOD,MODE_SMA,Method of averaging | //| Parameter=Applied,ENUM_APPLIED_PRICE,PRICE_CLOSE,Prices series | //+------------------------------------------------------------------+ // wizard description end //+------------------------------------------------------------------+ //| Class CSignalMA. | //| Purpose: Class of generator of trade signals based on | //| the 'Moving Average' indicator. | //| Is derived from the CExpertSignalCustom class. | //+------------------------------------------------------------------+ class CSignalMA : public CExpertSignalCustom { protected: CiMA m_ma; // built-in moving average (iMA) //--- adjusted parameters int m_ma_period; // the "period of averaging" parameter of the indicator int m_ma_type; // MA_TYPE_PRESETS == ENUM_MA_METHOD codes, passed to iMA as-is ENUM_APPLIED_PRICE m_ma_applied; // the "object of averaging" parameter of the indicator //--- "weights" of market models (0-100) int m_pattern_0; // model 0 "price is on the necessary side from the indicator" int m_pattern_1; // model 1 "price crossed the indicator with opposite direction" int m_pattern_2; // model 2 "price crossed the indicator with the same direction" int m_pattern_3; // model 3 "piercing" public: CSignalMA(void); ~CSignalMA(void); //--- methods of setting adjustable parameters void PeriodMA(int value) { m_ma_period = value; } //--- Shift() is the base class's (CExpertSignalCustom): the BAR this vote is evaluated on, not //--- iMA's ma_shift, which this class pins to 0. See InitMA(). void Method(int value) { m_ma_type = value; } void Applied(ENUM_APPLIED_PRICE value) { m_ma_applied = value; } //--- methods of adjusting "weights" of market models void Pattern_0(int value) { m_pattern_0 = value; } void Pattern_1(int value) { m_pattern_1 = value; } void Pattern_2(int value) { m_pattern_2 = value; } void Pattern_3(int value) { m_pattern_3 = value; } virtual void ApplyPatternWeight(int patternNumber, int weight); //--- method of verification of settings virtual bool ValidationSettings(void); //--- method of creating the indicator and timeseries virtual bool InitIndicators(CIndicators *indicators); //--- methods of checking if the market models are formed virtual int LongCondition(void); virtual int ShortCondition(void); //--- deep-history readiness for the meta candidate sweep - see CExpertSignalCustom::SweepPrepare virtual bool SweepPrepare(const int bars) override { if(!CExpertSignalCustom::SweepPrepare(bars)) return false; bool ok = m_ma.BufferResize(bars); m_ma.Refresh(-1); return ok; } protected: //--- method of initialization of the indicator bool InitMA(CIndicators *indicators); //--- methods of getting data double MA(int ind) { return(m_ma.GetData(0, ind)); } double DiffMA(int ind) { return(MA(ind) - MA(ind + 1)); } //--- Slope as it stood at the PREVIOUS bar, i.e. not yet influenced by bar `ind`'s own close. //--- Model 1 needs this and DiffMA() cannot serve: for any RECURSIVE average (EMA/SMMA, and the //--- exponential members of MA_TYPE_PRESETS) MA(ind) = a*Close(ind) + (1-a)*MA(ind+1), so //--- DiffMA(ind) = a * (Close(ind) - MA(ind + 1)) //--- DiffCloseMA(ind) = (1-a) * (Close(ind) - MA(ind + 1)) //--- are positive multiples of the same quantity and therefore ALWAYS share a sign. Model 1 asks //--- for "close below a RISING average", which is exactly the sign combination that identity //--- forbids - so under EMA or SMMA the model could never fire on any bar of any symbol. Both are //--- still selectable and tuner-reachable, so this is live, not historical. Reading the slope one //--- bar back breaks the algebraic tie for every MA type while keeping the model's stated meaning: //--- a pull-back closing against an established trend. double DiffMAPrev(int ind) { return(MA(ind + 1) - MA(ind + 2)); } double DiffOpenMA(int ind) { return(Open(ind) - MA(ind)); } double DiffHighMA(int ind) { return(High(ind) - MA(ind)); } double DiffLowMA(int ind) { return(Low(ind) - MA(ind)); } double DiffCloseMA(int ind) { return(Close(ind) - MA(ind)); } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSignalMA::CSignalMA(void) : m_ma_period(12), //--- SMA to match the MA_Type input seed. Warrior_EA.mq5 always pushes g_TunedMaType over this, //--- so the default only shows if the signal is constructed outside that path - it should still //--- agree with the documented default rather than quietly disagreeing. m_ma_type(MA_TYPE_SMA), m_ma_applied(PRICE_CLOSE), m_pattern_0(10), m_pattern_1(10), m_pattern_2(60), m_pattern_3(60) { m_id = "MA"; m_pattern_count = 4; //--- initialization of protected data m_used_series = USE_SERIES_OPEN + USE_SERIES_HIGH + USE_SERIES_LOW + USE_SERIES_CLOSE; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CSignalMA::~CSignalMA(void) { } //+------------------------------------------------------------------+ //| Validation settings protected data. | //+------------------------------------------------------------------+ bool CSignalMA::ValidationSettings(void) { //--- validation settings of additional filters if(!CExpertSignalCustom::ValidationSettings()) return(false); //--- initial data checks if(m_ma_period <= 0) { printf(__FUNCTION__ + ": period MA must be greater than 0"); return(false); } //--- iMA silently returns an invalid handle for a method outside ENUM_MA_METHOD, which shows up much //--- later as a vote that never fires. Fail here instead, where the cause is still visible. if(m_ma_type < MODE_SMA || m_ma_type > MODE_LWMA) { printf(__FUNCTION__ + ": MA type %d is not an ENUM_MA_METHOD value", m_ma_type); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| Create indicators. | //+------------------------------------------------------------------+ bool CSignalMA::InitIndicators(CIndicators *indicators) { //--- check pointer if(indicators == NULL) return(false); //--- initialization of indicators and timeseries of additional filters if(!CExpertSignalCustom::InitIndicators(indicators)) return(false); //--- create and initialize MA indicator if(!InitMA(indicators)) return(false); //--- ok return(true); } //+------------------------------------------------------------------+ //| Initialize MA indicators. | //+------------------------------------------------------------------+ bool CSignalMA::InitMA(CIndicators *indicators) { //--- check pointer if(indicators == NULL) return(false); //--- add object to collection if(!indicators.Add(GetPointer(m_ma))) { printf(__FUNCTION__ + ": error adding object"); return(false); } //--- built-in iMA. ma_shift is fixed at 0: it displaces the AVERAGE along the time axis, which is not //--- what this EA means by shift - the bar a condition is evaluated on is CExpertSignalCustom::Shift(). if(!m_ma.Create(m_symbol.Name(), m_period, m_ma_period, 0, (ENUM_MA_METHOD)m_ma_type, m_ma_applied)) { printf(__FUNCTION__ + ": error initializing object"); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| "Voting" that price will grow. | //+------------------------------------------------------------------+ int CSignalMA::LongCondition(void) { int result = 0; int idx = StartIndex(); //--- analyze positional relationship of the close price and the indicator at the first analyzed bar if(DiffCloseMA(idx) < 0.0) { //--- the close price is below the indicator if(IS_PATTERN_USAGE(1) && DiffOpenMA(idx) > 0.0 && DiffMAPrev(idx) > 0.0) { //--- the open price is above the indicator (i.e. there was an intersection), but the indicator is directed upwards //--- (measured at the previous bar - see DiffMAPrev() for why DiffMA() makes this unsatisfiable) result = m_pattern_1; m_active_pattern = "Pattern_1"; //--- consider that this is an unformed "piercing" and suggest to enter the market at the current price m_base_price = 0.0; } } else { //--- the close price is above the indicator (the indicator has no objections to buying) if(IS_PATTERN_USAGE(0)) { result = m_pattern_0; m_active_pattern = "Pattern_0"; } //--- if the indicator is directed upwards if(DiffMA(idx) > 0.0) { if(DiffOpenMA(idx) < 0.0) { //--- if the model 2 is used if(IS_PATTERN_USAGE(2)) { //--- the open price is below the indicator (i.e. there was an intersection) result = m_pattern_2; m_active_pattern = "Pattern_2"; //--- suggest to enter the market at the "roll back" m_base_price = m_symbol.NormalizePrice(MA(idx)); } } else { //--- if the model 3 is used and the open price is above the indicator if(IS_PATTERN_USAGE(3) && DiffLowMA(idx) < 0.0) { //--- the low price is below the indicator result = m_pattern_3; m_active_pattern = "Pattern_3"; //--- consider that this is a formed "piercing" and suggest to enter the market at the current price m_base_price = 0.0; } } } } if(result != 0) { m_active_direction = "Buy"; } //--- return the result return(result); } //+------------------------------------------------------------------+ //| "Voting" that price will fall. | //+------------------------------------------------------------------+ int CSignalMA::ShortCondition(void) { int result = 0; int idx = StartIndex(); //--- analyze positional relationship of the close price and the indicator at the first analyzed bar if(DiffCloseMA(idx) > 0.0) { //--- the close price is above the indicator if(IS_PATTERN_USAGE(1) && DiffOpenMA(idx) < 0.0 && DiffMAPrev(idx) < 0.0) { //--- the open price is below the indicator (i.e. there was an intersection), but the indicator is directed downwards //--- (measured at the previous bar - see DiffMAPrev() for why DiffMA() makes this unsatisfiable) result = m_pattern_1; m_active_pattern = "Pattern_1"; //--- consider that this is an unformed "piercing" and suggest to enter the market at the current price m_base_price = 0.0; } } else { //--- the close price is below the indicator (the indicator has no objections to buying) if(IS_PATTERN_USAGE(0)) { result = m_pattern_0; m_active_pattern = "Pattern_0"; } //--- the indicator is directed downwards if(DiffMA(idx) < 0.0) { if(DiffOpenMA(idx) > 0.0) { //--- if the model 2 is used if(IS_PATTERN_USAGE(2)) { //--- the open price is above the indicator (i.e. there was an intersection) result = m_pattern_2; m_active_pattern = "Pattern_2"; //--- suggest to enter the market at the "roll back" m_base_price = m_symbol.NormalizePrice(MA(idx)); } } else { //--- if the model 3 is used and the open price is below the indicator if(IS_PATTERN_USAGE(3) && DiffHighMA(idx) > 0.0) { //--- the high price is above the indicator result = m_pattern_3; m_active_pattern = "Pattern_3"; //--- consider that this is a formed "piercing" and suggest to enter the market at the current price m_base_price = 0.0; } } } } if(result != 0) { m_active_direction = "Sell"; } //--- return the result return(result); } //+------------------------------------------------------------------+ //| Set the specified pattern's weight to the specified value | //+------------------------------------------------------------------+ void CSignalMA::ApplyPatternWeight(int patternNumber, int weight) { switch(patternNumber) { default: break; case 0: Pattern_0(weight); break; case 1: Pattern_1(weight); break; case 2: Pattern_2(weight); break; case 3: Pattern_3(weight); break; } } //+------------------------------------------------------------------+