nardew / talipp

talipp - incremental technical analysis library for python
https://nardew.github.io/talipp
MIT License
388 stars 60 forks source link

Implement Least Square MA #23

Open nardew opened 4 years ago

femtotrader commented 4 months ago

This https://pythonnumericalmethods.studentorg.berkeley.edu/notebooks/chapter16.04-Least-Squares-Regression-in-Python.html could help (Least Squares Regression) What kind of dependencies is allowed? Numpy could help for this kind of work

QuantConnect Lean (C#) provides such an indicator https://github.com/QuantConnect/Lean/blob/master/Indicators/LeastSquaresMovingAverage.cs

How such an indicator should work when input data are not equally spaced in time?

femtotrader commented 4 months ago

I have done some tests with QC (Research environment)...

Here is code

CLOSE_TMPL = [10.5, 9.78, 10.46, 10.51, 10.55, 10.72, 10.16, 10.25, 9.4, 9.5, 9.23, 8.5, 8.8, 8.33, 7.53, 7.61, 6.78, 8.6, 9.21, 8.95, 9.22, 9.1, 8.31, 8.37, 8.3, 7.78, 8.05, 8.1, 8.08, 7.49, 7.58, 8.17, 8.83, 8.91, 9.2, 9.76, 9.42, 9.3, 9.32, 9.04, 9.0, 9.33, 9.34, 8.49, 9.21, 10.15, 10.3, 10.59, 10.23, 10.0]
TIME_TMPL = pd.date_range("2020-01-01", freq="D", periods=len(CLOSE_TMPL))
df = pd.DataFrame({"Close": CLOSE_TMPL}, index=TIME_TMPL)
period = 5
indicator = LeastSquaresMovingAverage(period)
indicator.is_ready, indicator.current.time, indicator.current.value
indicator_is_ready = []
indicator_output_values = []
prec = 2
for row in df.iterrows():
    current_time, current_value = row
    current_value = Decimal(current_value["Close"]).quantize(Decimal(10)**-prec)
    indicator.update(current_time, current_value)
    indicator_is_ready.append(indicator.is_ready)
    indicator_output_values.append(indicator.current.value)
    #print(current_time, current_value, indicator.is_ready, indicator.current.value)  # value = Intercept.Current.Value + Slope.Current.Value * Period
df["is_ready"] = indicator_is_ready
df["indicator_output_values"] = indicator_output_values
df

and output

            Close  is_ready  indicator_output_values
2020-01-01  10.50     False                   10.500
2020-01-02   9.78     False                    9.780
2020-01-03  10.46     False                   10.460
2020-01-04  10.51     False                   10.510
2020-01-05  10.55      True                   10.526
2020-01-06  10.72      True                   10.798
2020-01-07  10.16      True                   10.402
2020-01-08  10.25      True                   10.256
2020-01-09   9.40      True                    9.662
2020-01-10   9.50      True                    9.366
2020-01-11   9.23      True                    9.186
2020-01-12   8.50      True                    8.642
2020-01-13   8.80      True                    8.646
2020-01-14   8.33      True                    8.318
2020-01-15   7.53      True                    7.764
2020-01-16   7.61      True                    7.544
2020-01-17   6.78      True                    6.858
2020-01-18   8.60      True                    7.728
2020-01-19   9.21      True                    8.816
2020-01-20   8.95      True                    9.252
2020-01-21   9.22      True                    9.598
2020-01-22   9.10      True                    9.218
2020-01-23   8.31      True                    8.628
2020-01-24   8.37      True                    8.376
...
2020-02-15  10.15      True                    9.606
2020-02-16  10.30      True                   10.214
2020-02-17  10.59      True                   10.806
2020-02-18  10.23      True                   10.592
2020-02-19  10.00      True                   10.180

I can't output separately slope and intercept but only Intercept.Current.Value + Slope.Current.Value * Period but I think it can help to have reference data for tests


This kind of unit tests can also help (in LeastSquaresMovingAverageTest.cs)

        [Test]
        public void TalippCompare_with_period_2()
        {
            decimal[] CLOSE_TMPL = new decimal[]
            {
                10.5m, 9.78m, 10.46m, 10.51m, 10.55m, 10.72m, 10.16m, 10.25m, 9.4m, 9.5m,
                9.23m, 8.5m, 8.8m, 8.33m, 7.53m, 7.61m, 6.78m, 8.6m, 9.21m, 8.95m,
                9.22m, 9.1m, 8.31m, 8.37m, 8.3m, 7.78m, 8.05m, 8.1m, 8.08m, 7.49m,
                7.58m, 8.17m, 8.83m, 8.91m, 9.2m, 9.76m, 9.42m, 9.3m, 9.32m, 9.04m,
                9.0m, 9.33m, 9.34m, 8.49m, 9.21m, 10.15m, 10.3m, 10.59m, 10.23m, 10.0m
            };
            DateTime[] DATE_TMPL = Enumerable.Range(0, CLOSE_TMPL.Length)
                                         .Select(i => new DateTime(2024, 7, 7).AddDays(i))
                                         .ToArray();
            decimal[] expected_slope = new decimal[]
            {
                0m, -0.720m, 0.680m, 0.050m, 0.040m, 0.170m, -0.560m, 0.090m, -0.85m, 0.100m,
                -0.270m, -0.730m, 0.300m, -0.470m, -0.800m, 0.080m, -0.830m, 1.820m, 0.610m, -0.260m,
                0.270m, -0.120m, -0.790m, 0.060m, -0.070m, -0.520m, 0.270m, 0.050m, -0.020m, -0.590m,
                0.090m, 0.590m, 0.660m, 0.080m, 0.290m, 0.560m, -0.340m, -0.120m, 0.020m, -0.280m,
                -0.040m, 0.330m, 0.0100m, -0.850m, 0.720m, 0.940m, 0.150m, 0.290m, -0.360m, -0.230m
            };
            decimal[] expected_intercept = new decimal[]
            {
                0.000m, 11.220m, 9.100m, 10.410m, 10.470m, 10.380m, 11.280m, 10.070m, 11.100m, 9.300m,
                9.770m, 9.960m, 8.200m, 9.270m, 9.130m, 7.450m, 8.440m, 4.960m, 7.990m, 9.470m,
                8.680m, 9.340m, 9.890m, 8.250m, 8.440m, 8.820m, 7.510m, 8.000m, 8.120m, 8.670m,
                7.400m, 6.990m, 7.510m, 8.750m, 8.620m, 8.640m, 10.100m, 9.540m, 9.280m, 9.600m,
                9.080m, 8.670m, 9.320m, 10.190m, 7.770m, 8.270m, 10.000m, 10.010m, 10.950m, 10.460m
            };
            var indicator = new LeastSquaresMovingAverage(2);
            for (int i=0; i<CLOSE_TMPL.Length; i++)
            {
                indicator.Update(DATE_TMPL[i], CLOSE_TMPL[i]);
                Assert.AreEqual(Math.Round((decimal)indicator.Slope.Current.Value, 4), expected_slope[i]);
                Assert.AreEqual(indicator.Intercept.Current.Value, expected_intercept[i]);
                Assert.AreEqual(Math.Round((decimal)indicator.Current.Value, 4), CLOSE_TMPL[i]);
            }
        }

        [Test]
        public void TalippCompare_with_period_5()
        {
            decimal[] CLOSE_TMPL = new decimal[]
            {
                10.5m, 9.78m, 10.46m, 10.51m, 10.55m, 10.72m, 10.16m, 10.25m, 9.4m, 9.5m,
                9.23m, 8.5m, 8.8m, 8.33m, 7.53m, 7.61m, 6.78m, 8.6m, 9.21m, 8.95m,
                9.22m, 9.1m, 8.31m, 8.37m, 8.3m, 7.78m, 8.05m, 8.1m, 8.08m, 7.49m,
                7.58m, 8.17m, 8.83m, 8.91m, 9.2m, 9.76m, 9.42m, 9.3m, 9.32m, 9.04m,
                9.0m, 9.33m, 9.34m, 8.49m, 9.21m, 10.15m, 10.3m, 10.59m, 10.23m, 10.0m
            };
            DateTime[] DATE_TMPL = Enumerable.Range(0, CLOSE_TMPL.Length)
                                         .Select(i => new DateTime(2024, 7, 7).AddDays(i))
                                         .ToArray();
            //decimal[] expected_zeros = Enumerable.Repeat(0m, 50).ToArray();
            decimal[] expected_slope = new decimal[]
            {
                0.000m, 0.000m, 0.000m, 0.000m, 0.083m, 0.197m, -0.039m, -0.091m, -0.277m, -0.320m,
                -0.261m, -0.367m, -0.220m, -0.277m, -0.357m, -0.305m, -0.476m, -0.021m, 0.435m, 0.511m,
                0.523m, 0.101m, -0.165m, -0.207m, -0.257m, -0.265m, -0.111m, -0.079m, -0.012m, -0.055m,
                -0.155m, -0.036m, 0.218m, 0.409m, 0.398m, 0.355m, 0.203m, 0.100m, -0.022m, -0.154m,
                -0.110m, -0.026m, 0.033m, -0.076m, -0.042m, 0.151m, 0.358m, 0.529m, 0.248m, -0.037m
            };
            decimal[] expected_intercept = new decimal[]
            {
                0.000m, 0.000m, 0.000m, 0.000m, 10.111m, 9.813m, 10.597m, 10.711m, 11.047m, 10.966m,
                10.491m, 10.477m, 9.746m, 9.703m, 9.549m, 9.069m, 9.238m, 7.833m, 6.641m, 6.697m,
                6.983m, 8.713m, 9.453m, 9.411m, 9.431m, 9.167m, 8.495m, 8.357m, 8.098m, 8.065m,
                8.325m, 7.992m, 7.376m, 6.969m, 7.344m, 7.909m, 8.615m, 9.018m, 9.466m, 9.830m,
                9.546m, 9.276m, 9.107m, 9.268m, 9.200m, 8.851m, 8.424m, 8.161m, 9.352m, 10.365m
            };
            decimal[] expected_pred = new decimal[]
            {
                10.5m, 9.78m, 10.46m, 10.51m, 10.526m, 10.798m, 10.402m, 10.256m, 9.662m, 9.366m,
                9.186m, 8.642m, 8.646m, 8.318m, 7.764m, 7.544m, 6.858m, 7.728m, 8.816m, 9.252m,
                9.598m, 9.218m, 8.628m, 8.376m, 8.146m, 7.842m, 7.940m, 7.962m, 8.038m, 7.790m,
                7.550m, 7.812m, 8.466m, 9.014m, 9.334m, 9.684m, 9.630m, 9.518m, 9.356m, 9.060m,
                8.996m, 9.146m, 9.272m, 8.888m, 8.990m, 9.606m, 10.214m, 10.806m, 10.592m, 10.180m
            };
            var indicator = new LeastSquaresMovingAverage(5);
            for (int i = 0; i < CLOSE_TMPL.Length; i++)
            {
                indicator.Update(DATE_TMPL[i], CLOSE_TMPL[i]);
                Assert.AreEqual(Math.Round((decimal)indicator.Slope.Current.Value, 4), expected_slope[i]);
                Assert.AreEqual(indicator.Intercept.Current.Value, expected_intercept[i]);
                Assert.AreEqual(Math.Round((decimal)indicator.Current.Value, 4), expected_pred[i]);
            }
        }
femtotrader commented 4 months ago

https://github.com/nardew/talipp/pull/149 is a draft PR to showcase a possible solution using Numpy.

Reference data for unit tests come from QuantConnect Lean but I'm not 100% sure that it's good enough.

I think this implementation works correctly only with regularly time spaced incoming data (which is the case here) but it shouldn't behave correcly when data are irrregularly time spaced.

femtotrader commented 4 months ago

I wonder if LSMA is different from Time Series Forecast (TSF) implemented in Tulip https://tulipindicators.org/tsf https://github.com/TulipCharts/tulipindicators/blob/master/indicators/tsf.c or in C# https://github.com/QuantConnect/Lean/pull/7654

nardew commented 3 months ago

Thanks a lot for this! Unfortunately I cannot spend much time with the library but I am looking forward to look into this as soon as possible.

femtotrader commented 3 months ago

I wonder how TSF https://tulipindicators.org/tsf is different from LSMA