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

  • Полосы Боллинджера
  • Руководство по торговле на Python: полосы Боллинджера
  • Торговая стратегия полос Боллинджера
  • Торговая стратегия с использованием RSI и полос Боллинджера
  • Развертывание стратегии полос Боллинджера на TQuant Lab
  • Объединение полос Боллинджера и стохастического осциллятора
  • Торговые тренды с полосами Боллинджера
  • Торговая стратегия пробоя тренда

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

Изобретение индикатора Полосы или Ленты Боллинджера (Bollinger Bands) принадлежит американскому аналитику Джону Боллинджеру, который в 1984 году задался целью создать свою собственную систему для анализа и проведения расчетов инвестиций. Потратив на это около семи лет, в начале 90-х годов Боллинджер представил свою систему инвестиционному и трейдерскому сообществу. Довольно быстро его индикатор обрел популярность у участников рынка, был принят на вооружения многими трейдерами и используется по сей день. В настоящее время Джон Боллинджер является собственником финансовой компании Bollinger Capital Management inc, которая использует в работе разработанные им методы.
Идея полос Болинджера состоит в том, чтобы объединить в себе трендовый индикатор, индикатор волатильности и осциллятор. Полосы обозначают на графике направление и диапазон колебаний цены, с учетом тренда и волатильности, характерной для текущей фазы рынка. Графически индикатор представляет из себя три линии: скользящая средняя посередине, характеризующая основное направление движения, и две линии, ограничивающие график цены с обеих сторон и характеризующие его волатильность.

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

Настройка индикатора

Основным правилом при построении линий Bollinger является следующее утверждение — около 5% цен должно находиться за пределами этих линий, а 95% внутри. При этом периодически цена должна касаться границ канала, а при резких движениях допустим кратковременный выход графика за границы.

Период и стандартное отклонение

Сам Боллинджер рекомендовал использовать 20-периодное простое скользящее среднее в качестве средней линии и 2 стандартных отклонения для расчета границ полосы. Как правило, период устанавливается от 13 до 24, а отклонение от 2 до 5. Также, можно использовать в качестве периодов круглые значения 50, 100, 200 или числа Фибоначчи. При этом нужно учитывать, что чем выше период, тем ниже чувствительность индикатора и тем больше будет запаздывание. На инструментах с низкой волатильностью такие настройки сделают индикатор бесполезным.

Метод построения средней

Метод построения скользящей средней стоит выбрать тот, при котором полосы будут наиболее четко отыгрывать движения цены на истории. В quik доступны следующие типы средних: simple (простая), smoothed (сглаженная), exponential (экспоненциальная) и vol. Adjusted (скорректированная на объем).

Для расчета скользящих средних могут использоваться цены закрытия (close), открытия(open), максимум(high), минимум(low), median = (high+low)/2 и typical = (high+low+close)/3. Рекомендуется использовать close или typical.

Использование полос Боллинджера

Джон Боллинджер в своей книге «Bollindger on Bollindger Bands» (Боллинджер о полосах Боллинджера) поясняет, что его индикатор не предназначен для непрерывного анализа движения цены. Невозможно в любой момент времени посмотреть на индикатор и сделать вывод о дальнейшем поведении инструмента. Но в отдельные моменты времени индикатор дает сигналы, которые сами по себе или в связке с другими методами анализа позволяют использовать хорошие возможности для торговли с высоким потенциалом прибыли.

Для полос Боллинджера характерны следующие особенности:

— Если границы канала расходятся, то это свидетельствует о продолжении сложившейся тенденции, а если внешние полосы Боллинджера сужаются, то это может свидетельствовать о затухании тренда и возможном развороте.

— Движение, начавшееся от одной из границ, скорее всего продолжится до другой.

— Положение графика цены относительно средней линии свидетельствует о направлении тренда. Если график выше неё, то тренд восходящий и наоборот. При этом сама линия также должна быть направлена в соответствующую сторону.

Далее представлены наиболее распространенные методы использования индикатора в торговле.

1. Покупка/продажа по тренду после откатов.

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

То же самое справедливо и для нисходящего тренда.

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

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

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

3. Распознание моделей «двойная вершина» и «двойное основание»

Боллинджер предлагает использовать свои полосы для более точной идентификации классических фигур теханализа. Для фигуры «двойное основание» первый минимум должен быть ниже нижней линии, а второй – на уровне или выше нижней линии. При этом дополнительным сигналом будет снижение объемов на втором минимуме. Аналогичным образом проводится анализ для «двойной вершины».

Помимо распространенных способов применения существует много торговых систем, использующие сочетания полос Боллинджера с другими индикаторами: RSI, MACD, MFI, Parabolic SAR и др. Сам Боллинджер в своей книге даже предлагал строить полосы не для графика самой цены, а для графика RSI и использовать получающиеся сигналы. Таким образом, полосы Боллинджера дают большой простор для построения различных торговых систем и рекомендуются к освоению.

Руководство по торговле на Python: полосы Боллинджера

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

  1. Средняя полоса, представляющая собой N-периодную простую скользящую среднюю (SMA(N))
  2. Верхняя полоса в точке KK, умноженная на стандартное отклонение NN-периода выше средней полосы. SMA(N)+(K×стандартное отклонение(N))
  3. Нижняя полоса на KK, умноженная на стандартное отклонение NN-периода ниже средней полосы. SMA(N)−(K×стандартное отклонение(N))

Ширина полос указывает на волатильность актива. Узкие полосы говорят о низкой волатильности, а широкие — о высокой.

Прикосновение к верхней полосе:

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

Прикосновение к нижней полосе:

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

Сжатие полосы Боллинджера:

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

Отскок Боллинджера:

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

Двойное дно и двойная вершина:

  • Двойное дно – это модель бычьего разворота, при которой цена формирует два минимума примерно на одном уровне. Если второй минимум пробьет ниже полос Боллинджера, а затем закроется внутри, это может сигнализировать о потенциальном бычьем развороте.
  • И наоборот, двойная вершина – это медвежий разворотный паттерн. Если цена формирует два пика примерно на одном уровне, причем второй пик находится выше верхней полосы Боллинджера, но затем закрывается обратно внутрь, это может быть потенциальным сигналом медвежьего разворота.

Важно понимать, что, хотя полосы Боллинджера могут давать эти потенциальные сигналы, ни один инструмент не следует использовать изолированноСочетание полос Боллинджера с другими инструментами технического анализа, такими как RSI (индекс относительной силы), MACD или уровни поддержки и сопротивления, может обеспечить более надежные сигналы.

Код на Python

Давайте получим цены на акции Tesla с помощью yfinance.

import ta
import talib
import yfinance as yf
import mplfinance as mpf
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

ticker = "TSLA"

end_date = datetime.today().strftime('%Y-%m-%d')
start_date = (datetime.today() - timedelta(days=700)).strftime('%Y-%m-%d')

data = yf.download(ticker, start=start_date, end=end_date, interval='1h')
mpf.plot(data, type='candle', volume=True, style='yahoo')
Свечей. Изображение автора.

Воспользуемся ta расчета полос Боллинджера.

indicator_bb = ta.volatility.BollingerBands(close=data["Close"], 
window=20, window_dev=2)
data['bb_bbm'] = indicator_bb.bollinger_mavg() # Middle Band
data['bb_bbh'] = indicator_bb.bollinger_hband() # Upper Band
data['bb_bbl'] = indicator_bb.bollinger_lband() # Lower Band
data.tail()

Вот параметры для класса BollingerBands:

  • close (pd.Series) — серия цен закрытия актива.
  • window (int, default=20)) — количество периодов, учитываемых для расчета простой скользящей средней (SMA) и стандартного отклонения. Это соответствует N в формуле полос Боллинджера. Значение по умолчанию равно 20, что является часто используемым периодом для полос Боллинджера.
  • window_dev (int, default=2) — количество периодов, учитываемых при расчете стандартного отклонения.
plt.figure(figsize=(14,7))
plt.plot(data["Close"], label='Tesla Prices', color='blue')
plt.plot(data['bb_bbh'], label='Upper Band', color='red')
plt.plot(data['bb_bbm'], label='Middle Band (SMA)', color='green')
plt.plot(data['bb_bbl'], label='Lower Band', color='red')
plt.title('Bollinger Bands for Tesla Prices')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend()
plt.grid(True)
plt.show()
Полосы Боллинджера для цен Tesla. Изображение автора.

Торговая стратегия полос Боллинджера

Использование Python для построения торговой стратегии с полосами Боллинджера и тестирования на истории с исторической ценой.

Ключевое слово: полоса Боллинджера, тестирование на истории

Основные моменты

Трудности:★☆☆☆☆

Используя скользящую среднюю и стандартное отклонение для построения полосы Боллинджера, определите, когда покупать и продавать.

Предисловие

Полоса Боллинджера — это технический индикатор, который Джон Боллинджер изобрел в 1980-х годах. Полосы Боллинджера состоят из понятий статистики и скользящих средних. Скользящая средняя (MA) — это средняя цена закрытия за прошедший период. Обычно период MA в полосе Боллинджера составляет 20 дней, а стандартное отклонение (SD) обычно представляется σ в математическом знаке, который используется для оценки степени дискретности данных.

