Coppock Curve — кодирование и тестирование торговой стратегии на Python

В сегодняшней статье мы собираемся обсудить специальный индикатор, специально предназначенный для долгосрочных торговых целей, а именно кривую Коппока. Сначала мы обсудим концепции, которые являются предпосылками для кривой Коппока. Затем мы перейдем к изучению основной концепции этой статьи, которая представляет собой кривую Коппока, и математику, лежащую в основе индикатора. После этого мы перейдем к части кодирования, где мы будем использовать Python для создания индикатора с нуля, построения торговой стратегии на его основе, тестирования стратегии и сравнения результатов с результатами SPY ETF (ETF, специально разработанного для отслеживания движения рыночного индекса S&P 500). С учетом сказанного, давайте погрузимся в статью.

Прежде чем двигаться дальше, если вы хотите протестировать свои торговые стратегии без какого-либо кодирования, для этого есть решение. Это BacktestZone. Это платформа для бесплатного тестирования любого количества торговых стратегий на различных типах торгуемых активов без кодирования. Вы можете использовать инструмент прямо сейчас, используя ссылку здесь: https://www.backtestzone.com/

ROC и WMA

Как я уже говорил, сначала мы обсудим концепции, которые являются предпосылками для кривой Коппока, которые являются не чем иным, как скоростью изменения (ROC) и взвешенной скользящей средней (WMA). Не имея некоторых знаний об этих концепциях, будет трудно выучить кривую Коппока.

Во-первых, это показатель ROC. Индикатор Rate Of Change — это индикатор импульса, который используется трейдерами в качестве инструмента для определения процентного изменения цены от текущей цены закрытия и цены указанного количества периодов назад. В отличие от других индикаторов импульса, таких как RSI и CCI, индикатор Rate Of Change представляет собой неограниченный осциллятор, значения которого не привязаны к определенным пределам.

Чтобы рассчитать показания ROC, мы должны сначала определить значение «n», которое представляет собой не что иное, как то, со сколькими периодами назад сравнивается текущая цена закрытия. Определение «n» варьируется от одного трейдера к другому, но традиционная настройка — 9 (широко используется для краткосрочной торговли). При значении «n» 9 показания индикатора ROC рассчитываются следующим образом:

Во-первых, из текущей цены закрытия вычитается цена закрытия 9 периодов назад. Затем эта разница делится на цену закрытия 9 периодов назад и умножается на 100. Математически расчет можно представить следующим образом:

ROC 9 = [ ( C.CLOSE - PREV9.CLOSE ) / PREV9.CLOSE ] * 100where,
C.CLOSE = Current Closing Price
PREV9.CLOSE = Closing Price of 9 Periods ago

Далее идет WMA. Одна вещь, которая беспокоила трейдеров при использовании простой скользящей средней, заключается в том, что индикатор присваивал одинаковые веса всем точкам данных, присутствующим в серии. Здесь в игру вступает взвешенная скользящая средняя. Чтобы решить эту проблему, WMA присваивает больший вес (или большую важность) последней или последней точке данных и меньший вес точкам данных в прошлом. Чтобы определить WMA для заданного ряда, каждое значение умножается на определенные заранее определенные веса и результаты суммируются.

Теперь предположим ряд, в котором есть данные о цене закрытия за последние три дня, значения которых равны 12, 13, 15 соответственно. Теперь, чтобы рассчитать WMA этих трех цен закрытия, нам нужно сначала определить веса, которые будут равны 1, 2, 3, а сумма весов равна 6. Используя эти предопределенные веса и их сумму, расчет WMA выглядит следующим образом:

[ ( 15 * 3 ) + ( 13 * 2 ) + ( 12 * 1 ) ] / 6  = 21.333333

Из приведенного выше расчета вы могли видеть, что мы присвоили больший вес последней точке данных, которая равна 15, и меньшие веса последней точке данных, которая равна 12. Обратите внимание, что это очень простой пример того, как присваиваются веса для расчета WMA, но в реальном мире это намного сложнее. Иногда веса также могут быть десятичным числом. Вот и все о РПЦ и ВМА. Теперь давайте углубимся в основную концепцию этой статьи, которая представляет собой кривую Коппока.

