Торговля с каналом Кельтнера

Стохастическая торговая стратегия Кельтнера в Python

Создание и тестирование противоположной торговой стратегии в Python

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

Торговая книга Фибоначчи наконец-то вышла! Эта книга, наполненная методами торговли на основе Фибоначчи (инструментами, индикаторами, паттернами и стратегиями), поможет вам улучшить свою торговлю и анализ, включив важный подход к техническому анализу, которым является Фибоначчи [доступна версия в формате PDF, см. конец статьи).

Стохастический осциллятор

Стохастический осциллятор — это известный ограниченный технический индикатор, основанный на функции нормализации. Он фиксирует высокие, низкие и закрытые цены между 0 и 100, чтобы мы могли взглянуть на перегруженные рынки.

Стохастический осциллятор (сырой вариант) рассчитывается следующим образом:

  • Вычтите текущее закрытие из самого низкого минимума за последние 14 периодов. Назовем этот шаг первым.
  • Вычтите самый высокий максимум за последние 14 периодов из самого низкого минимума за последние 14 периодов. Назовем этот шаг вторым.
  • Разделите шаг один на шаг два и умножьте на 100.

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

def stochastic_oscillator(data, 
lookback,
high,
low,
close,
position,
slowing = False,
smoothing = False,
slowing_period = 1,
smoothing_period = 1):

data = add_column(data, 1)

for i in range(len(data)):

try:

data[i, position] = (data[i, close] - min(data[i - lookback + 1:i + 1, low])) / (max(data[i - lookback + 1:i + 1, high]) - min(data[i - lookback + 1:i + 1, low]))

except ValueError:

pass

data[:, position] = data[:, position] * 100

if slowing == True and smoothing == False:

data = ma(data, slowing_period, position, position + 1)

if smoothing == True and slowing == False:

data = ma(data, smoothing_period, position, position + 1)

if smoothing == True and slowing == True:

data = ma(data, slowing_period, position, position + 1)

data = ma(data, smoothing_period, position + 1, position + 2)

data = delete_row(data, lookback)

return data

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

def add_column(data, times):

for i in range(1, times + 1):

new = np.zeros((len(data), 1), dtype = float)

data = np.append(data, new, axis = 1)

return data

def delete_column(data, index, times):

for i in range(1, times + 1):

data = np.delete(data, index, axis = 1)
return data

def delete_row(data, number):

data = data[number:, ]

return data

def ma(data, lookback, close, position):

data = add_column(data, 1)

for i in range(len(data)):

try:

data[i, position] = (data[i - lookback + 1:i + 1, close].mean())

except IndexError:

pass

data = delete_row(data, lookback)

return data

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

Стохастический осциллятор.

Канал Кельтнера

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

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

Функция, которую мы можем использовать для кодирования канала Кельтнера при наличии массива numpy OHLC, выглядит следующим образом:

def keltner_channel(data, lookback, multiplier, close, position):

data = add_column(data, 2)

data = ema(data, 2, lookback, close, position)

data = atr(data, lookback, 1, 2, 3, position + 1)

data[:, position + 2] = data[:, position] + (data[:, position + 1] * multiplier)
data[:, position + 3] = data[:, position] - (data[:, position + 1] * multiplier)

data = delete_column(data, position, 2)
data = delete_row(data, lookback)

return data

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

def ema(data, alpha, lookback, close, position):

alpha = alpha / (lookback + 1.0)

beta = 1 - alpha

data = ma(data, lookback, close, position)

data[lookback + 1, position] = (data[lookback + 1, close] * alpha) + (data[lookback, position] * beta)

for i in range(lookback + 2, len(data)):

try:

data[i, position] = (data[i, close] * alpha) + (data[i - 1, position] * beta)

except IndexError:

pass

return data

def smoothed_ma(data, alpha, lookback, close, position):

lookback = (2 * lookback) - 1

alpha = alpha / (lookback + 1.0)

beta = 1 - alpha

data = ma(data, lookback, close, position)
data[lookback + 1, position] = (data[lookback + 1, close] * alpha) + (data[lookback, position] * beta)
for i in range(lookback + 2, len(data)):

try:

data[i, position] = (data[i, close] * alpha) + (data[i - 1, position] * beta)

except IndexError:

pass

return data

def atr(data, lookback, high_column, low_column, close_column, position):

data = add_column(data, 1)

for i in range(len(data)):

try:

data[i, position] = max(data[i, high_column] - data[i, low_column], abs(data[i, high_column] - data[i - 1, close_column]), abs(data[i, low_column] - data[i - 1, close_column]))

except ValueError:

pass

