Подробное руководство по повышению эффективности стратегии с помощью двух мощных технических индикаторов.

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

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

Полосы Боллинджера

Прежде чем перейти к изучению полос Боллинджера, важно знать, что такое простая скользящая средняя (SMA). Простая скользящая средняя - это не что иное, как средняя цена акции за определенный период времени. Полосы Боллинджера - это линии тренда, построенные выше и ниже SMA данной акции на определенном уровне стандартного отклонения. Чтобы лучше понять полосы Боллинджера, взгляните на следующий график, который представляет полосы Боллинджера акций Apple, рассчитанные с помощью SMA 20.

Полосы Боллинджера отлично подходят для наблюдения за волатильностью данной акции в течение определенного периода времени. Наблюдается, что волатильность акции ниже, когда расстояние или расстояние между верхней и нижней полосами меньше. Точно так же, когда пространство или расстояние между верхней и нижней полосой больше, акция имеет более высокий уровень волатильности. Наблюдая за графиком, вы можете наблюдать линию тренда под названием «MIDDLE BB 20», которая представляет собой не что иное, как SMA 20 акций Apple. Формула для расчета как верхней, так и нижней полосы акций выглядит следующим образом:

UPPER_BB = STOCK SMA + SMA STANDARD DEVIATION * 2
LOWER_BB = STOCK SMA - SMA STANDARD DEVIATION * 2

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

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

Значения стохастического осциллятора всегда находятся в диапазоне от 0 до 100 из-за его функции нормализации. Общие уровни перекупленности и перепроданности рассматриваются как 70 и 30 соответственно, но они могут варьироваться от одного человека к другому. Осциллятор стохастик состоит из двух основных компонентов:

  • % K Line: Эта линия является наиболее важным и основным компонентом индикатора Stochastic Oscillator. Он также известен как индикатор Fast Stochastic. Единственная цель этой линии - показать текущее состояние рынка (перекупленность или перепроданность). Эта линия рассчитывается путем вычитания минимальной цены, достигнутой акцией за определенное количество периодов, из цены закрытия акции, а затем эта разница делится на значение, вычисленное путем вычитания самой низкой цены, достигнутой акцией за определенное количество периодов. периоды от наивысшей цены акции. Окончательное значение получается путем умножения значения, вычисленного на вышеупомянутых шагах, на 100. Способ вычисления линии% K с наиболее популярной настройкой 14 в качестве количества периодов можно представить следующим образом:
%K = 100 * ((14 DAY CLOSING PRICE - 14 DAY LOWEST PRICE) - (14 DAY HIGHEST PRICE - 14 DAY LOWEST PRICE))
  • Линия% D: иначе известный как индикатор медленного стохастика, представляет собой не что иное, как скользящее среднее линии% K за указанный период. Он также известен как гладкая версия линии% K, поскольку линейный график линии% D будет выглядеть более гладким, чем линия% K. Стандартная установка линии% D - 3 как количество периодов.

Это весь процесс вычисления компонентов стохастического осциллятора. Теперь давайте проанализируем график, на котором данные цены закрытия Apple нанесены вместе с его стохастическим осциллятором, рассчитанным с 14 и 3 в качестве периодов ретроспективного анализа для линии% K и линии% D соответственно, чтобы получить твердое представление об индикаторе и о том, как он используется. .

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

Компоненты линии% K и линии% D, которые мы обсуждали ранее, показаны синим и оранжевым цветом соответственно. Вы также можете заметить две дополнительные черные пунктирные линии над и под линией% K и% D. Это дополнительный компонент стохастического осциллятора, известный как полосы. Эти полосы используются для выделения области перекупленности и перепроданности. Если обе линии% K и% D пересекают верхнюю полосу, акция считается перекупленной. Аналогичным образом, когда линии% K и% D пересекаются ниже нижней полосы, акция считается перепроданной.

Торговая стратегия

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

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

IF PREV_ST_COM > 30 AND CUR_ST_COMP < 30 AND CL < LOWER_BB ==> BUY
IF PREV_ST_COM > 70 AND CUR_ST_COMP < 70 AND CL < UPPER_BB ==> SELL
where,
PRE_ST_COM = Previous Day Stochastic Oscillator components' readings
CUR_ST_COM = Current Day Stochastic Oscillator components' readings
CL = Last Closing Price
LOWER_BB = Current Day Lower Band reading
UPPER_BB = Current Day Upper Band reading

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

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

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

1. Importing Packages
2. Extracting Stock Data from Twelve Data
3. Bollinger Bands Calculation
4. Stochastic Oscillator Calculation
5. Creating the Trading Strategy
6. Creating our Position
7. Backtesting
8. SPY ETF Comparison

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

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

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

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

