Можете ли вы объединить торговую стратегию Golden Cross и уровни перекупленности и перепроданности RSI, чтобы создать прибыльную торговую стратегию?

Отказ от ответственности: материал в этой статье предназначен исключительно для образовательных целей и не должен восприниматься как профессиональный совет по инвестициям. Инвестируйте по своему усмотрению

В этой статье мы объединим то, что мы сделали в предыдущих двух отчетах, чтобы, надеюсь, создать прибыльный бэктест. Мы будем проводить очень упрощенное ретроспективное тестирование комбинации уровней перепроданности/перекупленности RSI вместе с двумя EMA (экспоненциальными скользящими средними), чтобы определить тенденции и, надеюсь, превратить две ранее убыточные торговые системы в одну, которая тестирует прибыль. Наше тестирование будет проводиться на дневном таймфрейме для различных биржевых тикеров.

Стратегия будет очень простой, так как я хочу объединить то, что мы обсуждали в двух предыдущих статьях, и продемонстрировать, насколько важна идентификация и торговля по тренду.

Мы возьмем стандартную стратегию Золотого Креста с 50-ЕМА и 200-ЕМА, но вместо того, чтобы использовать ее в качестве пересечения для генерации сигналов покупки и продажи, мы будем использовать ее для определения тренда. Мы скажем, что если 50EMA выше 200EMA, мы находимся в восходящем тренде, а если 50EMA ниже 200EMA, мы находимся в нисходящем тренде. Для условия входа мы будем использовать уровни перекупленности и перепроданности RSI, в частности, если RSI ниже 50, мы будем открывать длинные позиции, а если выше 50 — открывать короткие позиции. Мы добавим еще одно условие, но об этом позже.

Прежде чем мы начнем, нам нужно установить некоторые требования Python.

pip install backtrader pandas numpy matplotlib quantstats yfinance

Теперь настроим стратегию RSI. Мы загрузим очень простой индикатор RSI, используя встроенную библиотеку индикаторов Backtrader, она также поддерживает TA-Lib для тех, кто предпочитает это.

Поскольку это предназначено для быстрого представления стратегии RSI, мы будем использовать Yfinance для данных. Для тестирования на истории и оптимизации системы мы бы рассмотрели возможность торговли, мы хотели бы использовать более точные данные, однако скорректированные цены закрытия Yfinance удовлетворительны.

daysBack = 365 * 8
startDate = (datetime.datetime.today() - timedelta(days=daysBack - 1)).date()
yesterday = datetime.datetime.now().date() - timedelta(days=1)

ticker = "META"

data = bt.feeds.PandasData(
    dataname=yf.download(ticker, start=startDate, end=yesterday, interval="1d")
)

Мы будем тестировать стратегию в течение 8 лет на дневном таймфрейме, используя скорректированные цены закрытия.

Теперь о логике. Мы будем использовать период RSI 14, так как это общепринято в качестве стандартного значения, и аналогично EMA мы будем использовать стандартные значения Золотого Креста 50 и 200. Для начала стратегия будет покупать, если RSI упадет ниже значение 50 и быстрая MA выше медленной MA. Обратное верно для коротких сигналов.

Чтобы закрыть стратегию, мы начнем с закрытия покупки, если 50-ЕМА снова пересекает 200-ЕМА и наоборот для короткой позиции.

