Как внедрить дискриминатор Ehlers Homodyne в серии Pandas - PullRequest
0 голосов
/ 10 июня 2019

Я хочу включить динамический «период просмотра» для моих фондовых индикаторов для заданного периода времени.Ранее я реализовал Homodyne Discriminator от Ehler, используя скользящее окно ;каждый раз, когда в моем алгоритме появляется новая точка данных, дискриминатор пересчитывается (но сохраняет память о предыдущих вычислениях ... см. ниже).Я бы предпочел определить период с использованием Pandas, так как он кажется более быстрым методом реализации обработки данных для больших наборов данных.

Обратите внимание, что я сталкиваюсь с данными двумя способами: во-первых, исторические данные генерируются в большом количестве;и, во-вторых, данные поступают по одной минуте за раз и будут добавляться к историческим данным для повторной обработки.

Проблемы, с которыми я сталкиваюсь:

  1. Расчеты зависят отЗначение индекса периода и периода зависит от других расчетов (см. оригинальный скрипт).Однако в настоящее время вычисления с использованием панд выполняются в большом количестве, поэтому данные никогда не изменяются со временем, как это должно быть.
  2. В кадре данных содержатся значения для нескольких активов (MultiIndex), поэтому в настоящее время я обрабатываю дискриминатор один раз для каждого актива;Есть ли способ, которым я могу выполнить это один раз и позволить Pandas выполнить группировку?
  3. Должен ли я просто повторно обрабатывать весь набор данных каждый раз, когда поступают новые данные, или я должен покончить с преимуществами Pandas и просто выполнить итерациюкаждая новая строка и использовать мой старый скрипт?

Исторические данные:

                            close   high     low     open     volume
symbol time                                                           
SPY    2019-06-07 15:41:00  288.03  288.060  287.98  288.030  132296.0
       2019-06-07 15:42:00  288.04  288.060  287.96  288.035  103635.0
       2019-06-07 15:43:00  288.15  288.160  288.04  288.045  144841.0
       2019-06-07 15:44:00  288.10  288.190  288.09  288.150  166086.0
       2019-06-07 15:45:00  287.93  288.120  287.93  288.100  145304.0
       2019-06-07 15:46:00  287.77  287.935  287.75  287.935  253202.0
       2019-06-07 15:47:00  287.86  287.870  287.76  287.760  140996.0
       2019-06-07 15:48:00  287.78  287.865  287.76  287.860  178082.0
       2019-06-07 15:49:00  287.83  287.855  287.62  287.790  631133.0
       2019-06-07 15:50:00  287.83  287.915  287.78  287.825  279326.0

Оригинальный скрипт (self.Value - фактический период).Если вы не используете QuantConnect, я уверен, что вы можете просто заменить все RollingWindows на массивы с обращенными данными или отменить ссылки.В этом сценарии Update вызывается каждый раз, когда в фрейме данных создается новая строка:

class HomodyneDiscriminatorPeriodOld():
    Values = RollingWindow[int](2)
    SmoothedPeriod = RollingWindow[float](2)
    Smooth = RollingWindow[float](7)
    Detrend = RollingWindow[float](7)
    Source = RollingWindow[float](4)
    I1 = RollingWindow[float](7)
    I2 = RollingWindow[float](7)
    Q1 = RollingWindow[float](7)
    Q2 = RollingWindow[float](7)
    Re = RollingWindow[float](2)
    Im = RollingWindow[float](2)

    def FillWindows(self, *args, value=0):
        for window in args:
            for i in range(window.Size):
                window.Add(value)

    def __init__(self, period=1):
        self.Value = period
        self.Period = period
        # Start with history
        self.FillWindows(self.Smooth, self.SmoothedPeriod, self.Detrend, self.I1, self.I2, self.Q1, self.Q2, self.Re, self.Im)
        self.FillWindows(self.Values, value=self.Value)


    def __repr__(self):
        return "{}".format(self.Value)


    def Weighted(self, first, second, percent=0.2):
        return percent * first + (1 - percent) * second


    def Quadrature(self, window):
        C1 = 0.0962
        C2 = 0.5769
        C3 = self.Period * 0.075 + 0.54
        return (window[0] * C1 + window[2] * C2 - window[4] * C2 - window[6] * C1) * C3


    def Update(self, data):
        self.Source.Add((data.High + data.Low) / 2)
        if not self.Source.IsReady: return self.Value

        #
        # --- Start the Homodyne Discriminator Caculations
        #
        # Mutable Variables (non-series)
        self.Smooth.Add((self.Source[0] * 4.0 + self.Source[1] * 3.0 + self.Source[2] * 2.0 + self.Source[3]) / 10.0)
        self.Detrend.Add(self.Quadrature(self.Smooth))

        # Compute InPhase and Quadrature components
        self.Q1.Add(self.Quadrature(self.Detrend))
        self.I1.Add(self.Detrend[3])

        # Advance Phase of I1 and Q1 by 90 degrees
        jI = self.Quadrature(self.I1)
        jQ = self.Quadrature(self.Q1)

        # Phaser addition for 3 bar averaging and 
        # Smooth i and q components before applying discriminator
        self.I2.Add(self.Weighted(self.I1[0] - jQ, self.I2[0]))
        self.Q2.Add(self.Weighted(self.Q1[0] + jI, self.Q2[0]))

        # Extract Homodyne Discriminator
        self.Re.Add(self.Weighted(self.I2[0] * self.I2[1] + self.Q2[0] * self.Q2[1], self.Re[0]))
        self.Im.Add(self.Weighted(self.I2[0] * self.Q2[1] - self.Q2[0] * self.I2[1], self.Im[0]))

        # Calculate the period
        period = ((math.pi * 2) / math.atan(self.Im[0] / self.Re[0])) if (self.Re[0] != 0 and self.Im[0] != 0) else 0
        period = min(max(max(min(period, 1.5 * self.Period), 0.6667 * self.Period), 6), 50)
        self.Period = self.Weighted(period, self.Period)
        self.SmoothedPeriod.Add(self.Weighted(self.Period, self.SmoothedPeriod[0], 0.33))
        self.Value = round(self.SmoothedPeriod[0] * 0.5 - 1)
        if self.Value < 1: self.Value = 1
        self.Values.Add(self.Value)

        return self.Value