data[0, position] = 0

data = smoothed_ma(data, 2, lookback, position, position + 1)
data = delete_column(data, position, 1)

data = delete_row(data, lookback)

return data

На следующем рисунке показан пример 20-периодного канала Кельтнера.

Канал Кельтнера.

Создание стратегии

Стратегия проста и имеет следующие условия:

  • Бычий сигнал генерируется всякий раз, когда 14-периодный стохастический осциллятор ниже 10, в то время как рынок только что превзошел нижний Кельтнера.
  • Медвежий сигнал генерируется всякий раз, когда 14-периодный стохастический осциллятор находится выше 90, в то время как рынок только что пробил вниз верхний Кельтнер.
def signal(data, close_column, stochastic_column, 
upper_keltner, lower_keltner, buy_column, sell_column):

data = add_column(data, 5)

for i in range(len(data)):

try:

# Bullish pattern
if data[i, stochastic_column] < lower_barrier and \
data[i, close_column] > data[i, lower_keltner] and \
data[i - 1, close_column] < data[i - 1, lower_keltner]:

data[i + 1, buy_column] = 1

# Bearish pattern
elif data[i, stochastic_column] > upper_barrier and \
data[i, close_column] < data[i, upper_keltner] and \
data[i - 1, close_column] > data[i - 1, upper_keltner]:

data[i + 1, sell_column] = -1

except IndexError:

pass

return data

На следующем рисунке показан пример сигнального графика.

Сигнальный график.

На следующем рисунке показан пример сигнального графика.

Сигнальный график.

Оценка эффективности

Если мы проведем простой бэк-тест, чтобы оценить предсказательную силу стратегии по GBPUSD и USDCHF, мы найдем следующие результаты:

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

Алгоритмическая торговля с каналом Кельтнера в Python

Знакомство

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

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

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

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

Важно знать, что такое средний истинный диапазон (ATR), поскольку он участвует в расчете канала Кельтнера.

Основанный Уайлдером Уайлсом (создателем самого популярного индикатора RSI), Average True Range — это технический индикатор, который измеряет, насколько актив движется в среднем. Это запаздывающий индикатор, означающий, что он учитывает исторические данные актива для измерения текущей стоимости, но не способен предсказывать будущие точки данных. Это не считается недостатком при использовании 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: Расчет среднего истинного диапазона прост. Нам просто нужно взять сглаженное среднее из ранее рассчитанных значений истинного диапазона за указанное количество периодов. Сглаженное среднее — это не просто SMA или EMA, а собственный тип сглаженного среднего, созданный самим Уайлдером Уайлсом, который представляет собой не что иное, как вычитание единицы из экспоненциальной скользящей средней истинного диапазона за определенное количество периодов и умножение разницы на два. Расчет ПТО за указанное число периодов можно представить следующим образом:
ATR N = EMA N [ TR ] - 1 * 2where,
ATR N = Average True Range of 'N' period
SMA N = Simple Moving Average of 'N' period
TR = True Range

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

Канал Кельтнера (KC)

Канал Кельтнера, основанный Честером Келтнером, представляет собой технический индикатор, который часто используется трейдерами для определения волатильности и направления рынка. Канал Кельтнера состоит из трех компонентов: верхней полосы, нижней полосы и средней линии. Теперь давайте обсудим, как рассчитывается каждый из компонентов.

Прежде чем погрузиться в расчет канала Кельтнера, важно знать о трех важных входных данных, участвующих в расчете. Во-первых, это период ретроспективного анализа ATR, который представляет собой не что иное, как количество периодов, учитываемых для расчета ATR. Во-вторых, ретроспективный период канала Кельтнера. Этот ввод более или менее похож на первый, но здесь мы определяем количество периодов, которые учитываются для расчета самого канала Кельтнера. Конечным входом является множитель, который представляет собой значение, умноженное на ATR. Типичные значения, которые принимаются в качестве входных данных: 10 в качестве периода ретроспективного анализа ATR, 20 в качестве периода ретроспективного анализа канала Кельтнера и 2 в качестве множителя. Помня об этих входных данных, давайте рассчитаем показания компонентов канала Кельтнера.

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

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

MIDDLE LINE 20 = EMA 20 [ C.STOCK ]where,
EMA 20 = 20-day Exponential Moving Average
C.STOCK = Closing price of the stock

Завершающим этапом является расчет верхней и нижней полос. Начнем с верхней полосы. Он рассчитывается путем сложения 20-дневной экспоненциальной скользящей средней цены закрытия акции на множитель (два), а затем умножения на 10-дневный ATR. Расчет нижней полосы почти аналогичен расчету верхней полосы, но вместо того, чтобы складывать, мы будем вычитать 20-дневную EMA на множитель. Расчет как верхней, так и нижней полос можно представить следующим образом:

UPPER BAND 20 = EMA 20 [ C.STOCK ] + MULTIPLIER * ATR 10
LOWER BAND 20
= EMA 20 [ C.STOCK ] - MULTIPLIER * ATR 10where,
EMA 20 = 20-day Exponential Moving Average
C.STOCK = Closing price of the stock
MULTIPLIER = 2
ATR 10 = 10-day Average True Range

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

Приведенная выше диаграмма представляет собой графическое представление 20-дневного Keltner Chanel от Intel. Мы могли заметить, что по обе стороны от линии цены закрытия построены две полосы, и это не что иное, как верхняя и нижняя полосы, а серая линия, проходящая между двумя полосами, является средней линией или 20-дневной EMA. Канал Кельтнера можно использовать по-разному, но наиболее популярными способами являются определение волатильности и направления рынка.

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

Глядя на график канала Кельтнера, он может напоминать полосы Боллинджера. Единственное различие между этими двумя показателями заключается в способе расчета каждого из них. Полосы Боллинджера используют стандартное отклонение для расчета, тогда как канал Кельтнера использует ATR для расчета своих показаний. Теперь давайте поговорим о торговой стратегии, которую мы собираемся реализовать в этой статье.

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

IF C.CLOSE < C.KCLOWER AND C.CLOSE < N.CLOSE ==> BUY SIGNAL
IF C.CLOSE > C.KCUPPER AND C.CLOSE > N.CLOSE ==> SELL SIGNAL

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

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

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

1. Importing Packages
2. Extracting Stock Data from Twelve Data
3. Keltner Channel Calculation
4. Creating the Breakout 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 matplotlib.pyplot as plt
import pandas as pd
from termcolor import colored as cl
from math import floor

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

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

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

На этом этапе мы собираемся извлечь исторические данные об акциях Intel с помощью конечной точки 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

intc = get_historical_data('INTC', '2020-01-01')
intc.tail()

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

Шаг 3: Расчет канала Кельтнера

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

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

# KELTNER CHANNEL CALCULATION

def get_kc(high, low, close, kc_lookback, multiplier, atr_lookback):
tr1 = pd.DataFrame(high - low)
tr2 = pd.DataFrame(abs(high - close.shift()))
tr3 = pd.DataFrame(abs(low - close.shift()))
frames = [tr1, tr2, tr3]
tr = pd.concat(frames, axis = 1, join = 'inner').max(axis = 1)
atr = tr.ewm(alpha = 1/atr_lookback).mean()

kc_middle = close.ewm(kc_lookback).mean()
kc_upper = close.ewm(kc_lookback).mean() + multiplier * atr
kc_lower = close.ewm(kc_lookback).mean() - multiplier * atr

return kc_middle, kc_upper, kc_lower

intc = intc.iloc[:,:4]
intc['kc_middle'], intc['kc_upper'], intc['kc_lower'] = get_kc(intc['high'], intc['low'], intc['close'], 20, 2, 10)
intc.tail()

Пояснение к коду: Сначала мы определяем функцию с именем «get_kc», которая принимает в качестве параметров данные о максимуме («максимум»), минимуме («минимуме») и цене закрытия акции («закрытие»), период ретроспективного анализа для канала Кельтнера («kc_lookback»), значение мультипликатора («мультипликатор») и период ретроспективного анализа для ATR («atr_lookback»). Код внутри функции можно разделить на две части: вычисление ATR и вычисление канала Кельтнера.

Расчет ATR: Чтобы определить показания среднего истинного диапазона, мы сначала вычисляем три разности и сохраняем их в соответствующих переменных. Затем мы объединяем все три различия в один кадр данных, используя функцию concat, и берем максимальные значения из трех коллективных различий, чтобы определить истинный диапазон. Затем, используя функции «ewm» и «mean», мы берем настроенную скользящую среднюю истинного диапазона за указанное количество периодов, чтобы получить значения ATR.

Расчет канала Кельтнера: Используя ранее рассчитанные значения ATR, мы сначала вычисляем среднюю линию канала Кельтнера, принимая EMA ATR за указанное количество периодов. Затем идет расчет как верхней, так и нижней полос. Мы подставляем значения ATR в формулу верхнего и нижнего диапазонов, которую мы обсуждали ранее, чтобы получить показания каждого из них. Наконец, мы возвращаемся и вызываем созданную функцию, чтобы получить значения канала Кельтнера Intel.

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

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

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

# KELTNER CHANNEL STRATEGY

def implement_kc_strategy(prices, kc_upper, kc_lower):
buy_price = []
sell_price = []
kc_signal = []
signal = 0

for i in range(len(prices)):
if prices[i] < kc_lower[i] and prices[i+1] > prices[i]:
if signal != 1:
buy_price.append(prices[i])
sell_price.append(np.nan)
signal = 1
kc_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
kc_signal.append(0)
elif prices[i] > kc_upper[i] and prices[i+1] < prices[i]:
if signal != -1:
buy_price.append(np.nan)
sell_price.append(prices[i])
signal = -1
kc_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
kc_signal.append(0)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
kc_signal.append(0)

return buy_price, sell_price, kc_signal

buy_price, sell_price, kc_signal = implement_kc_strategy(intc['close'], intc['kc_upper'], intc['kc_lower'])

Пояснение к коду: Во-первых, мы определяем функцию с именем ‘implement_kc_strategy’, которая принимает в качестве параметров цены акций (‘prices’) и компоненты индикатора канала Кельтнера (‘kc_upper’ и ‘kc_lower’).

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

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

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

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

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

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

# TRADING SIGNALS PLOT

plt.plot(intc['close'], linewidth = 2, label = 'INTC')
plt.plot(intc['kc_upper'], linewidth = 2, color = 'orange', linestyle = '--', label = 'KC UPPER 20')
plt.plot(intc['kc_middle'], linewidth = 1.5, color = 'grey', label = 'KC MIDDLE 20')
plt.plot(intc['kc_lower'], linewidth = 2, color = 'orange', linestyle = '--', label = 'KC LOWER 20')
plt.plot(intc.index, buy_price, marker = '^', color = 'green', markersize = 15, linewidth = 0, label = 'BUY SIGNAL')
plt.plot(intc.index, sell_price, marker = 'v', color= 'r', markersize = 15, linewidth = 0, label = 'SELL SIGNAL')
plt.legend(loc = 'lower right')
plt.title('INTC KELTNER CHANNEL 20 TRADING SIGNALS')
plt.show()

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

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

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

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

# STOCK POSITION

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

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

close_price = intc['close']
kc_upper = intc['kc_upper']
kc_lower = intc['kc_lower']
kc_signal = pd.DataFrame(kc_signal).rename(columns = {0:'kc_signal'}).set_index(intc.index)
position = pd.DataFrame(position).rename(columns = {0:'kc_position'}).set_index(intc.index)