class rsi(bt.Strategy):
    params = (
        ("rsiPeriod", 14),
        ("maFastPeriod", 50),
        ("maSlowPeriod", 200),
        )

    def log(self, txt, dt=None):
        dt = dt or self.datas[0].datetime.date(0)
        print("%s, %s" % (dt.isoformat(), txt))

    def __init__(self):
        self.dataclose = self.datas[0].close

        self.order = None
        self.buyprice = None
        self.buycomm = None

        self.rsi = bt.indicators.RelativeStrengthIndex(
            period=self.params.rsiPeriod, plot=True
        )

        self.maFast = bt.indicators.ExponentialMovingAverage(
                    period=self.params.maFastPeriod, plot=True
        )

        self.maSlow = bt.indicators.ExponentialMovingAverage(
            period=self.params.maSlowPeriod, plot=True
        )
        
    def notify_order(self, order):
        if order.status in [order.Completed]:
            if order.isbuy():
                self.buyprice = order.executed.price
                self.log("BUY EXECUTED, %.2f" % order.executed.price)
            else:
                self.sellprice = order.executed.price
                self.log("SELL EXECUTED, %.2f" % order.executed.price)
            self.bar_executed = len(self)
        elif order.status in [order.Canceled]:
            self.log("Order Canceled/Margin/Rejected")
        self.order = None

    def next(self):
        if self.order:
            return

        # If not in poisition
        if not self.position:

            # Create Buy
            if (
                (self.maFast[0] > self.maSlow[0]) and
                (self.rsi[0] < 50) 
                # (self.dataclose[0] > self.maSlow[0]) 

            ):
                self.buy()
                self.log("Create Buy, %.2f" % self.dataclose[0])

            # Create Sell
            if (
                (self.maFast[0] < self.maSlow[0]) and
                (self.rsi[0] > 50) 
                # (self.dataclose[0] < self.maSlow[0]) 


            ):
                self.sell()
                self.log("Create Sell, %.2f" % self.dataclose[0])

        # Close Buy position
        if self.position.size > 0:
            if (
                (self.maFast[0] < self.maSlow[0]) or
                # (self.rsi[0] > 70) or
                # (self.dataclose[0] < self.maSlow[0])
                ):
                self.log("Close Buy, %.2f" % self.dataclose[0])
                self.close()

        # Close Sell position
        if self.position.size < 0:
            if (
                (self.maFast[0] > self.maSlow[0]) or
                # (self.rsi[0] < 30) or 
                # (self.dataclose[0] > self.maSlow[0])
                ):
                self.log("Close Sell, %.2f" % self.dataclose[0])
                self.close()

        # Close all open position at the end of the backtest
        if len(self.dataclose) == (self.data.buflen() - 1):
            self.close()

    # Get the closing value of the portfolio
    def stop(self):
        self.log(self.broker.getvalue())

Теперь запускаем стратегию.

Некоторые моменты, на которые следует обратить внимание:

  • Выше мы рассчитали цену индикатора при закрытии каждой свечи.
  • Предполагается, что цена входа является ценой открытия следующей свечи.
  • Перед окончанием бэктеста мы закрываем все открытые ордера

Предположения:

  • Стартовый капитал 1000
  • Инвестируйте все доступное в каждую сделку
  • 0,1% комиссионных за открытие и закрытие каждой сделки
if __name__ == "__main__":

    cerebro = bt.Cerebro()
    cerebro.adddata(data)
    cerebro.addstrategy(rsi)

    cerebro.broker.setcommission(commission=0.001)
    cerebro.addsizer(bt.sizers.AllInSizerInt)
    cerebro.broker.setcash(1000)

    cerebro.addanalyzer(bt.analyzers.DrawDown, _name="drawdown")

    print("Starting Portfolio Value: %.2f" % cerebro.broker.getvalue())

    results = cerebro.run()
    strat = results[0]

    cerebro.plot()

    print("Final Portfolio Value: %.2f" % cerebro.broker.getvalue())
    drawdownDf = strat.analyzers.drawdown.get_analysis()
    maxDraw = drawdownDf["max"]["drawdown"]
    print(f"Drawdown: {(round(maxDraw,2))}%")
    print("************************")

Полученные результаты

  • МЕТА — дневной таймфрейм — 8 лет
  • Начальное значение: 1000, конечное значение: 2205,83, просадка: 49,31%%

Из приведенных выше результатов видно, что, используя стратегию RSI в сочетании с тенденцией Золотого Креста, мы бы удвоили наши первоначальные инвестиции и превзошли бы стратегию «купи и держи». Однако, если мы сделаем шаг назад и посмотрим на просадку и характер убыточных сделок, мы сможем улучшить ситуацию.

Также можно объяснить неудачу стратегии «купи и держи» тем фактом, что в 2022 году в META произошло резкое падение цены акций. Нисходящий тренд предполагает, что у этой стратегии есть некоторые достоинства.

Улучшения

Теперь давайте добавим некоторые дополнительные условия входа. Добавим, что теперь цена должна быть выше медленной MA при входе на покупку, а при открытии на продажу цена должна быть ниже медленной MA. Это должно гарантировать, что мы не будем открывать длинные позиции, когда цена уже пробила 200ЕМА, или короткие позиции, когда цена выше.

        if not self.position:

            # Create Buy
            if (
                (self.maFast[0] > self.maSlow[0]) and
                (self.rsi[0] < 50) and
                (self.dataclose[0] > self.maSlow[0]) 

            ):
                self.buy()
                self.log("Create Buy, %.2f" % self.dataclose[0])

            # Create Sell
            if (
                (self.maFast[0] < self.maSlow[0]) and
                (self.rsi[0] > 50) and
                (self.dataclose[0] < self.maSlow[0]) 

            ):
                self.sell()
                self.log("Create Sell, %.2f" % self.dataclose[0])