Кривая Коппока

Кривая Коппока, основанная Эдвином Коппоком, представляет собой индикатор долгосрочного импульса, который часто используется трейдерами или инвесторами для определения восходящих и нисходящих трендов на рынке. Этот индикатор в основном применяется к рыночным индексам, таким как S&P 500, для определения сигналов на покупку и продажу, но в этой статье мы собираемся применить его к акциям, и в этом нет никаких ограничений. Кроме того, этот индикатор разработан таким образом, что он реализован на месячном таймфрейме, но сегодня мы попробуем использовать его на дневном таймфрейме.

Показания кривой Коппока рассчитываются путем взятия WMA из общего количества двух ROC, один с меньшим, а другой с большим значением «n». Типичным параметром для определения кривой Коппока является 10 в качестве периода ретроспективного анализа для WMA, 14 и 11 в качестве значения «n» для длинной и короткой ROC соответственно. Формула для расчета кривой Коппока с типичной настройкой может быть представлена следующим образом:

COPPOCK CURVE = WMA 10 [ LONG ROC + SHORT ROC ]where,
WMA 10 = 10-day Weighted Moving Average
LONG ROC = 14-period Rate Of Change
SHORT ROC = 11-period Rate Of Change

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

Приведенный выше график разделен на две панели: верхнюю панель с данными о цене закрытия Apple и нижнюю панель с показаниями кривой Коппока. Из приведенного выше графика видно, что всякий раз, когда показания кривой Коппока выше нуля, гистограмма отображается зеленым цветом, и точно так же, когда показания ниже нуля или отрицательны, гистограмма становится красного цвета. Теперь, используя гистограмму, мы могли легко определить текущую тенденцию рынка. Если гистограмма построена зеленым цветом, это означает, что рынок находится в восходящем тренде, а если гистограмма построена красным цветом, то рынок находится в нисходящем тренде. Кривая Коппока также может быть использована для обнаружения диапазонных и трендовых рынков, но это не ее сильная сторона.

Теперь давайте обсудим торговую стратегию, которую можно построить с помощью кривой Коппока. Основной стратегией, реализованной на основе этого индикатора, является пересечение нулевой линии, которое показывает сигнал на покупку всякий раз, когда кривая Коппока поднимается снизу выше нулевой линии, точно так же сигнал на продажу выявляется всякий раз, когда кривая Коппока идет сверху ниже нулевой линии. Если нулевая линия звучит как модное слово, это не что иное, как ноль (0). Эту стратегию можно представить следующим образом:

IF P.COPPC < ZERO-LINE AND C.COPPC > ZERO-LINE ==> BUY SIGNAL
IF P.COPPC > ZERO-LINE AND C.COPPC < ZERO-LINE ==> SELL SIGNAL

Непосредственное применение этой стратегии может привести к катастрофическим результатам, поскольку кривая Коппока имеет два недостатка. Первый недостаток кривой Коппока заключается в том, что она очень запаздывает по своей природе. Поэтому нужно быть осторожным, как никогда, при использовании индикатора в торговых целях. Еще одним недостатком является то, что кривая Коппока склонна выявлять множество ложных сигналов, приводящих нас к плохим сделкам. Для того, чтобы добиться хороших результатов, необходимо настроить типичную стратегию пересечения нулевой линии. Наша настроенная стратегия показывает сигнал на покупку только в том случае, если последние четыре показания находятся ниже нулевой линии, а текущие показания выше нулевой линии. Точно так же продажа генерируется только в том случае, если последние четыре показания находятся выше нулевой линии, а текущие показания ниже нулевой линии. Настроенную стратегию можно представить следующим образом:

IF P.4 COPPCs < ZERO-LINE AND C.COPPC > ZERO-LINE ==> BUY SIGNAL
IF P.4 COPPCs > ZERO-LINE AND C.COPPC < ZERO-LINE ==> SELL SIGNAL

На этом мы завершаем нашу теоретическую часть о кривой Коппока. Теперь давайте перейдем к части программирования, где мы сначала собираемся построить индикатор с нуля, построить настроенную стратегию пересечения нулевой линии, которую мы только что обсудили, а затем сравнить эффективность нашей стратегии с эффективностью SPY ETF в Python. Давайте займемся кодированием! Прежде чем двигаться дальше, примечание об отказе от ответственности: единственная цель этой статьи — просвещать людей, и ее следует рассматривать как информационный материал, а не как инвестиционный совет или что-то в этом роде.

Реализация на Python

Часть кодирования подразделяется на различные этапы следующим образом:

1. Importing Packages
2. Extracting Stock Data from Twelve Data
3. Coppock Curve Calculation
4. Creating the Tuned Zero-line Crossover Trading Strategy
5. Plotting the Trading Lists
6. Creating our Position
7. Backtesting
8. SPY ETF Comparison

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

Шаг 1: Импорт пакетов

Импорт необходимых пакетов в среду python — это шаг, который нельзя пропустить. Первичными пакетами будут Pandas для работы с данными, NumPy для работы с массивами и сложными функциями, Matplotlib для построения графиков и Requests для выполнения вызовов API. Вторичными пакетами будут Math для математических функций и Termcolor для настройки шрифта (необязательно).

Реализация Python:

# IMPORTING PACKAGES

import requests
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from math import floor
from termcolor import colored as cl

plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (20,10)

Теперь, когда мы импортировали все необходимые пакеты в наш python. Давайте извлечем исторические данные Apple с помощью конечной точки API Twelve Data.

Шаг 2: Извлечение данных из двенадцати данных

На этом этапе мы собираемся извлечь исторические данные об акциях Apple, используя конечную точку API, предоставленную twelvedata.com. Перед этим заметка о twelvedata.com: Twelve Data — один из ведущих поставщиков рыночных данных, имеющий огромное количество конечных точек API для всех типов рыночных данных. Очень легко взаимодействовать с API, предоставляемыми Twelve Data, и имеет одну из лучших документаций за всю историю. Кроме того, убедитесь, что у вас есть учетная запись на twelvedata.com, только тогда вы сможете получить доступ к своему ключу API (жизненно важный элемент для извлечения данных с помощью API).

Реализация Python:

# EXTRACTING STOCK DATA

def get_historical_data(symbol, start_date):
api_key = 'YOUR API KEY'
api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
raw_df = requests.get(api_url).json()
df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
df = df[df.index >= start_date]
df.index = pd.to_datetime(df.index)
return df

aapl = get_historical_data('AAPL', '2020-01-01')
aapl.tail()

Пояснение к коду: Первое, что мы сделали, это определили функцию с именем «get_historical_data», которая принимает символ акции («символ») и дату начала исторических данных («start_date») в качестве параметров. Внутри функции мы определяем ключ API и URL-адрес и сохраняем их в соответствующей переменной. Далее мы извлекаем исторические данные в формате JSON с помощью функции «get» и сохраняем их в переменной «raw_df». После выполнения некоторых процессов по очистке и форматированию необработанных данных JSON мы возвращаем их в виде чистого кадра данных Pandas. Наконец, мы вызываем созданную функцию, чтобы извлечь исторические данные Apple с начала 2020 года и сохранить их в переменной ‘aapl’.

Шаг 3: Расчет кривой Коппока

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

Реализация Python:

# COPPOCK CURVE CALCULATION

def wma(data, lookback):
weights = np.arange(1, lookback + 1)
val = data.rolling(lookback)
wma = val.apply(lambda prices: np.dot(prices, weights) / weights.sum(), raw = True)
return wma

def get_roc(close, n):
difference = close.diff(n)
nprev_values = close.shift(n)
roc = (difference / nprev_values) * 100
return roc

def get_cc(data, roc1_n, roc2_n, wma_lookback):
longROC = get_roc(data, roc1_n)
shortROC = get_roc(data, roc2_n)
ROC = longROC + shortROC
cc = wma(ROC, wma_lookback)
return cc

aapl['cc'] = get_cc(aapl['close'], 14, 11, 10)
aapl = aapl.dropna()
aapl.tail()

Пояснение к коду: Приведенный выше код можно разделить на три категории: расчет взвешенной скользящей средней, расчет скорости изменения и расчет кривой Коппока.

Расчет WMA: В этой части мы сначала определяем функцию с именем «wma», которая принимает цены закрытия («данные») и период ретроспективного анализа («ретроспективный взгляд») в качестве параметров. Внутри функции мы сначала определяем веса, которые должны быть назначены каждой точке данных, и сохраняем их в переменной ‘weights’. Затем мы создаем переменную с именем ‘val для хранения последовательности скользящих данных за указанное количество периодов с помощью функции ‘rolling’, предоставляемой пакетом Pandas. Теперь, используя заданные веса и скользящие значения, мы вычисляем и сохраняем значения WMA в переменной ‘wma’.

Расчет ROC: Во-первых, мы определяем функцию с именем «get_roc», которая принимает цену закрытия акции («закрытие») и значение «n» («n») в качестве параметров. Внутри функции мы сначала берем разницу между текущей ценой закрытия и ценой закрытия за указанное количество периодов назад, используя функцию «diff», предоставляемую пакетом Pandas. С помощью функции ‘shift’ мы учитываем цену закрытия за указанное количество периодов назад и сохраняем ее в переменной ‘nprev_values’. Затем мы подставляем определенные значения в формулу индикатора ROC, которую мы обсуждали ранее, чтобы вычислить значения и, наконец, вернуть данные.

Расчет кривой Коппока: Как и в двух других функциях, здесь мы также сначала определяем функцию с именем «get_cc», которая принимает данные о цене закрытия акции («данные»), значение «n» для более длинного ROC («roc_1») и более короткого ROC («roc_2»), а также период ретроспективного анализа взвешенной скользящей средней («wma_lookback») в качестве параметров. Внутри функции мы сначала определяем два ROC, один с большим значением ‘n’, а другой с более коротким значением ‘n’, используя функцию ‘get_roc’, которую мы создали ранее. Затем мы добавляем оба ROC и сохраняем результаты в переменную «ROC». С помощью функции «wma», которую мы создали ранее, мы берем взвешенную скользящую среднюю суммы двух ROC, чтобы получить показания кривой Коппока.

Наконец, мы вызываем созданную функцию «get_cc» для хранения показаний кривой Коппока Apple. Теперь приступим к созданию обсуждаемой настроенной торговой стратегии пересечения нулевой линии.

Шаг 4: Создание торговой стратегии

На этом этапе мы собираемся реализовать обсуждаемую торговую стратегию кроссовера с нулевой линией, настроенную на кривую Коппока, в python.

Реализация Python:

# COPPOCK CURVE STRATEGY

def implement_cc_strategy(prices, cc):
buy_price = []
sell_price = []
cc_signal = []
signal = 0

for i in range(len(prices)):
if cc[i-4] < 0 and cc[i-3] < 0 and cc[i-2] < 0 and cc[i-1] < 0 and cc[i] > 0:
if signal != 1:
buy_price.append(prices[i])
sell_price.append(np.nan)
signal = 1
cc_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
cc_signal.append(0)
elif cc[i-4] > 0 and cc[i-3] > 0 and cc[i-2] > 0 and cc[i-1] > 0 and cc[i] < 0:
if signal != -1:
buy_price.append(np.nan)
sell_price.append(prices[i])
signal = -1
cc_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
cc_signal.append(0)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
cc_signal.append(0)

return buy_price, sell_price, cc_signal

buy_price, sell_price, cc_signal = implement_cc_strategy(aapl['close'], aapl['cc'])

Пояснение к коду: Во-первых, мы определяем функцию с именем «implement_cc_strategy», которая принимает цены акций («цены») и показания кривой Коппока («cc») в качестве параметров.

Внутри функции мы создаем три пустых списка (buy_price, sell_price и cc_signal), в которые будут добавлены значения при создании торговой стратегии.

После этого мы реализуем торговую стратегию через фор-цикл. Внутри for-цикла мы передаем определенные условия, и если условия выполняются, соответствующие значения будут добавлены в пустые списки. Если условие для покупки акций будет выполнено, цена покупки будет добавлена в список «buy_price», а значение сигнала будет добавлено как 1, представляющее покупку акций. Точно так же, если условие продажи акций будет выполнено, цена продажи будет добавлена к списку «sell_price», а значение сигнала будет добавлено как -1, представляющее продажу акции.

Наконец, мы возвращаем списки, добавленные со значениями. Затем мы вызываем созданную функцию и сохраняем значения в соответствующих переменных. Список не имеет никакого смысла, если мы не построим график значений. Итак, построим график значений созданных торговых списков.

Шаг 5: Построение торговых сигналов

На этом этапе мы собираемся построить созданные торговые списки, чтобы понять их.

Реализация Python:

# COPPOCK CURVE TRADING SIGNAL PLOT

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 6, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2, label = 'aapl')
ax1.plot(aapl.index, buy_price, marker = '^', color = 'green', markersize = 12, linewidth = 0, label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', color = 'r', markersize = 12, linewidth = 0, label = 'SELL SIGNAL')
ax1.legend()
ax1.set_title('AAPL CC TRADING SIGNALS')
for i in range(len(aapl)):
if aapl.iloc[i, 5] >= 0:
ax2.bar(aapl.iloc[i].name, aapl.iloc[i, 5], color = '#009688')
else:
ax2.bar(aapl.iloc[i].name, aapl.iloc[i, 5], color = '#f44336')
ax2.set_title('AAPL COPPOCK CURVE')
plt.show()

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

Шаг 6: Создание нашей позиции

На этом этапе мы собираемся создать список, в котором указывается 1, если мы владеем акциями, или 0, если мы не владеем акциями или не держим их.

Реализация Python:

# STOCK POSITION

position = []
for i in range(len(cc_signal)):
if cc_signal[i] > 1:
position.append(0)
else:
position.append(1)

for i in range(len(aapl['close'])):
if cc_signal[i] == 1:
position[i] = 1
elif cc_signal[i] == -1:
position[i] = 0
else:
position[i] = position[i-1]

close_price = aapl['close']
cc = aapl['cc']
cc_signal = pd.DataFrame(cc_signal).rename(columns = {0:'cc_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'cc_position'}).set_index(aapl.index)

frames = [close_price, cc, cc_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy

Пояснение к коду: Во-первых, мы создаем пустой список с именем «позиция». Мы проходим два цикла for, один из которых предназначен для генерации значений для списка «позиция», чтобы они точно соответствовали длине списка «сигналов». Другой цикл for-loop — это тот, который мы используем для генерации фактических значений позиции. Внутри второго цикла for, мы перебираем значения списка ‘signal’, и добавляются значения списка ‘position’ в зависимости от того, какое условие выполняется. Стоимость позиции остается 1, если мы владеем акциями, или остается 0, если мы продали или не владеем акциями. Наконец, мы делаем некоторые манипуляции с данными, чтобы объединить все созданные списки в один фрейм данных.

Из показанного вывода мы видим, что в первых двух строках наша позиция по акциям осталась 1 (поскольку сигнал кривой Коппока не изменился), но наша позиция внезапно превратилась в -1, когда мы продали акции, когда торговый сигнал кривой Коппока представляет собой сигнал на продажу (-1). Наша позиция будет оставаться равной 0 до тех пор, пока не произойдут какие-то изменения в торговом сигнале. Теперь пришло время внедрить процесс тестирования на истории!

Шаг 7: Тестирование на истории

Прежде чем двигаться дальше, важно знать, что такое тестирование на истории. Тестирование на истории — это процесс проверки того, насколько хорошо наша торговая стратегия работает с данными об акциях. В нашем случае мы собираемся внедрить процесс тестирования на истории для нашей торговой стратегии Coppock Curve на данных об акциях Apple.

Реализация Python:

# BACKTESTING

aapl_ret = pd.DataFrame(np.diff(aapl['close'])).rename(columns = {0:'returns'})
cc_strategy_ret = []

for i in range(len(aapl_ret)):
returns = aapl_ret['returns'][i]*strategy['cc_position'][i]
cc_strategy_ret.append(returns)

cc_strategy_ret_df = pd.DataFrame(cc_strategy_ret).rename(columns = {0:'cc_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
cc_investment_ret = []

for i in range(len(cc_strategy_ret_df['cc_returns'])):
returns = number_of_stocks*cc_strategy_ret_df['cc_returns'][i]
cc_investment_ret.append(returns)

cc_investment_ret_df = pd.DataFrame(cc_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(cc_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the CC strategy by investing $100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the CC strategy : {}%'.format(profit_percentage), attrs = ['bold']))

Выпуск:

Profit gained from the CC strategy by investing $100k in AAPL : 60850.26
Profit percentage of the CC strategy : 60%

Пояснение к коду: Во-первых, мы вычисляем доходность акций Apple, используя функцию «diff», предоставляемую пакетом NumPy, и сохраняем ее в виде кадра данных в переменной «aapl_ret». Затем мы передаем цикл for, чтобы перебрать значения переменной «aapl_ret» для расчета доходности, которую мы получили от нашей торговой стратегии Coppock Curve, и эти значения доходности добавляются к списку «cc_strategy_ret». Затем мы преобразуем список «cc_strategy_ret» в кадр данных и сохраняем его в переменной «cc_strategy_ret_df».

Далее идет процесс тестирования на истории. Мы собираемся протестировать нашу стратегию, вложив сто тысяч долларов США в нашу торговую стратегию. Итак, во-первых, мы храним сумму инвестиций в переменной «investment_value». После этого мы рассчитываем количество акций Apple, которые мы можем купить, используя сумму инвестиций. Вы можете заметить, что я использовал функцию «пола», предоставляемую математическим пакетом, потому что, деля сумму инвестиций на цену закрытия акций Apple, он выдает выходные данные с десятичными числами. Количество акций должно быть целым, а не десятичным числом. Используя функцию «пол», мы можем вырезать десятичные дроби. Помните, что функция «пол» намного сложнее, чем функция «круглый». Затем мы проходим цикл for, чтобы найти доходность инвестиций, за которой следуют некоторые задачи по манипулированию данными.

Наконец, мы печатаем общий доход, который мы получили, инвестировав сто тысяч в нашу торговую стратегию, и выясняется, что мы получили приблизительную прибыль в размере шестидесяти тысяч долларов США за один год. Это неплохо! Теперь давайте сравним нашу доходность с доходностью SPY ETF (ETF, предназначенный для отслеживания индекса фондового рынка S&P 500).

Шаг 8: Сравнение SPY ETF

Этот шаг не является обязательным, но настоятельно рекомендуется, так как мы можем получить представление о том, насколько хорошо наша торговая стратегия работает по сравнению с эталоном (SPY ETF). На этом этапе мы извлечем данные SPY ETF с помощью созданной нами функции «get_historical_data» и сравним доходность, которую мы получаем от SPY ETF, с нашей конфигурированной кривой Coppock Curve доходностью кроссовера с нулевой линией на Apple.

Возможно, вы заметили, что во всех своих статьях об алгоритмической торговле я сравнивал результаты стратегии не с самим рыночным индексом S&P 500, а с SPY ETF, и это связано с тем, что большинство поставщиков биржевых данных (например, Twelve Data) не предоставляют данные индекса S&P 500. Так что у меня нет другого выбора, кроме как пойти с SPY ETF. Если вам посчастливилось получить данные рыночного индекса S&P 500, рекомендуется использовать их для сравнения, а не какой-либо ETF.

Реализация Python:

# SPY ETF COMPARISON

def get_benchmark(start_date, investment_value):
spy = get_historical_data('SPY', start_date)['close']
benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})

investment_value = investment_value
number_of_stocks = floor(investment_value/spy[-1])
benchmark_investment_ret = []

for i in range(len(benchmark['benchmark_returns'])):
returns = number_of_stocks*benchmark['benchmark_returns'][i]
benchmark_investment_ret.append(returns)

benchmark_investment_ret_df = pd.DataFrame(benchmark_investment_ret).rename(columns = {0:'investment_returns'})
return benchmark_investment_ret_df

benchmark = get_benchmark('2020-01-01', 100000)
investment_value = 100000
total_benchmark_investment_ret = round(sum(benchmark['investment_returns']), 2)
benchmark_profit_percentage = floor((total_benchmark_investment_ret/investment_value)*100)
print(cl('Benchmark profit by investing $100k : {}'.format(total_benchmark_investment_ret), attrs = ['bold']))
print(cl('Benchmark Profit percentage : {}%'.format(benchmark_profit_percentage), attrs = ['bold']))
print(cl('CC Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Выпуск:

Benchmark profit by investing $100k : 22929.75
Benchmark Profit percentage : 22%
CC Strategy profit is 38% higher than the Benchmark Profit

Пояснение к коду: Код, используемый на этом этапе, почти аналогичен тому, который использовался на предыдущем этапе тестирования на истории, но вместо того, чтобы инвестировать в Apple, мы инвестируем в SPY ETF, не реализуя никаких торговых стратегий. Из результатов мы видим, что наша торговая стратегия с нулевой линией, настроенная на кривую Коппока, превзошла SPY ETF на 22%. Это здорово!

Заключительные мысли!

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

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

Ну вот! Если вы забыли следовать какой-либо из частей кодирования, не волнуйтесь. Я предоставил полный исходный код в конце статьи. Надеюсь, вы узнали что-то новое и полезное из этой статьи.

Полный код:

# IMPORTING PACKAGES

import requests
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from math import floor
from termcolor import colored as cl

plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (20,10)


# EXTRACTING STOCK DATA

def get_historical_data(symbol, start_date):
api_key = 'YOUR API KEY'
api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
raw_df = requests.get(api_url).json()
df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
df = df[df.index >= start_date]
df.index = pd.to_datetime(df.index)
return df

aapl = get_historical_data('AAPL', '2020-01-01')
aapl.tail()


# COPPOCK CURVE CALCULATION

def wma(data, lookback):
weights = np.arange(1, lookback + 1)
val = data.rolling(lookback)
wma = val.apply(lambda prices: np.dot(prices, weights) / weights.sum(), raw = True)
return wma

def get_roc(close, n):
difference = close.diff(n)
nprev_values = close.shift(n)
roc = (difference / nprev_values) * 100
return roc

def get_cc(data, roc1_n, roc2_n, wma_lookback):
longROC = get_roc(data, roc1_n)
shortROC = get_roc(data, roc2_n)
ROC = longROC + shortROC
cc = wma(ROC, wma_lookback)
return cc

aapl['cc'] = get_cc(aapl['close'], 14, 11, 10)
aapl = aapl.dropna()
aapl.tail()


# COPPOCK CURVE PLOT

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 6, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2.5)
ax1.set_title('AAPL CLOSING PRICES')
for i in range(len(aapl)):
if aapl.iloc[i, 5] >= 0:
ax2.bar(aapl.iloc[i].name, aapl.iloc[i, 5], color = '#009688')
else:
ax2.bar(aapl.iloc[i].name, aapl.iloc[i, 5], color = '#f44336')
ax2.set_title('AAPL COPPOCK CURVE')
plt.show()


# COPPOCK CURVE STRATEGY

def implement_cc_strategy(prices, cc):
buy_price = []
sell_price = []
cc_signal = []
signal = 0

for i in range(len(prices)):
if cc[i-4] < 0 and cc[i-3] < 0 and cc[i-2] < 0 and cc[i-1] < 0 and cc[i] > 0:
if signal != 1:
buy_price.append(prices[i])
sell_price.append(np.nan)
signal = 1
cc_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
cc_signal.append(0)
elif cc[i-4] > 0 and cc[i-3] > 0 and cc[i-2] > 0 and cc[i-1] > 0 and cc[i] < 0:
if signal != -1:
buy_price.append(np.nan)
sell_price.append(prices[i])
signal = -1
cc_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
cc_signal.append(0)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
cc_signal.append(0)

return buy_price, sell_price, cc_signal

buy_price, sell_price, cc_signal = implement_cc_strategy(aapl['close'], aapl['cc'])


# COPPOCK CURVE TRADING SIGNAL PLOT

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 6, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2, label = 'AAPL')
ax1.plot(aapl.index, buy_price, marker = '^', color = 'green', markersize = 12, linewidth = 0, label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', color = 'r', markersize = 12, linewidth = 0, label = 'SELL SIGNAL')
ax1.legend()
ax1.set_title('AAPL CC TRADING SIGNALS')
for i in range(len(aapl)):
if aapl.iloc[i, 5] >= 0:
ax2.bar(aapl.iloc[i].name, aapl.iloc[i, 5], color = '#009688')
else:
ax2.bar(aapl.iloc[i].name, aapl.iloc[i, 5], color = '#f44336')
ax2.set_title('AAPL COPPOCK CURVE')
plt.show()


# STOCK POSITION

position = []
for i in range(len(cc_signal)):
if cc_signal[i] > 1:
position.append(0)
else:
position.append(1)

for i in range(len(aapl['close'])):
if cc_signal[i] == 1:
position[i] = 1
elif cc_signal[i] == -1:
position[i] = 0
else:
position[i] = position[i-1]

close_price = aapl['close']
cc = aapl['cc']
cc_signal = pd.DataFrame(cc_signal).rename(columns = {0:'cc_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'cc_position'}).set_index(aapl.index)

frames = [close_price, cc, cc_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy
strategy[10:15]


# BACKTESTING

aapl_ret = pd.DataFrame(np.diff(aapl['close'])).rename(columns = {0:'returns'})
cc_strategy_ret = []

for i in range(len(aapl_ret)):
returns = aapl_ret['returns'][i]*strategy['cc_position'][i]
cc_strategy_ret.append(returns)

cc_strategy_ret_df = pd.DataFrame(cc_strategy_ret).rename(columns = {0:'cc_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
cc_investment_ret = []

for i in range(len(cc_strategy_ret_df['cc_returns'])):
returns = number_of_stocks*cc_strategy_ret_df['cc_returns'][i]
cc_investment_ret.append(returns)

cc_investment_ret_df = pd.DataFrame(cc_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(cc_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the CC strategy by investing $100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the CC strategy : {}%'.format(profit_percentage), attrs = ['bold']))


# SPY ETF COMPARISON

def get_benchmark(start_date, investment_value):
spy = get_historical_data('SPY', start_date)['close']
benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})

investment_value = investment_value
number_of_stocks = floor(investment_value/spy[-1])
benchmark_investment_ret = []

for i in range(len(benchmark['benchmark_returns'])):
returns = number_of_stocks*benchmark['benchmark_returns'][i]
benchmark_investment_ret.append(returns)

benchmark_investment_ret_df = pd.DataFrame(benchmark_investment_ret).rename(columns = {0:'investment_returns'})
return benchmark_investment_ret_df

benchmark = get_benchmark('2020-01-01', 100000)
investment_value = 100000
total_benchmark_investment_ret = round(sum(benchmark['investment_returns']), 2)
benchmark_profit_percentage = floor((total_benchmark_investment_ret/investment_value)*100)
print(cl('Benchmark profit by investing $100k : {}'.format(total_benchmark_investment_ret), attrs = ['bold']))
print(cl('Benchmark Profit percentage : {}%'.format(benchmark_profit_percentage), attrs = ['bold']))
print(cl('CC Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Источник