# IMPORTING PACKAGES
import pandas as pd
import requests
import numpy as np
import matplotlib.pyplot as plt
from math import floor
from termcolor import colored as cl
plt.rcParams[‘figure.figsize’] = (20, 10)
plt.style.use(‘fivethirtyeight’)

Теперь, когда мы импортировали все необходимые пакеты в наш 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', '2010-01-01')
aapl.tail()

Вывод:

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

Шаг 3: Расчет полос Боллинджера

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

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

# BOLLINGER BANDS CALCULATION

def sma(data, lookback):
    sma = data.rolling(lookback).mean()
    return sma

def get_bb(data, lookback):
    std = data.rolling(lookback).std()
    upper_bb = sma(data, lookback) + std * 2
    lower_bb = sma(data, lookback) - std * 2
    middle_bb = sma(data, lookback)
    return upper_bb, lower_bb, middle_bb

aapl['upper_bb'], aapl['middle_bb'], aapl['lower_bb'] = get_bb(aapl['close'], 20)
aapl = aapl.dropna()
aapl.tail()

Вывод:

Пояснение к коду: Вышеупомянутое можно разделить на две части: расчет SMA и расчет полос Боллинджера.

Расчет SMA: во-первых, мы определяем функцию с именем «sma», которая принимает в качестве параметров цены акций («данные») и количество периодов («ретроспективный анализ»). Внутри функции мы используем функцию «прокрутки», предоставляемую пакетом Pandas, для вычисления SMA для заданного количества периодов. Наконец, мы сохраняем вычисленные значения в переменной «sma» и возвращаем их.

Расчет полос Боллинджера. Сначала мы определяем функцию с именем «get_bb», которая принимает в качестве параметров цены акций («данные») и количество периодов («ретроспективный анализ»). Внутри функции мы используем функцию «Rolling» и «std» для вычисления стандартного отклонения данных запасов и сохраняем рассчитанные значения стандартного отклонения в переменной «std». Затем мы вычисляем значения полос Боллинджера, используя соответствующие формулы, и, наконец, возвращаем рассчитанные значения. Мы сохраняем значения полос Боллинджера в нашем фреймворке «aapl», используя созданную функцию «bb».

Шаг 4: Расчет стохастического осциллятора

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

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

# STOCHASTIC OSCILLATOR CALCULATION

def get_stoch_osc(high, low, close, k_lookback, d_lookback):
    lowest_low = low.rolling(k_lookback).min()
    highest_high = high.rolling(k_lookback).max()
    k_line = ((close - lowest_low) / (highest_high - lowest_low)) * 100
    d_line = k_line.rolling(d_lookback).mean()
    return k_line, d_line

aapl['%k'], aapl['%d'] = get_stoch_osc(aapl['high'], aapl['low'], aapl['close'], 14, 3)
aapl.tail()

Вывод:

Пояснение к коду: мы сначала определяем функцию с именем get_stoch_osc, которая принимает данные о максимуме (максимуме), минимуме (минимуме), цене закрытия (закрытие) и ретроспективном анализе. периоды строки% K ('k_lookback') и строки% D ('d_lookback') соответственно в качестве параметров. Внутри функции мы сначала вычисляем самые низкие и самые высокие точки данных для заданного количества периодов, используя функции 'Rolling', 'min' и 'max', предоставляемые пакетами Pandas, и сохраняем значения в 'low_low ', и переменные' high_high '.

Затем идет расчет строки% K, где мы подставляем формулу в наш код и сохраняем показания в переменной k_line, после чего мы вычисляем строку% D, которая является не чем иным, как взятием SMA строки% K показания за указанное количество периодов. Наконец, мы возвращаем значения и вызываем функцию для сохранения показаний стохастического осциллятора Apple с 14 и 3 периодами ретроспективного анализа для линий% K и% D соответственно.

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

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

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

# TRADING STRATEGY