Теперь об условиях выхода. Вместо закрытия на пересечении MA мы закроем нашу позицию на покупку, если цена упадет ниже 200 EMA, и закроем короткую позицию, если цена поднимется выше 200 EMA. мы также будем закрывать покупку, если RSI выше 70, что означает, что он перекуплен/переоценен, а также закроем короткую позицию, если RSI упадет ниже 30, что означает, что он перепродан и может подвергнуться повышательному давлению.

        # Close Buy position
        if self.position.size > 0:
            if (
                (self.maFast[0] < self.maSlow[0]) or
                (self.rsi[0] > 70) or
                (self.dataclose[0] < self.maSlow[0])
                ):
                self.log("Close Buy, %.2f" % self.dataclose[0])
                self.close()

        # Close Sell position
        if self.position.size < 0:
            if (
                (self.maFast[0] > self.maSlow[0]) or
                (self.rsi[0] < 30) or 
                (self.dataclose[0] > self.maSlow[0])
                ):
                self.log("Close Sell, %.2f" % self.dataclose[0])
                self.close()

  • МЕТА — дневной таймфрейм — 8 лет
  • Начальное значение: 1000, конечное значение: 2849,38, просадка: 36,11%

Это улучшает результаты, мы перешли от проверенных на истории 2205,83 до 2849,38 с уменьшением просадки на 13%.

Наконец, взглянув на бэктест, мы видим, что наши условия закрытия слишком агрессивны. Давайте удалим один из них. Теперь мы будем закрывать позицию только на пересечении MA или если RSI упадет ниже 30 или выше 70, чтобы закрыть продажу и покупку соответственно.

  # Close Buy position
        if self.position.size > 0:
            if (
                (self.maFast[0] < self.maSlow[0]) or
                (self.rsi[0] > 70)
                ):
                self.log("Close Buy, %.2f" % self.dataclose[0])
                self.close()

        # Close Sell position
        if self.position.size < 0:
            if (
                (self.maFast[0] > self.maSlow[0]) or
                (self.rsi[0] < 30) 
                ):
                self.log("Close Sell, %.2f" % self.dataclose[0])
                self.close()

Это снова улучшает результаты, идущие от проверенных на истории 2849,38 до 3384,71, в ущерб просадке, которая увеличилась до 45% с 36%, отметив, что это все еще меньше, чем при первоначальном тестировании на исторических данных (49%).

  • МЕТА — дневной таймфрейм — 8 лет
  • Начальное значение: 1000, конечное значение: 3384,71, просадка: 45,51%

В заключение, пока это очень специфичный тест и на одном таймфрейме. действительно кажется, что в пределах разумного и с несколькими настройками вы можете взять стандартный индикатор недопроданности / перепроданности RSI, уровни и объединить его со стратегией Золотого Креста для анализа тренда и создать прибыльный бэктест. С оптимизацией и форвард-тестированием результаты могут быть лучше.

Мы должны отметить, конечно, что это всего лишь бэктест, и ни в коем случае не рекомендуется торговать с ним. Нам пришлось бы учитывать ряд факторов, часы пострыночных торгов и связанный с ними спред, используемое кредитное плечо и вопросы ликвидности при торговле несколькими тикерами наряду со многими другими.

Мы также должны учитывать точность данных, использованных при тестировании на исторических данных, и проверенную временную шкалу. Мы хотели бы предложить несколько более продвинутых методов определения тренда, а затем торговать только в длинную или короткую позицию в зависимости от рыночного тренда, мы также хотели бы изучить, как можно использовать волатильность для улучшения результатов, что мы рассмотрим в следующих статьях. .

В BDPO мы разработали ряд стратегий, которые были проверены на истории, оптимизированы и, что наиболее важно, протестированы на невидимых данных, чтобы изучить наши тесты на истории и службу рыночных сигналов, а также оповещения о новостях Forex, брифинги о доходах, перепроданность Golden Cross и RSI. оповещения о перекупленности, пожалуйста, посетите:

www.BDPO.co.uk

Если у вас есть собственная стратегия, но вы не знаете, как кодировать, посетите BDPO, и мы можем создать собственный отчет о тестировании на истории для вашей собственной стратегии.