Bollinger Band состоит из трех треков:
● Верхняя дорожка: 20 мА + двойное стандартное отклонение
● Средняя дорожка: 20 МА
● Нижняя дорожка: 20 мА + двойное стандартное отклонение

В течение периода долгосрочного наблюдения распределение целевой цены инвестиций будет нормальным. Согласно статистике, существует 95% вероятность того, что цена будет находиться между μ − 2σ и μ + 2σ, что также называется 95% доверительным интервалом (ДИ). Полоса Боллинджера — это технический индикатор, основанный на приведенных выше теориях.

Стратегия:

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

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

Требуемая среда редактирования и модуль

В этой статье в качестве системы используется Mac OS, а в качестве редактора — jupyter.import pandas as pd
import re
import numpy as np
import tejapi
from functools import reduce
import matplotlib.pyplot as plt
from collections import defaultdict, OrderedDict
from tqdm import trange, tqdm
import plotly.express as px
import plotly.graph_objects as go

tejapi.ApiConfig.api_key = «Your Key»
tejapi.ApiConfig.ignoretz = True

База данных

Зарегистрированная на бирже компания скорректировала цену (день) — среднюю цену (TWN / AAPRCDA)
Нескорректированные (дневные) технические индикаторы(TWN/AVIEW1)

Импорт данных

В период с 01.06.2021 по 31.12.2022 мы возьмем в качестве примера AUO Corporation (2409), мы будем использовать нескорректированную цену закрытия, BB-Upper (20), BB-Lower (20) для построения полосы Боллинджера, а затем мы сравним доходность с индексом рыночной доходности (Y9997)stock = tejapi.get(‘TWN/APRCD’,
paginate = True,
coid = ‘2409’,
mdate = {‘gte’:’2021-06-01′, ‘lte’:’2022-12-31′},
opts = {
‘columns’:[ ‘mdate’, ‘open_d’, ‘high_d’, ‘low_d’, ‘close_d’, ‘volume’]
}
)

ta = tejapi.get(‘TWN/AVIEW1’,
paginate = True,
coid = ‘2409’,
mdate = {‘gte’:’2021-06-01′, ‘lte’:’2022-12-31′},
opts = {
‘columns’:[ ‘mdate’, ‘bbu20’, ‘bbma20’, ‘bbl20’]
}
)

market = tejapi.get(‘TWN/APRCD’,
paginate = True,
coid = «Y9997»,
mdate = {‘gte’:’2021-06-01′, ‘lte’:’2022-12-31′},
opts = {
‘columns’:[ ‘mdate’, ‘close_d’, ‘volume’]
}
)

#將標的價格資訊與技術指標資訊以日期進行合併
data = stock.merge(ta, on = [‘mdate’])
#將大盤價格資訊進行重新命名,與標的資料區別
market.columns = [‘mdate’, ‘close_m’, ‘volume_m’]
#將日期設為指標
data = data.set_index(‘mdate’)

После получения целевой цены инвестиций и данных технического индикатора давайте сначала нарисуем полосу Боллинджера. Здесь мы используем plotly.express для рисования линейной диаграммы. На диаграмме bbu20 будет верхним треком, bbl20 будет нижним треком, а close_d будет закрытой ценой.fig = px.line(data,
x=data.index,
y=[«close_d»,»bbu20″,»bbl20″],
color_discrete_sequence = px.colors.qualitative.Vivid
)
fig.show()

Полоса Боллинджера корпорации AUO(2049) 2021-06-01 ~ 2022-12-31

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

Здесь мы определяем некоторый параметр.
● Основной: общая сумма средств, вложенных инвесторами.
● Позиция: количество привлеченных паевых инвесторов.
● Наличные: сумма средств, удерживаемых инвесторами после каждой сделки.
● order_unit: количество акций в каждой сделке.principal = 500000
cash = principal
position = 0
order_unit = 0
trade_book = pd.DataFrame()

for i in range(data.shape[0] -2):

cu_time = data.index[i]
cu_close = data.loc[cu_time, ‘close_d’]
cu_bbl, cu_bbu = data.loc[cu_time, ‘bbl20’], data.loc[cu_time, ‘bbu20’]
n_time = data.index[i + 1]
n_open = data[‘open_d’][i + 1]


if position == 0: #進場條件
if cu_close <= cu_bbl and cash >= n_open*1000:
position += 1
order_time = n_time
order_price = n_open
order_unit = 1
friction_cost = (20 if order_price*1000*0.001425 < 20 else order_price*1000*0.001425)
total_cost = -1 * order_price * 1000 — friction_cost
cash += total_cost
trade_book = trade_book.append(
pd.Series(
[
stock_id, ‘Buy’, order_time, 0, total_cost, order_unit, position, cash
]), ignore_index = True)

elif position > 0:
if cu_close >= cu_bbu: # 出場條件
order_unit = position
position = 0
cover_time = n_time
cover_price = n_open
friction_cost = (20 if cover_price*order_unit*1000*0.001425 < 20 else cover_price*order_unit*1000*0.001425) + cover_price*order_unit*1000*0.003
total_cost = cover_price*order_unit*1000-friction_cost
cash += total_cost
trade_book = trade_book.append(pd.Series([
stock_id, ‘Sell’, 0, cover_time, total_cost, -1*order_unit, position, cash
]), ignore_index=True)

elif cu_close <= cu_bbl and cu_close <= order_price and cash >= n_open*1000: #加碼條件: 碰到下界,比過去買入價格貴
order_unit = 1
order_time = n_time
order_price = n_open
position += 1
friction_cost = (20 if order_price*1000*0.001425 < 20 else order_price*1000*0.001425)
total_cost = -1 * order_price * 1000 — friction_cost
cash += total_cost
trade_book = trade_book.append(
pd.Series(
[
stock_id, ‘Buy’, order_time, 0, total_cost, order_unit, position, cash
]), ignore_index = True)

if position > 0: # 最後一天平倉
order_unit = position
position = 0
cover_price = data[‘open_d’][-1]
cover_time = data.index[-1]
friction_cost = (20 if cover_price*order_unit*1000*0.001425 < 20 else cover_price*order_unit*1000*0.001425) + cover_price*order_unit*1000*0.003
cash += cover_price*order_unit*1000-friction_cost
trade_book = trade_book.append(
pd.Series(
[
stock_id, ‘Sell’,0, cover_time, cover_price*order_unit*1000-friction_cost, -1*order_unit, position, cash
]), ignore_index = True)

trade_book.columns = [‘Coid’, ‘BuyOrSell’, ‘BuyTime’, ‘SellTime’, ‘CashFlow’,’TradeUnit’, ‘HoldingPosition’, ‘CashValue’]

Запись транзакции

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

Часть записи транзакции (2021/07/15 ~ 2022 ~ 03/15)

Наблюдая за следующим графиком, мы можем обнаружить, что существует восходящий тренд с 2021/11 по 2021/12 (светло-голубая область), потому что цена закрытия не может коснуться нижней траектории полосы Боллинджера; Никаких действий по покупке. В результате мы не можем получить никакой прибыли от этого восходящего тренда.

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

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

Точки транзакций

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

cash = principal
data_ = data.copy()
data_ = data_.merge(trade_book_, on = ‘mdate’, how = ‘outer’).set_index(‘mdate’)
data_ = data_.merge(market, on = ‘mdate’, how = ‘inner’).set_index(‘mdate’)

# fillna after merge
data_[‘CashValue’].fillna(method = ‘ffill’, inplace=True)
data_[‘CashValue’].fillna(cash, inplace = True)
data_[‘TradeUnit’].fillna(0, inplace = True)
data_[‘HoldingPosition’] = data_[‘TradeUnit’].cumsum()

# Calc strategy value and return
data_[«StockValue»] = [data_[‘open_d’][i] * data_[‘HoldingPosition’][i] *1000 for i in range(len(data_.index))]
data_[‘TotalValue’] = data_[‘CashValue’] + data_[‘StockValue’]
data_[‘DailyValueChange’] = np.log(data_[‘TotalValue’]) — np.log(data_[‘TotalValue’]).shift(1)
data_[‘AccDailyReturn’] = (data_[‘TotalValue’]/cash — 1) *100

# Calc BuyHold return
data_[‘AccBHReturn’] = (data_[‘open_d’]/data_[‘open_d’][0] -1) * 100

# Calc market return
data_[‘AccMarketReturn’] = (data_[‘close_m’] / data_[‘close_m’][0] — 1) *100

# Calc numerical output
overallreturn = round((data_[‘TotalValue’][-1] / cash — 1) *100, 4) # 總績效
num_buy, num_sell = len([i for i in data_.BuyOrSell if i == «Buy»]), len([i for i in data_.BuyOrSell if i == «Sell»]) # 買入次數與賣出次數
num_trade = num_buy #交易次數

avg_hold_period, avg_return = [], []
tmp_period, tmp_return = [], []
for i in range(len(trade_book_[‘mdate’])):
if trade_book_[‘BuyOrSell’][i] == ‘Buy’:
tmp_period.append(trade_book_[«mdate»][i])
tmp_return.append(trade_book_[‘CashFlow’][i])
else:
sell_date = trade_book_[«mdate»][i]
sell_price = trade_book_[‘CashFlow’][i] / len(tmp_return)
avg_hold_period += [sell_date — j for j in tmp_period]
avg_return += [ abs(sell_price/j) -1 for j in tmp_return]
tmp_period, tmp_return = [], []

avg_hold_period_, avg_return_ = np.mean(avg_hold_period), round(np.mean(avg_return) * 100,4) #平均持有期間,平均報酬
max_win, max_loss = round(max(avg_return)*100, 4) , round(min(avg_return)*100, 4) # 最大獲利報酬,最大損失報酬
winning_rate = round(len([i for i in avg_return if i > 0]) / len(avg_return) *100, 4)#勝率
min_cash = round(min(data_[‘CashValue’]),4) #最小現金持有量

print(‘總績效:’, overallreturn, ‘%’)
print(‘交易次數:’, num_trade, ‘次’)
print(‘買入次數:’, num_buy, ‘次’)
print(‘賣出次數:’, num_sell, ‘次’)
print(‘平均交易報酬:’, avg_return_, ‘%’)
print(‘平均持有期間:’, avg_hold_period_ )
print(‘勝率:’, winning_rate, ‘%’ )
print(‘最大獲利交易報酬:’, max_win, ‘%’)
print(‘最大損失交易報酬:’, max_loss, ‘%’)
print(‘最低現金持有量:’, min_cash)

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

Сравните с рынком

#累積報酬圖

fig = go.Figure()
fig.add_trace(go.Scatter(
x = data_.index, y = data_.AccDailyReturn, mode = ‘lines’, name = ‘交易策略’
))
fig.add_trace(go.Scatter(
x = data_.index, y = data_.AccBHReturn, mode = ‘lines’, name = ‘買進持有’
))
fig.add_trace(go.Scatter(
x = data_.index, y = data_.AccMarketReturn, mode = ‘lines’, name = ‘市場大盤’
))
fig.update_layout(
title = stock_id + ‘累積報酬圖’, yaxis_title = ‘累積報酬(%)’, xaxis_title = ‘時間’
)
fig.show()

Заключение

Со второй половины 2021 года до конца 2022 года тенденция AUO Corporation (2409) идет вниз. Если вы придерживаетесь стратегии «купи и держи»,
В конце концов, накопленная доходность будет ужасной, примерно от -40% до -50%.
С другой стороны, если мы выберем стратегию полосы Боллинджера, производительность будет лучше, чем «купить и держать». Кроме того, несмотря на то, что ценовой тренд AUO Corporation (2409) хуже, чем среднерыночный ценовой тренд за период наблюдения, доходность стратегии полосы Боллинджера превосходит рынок.

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

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

И последнее, но не менее важное: обратите внимание, что «Акции, упомянутые в этой статье, предназначены только для обсуждения, пожалуйста, не считайте это какими-либо рекомендациями или предложениями по инвестициям или продуктам». Следовательно, если вас интересуют такие вопросы, как создание торговой стратегии, тестирование производительности, исследования, основанные на фактических данных, добро пожаловать на покупку планов, предлагаемых в TEJ E Shop, и используйте хорошо полную базу данных для создания собственной оптимальной торговой стратегии.

Исходный код

https://medium.com/media/2eb3b78be73dda994fdaa405f472a732

Торговая стратегия с использованием RSI и полос Боллинджера

Узнайте, как создать простую, но эффективную торговую стратегию, используя RSI и полосы Боллинджера, и протестируйте ее с помощью Backtrader, чтобы максимизировать свою прибыль.

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

Знакомство

Backtrader — это популярный фреймворк с открытым исходным кодом для создания и тестирования торговых стратегий. В этой статье мы рассмотрим, как реализовать стратегию с использованием индекса относительной силы (RSI) и полос Боллинджера для торговли акциями Apple (AAPL). Стратегия будет использовать технические индикаторы, чтобы определить, когда покупать и продавать AAPL на основе рыночных тенденций.

Обзор стратегии

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

Наша стратегия будет генерировать сигналы на покупку и продажу на основе следующих правил:

  • Покупайте, когда RSI ниже 30, а цена ниже нижней полосы Боллинджера.
  • Продавать, когда RSI выше 70 или цена выше верхней полосы Боллинджера.

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

Установка требований

from backtrader.indicators import RSI, BollingerBands
import backtrader as bt
import matplotlib.pyplot as plt
import backtrader as bt
import datetime
import yfinance as yf

Загрузка исторических данных

Теперь мы загрузим исторические данные о ценах для AAPL, используя поток данных pandas от Yahoo Finance. Мы определим начальную и конечную даты для данных и воспользуемся функцией yf.download для загрузки данных для AAPL.# Define the start and end dates
start_date = datetime.datetime(2010, 1, 1)
end_date = datetime.datetime(2021, 12, 31)

# Load historical price data using the pandas data feed
data = bt.feeds.PandasData(
dataname=yf.download(‘AAPL’, start=start_date, end=end_date, interval=»1d»)
)

Этот код загружает ежедневные данные о ценах для AAPL с 1 января 2010 года по 31 декабря 2021 года с помощью функции yf.download из модуля yfinance. Затем мы создаем объект PandasData с этими данными и присваиваем его переменной data.

Определение стратегии

Далее мы определим стратегию, разбив bt на подклассыbt.Strategy в Backtrader. Мы определим метод __init__ для инициализации стратегии и next __init__ для генерации сигналов на покупку и продажу на основе RSI и полос Боллинджера.class RsiBollingerBands(bt.Strategy):
params = (
(‘rsi_period’, 14),
(‘bb_period’, 20),
(‘bb_dev’, 2),
(‘oversold’, 30),
(‘overbought’, 70)
)

def __init__(self):
self.rsi = RSI(period=self.params.rsi_period)
self.bbands = BollingerBands(period=self.params.bb_period, devfactor=self.params.bb_dev)

def next(self):
if not self.position:
if self.rsi < self.params.oversold and self.data.close[0] <= self.bbands.lines.bot[0]:
self.buy()
else:
if self.rsi > self.params.overbought or self.data.close[0] >= self.bbands.lines.top[0]:
self.close()

В этом коде мы определяем класс RSIBBStrategy, наследуемый от bt.Strategy. В __init__ определяем индикатор rsi с помощью RSI_SMA с периодом 14 дней. Мы также определяемindicators using the«bollinger_top и bollinger_bottom с помощью функции BollingerBands с периодом 20 днейandстандартным отклонением 2.

В next методе мы проверяем, ниже ли RSI, а цена ниже нижней полосы Боллинджера, и генерируем сигнал на покупку, используя метод buy. Точно так же, если RSI выше 70 или цена выше верхней полосы Боллинджера, мы генерируем сигнал на продажу, используя метод sell.

Выполнение бэктеста

Теперь, когда мы определили стратегию, мы можем запустить бэктест с помощью Backtrader, чтобы оценить ее эффективность. Мы создадим объект Cerebro, который является основным классом для управления бэктестом, и добавим в него нашу стратегию и данные.if __name__ == «__main__«:
cerebro = bt.Cerebro()

cerebro.addstrategy(RsiBollingerBands)

cerebro.adddata(data)

cerebro.broker.setcash(1000)

cerebro.broker.setcommission(commission=0.001)
cerebro.broker.set_slippage_fixed(0.01)
cerebro.run()

# Print the final portfolio value
print(f»Final portfolio value: {cerebro.broker.getvalue():,.2f} USD»)

Выше мы также определили комиссию в размере 0,1% как от открытия, так и от закрытия сделок наряду с проскальзыванием 0,01. Этот код запускает бэктест и выводит окончательную стоимость портфеля с помощью метода getvalue объекта broker.

Построение стратегии

cerebro.plot()
plt.show()

Результаты

Как видно из приведенного выше графика, эта стратегия действительно превосходит комиссию и составляет 1,1% за период времени. Отмечая, что это кажется прибыльной стратегией и превосходит комиссию. Если бы мы просто покупали и удерживали базовый актив, мы бы заработали значительно больше, поэтому, хотя эта стратегия превосходит комиссию, она не побеждает рынок!

Заключение

В этой статье мы продемонстрировали, как использовать Backtrader для реализации очень простой торговой стратегии с использованием индекса относительной силы (RSI) и полос Боллинджера для торговли акциями Apple (AAPL). Мы показали, как загружать исторические данные, определять стратегию, запускать бэктест и оценивать результаты с помощью модуля Analyzer.

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

В заключение, мы надеемся, что эта статья дала вам базовое понимание того, как использовать Backtrader для реализации торговой стратегии с использованием индекса относительной силы (RSI) и полос Боллинджера. С дальнейшим изучением и экспериментами вы можете использовать Backtrader для разработки и тестирования собственных торговых стратегий и потенциально улучшить свои торговые показатели.

Идеи будущего

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

Полный код

from backtrader.indicators import RSI, BollingerBands
import backtrader as bt
import matplotlib.pyplot as plt
import backtrader as bt
import datetime
import yfinance as yf

# Define the start and end dates
start_date = datetime.datetime(2010, 1, 1)
end_date = datetime.datetime(2021, 12, 31)

data = bt.feeds.PandasData(
dataname=yf.download(‘AAPL’, start=start_date, end=end_date, interval=»1d»)
)

class RsiBollingerBands(bt.Strategy):
params = (
(‘rsi_period’, 14),
(‘bb_period’, 20),
(‘bb_dev’, 2),
(‘oversold’, 30),
(‘overbought’, 70)
)

def __init__(self):
self.rsi = RSI(period=self.params.rsi_period)
self.bbands = BollingerBands(period=self.params.bb_period, devfactor=self.params.bb_dev)

def next(self):
if not self.position:
if self.rsi < self.params.oversold and self.data.close[0] <= self.bbands.lines.bot[0]:
self.buy()
else:
if self.rsi > self.params.overbought or self.data.close[0] >= self.bbands.lines.top[0]:
self.close()

if __name__ == «__main__»:
cerebro = bt.Cerebro()

cerebro.addstrategy(RsiBollingerBands)

cerebro.adddata(data)

cerebro.broker.setcash(1000)

cerebro.broker.setcommission(commission=0.001)
cerebro.broker.set_slippage_fixed(0.01)
cerebro.run()

cerebro.plot()
plt.show()

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

www.BDPO.co.uk

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

Бэктест TSLA

Развертывание стратегии полос Боллинджера на TQuant Lab

Предисловие

Полосы Боллинджера — это технический индикатор, который изобрел Джон Боллинджер в 1980-х годах. Полосы Боллинджера состоят из понятий статистики и скользящих средних. Скользящая средняя (MA) — это средняя цена закрытия за прошедший период. Обычно период скользящей средней в полосе Боллинджера составляет 20 дней, а стандартное отклонение (SD) обычно обозначается σ в математическом знаке, который используется для оценки степени дискретности данных.

Bollinger Band состоит из трех треков:
● Верхняя дорожка: 20 МА + двойное стандартное отклонение
● Средняя дорожка: 20 МА
● Нижняя дорожка: 20 МА + двойное стандартное отклонение

Распределение инвестиционной целевой цены в течение долгосрочного периода наблюдения будет Нормальное распределение. Согласно статистике, существует 95% вероятность того, что цена будет находиться в диапазоне от μ − 2σ до μ + 2σ, что также называется 95% доверительным интервалом (CI). Полосы Боллинджера — это технический индикатор, основанный на вышеописанных теориях.

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

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

Требуемая среда редактирования и модуль

В этой статье Windows 11 используется в качестве системы, а записная книжка Jupyter — в качестве редактора.

Импорт данных и модулей

Период тестирования на истории — с 01.04.2021 по 31.12.2022, в качестве примера возьмем AUO(2409).import pandas as pd
import numpy as np
import tejapi
import os
import matplotlib.pyplot as plt

os.environ[‘TEJAPI_BASE’] = ‘https://api.tej.com.tw’
os.environ[‘TEJAPI_KEY’] = ‘Your Key’
os.environ[‘mdate’] = ‘20210401 20221231’
os.environ[‘ticker’] = ‘2409’

# 使用 ingest 將股價資料導入暫存,並且命名該股票組合 (bundle) 為 tquant
!zipline ingest -b tquant

from zipline.api import set_slippage, set_commission, set_benchmark, attach_pipeline, order, order_target, symbol, pipeline_output, record
from zipline.finance import commission, slippage
from zipline.data import bundles
from zipline import run_algorithm
from zipline.pipeline import Pipeline
from zipline.pipeline.filters import StaticAssets
from zipline.pipeline.factors import BollingerBands
from zipline.pipeline.data import EquityPricing

Создание функции конвейера

Pipeline() позволяет пользователям быстро обрабатывать данные, связанные с торговлей несколькими активами. В сегодняшней статье мы используем его для обработки:

  • Верхняя граница полос Боллинджера в течение 20 дней.
  • Средняя граница для полос Боллинджера в течение 20 дней.
  • Нижняя граница полос Боллинджера в течение 20 дней.
  • Текущая цена закрытия.

def make_pipeline():

perf = BollingerBands(inputs=[EquityPricing.close], window_length=20, k=2)
upper,middle,lower = perf.upper,perf.middle, perf.lower
curr_price = EquityPricing.close.latest

return Pipeline(
columns = {
‘upper’: upper,
‘middle’: middle,
‘lower’: lower,
‘curr_price’:curr_price
}
)

Создание функции инициализации

Initialize() позволяет пользователям настроить торговую среду в начале периода тестирования на истории. В этой статье мы настроим:

  • Проскальзывание
  • Комиссия
  • Установите доходность покупки и удержания AUO в качестве ориентира.
  • Прикрепите функцию Pipline() к тестированию на истории.
  • Установите context.last_signal_price для записи последней цены покупки.

def initialize(context):
context.last_buy_price = 0
set_slippage(slippage.VolumeShareSlippage())
set_commission(commission.PerShare(cost=0.00285))
set_benchmark(symbol(‘2409’))
attach_pipeline(make_pipeline(), ‘mystrategy’)
context.last_signal_price = 0

Создание функции Handle_data

handle_data() используется для ежедневной обработки данных и совершения заказов.def handle_data(context, data):
out_dir = pipeline_output(‘mystrategy’) # 取得每天 pipeline 的布林通道上中下軌
for i in out_dir.index:
upper = out_dir.loc[i, ‘upper’]
middle = out_dir.loc[i, ‘middle’]
lower = out_dir.loc[i, ‘lower’]
curr_price = out_dir.loc[i, ‘curr_price’]
cash_position = context.portfolio.cash
stock_position = context.portfolio.positions[i].amount

buy, sell = False, False
record(price = curr_price, upper = upper, lower = lower, buy = buy, sell = sell)

if stock_position == 0:
if (curr_price <= lower) and (cash_position >= curr_price * 1000):
order(i, 1000)
context.last_signal_price = curr_price
buy = True
record(buy = buy)
elif stock_position > 0:
if (curr_price <= lower) and (curr_price <= context.last_signal_price) and (cash_position >= curr_price * 1000):
order(i, 1000)
context.last_signal_price = curr_price
buy = True
record(buy = buy)
elif (curr_price >= upper):
order_target(i, 0)
context.last_signal_price = 0
sell = True
record(sell = sell)
else:
pass
else:
pass

Создание функции анализа

Здесь мы применяем matplotlib.pyplot для визуализации торговых сигналов и стоимости портфеля.def analyze(context, perf):
fig = plt.figure()
ax1 = fig.add_subplot(211)
perf.portfolio_value.plot(ax=ax1)
ax1.set_ylabel(«Portfolio value (NTD)»)
ax2 = fig.add_subplot(212)
ax2.set_ylabel(«Price (NTD)»)
perf.price.plot(ax=ax2)
perf.upper.plot(ax=ax2)
perf.lower.plot(ax=ax2)
ax2.plot( # 繪製買入訊號
perf.index[perf.buy],
perf.loc[perf.buy, ‘price’],
‘^’,
markersize=5,
color=’red’
)
ax2.plot( # 繪製賣出訊號
perf.index[perf.sell],
perf.loc[perf.sell, ‘price’],
‘v’,
markersize=5,
color=’green’
)
plt.legend(loc=0)
plt.gcf().set_size_inches(18,8)
plt.show()

Алгоритмы запуска

С помощью run_algorithm(), мы можем выполнить только что построенную стратегию. Период тестирования на истории установлен с 01.06.2021 по 31.12.2022. Мы используем пакет данных tquant. Мы предполагаем, что начальная база капитала составляет 500 000. Вывод run_algorithm(), который является результатомs, содержит информацию о ежедневных показателях и торговых поступлениях.

Наблюдая за следующим графиком, можно обнаружить, что между 2021/11 и 2021/12 годами наблюдается восходящий тренд. Так как цены закрытия не смогли достичь нижней границы, сделки на покупку в этот период времени не было. И из-за этого мы не смогли получить прибыль.

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

На самом деле, из-за задержки 20-дневной полосы Боллинджера полоса испытывает трудности с отражением краткосрочного движения цены с высокой волатильностью. Если ваш целевой актив более волатилен, мы предложили сократить продолжительность полосы Боллинджера или добавить индикаторы, связанные с трендом, для точной настройки вашей стратегии.results = run_algorithm(
start = pd.Timestamp(‘2021-06-01′, tz=’UTC’),
end = pd.Timestamp(‘2022-12-31′, tz =’UTC’),
initialize=initialize,
bundle=’tquant’,
analyze=analyze,
capital_base=5e5,
handle_data = handle_data
)

results

Стоимость портфеля, время торговли и торговые записи

Анализ производительности

Затем мы использовали модуль Pyfolio , входящий в комплект поставки TQuant Lab, для анализа эффективности стратегии и рисков. Во-первых, мы используем extract_rets_pos_txn_from_zipline() для расчета доходности, позиций и торговых записей.import pyfolio as pf
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results)