Pandas Script.Update в настоящее время вызывается только один раз после массового импорта исторических данных.Я до сих пор не реализовал метод расчета, как указано в Q3, если он даже требуется:

class HomodyneDiscriminatorPeriod():


    def Weighted(self, series, other=None, percent=0.2):
        if other is None: other = series
        return percent * series + (1 - percent) * other


    def Quadrature(self, series):
        C1 = 0.0962
        C2 = 0.5769
        C3 = self.Frame.period * 0.075 + 0.54
        return (series * C1 + series.shift(2) * C2 - series.shift(4) * C2 - series.shift(6) * C1) * C3


    def Update(self, frame):
        # Add period column to timeframe's dataframe
        frame['period'] = 1

        # Initialize internal dataframe with same structure
        # as timeframe's dataframe but without original columns
        self.Frame = pd.DataFrame().reindex_like(frame)
        self.Frame.drop(frame.columns, axis=1)
        self.Frame['period'] = 1
        self.Frame['smoothed_period'] = 1
        self.Frame['i2'] = 0
        self.Frame['q2'] = 0
        self.Frame['re'] = 0
        self.Frame['im'] = 0

        # Shorthand references
        period = self.Frame['period']
        smoothed_period = self.Frame['smoothed_period']
        i2 = self.Frame['i2']
        q2 = self.Frame['q2']
        re = self.Frame['re']
        im = self.Frame['im']

        #
        # --- Start the Homodyne Discriminator Caculations
        #
        # Mutable Variables (non-series)
        hl2 = (frame.high + frame.low) / 2
        smooth = (hl2 * 4.0 + hl2.shift(1) * 3.0 + hl2.shift(2) * 2.0 + hl2.shift(3)) / 10.0
        detrend = self.Quadrature(smooth)

        # Compute InPhase and Quadrature components
        q1 = self.Quadrature(detrend)
        i1 = detrend.shift(3)

        # Advance Phase of I1 and Q1 by 90 degrees
        ji = self.Quadrature(i1)
        jq = self.Quadrature(q1)

        # Phaser addition for 3 bar averaging and
        # smooth i and q components before applying discriminator
        i2 = self.Weighted(i1 - jq)
        q2 = self.Weighted(q1 + ji)

        # Extract Homodyne Discriminator
        re = self.Weighted(i2 * i2.shift(1) + q2 * q2.shift(1))
        im = self.Weighted(i2 * q2.shift(1) - q2 * i2.shift(1))

        # Calculate the period
        # TODO: Use 360 or 2 * np.pi???? Official doc says 360...
        _period = (2 * np.pi / np.arctan(im / re)).clip(upper=1.5 * period, lower=0.6667 * period).clip(upper=50, lower=6)
        period = self.Weighted(_period, period)
        smoothed_period = self.Weighted(period, smoothed_period, 0.33)
        return (smoothed_period * 0.5 - 1).round().clip(lower=1)
...