//+------------------------------------------------------------------+ //| SignalRegime.mqh | //| AnimateDread | //| | //| WHICH GAME IS BEING PLAYED: trending, consolidating, or mean- | //| reverting. A classic module in the ordinary shape - it votes | //| 0..100, names its pattern, and the journal ranks it like any | //| other. | //| | //| WHY THIS AND NOT MORE WYCKOFF. Wyckoff answers "where are we in | //| a campaign" - events, phases, springs. That is structure. This | //| answers a blunter and more useful question first: does price | //| here travel, or does it come back? Those demand opposite trades | //| from the same indicator reading, which is why an ensemble of | //| oscillators and trend modules voting together can be right about | //| everything and still lose: in a trend the oscillators are wrong, | //| in a range the trend modules are. | //| | //| TWO MEASURES, BOTH FROM CLOSES, BOTH SCALE-FREE. | //| | //| EFFICIENCY RATIO (Kaufman): net distance divided by the path | //| length walked to get there. | //| ER = |C[t] - C[t-n]| / SUM |C[i] - C[i-1]| | //| 1.0 is a straight line, 0.0 is thrashing that ends where it | //| began. It needs no volatility estimate and no threshold that | //| means different things on different symbols - a ratio of two | //| distances in the same units cancels the instrument entirely. | //| | //| VARIANCE RATIO: the variance of q-bar returns against q times the | //| variance of 1-bar returns. | //| VR = Var(q-bar) / (q * Var(1-bar)) | //| A random walk gives 1.0 because variance scales with time. Above | //| 1 the moves compound - trending. Below 1 they cancel - mean | //| reverting. This is the sharper of the two: ER says "is there a | //| trend", VR says "does this market CONTINUE or REVERSE", which is | //| the question an entry actually rests on. | //| | //| ⚠ IT DOES NOT VOTE IN CONSOLIDATION, deliberately. Neither the | //| trend patterns nor the reversion patterns have an edge when the | //| market is doing neither, and a module that always finds something | //| to say is a module whose vote means nothing. Returning 0/0 is a | //| real answer here, and the standard library counts it as one. | //+------------------------------------------------------------------+ #ifndef WARRIOR_SIGNALREGIME_MQH #define WARRIOR_SIGNALREGIME_MQH #include "..\Expert\WarriorSignal.mqh" class CSignalRegime : public CWarriorSignal { protected: int m_erPeriod; // bars for the efficiency ratio int m_vrPeriod; // bars for the variance ratio int m_vrQ; // the q in VR(q) int m_maPeriod; // the reference the trend is measured against double m_erTrend; // ER at or above this is a trend double m_erChop; // ER at or below this is consolidation double m_vrRevert; // VR at or below this is mean reverting int m_pattern_0; // trend, long int m_pattern_1; // trend, short int m_pattern_2; // mean reversion, long int m_pattern_3; // mean reversion, short double Sma(const int shift, const int period) const; public: CSignalRegime(void); ~CSignalRegime(void) {} void ErPeriod(const int v) { m_erPeriod = v; } void VrPeriod(const int v) { m_vrPeriod = v; } void MaPeriod(const int v) { m_maPeriod = v; } void Pattern_0(const int v) { m_pattern_0 = v; } void Pattern_1(const int v) { m_pattern_1 = v; } void Pattern_2(const int v) { m_pattern_2 = v; } void Pattern_3(const int v) { m_pattern_3 = v; } virtual void ApplyPatternWeight(int pattern, int weight) { if(pattern == 0) m_pattern_0 = weight; if(pattern == 1) m_pattern_1 = weight; if(pattern == 2) m_pattern_2 = weight; if(pattern == 3) m_pattern_3 = weight; } //--- Published so the neural module can read the regime as context rather than recompute it. double ER(const int shift = 1) const { return EfficiencyRatio(shift, m_erPeriod); } double VR(const int shift = 1) const { return VarianceRatio(shift, m_vrPeriod, m_vrQ); } virtual bool ValidationSettings(void) override; virtual int LongCondition(void) override; virtual int ShortCondition(void) override; }; //+------------------------------------------------------------------+ CSignalRegime::CSignalRegime(void) : m_erPeriod(20), m_vrPeriod(60), m_vrQ(5), m_maPeriod(50), m_erTrend(0.35), m_erChop(0.15), m_vrRevert(0.85), m_pattern_0(60), m_pattern_1(60), m_pattern_2(50), m_pattern_3(50) { m_id = "REGIME"; m_pattern_count = 4; m_used_series = USE_SERIES_CLOSE; } //+------------------------------------------------------------------+ bool CSignalRegime::ValidationSettings(void) { if(!CWarriorSignal::ValidationSettings()) return false; if(m_erPeriod < 5 || m_vrPeriod < 20 || m_vrQ < 2 || m_vrPeriod < m_vrQ * 4) { //--- VR(q) needs several non-overlapping q-blocks to estimate a variance at all; with fewer //--- than four the ratio is noise with a decimal point. Print("CSignalRegime: periods are too short for a stable variance ratio"); return false; } return true; } //+------------------------------------------------------------------+ double CSignalRegime::Sma(const int shift, const int period) const { double s = 0.0; for(int i = 0; i < period; i++) s += Close(shift + i); return s / period; } //+------------------------------------------------------------------+ int CSignalRegime::LongCondition(void) { const int idx = StartIndex(); const double er = EfficiencyRatio(idx, m_erPeriod); const double vr = VarianceRatio(idx, m_vrPeriod, m_vrQ); const double c = Close(idx); const double ma = Sma(idx, m_maPeriod); //--- TRENDING AND UP. ER says the path is efficient, the reference says which way. if(er >= m_erTrend && c > ma) { m_active_pattern = "Pattern_0"; m_active_direction = "Buy"; return m_pattern_0; } //--- MEAN REVERTING AND BELOW THE REFERENCE. VR below 1 says moves cancel; being under the mean //--- is then a reason to buy rather than a reason to worry. This is the exact setup where the //--- trend modules are wrong, which is the whole point of separating the two regimes. if(vr <= m_vrRevert && er <= m_erChop && c < ma) { m_active_pattern = "Pattern_2"; m_active_direction = "Buy"; return m_pattern_2; } return 0; } //+------------------------------------------------------------------+ int CSignalRegime::ShortCondition(void) { const int idx = StartIndex(); const double er = EfficiencyRatio(idx, m_erPeriod); const double vr = VarianceRatio(idx, m_vrPeriod, m_vrQ); const double c = Close(idx); const double ma = Sma(idx, m_maPeriod); if(er >= m_erTrend && c < ma) { m_active_pattern = "Pattern_1"; m_active_direction = "Sell"; return m_pattern_1; } if(vr <= m_vrRevert && er <= m_erChop && c > ma) { m_active_pattern = "Pattern_3"; m_active_direction = "Sell"; return m_pattern_3; } return 0; } #endif // WARRIOR_SIGNALREGIME_MQH