Ежедневная доходность

Расчет ежедневной доходности портфеля.

Ежедневная доходность портфеля

Удержание позиций

  • Эквити(0 [2406]): AUO
  • Наличные
Рекорд удержания позиции

Запись транзакции

  • sid: индекс
  • symbol: тикерный символ
  • Цена: цена покупки/продажи
  • order_id: номер заказа
  • amount: сумма сделки
  • COMMISSION: Стоимость комиссии
  • DT: Дата торгов
  • txn_dollar: объем торгов долларом
Торговый рекорд

Построение графика накопленной доходности и эталонной доходности

benchmark_rets = results[‘benchmark_return’]
pf.plotting.plot_rolling_returns(returns, factor_returns=benchmark_rets)

Цифра доходности стратегии

Составление таблицы производительности

С помощью show_perf_stats() можно легко продемонстрировать таблицу анализа производительности и рисков.pf.plotting.show_perf_stats(
returns,
benchmark_rets,
positions=positions,
transactions=transactions)

Таблица производительности

Заключение

С конца 2021 по 2022 год цена акций AUO явно находится в нисходящей спирали. Если выбрать стратегию «купи и держи», то накопленная доходность окажется ужасно -40% на -50%. Напротив, стратегия полос Боллинджера намного лучше, чем стратегия «купи и держи».

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

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

Исходный код

https://medium.com/media/0f3cb18edbc366260e4fb8145bed4d25

Расширенное чтение

Ссылки по теме

Объединение полос Боллинджера и стохастического осциллятора

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

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

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

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

