Fuzzy Logic for Indicators, part 2
Ok, in the beginning we have to determine our goals and instruments.
We would draw our membership functions as lines in a subchart area. Our arithmetic average would be drawn as histogram and would be colored depending to absolute value of resulting function, from red to green. Also it’s good idea to draw our source indicators on chart in order to achieve the whole picture.
cAlgo Implementation
cAlgo is clean and simple but it lacks a few useful possibilities at this moment. One of them is inability to draw indicator lines on main chart and subchart at the same time, second one is the absence of multicolor histograms and third missed feature is an inability to use an already defined custom indicators. But we are able to overcome all these obstacles and complete our nice-looking indicator.
At first we’ll implement our source indicators. It is very simple at this moment because all that we need is two Moving Averages Envelopes and one Exponential Moving Average based on typical price so we’ll need a custom data series.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | using System; using cAlgo.API; using cAlgo.API.Indicators; namespace cAlgo.Indicators { [Indicator("Fuzzy Logic - Lines", IsOverlay = true)] public class FuzzyLogicLines : Indicator { [Parameter("MA Fast period", DefaultValue = 8)] public int fastPeriod { get; set; } [Parameter("MA Fast dev", DefaultValue = 0.08)] public double fastDev { get; set; } [Parameter("MA Slow period", DefaultValue = 32)] public int slowPeriod { get; set; } [Parameter("MA Slow dev", DefaultValue = 0.15)] public double slowDev { get; set; } [Parameter("Signal period", DefaultValue = 2)] public int signalPeriod { get; set; } [Output("FastUpperLine", Color = Colors.Red)] public IndicatorDataSeries FastUpperLine { get; set; } [Output("FastLowerLine", Color = Colors.Red)] public IndicatorDataSeries FastLowerLine { get; set; } [Output("SlowUpperLine", Color = Colors.Blue)] public IndicatorDataSeries SlowUpperLine { get; set; } [Output("SlowLowerLine", Color = Colors.Blue)] public IndicatorDataSeries SlowLowerLine { get; set; } [Output("Signal", Color = Colors.Yellow, Thickness = 2)] public IndicatorDataSeries Signal { get; set; } private IndicatorDataSeries priceTypical; private MovingAverage signalMA; private MovingAverage fastMA; private MovingAverage slowMA; protected override void Initialize() { // Initialize and create nested indicators priceTypical = CreateDataSeries(); signalMA = Indicators.MovingAverage(priceTypical, signalPeriod, MovingAverageType.Exponential); fastMA = Indicators.MovingAverage(MarketSeries.Close, fastPeriod, MovingAverageType.Simple); slowMA = Indicators.MovingAverage(MarketSeries.Close, slowPeriod, MovingAverageType.Simple); } public override void Calculate(int index) { priceTypical[index] = (MarketSeries.High[index] + MarketSeries.Low[index] + MarketSeries.Close[index]) / 3; signalMA.Calculate(index); Signal[index] = signalMA.Result[index]; FastUpperLine[index] = fastMA.Result[index] * (1 + fastDev / 100); FastLowerLine[index] = fastMA.Result[index] * (1 - fastDev / 100); SlowUpperLine[index] = slowMA.Result[index] * (1 + slowDev / 100); SlowLowerLine[index] = slowMA.Result[index] * (1 - slowDev / 100); } } } |
Then we left this indicator alone and start to write a second one, based on first’s code. This new indicator will use a subchart area with a vertical range from -1 to 1 and will show our membership functions and final colorful histogram. So if you’d like to see all initial conditions for our membership functions you simply add both indicators to chart. Of course they should use the same parameters.
We leave the parameters block untouched and replace output block with two lines and five histogram. Since we cannot change the color of specific bar of our histogram we would simply specify a value of only one histogram series. The remaining four series would have the NaN (“Not A Number”) value and wouldn’t be drawn on chart.
[Output(Thickness = 2, Color = Colors.Red)] public IndicatorDataSeries SignalFast { get; set; } [Output(Thickness = 2, Color = Colors.Blue)] public IndicatorDataSeries SignalSlow { get; set; } [Output(IsHistogram = true, Thickness = 4, Color = Colors.Red)] public IndicatorDataSeries Signal1 { get; set; } [Output(IsHistogram = true, Thickness = 4, Color = Colors.DarkOrange)] public IndicatorDataSeries Signal2 { get; set; } [Output(IsHistogram = true, Thickness = 4, Color = Colors.Gold)] public IndicatorDataSeries Signal3 { get; set; } [Output(IsHistogram = true, Thickness = 4, Color = Colors.GreenYellow)] public IndicatorDataSeries Signal4 { get; set; } [Output(IsHistogram = true, Thickness = 4, Color = Colors.Lime)] public IndicatorDataSeries Signal5 { get; set; } |
Now let’s duplicate our first indicator’s calculations. But we need values only for current bar, so we can replace IndicatorDataSeries objects with simple double-precision variables.
private IndicatorDataSeries priceTypical; private MovingAverage signalMA; private MovingAverage fastMA; private MovingAverage slowMA; // We can't draw this lines on non-overlay chart, // so we don't need IndicatorDataSeries variables private double FastUpperLine; private double FastLowerLine; private double SlowUpperLine; private double SlowLowerLine; private double SignalLine; private double SignalResult; private double AbsResult; protected override void Initialize() { // Initialize and create nested indicators priceTypical = CreateDataSeries(); signalMA = Indicators.MovingAverage(priceTypical, signalPeriod, MovingAverageType.Exponential); fastMA = Indicators.MovingAverage(MarketSeries.Close, fastPeriod, MovingAverageType.Simple); slowMA = Indicators.MovingAverage(MarketSeries.Close, slowPeriod, MovingAverageType.Simple); } |
Also we need our membership function. It’s very simple because we’ve formalized it earlier.
private double Fuzzy(double testVal, double upLine, double lowLine) { double F = 0; if (testVal > upLine) F = 1; // 100% uptrend if (testVal <= upLine && testVal >= lowLine) { F = (1 - 2*(upLine-testVal)/(upLine-lowLine)); } if (testVal < lowLine) F = -1; // 100% downtrend return F; } |
And now it is time to our Calculate() function. We can duplicate it from our first indicator, changing result variables. Then we calculate two our membership function and final function. Last thing to implement is our nice colored histogram. As I’ve wrote before we simply compute only one of our five histogram bars leaving others undefined.
public override void Calculate(int index) { priceTypical[index] = (MarketSeries.High[index] + MarketSeries.Low[index] + MarketSeries.Close[index]) / 3; signalMA.Calculate(index); SignalLine = signalMA.Result[index]; FastUpperLine = fastMA.Result[index] * (1 + fastDev / 100); FastLowerLine = fastMA.Result[index] * (1 - fastDev / 100); SlowUpperLine = slowMA.Result[index] * (1 + slowDev / 100); SlowLowerLine = slowMA.Result[index] * (1 - slowDev / 100); SignalFast[index] = Fuzzy(SignalLine, FastUpperLine, FastLowerLine); SignalSlow[index] = Fuzzy(SignalLine, SlowUpperLine, SlowLowerLine); SignalResult = (SignalFast[index] + SignalSlow[index]) / 2; AbsResult = Math.Abs(SignalResult); if (AbsResult > 0.0 && AbsResult <= 0.2) Signal1[index] = SignalResult; if (AbsResult > 0.2 && AbsResult <= 0.4) Signal2[index] = SignalResult; if (AbsResult > 0.4 && AbsResult <= 0.6) Signal3[index] = SignalResult; if (AbsResult > 0.6 && AbsResult <= 0.8) Signal4[index] = SignalResult; if (AbsResult > 0.8 && AbsResult <= 1.0) Signal5[index] = SignalResult; } |
So let’s compile both indicators and put them on one chart. As a result, we’ll see something like this: