Научитесь создавать индикатор ADX с нуля и протестируйте торговую стратегию на Python

Существует четыре различных ветви технических индикаторов, но самая популярная из них известна как индикаторы тренда. Эти индикаторы помогают трейдерам определять направление и силу рыночного тренда и торговать вместе с ними. В большинстве случаев индикаторы, подпадающие под категорию тренда, показывают хорошие результаты, если мы не используем их эффективно.

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

Средний истинный диапазон (ATR)

Прежде чем перейти к обнаружению ADX, важно знать, что такое средний истинный диапазон (ATR), поскольку он участвует в вычислении индекса среднего направленности (ADX).

Средний истинный диапазон - это технический индикатор, который измеряет, сколько в среднем движется актив. Это запаздывающий индикатор, означающий, что он учитывает исторические данные об активе для измерения текущей стоимости, но не может предсказать будущие точки данных. Это не считается недостатком при использовании ATR, поскольку это один из индикаторов для более точного отслеживания волатильности рынка. ATR является не только запаздывающим индикатором, но и ненаправленным индикатором, означающим, что движение ATR обратно пропорционально фактическому движению рынка. Чтобы рассчитать ATR, необходимо выполнить два шага:

  • Рассчитать истинный диапазон (TR): Истинный диапазон актива рассчитывается путем взятия наибольших значений трех разностей цен, а именно: рыночный максимум минус минимум маркера, рыночный максимум минус предыдущее закрытие рынка, предыдущее закрытие рынка. минус рыночный минимум. Его можно представить следующим образом:
MAX [ {HIGH - LOW}, {HIGH - P.CLOSE}, {P.CLOSE - LOW} ]
where,
MAX = Maximum values
HIGH = Market High
LOW = Market Low
P.CLOSE = Previous market close
  • Рассчитать ATR. Расчет среднего истинного диапазона прост. Нам просто нужно взять сглаженное среднее значение ранее рассчитанных значений True Range за указанное количество периодов. Сглаженное среднее - это не просто SMA или EMA, а собственный тип сглаженного среднего, созданный самим Уайлдером Уайлсом, но нет никаких ограничений и на использование других скользящих средних. В этой статье мы будем использовать SMA для расчета ATR, а не специальную скользящую среднюю, созданную основателем индикатора, чтобы упростить задачу. Расчет ATR с традиционным значением 14 в качестве количества периодов можно представить следующим образом:
ATR 14 = SMA 14 [ TR ]
where,
ATR 14 = 14 Period Average True Range
SMA 14 = 14 Period Simple Moving Average
TR = True Range

Используя ATR в качестве индикатора для торговых целей, трейдеры должны быть осторожны, чем когда-либо, поскольку индикатор сильно отстает. Теперь, когда мы понимаем, что такое средний истинный диапазон. Давайте теперь погрузимся в основную концепцию этой статьи - индекс среднего направленности.

Индекс среднего направленности (ADX)

ADX - это технический индикатор, который широко используется для измерения силы рыночного тренда. Теперь ADX не измеряет направление тренда, будь то бычий или медвежий, а просто показывает, насколько сильна тренд. Итак, чтобы определить направление тренда, ADX комбинируется с индексом положительного направления (+ DI) и индексом отрицательного направления (- DI). Как следует из названия, + DI измеряет бычий или положительный тренд рынка, аналогично - -DI измеряет медвежий или отрицательный тренд рынка. Значения всех компонентов ограничены от 0 до 100, следовательно, они действуют как осциллятор. Традиционное значение ADX составляет 14 в качестве периода ретроспективного анализа.

Чтобы рассчитать значения ADX с 14 в качестве периода ретроспективного обзора, сначала определяется положительное (+ DM) и отрицательное направленное движение (- DM). + DM рассчитывается путем нахождения разницы между текущим максимумом и предыдущим максимумом, и аналогично - DM рассчитывается путем нахождения разницы между предыдущим минимумом и текущим минимумом. Его можно представить следующим образом:

+ DM = CURRENT HIGH - PREVIOUS HIGH
- DM = PREVIOUS LOW - CURRENT LOW

Затем рассчитывается ATR с 14 в качестве периода ретроспективного анализа. Теперь, используя рассчитанное направленное движение и значения ATR, рассчитываются индекс положительного направления (+ DI) и индекс отрицательного направления (- DI). Чтобы определить значения + DI, значение, полученное путем взятия экспоненциальной скользящей средней (EMA) положительного направленного движения (+ DM) с 14 в качестве периода ретроспективного анализа, делится на ранее рассчитанный 14-дневный ATR, а затем умножается на 100. То же самое относится и к определению - DI, но вместо 14-дневной EMA + DM учитывается отрицательное направленное движение (- DM). Формула для вычисления как + DI, так и - DI может быть представлена ​​следующим образом:

+ DI 14 = 100 * [ EMA 14 ( + DM ) / ATR 14 ]
- DI 14 = 100 * [ EMA 14 ( - DM ) / ATR 14 ]

Следующим шагом является использование + DI и - DI для расчета индекса направленности. Его можно определить, разделив абсолютное значение разницы между + DI и - DI на абсолютное значение суммы + DI и - DI, умноженное на 100. Формула для расчета индекса направленности может быть представлена ​​следующим образом:

DI 14 = | (+ DI 14) - (- DI 14) | / | (+ DI 14) + (- DI 14) |  * 100

Последним шагом является расчет самого ADX с использованием определенных значений индекса направленности. ADX рассчитывается путем умножения предыдущего значения индекса направления на 13 (период ретроспективного анализа - 1) и добавления его с индексом направления, а затем умножения на 100. Формула для расчета значений ADX может быть представлена ​​следующим образом:

ADX 14 = [ ( PREV DI 14 * 13 ) + DI 14 ] * 100

ADX нельзя использовать как есть, его нужно сгладить. Поскольку он был основан Уайлдером Уайлсом (также основателем ATR), ADX сглаживается специальной скользящей средней, которую мы обсуждали ранее. Мы пренебрегли использованием этой настраиваемой скользящей средней при вычислении ATR, поскольку можно использовать другие типы скользящих средних, но их необходимо использовать при сглаживании ADX для получения точных значений.

Вот и весь процесс расчета значений ADX. Теперь давайте обсудим, как можно построить простую торговую стратегию на основе ADX.

О нашей торговой стратегии: В этой статье мы собираемся построить простую стратегию пересечения, которая выявляет сигнал на покупку всякий раз, когда линия ADX пересекает снизу вверх и выше 25, а линия + DI находится выше - DI. линия. Точно так же сигнал на продажу генерируется всякий раз, когда линия ADX пересекает снизу вверх 25, а линия - DI находится выше линии + DI. Нашу торговую стратегию можно представить следующим образом:

IF P.ADX < 25 AND C.ADX > 25 AND + DI LINE > - DI LINE ==> BUY
IF P.ADX < 25 AND C.ADX > 25 AND + DI LINE < - DI LINE ==> SELL

На этом завершается теоретическая часть статьи. Теперь давайте запрограммируем этот индикатор с нуля, построим на его основе обсуждаемую торговую стратегию на Python и протестируем ее на исторических данных с акциями Apple, чтобы увидеть некоторые впечатляющие результаты. Мы также сравним доходность нашей стратегии кроссовера ADX с доходностью SPY ETF, чтобы увидеть, насколько хорошо наша торговая стратегия работает по сравнению с эталоном. Без лишних слов, давайте перейдем к кодированию.

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

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

1. Importing Packages
2. Extracting Stock Data from Twelve Data
3. ADX Calculation
4. ADX Indicator Plot
5. Creating the Trading Strategy
6. Plotting the Trading Lists
7. Creating our Position
8. Backtesting
9. SPY ETF Comparison

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

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

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

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

import pandas as pd
import numpy as np
import requests
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:

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

Вывод:

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

Шаг 4: Расчет ADX

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

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

def get_adx(high, low, close, lookback):
    plus_dm = high.diff()
    minus_dm = low.diff()
    plus_dm[plus_dm < 0] = 0
    minus_dm[minus_dm > 0] = 0
    
    tr1 = pd.DataFrame(high - low)
    tr2 = pd.DataFrame(abs(high - close.shift(1)))
    tr3 = pd.DataFrame(abs(low - close.shift(1)))
    frames = [tr1, tr2, tr3]
    tr = pd.concat(frames, axis = 1, join = 'inner').max(axis = 1)
    atr = tr.rolling(lookback).mean()
    
    plus_di = 100 * (plus_dm.ewm(alpha = 1/lookback).mean() / atr)
    minus_di = abs(100 * (minus_dm.ewm(alpha = 1/lookback).mean() / atr))
    dx = (abs(plus_di - minus_di) / abs(plus_di + minus_di)) * 100
    adx = ((dx.shift(1) * (lookback - 1)) + dx) / lookback
    adx_smooth = adx.ewm(alpha = 1/lookback).mean()
    return plus_di, minus_di, adx_smooth

aapl['plus_di'] = pd.DataFrame(get_adx(aapl['high'], aapl['low'], aapl['close'], 14)[0]).rename(columns = {0:'plus_di'})
aapl['minus_di'] = pd.DataFrame(get_adx(aapl['high'], aapl['low'], aapl['close'], 14)[1]).rename(columns = {0:'minus_di'})
aapl['adx'] = pd.DataFrame(get_adx(aapl['high'], aapl['low'], aapl['close'], 14)[2]).rename(columns = {0:'adx'})
aapl = aapl.dropna()
aapl.tail()

Вывод:

Пояснение к коду: мы сначала определяем функцию с именем get_adx, которая принимает данные о максимуме («high»), low («low») и закрытии («close») акции вместе с ретроспективным анализом. период (ретроспективный анализ) в качестве параметров.

Внутри функции мы сначала вычисляем и сохраняем + DM и - DM в «plus_dm» и «minus_dm» соответственно. Затем идет расчет ATR, при котором мы сначала вычисляем три разницы и определяем переменную «tr» для хранения наивысших значений среди определенных различий, затем мы вычисляем и сохраняем значения ATR в переменной «atr».

Используя рассчитанные направленные движения и значения ATR, мы вычисляем + DI и - DI и сохраняем их в переменных «plus_di» и «minus_di» соответственно. С помощью ранее обсуждавшейся формулы мы вычисляем значения индекса направленности и сохраняем их в переменной «dx», а затем применяем эти значения в формуле ADX для расчета значений индекса среднего направления. Затем мы определили переменную adx_smooth для хранения сглаженных значений ADX. Наконец, мы возвращаемся и вызываем функцию для получения значений + DI, - DI и ADX Apple с 14 в качестве периода ретроспективного анализа.

Шаг 4: график ADX

На этом этапе мы собираемся построить рассчитанные значения ADX Apple, чтобы понять их смысл. Основная цель этой части заключается не в разделе кодирования, а в наблюдении за графиком, чтобы получить твердое представление об индексе среднего направленности.

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

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2, color = '#ff9800')
ax1.set_title('AAPL CLOSING PRICE')
ax2.plot(aapl['plus_di'], color = '#26a69a', label = '+ DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['minus_di'], color = '#f44336', label = '- DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['adx'], color = '#2196f3', label = 'ADX 14', linewidth = 3)
ax2.axhline(25, color = 'grey', linewidth = 2, linestyle = '--')
ax2.legend()
ax2.set_title('AAPL ADX 14')
plt.show()

Вывод:

Приведенный выше график разделен на две панели: верхняя панель с ценами закрытия Apple и нижняя панель с компонентами ADX. Наряду с компонентами нанесена серая пунктирная линия, которая представляет собой не что иное, как порог для ADX, построенный на уровне 25. Как я уже говорил ранее, ADX отслеживает не направление тренда, а вместо этого его силу и ее силу. можно несколько раз увидеть на графике, где линия ADX увеличивается, когда рынок показывает сильный тренд (вверх или вниз), и уменьшается, когда рынок должен консолидироваться. То же самое и с обоими указательными линиями направления. Мы могли видеть, что линия + DI увеличивается, когда рынок показывает устойчивый восходящий тренд, и уменьшается во время нисходящего тренда, и наоборот, для линии - DI.

ADX не только используется для количественной оценки силы рыночного тренда, но также становится удобным инструментом для определения ранжированных рынков (рынков, на которых акция движется назад и вперед между определенными высокими и низкими уровнями, демонстрируя нулевой импульс). Всякий раз, когда линии приближаются друг к другу, наблюдается колебание рынка; аналогично, чем шире расстояние между линиями, тем больше на рынках наблюдается тренд. Те, кто впервые знакомится с графиком ADX, могут запутаться, поскольку движение каждой линии косвенно пропорционально движению рынка.

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

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

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

def implement_adx_strategy(prices, pdi, ndi, adx):
    buy_price = []
    sell_price = []
    adx_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if adx[i-1] < 25 and adx[i] > 25 and pdi[i] > ndi[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                adx_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                adx_signal.append(0)
        elif adx[i-1] < 25 and adx[i] > 25 and ndi[i] > pdi[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                adx_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                adx_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            adx_signal.append(0)
            
    return buy_price, sell_price, adx_signal

buy_price, sell_price, adx_signal = implement_adx_strategy(aapl['close'], aapl['plus_di'], aapl['minus_di'], aapl['adx'])

Пояснение к коду. Во-первых, мы определяем функцию с именем «implementation_adx_strategy», которая принимает цены акций («цены) и компоненты ADX (« pdi »,« ndi »,« adx ») как параметры.

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

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

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

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

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

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

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(aapl['close'], linewidth = 3, color = '#ff9800', alpha = 0.6)
ax1.set_title('AAPL CLOSING PRICE')
ax1.plot(aapl.index, buy_price, marker = '^', color = '#26a69a', markersize = 14, linewidth = 0, label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', color = '#f44336', markersize = 14, linewidth = 0, label = 'SELL SIGNAL')
ax2.plot(aapl['plus_di'], color = '#26a69a', label = '+ DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['minus_di'], color = '#f44336', label = '- DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['adx'], color = '#2196f3', label = 'ADX 14', linewidth = 3)
ax2.axhline(25, color = 'grey', linewidth = 2, linestyle = '--')
ax2.legend()
ax2.set_title('AAPL ADX 14')
plt.show()

Вывод:

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

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

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

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

position = []
for i in range(len(adx_signal)):
    if adx_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(aapl['close'])):
    if adx_signal[i] == 1:
        position[i] = 1
    elif adx_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
close_price = aapl['close']
plus_di = aapl['plus_di']
minus_di = aapl['minus_di']
adx = aapl['adx']
adx_signal = pd.DataFrame(adx_signal).rename(columns = {0:'adx_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'adx_position'}).set_index(aapl.index)

frames = [close_price, plus_di, minus_di, adx, adx_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy

Вывод:

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

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

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

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

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

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

for i in range(len(aapl_ret)):
    returns = aapl_ret['returns'][i]*strategy['adx_position'][i]
    adx_strategy_ret.append(returns)
    
adx_strategy_ret_df = pd.DataFrame(adx_strategy_ret).rename(columns = {0:'adx_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][-1])
adx_investment_ret = []

for i in range(len(adx_strategy_ret_df['adx_returns'])):
    returns = number_of_stocks*adx_strategy_ret_df['adx_returns'][i]
    adx_investment_ret.append(returns)

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

Вывод:

Profit gained from the ADX strategy by investing $100k in AAPL : 30375.66
Profit percentage of the ADX strategy : 30%

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

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

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

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

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

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

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('ADX Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Вывод:

Benchmark profit by investing $100k : 21621.6
Benchmark Profit percentage : 21%
ADX Strategy profit is 9% higher than the Benchmark Profit

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

Последние мысли!

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

С моей точки зрения, вся мощь ADX раскрывается, когда он сопровождается другим техническим индикатором, особенно с RSI, чтобы получить качественные точки входа и выхода для ваших сделок. Поэтому настоятельно рекомендуется попробовать импровизировать в этой статье, настроив стратегию ADX в сочетании с другими техническими индикаторами и протестировав как можно больше на исторических данных. Это может помочь в достижении лучших результатов на реальном рынке. Вот и все! Надеюсь, вы узнали что-то полезное из этой статьи. Если вы забыли выполнить какую-либо часть кода, не волнуйтесь. Я предоставил полный исходный код в конце этой статьи.

Полный код:

import pandas as pd
import numpy as np
import requests
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)

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

def get_adx(high, low, close, lookback):
    plus_dm = high.diff()
    minus_dm = low.diff()
    plus_dm[plus_dm < 0] = 0
    minus_dm[minus_dm > 0] = 0
    
    tr1 = pd.DataFrame(high - low)
    tr2 = pd.DataFrame(abs(high - close.shift(1)))
    tr3 = pd.DataFrame(abs(low - close.shift(1)))
    frames = [tr1, tr2, tr3]
    tr = pd.concat(frames, axis = 1, join = 'inner').max(axis = 1)
    atr = tr.rolling(lookback).mean()
    
    plus_di = 100 * (plus_dm.ewm(alpha = 1/lookback).mean() / atr)
    minus_di = abs(100 * (minus_dm.ewm(alpha = 1/lookback).mean() / atr))
    dx = (abs(plus_di - minus_di) / abs(plus_di + minus_di)) * 100
    adx = ((dx.shift(1) * (lookback - 1)) + dx) / lookback
    adx_smooth = adx.ewm(alpha = 1/lookback).mean()
    return plus_di, minus_di, adx_smooth

aapl['plus_di'] = pd.DataFrame(get_adx(aapl['high'], aapl['low'], aapl['close'], 14)[0]).rename(columns = {0:'plus_di'})
aapl['minus_di'] = pd.DataFrame(get_adx(aapl['high'], aapl['low'], aapl['close'], 14)[1]).rename(columns = {0:'minus_di'})
aapl['adx'] = pd.DataFrame(get_adx(aapl['high'], aapl['low'], aapl['close'], 14)[2]).rename(columns = {0:'adx'})
aapl = aapl.dropna()
aapl.tail()

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2, color = '#ff9800')
ax1.set_title('AAPL CLOSING PRICE')
ax2.plot(aapl['plus_di'], color = '#26a69a', label = '+ DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['minus_di'], color = '#f44336', label = '- DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['adx'], color = '#2196f3', label = 'ADX 14', linewidth = 3)
ax2.axhline(25, color = 'grey', linewidth = 2, linestyle = '--')
ax2.legend()
ax2.set_title('AAPL ADX 14')
plt.show()

def implement_adx_strategy(prices, pdi, ndi, adx):
    buy_price = []
    sell_price = []
    adx_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if adx[i-1] < 25 and adx[i] > 25 and pdi[i] > ndi[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                adx_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                adx_signal.append(0)
        elif adx[i-1] < 25 and adx[i] > 25 and ndi[i] > pdi[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                adx_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                adx_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            adx_signal.append(0)
            
    return buy_price, sell_price, adx_signal

buy_price, sell_price, adx_signal = implement_adx_strategy(aapl['close'], aapl['plus_di'], aapl['minus_di'], aapl['adx'])

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(aapl['close'], linewidth = 3, color = '#ff9800', alpha = 0.6)
ax1.set_title('AAPL CLOSING PRICE')
ax1.plot(aapl.index, buy_price, marker = '^', color = '#26a69a', markersize = 14, linewidth = 0, label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', color = '#f44336', markersize = 14, linewidth = 0, label = 'SELL SIGNAL')
ax2.plot(aapl['plus_di'], color = '#26a69a', label = '+ DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['minus_di'], color = '#f44336', label = '- DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['adx'], color = '#2196f3', label = 'ADX 14', linewidth = 3)
ax2.axhline(25, color = 'grey', linewidth = 2, linestyle = '--')
ax2.legend()
ax2.set_title('AAPL ADX 14')
plt.show()

position = []
for i in range(len(adx_signal)):
    if adx_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(aapl['close'])):
    if adx_signal[i] == 1:
        position[i] = 1
    elif adx_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
close_price = aapl['close']
plus_di = aapl['plus_di']
minus_di = aapl['minus_di']
adx = aapl['adx']
adx_signal = pd.DataFrame(adx_signal).rename(columns = {0:'adx_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'adx_position'}).set_index(aapl.index)

frames = [close_price, plus_di, minus_di, adx, adx_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy
strategy[25:30]

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

for i in range(len(aapl_ret)):
    returns = aapl_ret['returns'][i]*strategy['adx_position'][i]
    adx_strategy_ret.append(returns)
    
adx_strategy_ret_df = pd.DataFrame(adx_strategy_ret).rename(columns = {0:'adx_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][-1])
adx_investment_ret = []

for i in range(len(adx_strategy_ret_df['adx_returns'])):
    returns = number_of_stocks*adx_strategy_ret_df['adx_returns'][i]
    adx_investment_ret.append(returns)

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

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('ADX Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))