Warrior_EA/Signals/SignalMA.mqh
AnimateDread 61c0d19ca9 feat(indicators): run the built-in iMA and MetaTrader's ZigZag; add a classic-vote shift
MA: CustomIndicators\ADMovingAverage is replaced by the built-in iMA (CiMA) on
both consumers - the classic vote and the NN MA input feature. This drops the
five advanced types ALMA/DEMA/ZLEMA/T3/Kalman, which have no iMA equivalent;
MA_TYPE_PRESETS is now ENUM_MA_METHOD's own codes and the tuner searches all
four. It also removes a documented failure mode: a custom indicator's depth is
bounded by TERMINAL_MAXBARS, and m_MA was the one whose feature block REJECTS
the bar on a short read - the "feature 25 fails on every bar" incident of
2026-08-17. A built-in is served at any depth.

MIGRATION. SMA moves from code 5 to 0, so persisted type codes change meaning.
SanitizeMaType() is the single validity rule; TunedPeriods records now carry a
version field and a v1 record remaps 5..8 -> 0..3, falling back to SMA for a
stored advanced type (unrecoverable - old 0..4 are indistinguishable from valid
new codes). Existing .nnw files re-key on their own, because MA_Type is hashed
into the topology fingerprint, so models retrain rather than silently running
on different MA values. EXPECT A FULL RETRAIN.

ZigZag: ADZigZag was a byte-identical rename of MetaQuotes' Examples\ZigZag -
verified by normalising identifiers and stripping comments, 233 significant
lines each with only renamed symbols differing. It now loads the stock one, so
nothing is bundled and MetaQuotes' fixes arrive without a rebuild here. Both
#resource entries are gone.

Classic_Shift: a new input, the BAR the four classic votes evaluate on (0 =
forming, 1 = last closed, default 1). One implementation on CExpertSignalCustom,
inherited by all four rather than repeated per module. Defaults to a sentinel
meaning "unset", so the AI signals and the aggregate keep the stock every_tick
rule and their feature/label alignment is untouched. The META corpus sweep still
takes precedence. CExpertBase::StartIndex turns out to be virtual, so this is a
real override, not the name-hiding the old comment claimed.

Not compiled - MetaEditor compile pending.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:09:58 -04:00

340 lines
15 KiB
MQL5

//+------------------------------------------------------------------+
//| 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;
}
}
//+------------------------------------------------------------------+