How to correctly calculate Fisher Transform indicator
Asked Answered
N

1

14

I'm writing a small technical analysis library that consists of items that are not availabile in TA-lib. I've started with an example I found on cTrader and matched it against the code found in the TradingView version.

Here's the Pine Script code from TradingView:

len = input(9, minval=1, title="Length")

high_ = highest(hl2, len)
low_ = lowest(hl2, len)

round_(val) => val > .99 ? .999 : val < -.99 ? -.999 : val

value = 0.0
value := round_(.66 * ((hl2 - low_) / max(high_ - low_, .001) - .5) + .67 * nz(value[1]))

fish1 = 0.0
fish1 := .5 * log((1 + value) / max(1 - value, .001)) + .5 * nz(fish1[1])

fish2 = fish1[1]

Here's my attempt to implement the indicator:

    public class FisherTransform : IndicatorBase
    {
        public int Length = 9;

        public decimal[] Fish { get; set; }
        public decimal[] Trigger { get; set; }

        decimal _maxHigh;
        decimal _minLow;
 
        private decimal _value1;
        private decimal _lastValue1;

        public FisherTransform(IEnumerable<Candle> candles, int length) 
            : base(candles)
        {
            Length = length;
            RequiredCount = Length;
            _lastValue1 = 1;
        }

        protected override void Initialize()
        {
            Fish = new decimal[Series.Length];
            Trigger = new decimal[Series.Length];
        }

        public override void Compute(int startIndex = 0, int? endIndex = null)
        {
            if (endIndex == null)
                endIndex = Series.Length;

            for (int index = 0; index < endIndex; index++)
            {
                if (index == 1)
                {
                    Fish[index - 1] = 1;
                }
              
                _minLow = Series.Average.Lowest(Length, index);
                _maxHigh = Series.Average.Highest(Length, index);

                _value1 = Maths.Normalize(0.66m * ((Maths.Divide(Series.Average[index] - _minLow, Math.Max(_maxHigh - _minLow, 0.001m)) - 0.5m) + 0.67m * _lastValue1));

                _lastValue1 = _value1;

                Fish[index] = 0.5m * Maths.Log(Maths.Divide(1 + _value1, Math.Max(1 - _value1, .001m))) + 0.5m * Fish[index - 1];
                Trigger[index] = Fish[index - 1];
            }
        }
    }

IndicatorBase class and CandleSeries class

Math Helpers

The problem

The output values appear to be within the expected range however my Fisher Transform cross-overs do not match up with what I am seeing on TradingView's version of the indicator.

Question

How do I properly implement the Fisher Transform indicator in C#? I'd like this to match TradingView's Fisher Transform output.

What I Know

I've check my data against other indicators that I have personally written and indicators from TA-Lib and those indicators pass my unit tests. I've also checked my data against the TradingView data candle by candle and found that my data matches as expected. So I don't suspect my data is the issue.

Specifics

CSV Data - NFLX 5 min agg

Pictured below is the above-shown Fisher Transform code applied to a TradingView chart. My goal is to match this output as close as possible.

enter image description here

Fisher Cyan Trigger Magenta

Expected Outputs:

Crossover completed at 15:30 ET

  • Approx Fisher Value is 2.86

  • Approx Trigger Value is 1.79

Crossover completed at 10:45 ET

  • Approx Fisher Value is -3.67

  • Approx Trigger Value is -3.10

My Actual Outputs:

Crossover completed at 15:30 ET

  • My Fisher Value is 1.64

  • My Trigger Value is 1.99

Crossover completed at 10:45 ET

  • My Fisher Value is -1.63

  • My Trigger Value is -2.00

Bounty

To make your life easier I'm including a small console application complete with passing and failing unit tests. All unit tests are conducted against the same data set. The passing unit tests are from a tested working Simple Moving Average indicator. The failing unit tests are against the Fisher Transform indicator in question.

Project Files (updated 5/14)

Help get my FisherTransform tests to pass and I'll award the bounty.

enter image description here

Just comment if you need any additional resources or information.

Alternative Answers that I'll consider

  • Submit your own working FisherTransform in C#

  • Explain why my FisherTransform is actually working as expected