def bb_stoch_strategy(prices, k, d, upper_bb, lower_bb):
    buy_price = []
    sell_price = []
    bb_stoch_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if k[i-1] > 30 and d[i-1] > 30 and k[i] < 30 and d[i] < 30 and prices[i] < lower_bb[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                bb_stoch_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                bb_stoch_signal.append(0)
        elif k[i-1] < 70 and d[i-1] < 70 and k[i] > 70 and d[i] > 70 and prices[i] > upper_bb[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                bb_stoch_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                bb_stoch_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            bb_stoch_signal.append(0)
    
    sell_price[-1] = prices[-1]
    bb_stoch_signal[-1] = -1
    return buy_price, sell_price, bb_stoch_signal

buy_price, sell_price, bb_stoch_signal = bb_stoch_strategy(aapl['close'], aapl['%k'], aapl['%d'], aapl['upper_bb'], aapl['lower_bb'])

Пояснение к коду: сначала мы определяем функцию с именем 'bb_stoch_strategy', которая принимает значения цен на акции ('цены'), показания линии% K ('k'), показания линии% D ('d' ), Верхняя полоса Боллинджера ('upper_bb') и Нижняя полоса Боллинджера ('lower_bb') в качестве параметров.

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

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

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

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

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

# POSITION
position = []
for i in range(len(bb_stoch_signal)):
    if bb_stoch_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(aapl['close'])):
    if bb_stoch_signal[i] == 1:
        position[i] = 1
    elif bb_stoch_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
k = aapl['%k']
d = aapl['%d']
upper_bb = aapl['upper_bb'] 
lower_bb = aapl['lower_bb']
close_price = aapl['close']
bb_stoch_signal = pd.DataFrame(bb_stoch_signal).rename(columns = {0:'bb_stoch_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'bb_stoch_position'}).set_index(aapl.index)

frames = [close_price, k, d, upper_bb, lower_bb, bb_stoch_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy.tail()

Вывод:

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

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

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

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

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

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

# BACKTESTING

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

for i in range(len(aapl_ret)):
    returns = aapl_ret['returns'][i]*strategy['bb_stoch_position'][i]
    bb_stoch_strategy_ret.append(returns)
    
bb_stoch_strategy_ret_df = pd.DataFrame(bb_stoch_strategy_ret).rename(columns = {0:'bb_stoch_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
bb_stoch_investment_ret = []

for i in range(len(bb_stoch_strategy_ret_df['bb_stoch_returns'])):
    returns = number_of_stocks*bb_stoch_strategy_ret_df['bb_stoch_returns'][i]
    bb_stoch_investment_ret.append(returns)

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

Вывод:

Profit gained from the BB STOCH strategy by investing $100k in AAPL : 1713231.15
Profit percentage of the BB STOCH strategy : 1713%

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

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

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

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

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

Вы могли заметить, что во всех моих статьях по алгоритмической торговле я сравнивал результаты стратегии не с самим рыночным индексом S&P 500, а с SPY ETF, и это связано с тем, что большинство поставщиков данных по акциям (например, Twelve Data) этого не делают. t предоставить данные индекса 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[0])
    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('2010-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('BB STOCH Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Вывод:

Benchmark profit by investing $100k : 284127.48
Benchmark Profit percentage : 284%
BB STOCH Strategy profit is 1429% higher than the Benchmark Profit

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

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

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

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

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

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

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

Кроме того, если вас интересует алгоритмическая торговля с помощью Python, настоятельно рекомендуется воспользоваться онлайн-программой Алгоритмическая торговля для всех, которая является специализацией, проводимой Quantra (Платформа Python для количественных финансов).

Полный код:

# IMPORTING PACKAGES

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

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

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

# BOLLINGER BANDS CALCULATION

def sma(data, lookback):
    sma = data.rolling(lookback).mean()
    return sma

def get_bb(data, lookback):
    std = data.rolling(lookback).std()
    upper_bb = sma(data, lookback) + std * 2
    lower_bb = sma(data, lookback) - std * 2
    middle_bb = sma(data, lookback)
    return upper_bb, lower_bb, middle_bb

aapl['upper_bb'], aapl['middle_bb'], aapl['lower_bb'] = get_bb(aapl['close'], 20)
aapl = aapl.dropna()
aapl.tail()

# STOCHASTIC OSCILLATOR CALCULATION

def get_stoch_osc(high, low, close, k_lookback, d_lookback):
    lowest_low = low.rolling(k_lookback).min()
    highest_high = high.rolling(k_lookback).max()
    k_line = ((close - lowest_low) / (highest_high - lowest_low)) * 100
    d_line = k_line.rolling(d_lookback).mean()
    return k_line, d_line

aapl['%k'], aapl['%d'] = get_stoch_osc(aapl['high'], aapl['low'], aapl['close'], 14, 3)
aapl.tail()

# PLOTTING THE DATA

plot_data = aapl[aapl.index >= '2020-01-01']

plt.plot(plot_data['close'], linewidth = 2.5)
plt.plot(plot_data['upper_bb'], label = 'UPPER BB 20', linestyle = '--', linewidth = 1, color = 'black')
plt.plot(plot_data['middle_bb'], label = 'MIDDLE BB 20', linestyle = '--', linewidth = 1.2, color = 'grey')
plt.plot(plot_data['lower_bb'], label = 'LOWER BB 20', linestyle = '--', linewidth = 1, color = 'black')
plt.title('AAPL BB 20')
plt.legend(loc = 'upper left')
plt.show()

ax1 = plt.subplot2grid((14,1), (0,0), rowspan = 7, colspan = 1)
ax2 = plt.subplot2grid((15,1), (9,0), rowspan = 6, colspan = 1)
ax1.plot(plot_data['close'], linewidth = 2.5)
ax1.set_title('AAPL STOCK PRICES')
ax2.plot(plot_data['%k'], color = 'deepskyblue', linewidth = 1.5, label = '%K')
ax2.plot(plot_data['%d'], color = 'orange', linewidth = 1.5, label = '%D')
ax2.axhline(70, color = 'black', linewidth = 1, linestyle = '--')
ax2.axhline(30, color = 'black', linewidth = 1, linestyle = '--')
ax2.set_title(f'AAPL STOCH 14,3')
ax2.legend(loc = 'right')
plt.show()

# TRADING STRATEGY

def bb_stoch_strategy(prices, k, d, upper_bb, lower_bb):
    buy_price = []
    sell_price = []
    bb_stoch_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if k[i-1] > 30 and d[i-1] > 30 and k[i] < 30 and d[i] < 30 and prices[i] < lower_bb[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                bb_stoch_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                bb_stoch_signal.append(0)
        elif k[i-1] < 70 and d[i-1] < 70 and k[i] > 70 and d[i] > 70 and prices[i] > upper_bb[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                bb_stoch_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                bb_stoch_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            bb_stoch_signal.append(0)
    
    sell_price[-1] = prices[-1]
    bb_stoch_signal[-1] = -1
    return buy_price, sell_price, bb_stoch_signal

buy_price, sell_price, bb_stoch_signal = bb_stoch_strategy(aapl['close'], aapl['%k'], aapl['%d'], aapl['upper_bb'], aapl['lower_bb'])

# PLOTTING TRADING SIGNALS

ax1 = plt.subplot2grid((14,1), (0,0), rowspan = 7, colspan = 1)
ax2 = plt.subplot2grid((15,1), (9,0), rowspan = 6, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2.5)
ax1.plot(aapl['upper_bb'], label = 'UPPER BB 20', linestyle = '--', linewidth = 1, color = 'black')
ax1.plot(aapl['middle_bb'], label = 'MIDDLE BB 20', linestyle = '--', linewidth = 1.2, color = 'grey')
ax1.plot(aapl['lower_bb'], label = 'LOWER BB 20', linestyle = '--', linewidth = 1, color = 'black')
ax1.plot(aapl.index, buy_price, marker = '^', markersize = 10, color = 'green', label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', markersize = 10, color = 'r', label = 'SELL SIGNAL')
ax1.set_title('AAPL BB 20')
ax1.legend(loc = 'right')
ax2.plot(aapl['%k'], color = 'deepskyblue', linewidth = 1.5, label = '%K')
ax2.plot(aapl['%d'], color = 'orange', linewidth = 1.5, label = '%D')
ax2.axhline(70, color = 'black', linewidth = 1, linestyle = '--')
ax2.axhline(30, color = 'black', linewidth = 1, linestyle = '--')
ax2.set_title(f'AAPL STOCH 14,3')
ax2.legend()
plt.show()# POSITION

position = []
for i in range(len(bb_stoch_signal)):
    if bb_stoch_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(aapl['close'])):
    if bb_stoch_signal[i] == 1:
        position[i] = 1
    elif bb_stoch_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
k = aapl['%k']
d = aapl['%d']
upper_bb = aapl['upper_bb'] 
lower_bb = aapl['lower_bb']
close_price = aapl['close']
bb_stoch_signal = pd.DataFrame(bb_stoch_signal).rename(columns = {0:'bb_stoch_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'bb_stoch_position'}).set_index(aapl.index)

frames = [close_price, k, d, upper_bb, lower_bb, bb_stoch_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy.tail()

# BACKTESTING

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

for i in range(len(aapl_ret)):
    returns = aapl_ret['returns'][i]*strategy['bb_stoch_position'][i]
    bb_stoch_strategy_ret.append(returns)
    
bb_stoch_strategy_ret_df = pd.DataFrame(bb_stoch_strategy_ret).rename(columns = {0:'bb_stoch_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
bb_stoch_investment_ret = []

for i in range(len(bb_stoch_strategy_ret_df['bb_stoch_returns'])):
    returns = number_of_stocks*bb_stoch_strategy_ret_df['bb_stoch_returns'][i]
    bb_stoch_investment_ret.append(returns)

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