frames = [close_price, kc_upper, kc_lower, kc_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy

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

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

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

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

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

# BACKTESTING

intc_ret = pd.DataFrame(np.diff(intc['close'])).rename(columns = {0:'returns'})
kc_strategy_ret = []

for i in range(len(intc_ret)):
returns = intc_ret['returns'][i]*strategy['kc_position'][i]
kc_strategy_ret.append(returns)

kc_strategy_ret_df = pd.DataFrame(kc_strategy_ret).rename(columns = {0:'kc_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/intc['close'][0])
kc_investment_ret = []

for i in range(len(kc_strategy_ret_df['kc_returns'])):
returns = number_of_stocks*kc_strategy_ret_df['kc_returns'][i]
kc_investment_ret.append(returns)

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

Выпуск:

Profit gained from the KC strategy by investing $100k in INTC : 47786.65
Profit percentage of the KC strategy : 47%

Пояснение к коду: Во-первых, мы вычисляем доходность акций Intel, используя функцию «diff», предоставляемую пакетом NumPy, и мы сохранили ее в виде кадра данных в переменной «intc_ret». Затем мы передаем цикл for, чтобы перебрать значения переменной «intc_ret» для расчета доходности, которую мы получили от нашей торговой стратегии индикатора канала Кельтнера, и эти значения доходности добавляются к списку «kc_strategy_ret». Затем мы преобразуем список «kc_strategy_ret» в кадр данных и сохраняем его в переменной «kc_strategy_ret_df».

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

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

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

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

Возможно, вы заметили, что во всех своих статьях об алгоритмической торговле я сравнивал результаты стратегии не с самим рыночным индексом 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('KC Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Выпуск:

Benchmark profit by investing $100k : 22631.16
Benchmark Profit percentage : 22%
KC Strategy profit is 25% higher than the Benchmark Profit

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

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

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

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

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

Полный код:

# IMPORTING PACKAGES

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

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


# 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

intc = get_historical_data('INTC', '2020-01-01')
print(intc.tail())


# KELTNER CHANNEL CALCULATION

def get_kc(high, low, close, kc_lookback, multiplier, atr_lookback):
tr1 = pd.DataFrame(high - low)
tr2 = pd.DataFrame(abs(high - close.shift()))
tr3 = pd.DataFrame(abs(low - close.shift()))
frames = [tr1, tr2, tr3]
tr = pd.concat(frames, axis = 1, join = 'inner').max(axis = 1)
atr = tr.ewm(alpha = 1/atr_lookback).mean()

kc_middle = close.ewm(kc_lookback).mean()
kc_upper = close.ewm(kc_lookback).mean() + multiplier * atr
kc_lower = close.ewm(kc_lookback).mean() - multiplier * atr

return kc_middle, kc_upper, kc_lower

intc = intc.iloc[:,:4]
intc['kc_middle'], intc['kc_upper'], intc['kc_lower'] = get_kc(intc['high'], intc['low'], intc['close'], 20, 2, 10)
print(intc.tail())


# KELTNER CHANNEL PLOT

plt.plot(intc['close'], linewidth = 2, label = 'INTC')
plt.plot(intc['kc_upper'], linewidth = 2, color = 'orange', linestyle = '--', label = 'KC UPPER 20')
plt.plot(intc['kc_middle'], linewidth = 1.5, color = 'grey', label = 'KC MIDDLE 20')
plt.plot(intc['kc_lower'], linewidth = 2, color = 'orange', linestyle = '--', label = 'KC LOWER 20')
plt.legend(loc = 'lower right', fontsize = 15)
plt.title('INTC KELTNER CHANNEL 20')
plt.show()


# KELTNER CHANNEL STRATEGY

def implement_kc_strategy(prices, kc_upper, kc_lower):
buy_price = []
sell_price = []
kc_signal = []
signal = 0

for i in range(len(prices)):
if prices[i] < kc_lower[i] and prices[i+1] > prices[i]:
if signal != 1:
buy_price.append(prices[i])
sell_price.append(np.nan)
signal = 1
kc_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
kc_signal.append(0)
elif prices[i] > kc_upper[i] and prices[i+1] < prices[i]:
if signal != -1:
buy_price.append(np.nan)
sell_price.append(prices[i])
signal = -1
kc_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
kc_signal.append(0)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
kc_signal.append(0)

return buy_price, sell_price, kc_signal

buy_price, sell_price, kc_signal = implement_kc_strategy(intc['close'], intc['kc_upper'], intc['kc_lower'])


# TRADING SIGNALS PLOT

plt.plot(intc['close'], linewidth = 2, label = 'INTC')
plt.plot(intc['kc_upper'], linewidth = 2, color = 'orange', linestyle = '--', label = 'KC UPPER 20')
plt.plot(intc['kc_middle'], linewidth = 1.5, color = 'grey', label = 'KC MIDDLE 20')
plt.plot(intc['kc_lower'], linewidth = 2, color = 'orange', linestyle = '--', label = 'KC LOWER 20')
plt.plot(intc.index, buy_price, marker = '^', color = 'green', markersize = 15, linewidth = 0, label = 'BUY SIGNAL')
plt.plot(intc.index, sell_price, marker = 'v', color= 'r', markersize = 15, linewidth = 0, label = 'SELL SIGNAL')
plt.legend(loc = 'lower right')
plt.title('INTC KELTNER CHANNEL 20 TRADING SIGNALS')
plt.show()


# STOCK POSITION

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

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

close_price = intc['close']
kc_upper = intc['kc_upper']
kc_lower = intc['kc_lower']
kc_signal = pd.DataFrame(kc_signal).rename(columns = {0:'kc_signal'}).set_index(intc.index)
position = pd.DataFrame(position).rename(columns = {0:'kc_position'}).set_index(intc.index)

frames = [close_price, kc_upper, kc_lower, kc_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

print(strategy)
print(strategy[14:19])


# BACKTESTING

intc_ret = pd.DataFrame(np.diff(intc['close'])).rename(columns = {0:'returns'})
kc_strategy_ret = []

for i in range(len(intc_ret)):
returns = intc_ret['returns'][i]*strategy['kc_position'][i]
kc_strategy_ret.append(returns)

kc_strategy_ret_df = pd.DataFrame(kc_strategy_ret).rename(columns = {0:'kc_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/intc['close'][0])
kc_investment_ret = []

for i in range(len(kc_strategy_ret_df['kc_returns'])):
returns = number_of_stocks*kc_strategy_ret_df['kc_returns'][i]
kc_investment_ret.append(returns)

kc_investment_ret_df = pd.DataFrame(kc_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(kc_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the KC strategy by investing $100k in INTC : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the KC 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('KC Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Источник