| //+------------------------------------------------------------------+
|
| //| RF.mqh |
|
| //| Copyright 2026, Allan Niñal |
|
| //| https://www.allanninal.dev/mql5 |
|
| //+------------------------------------------------------------------+
|
| //| Range Filter engine, one CLOSED bar per step (no repaint). |
|
| //| Own implementation of the published range-filter method: |
|
| //| range r = EMA(EMA(|close - previous close|, period), |
|
| //| 2 x period - 1) x multiplier |
|
| //| filter: when the close is above the previous filter, the |
|
| //| filter rises to close - r (it never falls in that case); |
|
| //| below it, the filter falls to close + r (never rises); so it |
|
| //| only moves when price travels more than r away from it. |
|
| //| bands = filter +/- r. |
|
| //| trend counter: bars in a row the filter has risen (up) or |
|
| //| fallen (down); an unchanged filter keeps the counts. |
|
| //| buy setup: close above the filter, close not equal to the |
|
| //| previous close, filter rising; sell mirrors it. A signal is |
|
| //| the first buy setup after a sell setup (and vice versa). |
|
| //| EMAs are seeded with their first value (as TradingView's ema). |
|
| //| Tested against tests/reference.py. |
|
| //+------------------------------------------------------------------+
|
| // SPDX-License-Identifier: MPL-2.0
|
| // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not
|
| // distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/
|
| // Origin of the maths: UT Bot idea by Yo_adriiiiaan (from an original idea by HPotter), TradingView "UT Bot Alerts" by
|
| // QuantNomad; SuperTrend follows the widely published TradingView ATR-band script; Range Filter is an own implementation
|
| // of the published range-filter method.
|
| #ifndef RF_MQH
|
| #define RF_MQH
|
|
|
| struct RFParams
|
| {
|
| int period; // sampling period (100 by default)
|
| double mult; // range multiplier (3.0 by default)
|
| };
|
|
|
| struct RFState
|
| {
|
| int cnt; // bars fed; bar k = the k-th fed bar (0-based)
|
| double prev_src;
|
| double avrng; // EMA of |close change|
|
| double smrng; // EMA of avrng over 2*period-1, before the multiplier
|
| double range; // smrng * mult
|
| double filt; // EMPTY_VALUE until it starts (bar 1)
|
| double prev_filt;
|
| int upward,downward; // bars the filter has risen / fallen in a row
|
| int cond; // last setup: +1 buy, -1 sell, 0 none yet
|
| int ev; // +1/-1 when the last bar gave a signal, else 0
|
| int last_ev; // direction of the latest signal, 0 if none
|
| int last_ev_bar; // bar of the latest signal, -1 if none
|
| };
|
|
|
| void RFInit(RFState &s)
|
| {
|
| s.cnt=0; s.prev_src=0; s.avrng=EMPTY_VALUE; s.smrng=EMPTY_VALUE; s.range=EMPTY_VALUE;
|
| s.filt=EMPTY_VALUE; s.prev_filt=EMPTY_VALUE; s.upward=0; s.downward=0; s.cond=0;
|
| s.ev=0; s.last_ev=0; s.last_ev_bar=-1;
|
| }
|
|
|
| //--- feed one CLOSED bar's close; true when it gave a signal (s.ev)
|
| bool RFFeed(RFState &s,const RFParams &p,const double src)
|
| {
|
| int k=s.cnt;
|
| s.ev=0;
|
| if(k>=1)
|
| {
|
| double d=MathAbs(src-s.prev_src);
|
| double a1=2.0/(p.period+1);
|
| s.avrng=(s.avrng==EMPTY_VALUE ? d : s.avrng+a1*(d-s.avrng));
|
| int wper=2*p.period-1;
|
| double a2=2.0/(wper+1);
|
| s.smrng=(s.smrng==EMPTY_VALUE ? s.avrng : s.smrng+a2*(s.avrng-s.smrng));
|
| s.range=s.smrng*p.mult;
|
| s.prev_filt=s.filt;
|
| if(s.filt==EMPTY_VALUE)
|
| s.filt=src; // the filter starts at the first close with a range
|
| else
|
| {
|
| double f=s.filt,r=s.range;
|
| if(src>f)
|
| s.filt=(src-r<f ? f : src-r);
|
| else
|
| s.filt=(src+r>f ? f : src+r);
|
| }
|
| if(s.prev_filt!=EMPTY_VALUE)
|
| {
|
| if(s.filt>s.prev_filt)
|
| { s.upward++; s.downward=0; }
|
| else
|
| if(s.filt<s.prev_filt)
|
| { s.downward++; s.upward=0; }
|
| }
|
| bool moved=(src!=s.prev_src);
|
| bool buy=(src>s.filt && moved && s.upward>0);
|
| bool sell=(src<s.filt && moved && s.downward>0);
|
| int prev=s.cond;
|
| if(buy)
|
| s.cond=1;
|
| else
|
| if(sell)
|
| s.cond=-1;
|
| if(buy && prev==-1)
|
| s.ev=1;
|
| else
|
| if(sell && prev==1)
|
| s.ev=-1;
|
| }
|
| s.prev_src=src;
|
| s.cnt++;
|
| if(s.ev!=0)
|
| {
|
| s.last_ev=s.ev;
|
| s.last_ev_bar=k;
|
| return(true);
|
| }
|
| return(false);
|
| }
|
|
|
| #endif
|
| //+------------------------------------------------------------------+
|