Прежде чем перейти к изучению полос Боллинджера, важно знать, что такое простая скользящая средняя (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 Линия: Эта линия является наиболее важной и стержневой составляющей индикатора осциллятора стохастик. Он также известен как индикатор 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. Нижняя панель состоит из компонентов осциллятора Stochastic. Будучи опережающим индикатором, стохастический осциллятор не может быть построен рядом с ценой закрытия, так как значения индикатора и цены закрытия сильно различаются. Таким образом, он строится отдельно от цены закрытия (ниже цены закрытия в нашем случае).

Компоненты %K line и %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 ==> SELLwhere,
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 для целей построения графиков и Requests для выполнения вызовов API. Вторичными пакетами будут Math для математических функций и Termcolor для настройки шрифта (необязательно).

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

# IMPORTING PACKAGESimport pandas as pd
import requests
import numpy as np
import matplotlib.pyplot as plt
from math import floor
from termcolor import colored as clplt.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. Наконец, мы вызываем созданную функцию, чтобы извлечь исторические данные 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’, которая принимает цены акций (‘data’) и количество периодов (‘lookback’) в качестве параметров. Внутри функции мы используем функцию «rolling», предоставляемую пакетом 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, и сохраняем значения в переменных ‘lowest_low’ и ‘highest_high’.

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

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

На этом этапе мы собираемся реализовать обсуждаемую торговую стратегию полос Боллинджера и стохастического осциллятора на 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-loop. Внутри цикла for-loop мы проходим определенные условия, и если условия выполнены, соответствующие значения будут добавлены к пустым спискам. Если условие для покупки акции будет выполнено, цена покупки будет добавлена в список «buy_price», а значение сигнала будет добавлено как 1, представляющее покупку акции. Аналогичным образом, если условие о продаже акций будет выполнено, цена продажи будет добавлена к списку «sell_price», а значение сигнала будет добавлено как -1, представляющее продажу акции. Наконец, мы возвращаем списки, добавленные со значениями. Затем мы вызываем созданную функцию и сохраняем значения в соответствующих переменных.

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

На этом шаге мы создадим список, который указывает 1, если мы держим акцию, или 0, если мы не владеем или не держим акции.

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

# POSITIONposition = []
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-loops, один из которых заключается в генерации значений для списка ‘position’, чтобы они соответствовали длине ‘signal’ списка. Другой цикл for-loop — это тот, который мы используем для генерации фактических значений позиции.

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

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

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

Прежде чем двигаться дальше, важно знать, что такое бэктестинг. Бэктестинг — это процесс наблюдения за тем, насколько хорошо наша торговая стратегия работает на данных по акциям. В нашем случае мы собираемся реализовать процесс бэктестирования для нашей торговой стратегии Bollinger Bands Stochastic Oscillator на основе данных по акциям 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-loop для итерации значений переменной ‘aapl_ret’ для расчета доходности, которую мы получили от нашей торговой стратегии, и эти значения доходности добавляются в список ‘bb_stoch_strategy_ret’. Затем мы преобразуем список ‘bb_stoch_strategy_ret’ в кадр данных и сохраняем его в переменную ‘bb_stoch_strategy_ret_df’.

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

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

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

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

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

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

# SPY ETF COMPARISON

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

investment_value = investment_value
number_of_stocks = floor(investment_value/spy[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, не реализуя никаких торговых стратегий. Из выходных данных мы видим, что наша торговая стратегия Stoochastic Oscillator Bollinger Bands превзошла SPY ETF на 1429%. Замечательно!

Заключение

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

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

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

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

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

Спонсируемый: EOD Historical Data является одним из лидеров на рынке финансовых API, предоставляя широкий спектр API, начиная от базовых, таких как рыночные данные на конец дня, до настраиваемых API, таких как API финансовых новостей и API проверки акций. Все их API спроектированы таким образом, что их легко использовать в природе, чтобы новички могли работать с ними без каких-либо препятствий. Я лично использовал API EOD Historical Data, и, по моему опыту, их API подходят как для любителей, так и для профессионалов и используются для побочных проектов и для создания приложений корпоративного уровня.

Полный код:

# 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']))

Торговые тренды с полосами Боллинджера

Определение тренда цены акций очень важно при разработке технической торговой системы. Если мы можем определить, когда тренд разворачивается, то есть достигает вершины или дна, мы можем определить хорошие точки покупки или продажи для наших ценных бумаг. В большинстве случаев технического анализа одного индикатора обычно недостаточно для разработки торговой системы, а фактические прогнозы движения цены зависят от одновременного «срабатывания» нескольких разных индикаторов. Полосы Боллинджера — это интересный инструмент анализа тренда, который формирует полосы вокруг цены акции, чтобы определить, когда цена акции вот-вот изменит направление. В этом посте я опишу торговую стратегию, основанную только на полосах Боллинджера, реализую стратегию на Python, протестирую ее на акциях в S&P 500 и проанализирую результаты.

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

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

Полосы Боллинджера — это скользящие средние линии, построенные выше и ниже заданной скользящей средней. В отличие от других трендовых индикаторов, полосы не строятся на основе заданного процента выше или ниже скользящей средней линии. Вместо этого полосы Боллинджера строятся как стандартные отклонения над скользящей средней линией. Как правило, «средняя полоса» определяется путем взятия скользящей средней n-периода цен закрытия акции. Затем «верхняя полоса» может быть построена путем добавления двух стандартных отклонений к средней полосе, и, аналогичным образом, «нижняя полоса» строится путем вычитания двух стандартных отклонений из средней полосы. Торговые индикаторы (в основном развороты) формируются, когда цена закрытия акции движется относительно этих линий, например, падает ниже нижней полосы или движется выше верхней полосы.

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

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

  1. Коротких продаж нет, т.е. мы можем продавать только те акции, которыми владеем
  2. Предполагается, что мы принимаем решение в режиме реального времени в день наступления события. То есть, если цена акций запускает одно из наших правил, сделка будет выполнена, мы не будем спекулировать на более высоких максимумах или более низких минимумах.
  3. Многие акции могут быть куплены до того, как их нужно будет продать (алгоритм «покупает» одну акцию, но умножает результаты на любое число, чтобы изменить размер лота).
  4. При продаже мы продаем все ранее купленные акции.

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

Эта торговая стратегия будет реализована на Python и выполняться на акциях S&P 500 в течение 10-летних, 5-летних и 1-летних периодов времени. К сожалению, период в 1 год может вводить в заблуждение из-за «ковидного краха» 2020 года, но, тем не менее, мы будем его использовать.

Реализация

Реализовать эту стратегию в Python довольно просто, при условии библиотеки Pandas и моего инструмента поиска цен на акции Yahoo Finance, о котором я писал в предыдущем посте (этот инструмент можно установить через pip). Во-первых, нам нужно включить все зависимости для выборки, обработки и анализа (построения) данных:

from yfapi import YahooFinanceAPI, Interval # fetching data
import datetime
import matplotlib.pyplot as plt # plotting results
import pandas as pd # SMA, STD, data processing

view rawdeps.py hosted with ❤ by GitHub

Реализованы две простые функции для расчета скользящих средних и определения полос Боллинджера на основе стандартного отклонения от скользящей средней. В этом посте 20-дневная скользящая средняя используется повсеместно.

def get_sma(data, period):
return data.rolling(window=period).mean() # moving average using panda’s rolling
def get_bollinger_bands(data, sma, periods):
std = data.rolling(window=periods).std()
upper = sma + std * 2 # add 2 standard deviations (upper band)
lower = sma — std * 2 # substract 2 standard deviations (lower band)
return upper, lower

view rawsma_bands.py hosted with ❤ by GitHub

Построение полос Боллинджера с ценой закрытия Apple (тикер: AAPL) за период времени 1 год дает график ниже (я предоставлю код для этого позже).

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

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

Потенциальные сделки по простой стратегии, реализованной здесь. Красные круги — это потенциальные сигналы на продажу, а зеленые круги — потенциальные сигналы на покупку.

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

def get_buy_sell_points(data):
# iterate the dataset, check for cross above upper and below lower bb
trades = {
«buy_indices»: [],
«sell_indices»: [],
«sell_prices»: [],
«buy_prices»: []
}
for idx, row in data.iterrows():
if idx == 0:
continue
# above upper band and previously below upper band
if row[«upper»] < row[«Close»] and row[«upper»] > data.loc[idx-1][«Close»]:
# no short selling, just sell the shares previously purchased
if len(trades[«buy_indices»]) == 0:
continue # we don’t own any shares here
elif len(trades[«sell_indices»]) > 0 and trades[«buy_indices»][len(trades[«buy_indices»]) — 1] < trades[«sell_indices»][len(trades[«sell_indices»]) — 1]:
# have we bought since the last sell?
continue
trades[«sell_indices»].append(idx) # indicate a sell at this point
trades[«sell_prices»].append(row[«Close»]) # record the price
# below lower band
if row[«lower»] > row[«Close»] and data.loc[idx-1][«lower»] < data.loc[idx-1][«Close»]:
trades[«buy_indices»].append(idx) # buy at this point
trades[«buy_prices»].append(row[«Close»]) # record the price
return trades

view rawget_buy_sell.py hosted with ❤ by GitHub

Эта функция создает словарь, который записывает индекс (кадра данных pandas), в котором мы будем покупать и продавать акции вместе с ценой акции в эти моменты времени. Построение этих решений о покупке/продаже на одном и том же 1-летнем временном горизонте для Apple дает следующие сделки.

Покупки (зеленые треугольники) и продажи (красные треугольники) нанесены на график цены закрытия Apple с полосами Боллинджера. На первый взгляд, это выглядит прибыльной стратегией за этот период времени для этой акции.

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

def calculate_profits(trades, ticker, base_dir):
# iterate buy/sell lists
sell_idx = 0
buy_idx = 0
avg_price = 0
total_profit = 0
ret = 0
buy_count = 0
trade_count = 0
with open(base_dir + «trades/{}.trades».format(ticker.upper()), ‘w’) as f:
while buy_idx < len(trades[«buy_indices»]):
# get average price of all buys before a sell
avg_price += trades[«buy_prices»][buy_idx]
buy_count += 1 # in case we buy more than once before a sell
# is the next sell before the next buy? what’s our next trade
if (buy_idx + 1) < (len(trades[«buy_indices»])) and \
sell_idx < (len(trades[«sell_indices»])) and \
trades[«sell_indices»][sell_idx] < trades[«buy_indices»][buy_idx + 1]:
# sell all shares at sell point
price = avg_price / buy_count
profit = (trades[«sell_prices»][sell_idx] — price) * buy_count
ret += profit / price # % return
total_profit += profit
trade_count += 1
f.write(«Selling {} shares at {} with an avg price of {} for a return of {} and profit of {}. (indicies buy/sell: {}/{})».format(
buy_count, trades[«sell_prices»][sell_idx], price, profit/price, profit, trades[«buy_indices»][buy_idx], trades[«sell_indices»][sell_idx]))
f.write(«\n»)
# reset variables
buy_count = 0
avg_price = 0
sell_idx += 1
buy_idx += 1
if sell_idx < len(trades[«sell_indices»]) and buy_count > 0:
# sell any remaining shares
price = avg_price / buy_count
profit = (trades[«sell_prices»][sell_idx] — price) * buy_count
ret += profit / price # % return
total_profit += profit
trade_count += 1
f.write(«Selling {} shares at {} with an avg price of {} for a return of {} and profit of {}. (indicies buy/sell: {}/{})».format(
buy_count, trades[«sell_prices»][sell_idx], price, profit/price, profit, trades[«buy_indices»][buy_idx-1], trades[«sell_indices»][sell_idx]))
f.write(«\n»)
if trade_count > 0:
ret /= trade_count
return ret, total_profit

view rawcalc_profits.py hosted with ❤ by GitHub

Применение этой функции к ранее сгенерированным сделкам показывает среднюю доходность на сделку 6,3% и общую прибыль за торговлю одной акцией в размере 21,76 доллара США. В этом конкретном случае вам было бы лучше (и, вероятно, вы меньше рисковали), просто купив одну акцию Apple и удерживая ее в течение всего периода времени. Наличие идеальной информации о цене за период времени позволяет нам это увидеть. Однако что, если бы Apple не испытала огромный рост ближе к концу этого периода времени? То есть, что, если бы график выглядел примерно так:

Маловероятно (хотя и идеально), что это произошло бы сразу после нашей последней торговой точки, которая максимизировала бы нашу прибыль, но это могло произойти почти в любое время после этой точки, и история была бы такой же: покупка акций принесла бы худшую прибыль, чем торговля пересечениями полос Боллинджера. Суть в том, что мы не знаем, что произойдет с ценой акций в будущем, поэтому неразумно сбрасывать со счетов торговую стратегию только потому, что что-то произошло, поскольку этого не должно было произойти. (При этом я не продвигаю торговлю только на основе технического анализа [или вообще], я лично являюсь долгосрочным инвестором в покупку и удержание.)

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

Тестирование на истории

Для тестирования стратегии используется список тикеров в S&P 500, чтобы применить эту технику к каждой акции в индексе (имя файла в коде: sp500tickers.csv). Это делается для 1-, 5- и 10-летних периодов, как указано выше. Результаты (прибыль/убытки) обсуждаются в разделе «Результаты» ниже. Код для запуска этого для всех тикеров также показан ниже. Графики, которые были созданы выше, и сделки, которые будут исполнены, генерируются для каждого тикера. Их также можно найти в репозитории GitHub для этого проекта вместе с исходным кодом.

if __name__ == ‘__main__’:
# the subdirectory for my project you may need to change this
base_dir = «./BollingerBot/»
img_dir = base_dir + «imgs/»
tickers = pd.read_csv(base_dir + «sp500tickers.csv»)[«symbol»].tolist()
end_dt = datetime.datetime.today()
start_dt = datetime.datetime(end_dt.year — 1, end_dt.month, end_dt.day)
api = YahooFinanceAPI(Interval.DAILY)
progress_count = 1
results_dict = {
«ticker»: [],
«profit»: [],
«avg_return»: [],
«start_price»: []
}
for ticker in tickers:
print(«Processing ticker symbol {} ({} out of {}).».format(ticker.upper(), progress_count, len(tickers)))
try:
# fetch the data
data = api.get_ticker_data(ticker, start_dt, end_dt)
except:
# don’t care if we can’t
progress_count += 1
continue
data[‘ma’] = get_sma(data[«Close»], 20) # get 20-period SMA
data[‘upper’], data[‘lower’] = get_bollinger_bands(data[‘Close’], data[‘ma’], 20)
# plot the bands + close price
data[‘Close’][20:].plot(label=’close’, color=’darkcyan’)
data[‘ma’].plot(label=’mid’, linestyle=’—‘, linewidth=’0.9′, color=’darkturquoise’)
data[‘upper’].plot(label=’upper’, linestyle=’—‘, linewidth=’1.1′, color=’indianred’)
data[‘lower’].plot(label=’lower’, linestyle=’—‘, linewidth=’1.1′, color=’lightgreen’)
# determine trades, calculate profit, and store results
trades = get_buy_sell_points(data)
avg_return, profit = calculate_profits(trades, ticker, base_dir)
results_dict[«ticker»].append(ticker.upper())
results_dict[«profit»].append(profit)
results_dict[«avg_return»].append(avg_return)
results_dict[«start_price»].append(data.loc[0][«Close»])
print(«Average return per trade: {}\t\tTotal profit trading one share: {}».format(avg_return, profit))
# plot the trades and make the graph prettier
plt.scatter(trades[«buy_indices»], trades[«buy_prices»], marker=»^», color=»darkgreen», s=100, label=»buy»)
plt.scatter(trades[«sell_indices»], trades[«sell_prices»], marker=»v», color=»darkred», s=100, label=»sell»)
plt.title(«Bollinger Bands w/ Trades for {}».format(ticker.upper()))
plt.legend(loc=’upper left’)
plt.savefig(img_dir + «{}_plot.png».format(ticker.upper()))
#plt.show()
plt.clf()
progress_count += 1
# export the results to CSV
results_df = pd.DataFrame.from_dict(results_dict)
results_df.to_csv(base_dir + «results.dat», index=False)

view rawrun_all.py hosted with ❤ by GitHub

Результаты

Используя файлы результатов.dat сгенерированные каждым из трех запусков за три периода времени, можно проанализировать результаты тестирования на истории. Для начала было бы интересно узнать, какой процент тикеров S&P 500 был прибыльным при торговле по этой стратегии. Чтобы определить это, количество прибыльных записей в наборе данных (столбец прибыли > 0) делится на общее количество строк:

def percent_profitable(data):
return len(data.loc[data.profit > 0]) / len(data)

view rawpct_profit.py hosted with ❤ by GitHub

Выполнение этого для всех трех временных периодов показывает, что стратегия прибыльна в 98,98% случаев за один год, 84,71% времени за 5 лет и 90,54% времени за 10 лет. Эти результаты отфильтровывают тикеры с 0 прибылью, поскольку они, вероятно, были тикерами там, где не было сделок или где данные не были доступны из Yahoo Finance.

Приятно видеть, что большая часть индекса была заполнена победителями при торговле таким образом, но также интересно посмотреть, какие акции оказались в проигрыше. Это можно сделать через:

def show_losers(data):
return data.loc[data.profit < 0]

view rawlosers.py hosted with ❤ by GitHub

Проигравшие за один год:

TickerProfit ($)Avg. Return (%)
BKR-1.90-2.99
HOLX-6.81-9.83
INTC-1.19-2.47
REGN-9.22-1.79
VIAC-1.45-0.75
WBA-1.081.30

view raw1_year_losers.csv hosted with ❤ by GitHub

Неудачники за пять лет:

TickerProfit ($)Avg. Return (%)
ALK-112.55-12.37
ADS-78.48-16.29
MO-7.58-0.33
AIG-24.18-4.46
APA-80.60-19.56
BKR-173.61-38.02
BWA-5.37-0.57
CCL-56.30-10.79
CE-36.47-3.34
C-33.34-2.71
CFG-15.76-2.81
CMA-129.72-16.40
COTY-42.05-26.77
XRAY-9.31-0.22
DVN-78.62-15.25
DFS-63.44-10.23
DISH-20.16-1.91
DOW-17.92-6.28
DD-53.10-11.74
DXC-40.44-9.27
EOG-31.52-4.77
XOM-13.82-4.11
FLS-84.78-13.26
F-6.17-9.08
GE-111.58-0.20
HAL-111.03-36.68
HBI-10.37-8.37
HIG-90.11-16.41
HPE-3.72-2.02
HFC-84.15-20.35
HWM-10.39-3.36
HBAN-0.530.05
IVZ-64.83-22.88
KEY-7.02-2.84
KIM-57.48-24.78
KSS-15.37-4.78
KHC-80.22-11.24
LB-26.37-11.04
LEG-36.90-6.03
LKQ-61.86-17.58
LYB-15.89-1.10
MRO-23.77-16.73
MPC-8.66-1.98
MHK-196.17-10.62
TAP-4.820.95
MOS-5.17-1.08
NOV-8.384.83
NWL-84.93-12.71
JWN-33.45-7.27
NCLH-39.92-1.20
OXY-58.13-11.12
OKE-10.632.95
PM-6.220.21
PSX-188.37-14.70
PXD-23.47-1.77
PPL-18.97-2.51
PHM-2.551.37
PVH-163.30-9.25
SLB-72.93-9.12
SPG-403.74-29.23
SLG-26.33-1.23
STT-18.27-2.06
SIVB-267.05-9.67
SYF-64.93-21.29
SYY-14.29-0.77
TPR-9.94-1.17
FTI-0.601.98
TFC-30.15-2.84
UAL-216.43-24.25
UNM-10.230.36
VTR-57.24-7.77
VIAC-48.91-10.69
WFC-36.40-5.17
WDC-87.60-10.98
WU-4.57-0.50
XRX-41.70-5.98

view raw5_year_losers.csv hosted with ❤ by GitHub

Неудачники за 10 лет:

TickerProfit ($)Avg. Return (%)
ALK-68.46-0.60
ADS-20.73-6.42
APA-109.95-10.40
BKR-263.77-23.27
BWA-53.00-5.24
COG-0.411.62
CNP-26.03-5.49
CFG-9.58-0.03
CMA-90.20-3.64
COP-67.47-2.60
COTY-0.08-5.26
DVN-168.21-13.50
DISH-2.730.74
DOW-17.92-6.28
XOM-20.77-1.77
FLS-62.14-3.64
FCX-111.73-17.75
HAL-129.50-13.90
HIG-54.01-2.05
HFC-56.89-4.79
IVZ-29.88-5.96
KIM-66.86-10.47
KMI-136.97-18.65
KSS-84.79-8.56
KHC-53.89-7.37
LKQ-15.150.09
MRO-8.62-6.19
NOV-53.88-0.19
NWL-26.122.53
NWSA-3.73-2.00
JWN-101.37-7.54
NCLH-15.882.76
OKE-21.401.89
PRGO-154.71-0.20
PSX-87.98-1.66
PXD-40.361.24
SLB-54.95-2.40
SPG-268.03-8.89
SIVB-187.510.51
SYF-49.20-10.62
TPR-9.530.36
TWTR-68.14-8.41
UAL-128.53-1.92
WDC-92.97-4.64
WRK-44.26-6.29
WMB-25.383.44
XRX-12.032.24

view raw10_year_losers.csv hosted with ❤ by GitHub

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

def top_performers_per_trade(data):
return data.sort_values(by=[«avg_return»], ascending=False)[:5] # top 5

view rawtop_per_trade.py hosted with ❤ by GitHub

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

def best_returns_wrt_start_price(data):
data[«pct_return_start»] = data[«profit»]/data[«start_price»]
return data.sort_values(by=[«pct_return_start»], ascending=False)[:5] # top 5

view rawbest_returns.py hosted with ❤ by GitHub

Глядя на цены акций этих топ-5 тикеров этих периодов, казалось, что это акции, которые имели наибольший рост в цене. Это имеет смысл, но почти бесполезно при определении того, какие акции использовать для этой стратегии (что было моей целью при просмотре этого анализа), поскольку мы не узнаем, какие акции будут испытывать лучший рост цен в ближайшие 1-10 лет. Поэтому я исключу результаты из этого поста.

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

Заключение

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

Полный код

bollingerbot.py

from yfapi import YahooFinanceAPI, Interval
import datetime
import matplotlib.pyplot as plt
import pandas as pd
def get_sma(data, period):
return data.rolling(window=period).mean()
def get_bollinger_bands(data, sma, periods):
std = data.rolling(window=periods).std()
upper = sma + std * 2
lower = sma — std * 2
return upper, lower
def get_buy_sell_points(data):
# iterate the dataset, check for cross above upper and below lower bb
trades = {
«buy_indices»: [],
«sell_indices»: [],
«sell_prices»: [],
«buy_prices»: []
}
for idx, row in data.iterrows():
if idx == 0:
continue
# above upper band
if row[«upper»] < row[«Close»] and row[«upper»] > data.loc[idx-1][«Close»]:
# no short selling, just sell the shares previously purchased
if len(trades[«buy_indices»]) == 0:
continue
elif len(trades[«sell_indices»]) > 0 and trades[«buy_indices»][len(trades[«buy_indices»]) — 1] < trades[«sell_indices»][len(trades[«sell_indices»]) — 1]:
# have we bought since the last sell?
continue
trades[«sell_indices»].append(idx)
trades[«sell_prices»].append(row[«Close»])
# print(idx, «top», row[«Close»], row[«upper»], row[«Date»])
# below lower band
if row[«lower»] > row[«Close»] and data.loc[idx-1][«lower»] < data.loc[idx-1][«Close»]:
trades[«buy_indices»].append(idx)
trades[«buy_prices»].append(row[«Close»])
# print(idx, «bottom», row[«Close»], row[«lower»], data.loc[idx-1][«Close»], row[«Date»])
return trades
def calculate_profits(trades, ticker, base_dir):
# iterate buy/sell lists
sell_idx = 0
buy_idx = 0
avg_price = 0
total_profit = 0
ret = 0
buy_count = 0
trade_count = 0
with open(base_dir + «trades/{}.trades».format(ticker.upper()), ‘w’) as f:
while buy_idx < len(trades[«buy_indices»]):
# get average price of all buys before a sell
avg_price += trades[«buy_prices»][buy_idx]
buy_count += 1 # in case we buy more than once before a sell
# is the next sell before the next buy? what’s our next trade
if (buy_idx + 1) < (len(trades[«buy_indices»])) and \
sell_idx < (len(trades[«sell_indices»])) and \
trades[«sell_indices»][sell_idx] < trades[«buy_indices»][buy_idx + 1]:
# sell all shares at sell point
price = avg_price / buy_count
profit = (trades[«sell_prices»][sell_idx] — price) * buy_count
ret += profit / price # % return
total_profit += profit
trade_count += 1
f.write(«Selling {} shares at {} with an avg price of {} for a return of {} and profit of {}. (indicies buy/sell: {}/{})».format(
buy_count, trades[«sell_prices»][sell_idx], price, profit/price, profit, trades[«buy_indices»][buy_idx], trades[«sell_indices»][sell_idx]))
f.write(«\n»)
# reset variables
buy_count = 0
avg_price = 0
sell_idx += 1
buy_idx += 1
if sell_idx < len(trades[«sell_indices»]) and buy_count > 0:
# sell any remaining shares
price = avg_price / buy_count
profit = (trades[«sell_prices»][sell_idx] — price) * buy_count
ret += profit / price # % return
total_profit += profit
trade_count += 1
f.write(«Selling {} shares at {} with an avg price of {} for a return of {} and profit of {}. (indicies buy/sell: {}/{})».format(
buy_count, trades[«sell_prices»][sell_idx], price, profit/price, profit, trades[«buy_indices»][buy_idx-1], trades[«sell_indices»][sell_idx]))
f.write(«\n»)
if trade_count > 0:
ret /= trade_count
return ret, total_profit
if __name__ == ‘__main__’:
base_dir = «./BollingerBot/»
img_dir = base_dir + «imgs/»
tickers = pd.read_csv(base_dir + «sp500tickers.csv»)[«symbol»].tolist()
# tickers=[«ual»]
end_dt = datetime.datetime.today()
start_dt = datetime.datetime(end_dt.year — 1, end_dt.month, end_dt.day)
api = YahooFinanceAPI(Interval.DAILY)
progress_count = 1
results_dict = {
«ticker»: [],
«profit»: [],
«avg_return»: [],
«start_price»: []
}
for ticker in tickers:
print(«Processing ticker symbol {} ({} out of {}).».format(ticker.upper(), progress_count, len(tickers)))
try:
data = api.get_ticker_data(ticker, start_dt, end_dt)
except:
progress_count += 1
continue
data[‘ma’] = get_sma(data[«Close»], 20) # get 20-period SMA
data[‘upper’], data[‘lower’] = get_bollinger_bands(data[‘Close’], data[‘ma’], 20)
data[‘Close’][20:].plot(label=’close’, color=’darkcyan’)
data[‘ma’].plot(label=’mid’, linestyle=’—‘, linewidth=’0.9′, color=’darkturquoise’)
data[‘upper’].plot(label=’upper’, linestyle=’—‘, linewidth=’1.1′, color=’indianred’)
data[‘lower’].plot(label=’lower’, linestyle=’—‘, linewidth=’1.1′, color=’lightgreen’)
trades = get_buy_sell_points(data)
avg_return, profit = calculate_profits(trades, ticker, base_dir)
results_dict[«ticker»].append(ticker.upper())
results_dict[«profit»].append(profit)
results_dict[«avg_return»].append(avg_return)
results_dict[«start_price»].append(data.loc[0][«Close»])
print(«Average return per trade: {}\t\tTotal profit trading one share: {}».format(avg_return, profit))
plt.scatter(trades[«buy_indices»], trades[«buy_prices»], marker=»^», color=»darkgreen», s=100, label=»buy»)
plt.scatter(trades[«sell_indices»], trades[«sell_prices»], marker=»v», color=»darkred», s=100, label=»sell»)
plt.title(«Bollinger Bands w/ Trades for {}».format(ticker.upper()))
plt.legend(loc=’upper left’)
plt.savefig(img_dir + «{}_plot.png».format(ticker.upper()))
# plt.show()
plt.clf()
progress_count += 1
results_df = pd.DataFrame.from_dict(results_dict)
results_df.to_csv(base_dir + «results.dat», index=False)

view rawbollingerbot.py hosted with ❤ by GitHub

analysis.py

import pandas as pd
def percent_profitable(data):
return len(data.loc[data.profit > 0]) / len(data)
def show_losers(data):
return data.loc[data.profit <= 0]
def top_performers_per_trade(data):
return data.sort_values(by=[«avg_return»], ascending=False)[:5]
def best_returns_wrt_start_price(data):
data[«pct_return_start»] = data[«profit»]/data[«start_price»]
return data.sort_values(by=[«pct_return_start»], ascending=False)[:5]
if __name__ == ‘__main__’:
d1 = pd.read_csv(‘BollingerBot/results_1yr.dat’)
d5 = pd.read_csv(‘BollingerBot/results_5yr.dat’)
d10 = pd.read_csv(‘BollingerBot/results_10yr.dat’)
d1 = d1[d1.profit != 0]
d5 = d5[d5.profit != 0]
d10 = d10[d10.profit != 0]
data = [d1, d5, d10]
for d in data:
print(«% Profitable:», percent_profitable(d))
print(«Unprofitable:», show_losers(d))
print(«Top per trade:», top_performers_per_trade(d))
print(«Top w.r.t start:», best_returns_wrt_start_price(d))

view rawanalysis.py hosted with ❤ by GitHub

Первоначально опубликовано вhttps://www.anthonymorast.com

Торговая стратегия пробоя тренда

Обзор

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

Логика стратегии

Основная логика стратегии состоит из следующих компонентов:

  1. Полосы Боллинджера строятся как 20-периодная EMA +/- 1,5 стандартных отклонения для определения верхней и нижней полос.
  2. Отслеживание, когда цена закрывается за пределами полос Боллинджера 2 периода назад, чтобы предвидеть потенциальные развороты.
  3. Сигналы на вход срабатывают, когда текущий бар пробивает максимум/минимум свечи 2-периодической давности, которая закрылась за противоположной полосой.
  4. Стоп-лосс устанавливается сразу за максимумом/минимумом текущего бара.
  5. Тейк-профит основан на заранее определенном соотношении риска и прибыли.

Преимущества

Ключевыми преимуществами стратегии являются:

  1. Полосы Боллинджера адаптируются к изменяющейся волатильности рынка. Более широкие полосы на волатильных рынках снижают вероятность ложных сигналов.
  2. Нацелен на раннюю ловлю разворотов тренда, когда цена пробивает обратно внутри полос.
  3. Гибкое управление рисками с настраиваемым соотношением риска и доходности.
  4. Надежное тестирование на истории приводит к трендовым рынкам с устойчивыми направленными движениями.
  5. Автоматический вход, выход и управление сделками, закодированными в торговой платформе.

Риски

Основные риски, которые следует учитывать:

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

Риски можно снизить с помощью дополнительных фильтров, надежно оценивая производительность и проводя тщательное тестирование перед развертыванием в реальном времени.

Усовершенствования

Вот некоторые способы, с помощью которых можно было бы усовершенствовать эту стратегию:

  1. Добавление фильтров, таких как объем, RSI или MACD, для улучшения синхронизации и точности сигналов.
  2. Оптимизация длин полос Боллинджера или мультипликаторов стандартного отклонения для конкретных инструментов.
  3. Использование различных соотношений риска и прибыли для каждого рынка на основе ожиданий при тестировании на истории.
  4. Включение адаптивных стопов, которые следят за ценой, когда она приносит прибыль.
  5. Построение в виде алгоритма с автоматическим обходом ордеров при входе.

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

Заключение

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

Исходный код стратегии

/*backtest
start: 2024-02-25 00:00:00
end: 2024-02-26 00:00:00
period: 4h
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/

// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/


//@version=5
strategy("Bollinger Band Strategy with Early Signal (v5)", overlay=true)

// Inputs
length = 20
mult = 1.5
src = close
riskRewardRatio = input(3.0, title="Risk-Reward Ratio")

// Calculating Bollinger Bands
basis = ta.ema(src, length)
dev = mult * ta.stdev(src, length)
upper = basis + dev
lower = basis - dev

// Plotting Bollinger Bands
plot(upper, "Upper Band", color=color.red)
plot(lower, "Lower Band", color=color.green)

// Tracking Two Candles Ago Crossing Bollinger Bands
var float twoCandlesAgoUpperCrossLow = na
var float twoCandlesAgoLowerCrossHigh = na

if (close[2] > upper[2])
twoCandlesAgoUpperCrossLow := low[2]
if (close[2] < lower[2])
twoCandlesAgoLowerCrossHigh := high[2]

// Entry Conditions
longCondition = (not na(twoCandlesAgoLowerCrossHigh)) and (high > twoCandlesAgoLowerCrossHigh)
shortCondition = (not na(twoCandlesAgoUpperCrossLow)) and (low < twoCandlesAgoUpperCrossLow)

// Plotting Entry Points
plotshape(longCondition, title="Buy Signal", location=location.belowbar, color=color.green, style=shape.labelup, text="BUY")
plotshape(shortCondition, title="Sell Signal", location=location.abovebar, color=color.red, style=shape.labeldown, text="SELL")

// Strategy Execution
if (longCondition)
stopLoss = low - (high - low) * 0.05
takeProfit = close + (close - stopLoss) * riskRewardRatio
strategy.entry("Buy", strategy.long)
strategy.exit("Exit Buy", "Buy", stop=stopLoss, limit=takeProfit)

if (shortCondition)
stopLoss = high + (high - low) * 0.05
takeProfit = close - (stopLoss - close) * riskRewardRatio
strategy.entry("Sell", strategy.short)
strategy.exit("Exit Sell", "Sell", stop=stopLoss, limit=takeProfit)

Параметры стратегии

Исходный адрес: FMZ — Торговая платформа FMZ Quant

Источник

Источник

Источник

Источник

Источник

Источник

Источник