Nearsighted answered 12/5, 2019 at 0:32 Comment(8)
So how do we test if we are right or not? how do we know if we have it working to your specifications?Dhoti
If it matches TradingView's output we're golden. @DhotiNearsighted
I think you need to think about this a lot more, there is a chance that TradingViews implementation is incorrect, also we have no test data to match by, only a answer and fail by you testing it and telling us this is not working. i mean this could go on foreverDhoti
@Dhoti I understand your points... totally valid. I could provide a CSV with the data and my expected final outputs if that would make the question more reasonable. Also, anything else you can think of.Nearsighted
That would probably be sufficient, at least contributors would have a test case to go byDhoti
I'd agree with @TheGeneral, I've found more than once where TradingView formulas did not make sense. Their business model relies mostly on user-provided, likely unproven, formulas.Catachresis
@DaveSkender all good, I have access to your Fisher Transform now ;)Nearsighted
For comparison, feel free to check the implementation of Ehlers Fisher Transform in my open-source .NET library. I include manual calculations for comparison, in the GitHub repo.Catachresis
O
6

The code has two errors.

1) wrong extra brackets. The correct line is:

_value1 = Maths.Normalize(0.66m * (Maths.Divide(Series.Average[index] - _minLow, Math.Max(_maxHigh - _minLow, 0.001m)) - 0.5m) + 0.67m * _lastValue1);

2) Min and max functions must be:

public static decimal Highest(this decimal[] series, int length, int index)
{
    var maxVal = series[index]; // <----- HERE WAS AN ERROR!

    var lookback = Math.Max(index - length, 0);

    for (int i = index; i-- > lookback;)
        maxVal = Math.Max(series[i], maxVal);

    return maxVal;
}

public static decimal Lowest(this decimal[] series, int length, int index)
{
    var minVal = series[index]; // <----- HERE WAS AN ERROR!

    var lookback = Math.Max(index - length, 0);

    for (int i = index; i-- > lookback;)
    {
        //if (series[i] != 0) // <----- HERE WAS AN ERROR!
            minVal = Math.Min(series[i], minVal);
    }

    return minVal;
}

3) confusing test params. Please recheck your unittest values. AFTER THE UPDATE TESTS STILL NOT FIXED. For an example, the first FisherTransforms_ValuesAreReasonablyClose_First() has mixed values

var fish = result.Fish.Last(); //is equal to -3.1113144510775780365063063706
var trig = result.Trigger.Last(); //is equal to -3.6057793808025449204415435710

// TradingView Values for NFLX 5m chart at 10:45 ET
var fisherValue = -3.67m;
var triggerValue = -3.10m;
Opposite answered 14/5, 2019 at 17:13 Comment(8)
I’ll give this a shot here shortly. The unit test values are derived from the TradingView indicator as circled in the image in the OP. The FisherTransform signals are created based on crossovers. My goal is to get these crossovers reasonably close to what I’m seeing on TradingView, which should be possible since we’re using the same calculation as TradingView.Nearsighted
I don't trust the tests because in one line there is a comparison between Fish and Trigger. In another place fish and trigger clearly are in wrong places.Opposite
I see. I must have made an error in that case. I’ll check my code and reuplod the project. Thank you.Nearsighted
So, after switching to your updated code my indicator results are outside of the expected range (by a couple of integers). This doesn't mean your fix is incorrect. It could mean I have an additional miscalculation somewhere. I'm still tinkering with this as well so I'm not ruling this out as a potential answer.Nearsighted
int startIndex must be 1, not 0. Lowest() and Highest() functions must be properly initialized (not 0 or 9999) by value[index]. Maybe something else, but I stuck on tests.Opposite
I worked through those last night. Wrote unit tests for highest/lowest, I’ll upload a new project soon. Going to continue combing through this tonight. Thanks for your time Andrey.Nearsighted
Another error (I didn't test it) in Unittests is timezone. CSV has +5 timezone (I suppose it's yours). But the unittest code mentions local timezone (new DateTime(2019, 05, 10, 10, 45, 00)). My timezone isn't yours.Opposite
You nailed it Andrey. Thank you!Nearsighted

© 2022 - 2024 — McMap. All rights reserved.