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

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

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

Индекс истинной силы (TSI)

Индекс истинной силы (TSI) - это осциллятор импульса, который в основном используется трейдерами для определения того, является ли рынок восходящим или нисходящим импульсом, и торговать вместе с ним. Он также используется для определения текущего состояния рынка, перекупленности или перепроданности, но это не главная сильная сторона индикатора. Индекс истинной силы состоит из двух компонентов:

  • Линия TSI: первый компонент - это сама линия TSI, которая рассчитывается путем определения сначала фактического изменения цены (текущая цена закрытия минус предыдущая цена закрытия) и абсолютного изменения цены (абсолютные значения фактического изменения цены). Затем EMA с 25 в качестве количества периодов (длинных) берется как для фактического изменения цены, так и для абсолютного изменения цены. Затем эти две EMA снова сглаживаются 13-дневной (короткой) экспоненциальной скользящей средней. Этот процесс сглаживания серии данных с двумя EMA известен как двойное сглаживание, и его цель - устранить шум из данных. Теперь дважды сглаженное фактическое изменение цены делится на двойное сглаженное абсолютное изменение цены, а затем умножается на 100, чтобы получить показания линии TSI. Обратите внимание, что параметры (25, 13), которые мы приняли во внимание, являются типичными настройками, но могут быть настроены соответствующим образом. Расчет может быть нечетким, но его легко понять, если мы интерпретируем его в форме формулы или представления:
TSI LINE = [ DS. ACTUAL PC / DS. ABSOLUTE PC ] * 100
where,
DS. ACTUAL PC = Double smoothed actual price change with the length of 25 and 13
DS. ABSOLUTE PC = Double smoothed absolute price change with the length of 25 and 13
  • Сигнальная линия: следующий компонент - это компонент сигнальной линии, который представляет собой не что иное, как экспоненциальную скользящую среднюю TSI за указанное количество периодов (от 7 до 12 периодов). Большинство трейдеров предпочитают периоды около 7 для дневной торговли и около 12 для долгосрочного инвестирования. В этой статье мы используем 12 в качестве количества периодов, поскольку мы будем иметь дело с данными по запасам на дневном таймфрейме, а не с минутным таймфреймом. Расчет можно представить следующим образом:
SIGNAL LINE = EXP.MA 13 [ TSI LINE ]

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

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

Теперь давайте посмотрим, как можно использовать TSI, чтобы определить, находится ли рынок в состоянии перекупленности или перепроданности. Обычно индикаторы, такие как RSI, имеют стандартный порог уровней перекупленности и перепроданности, который составляет 70 и 30 соответственно, и эти пороги применимы к любому торгуемому активу. Принимая во внимание, что уровни перекупленности и перепроданности варьируются от одного актива к другому при использовании индекса истинной силы, и в нашем случае мы могли бы рассматривать -10 как уровень перепроданности и 10 как уровень перекупленности. Но все же он не так эффективен, как другие популярные осцилляторы импульса.

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

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

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

IF PREV.TLINE < PREV.SLINE AND CUR.TLINE > CUR.SLINE ==> BUY SIGNAL
IF PREV.TLINE > PREV.SLINE AND CUR.TLINE < CUR.SLINE ==> SELL SIGNAL

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

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

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

1. Importing Packages
2. Extracting Stock Data from Twelve Data
3. True Strength Index Calculation
4. Creating the Signal line crossover Trading Strategy
5. Plotting the Trading Lists
6. Creating our Position
7. Backtesting
8. SPY ETF Comparison

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

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

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

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

# IMPORTING PACKAGES

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

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

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

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

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

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

# EXTRACTING STOCK DATA

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

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

Вывод:

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

Шаг 3: Расчет истинного индекса силы

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

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

# TRUE STRENGTH INDEX CALCULATION 

def get_tsi(close, long, short, signal):
    diff = close - close.shift(1)
    abs_diff = abs(diff)
    
    diff_smoothed = diff.ewm(span = long, adjust = False).mean()
    diff_double_smoothed = diff_smoothed.ewm(span = short, adjust = False).mean()
    abs_diff_smoothed = abs_diff.ewm(span = long, adjust = False).mean()
    abs_diff_double_smoothed = abs_diff_smoothed.ewm(span = short, adjust = False).mean()
    
    tsi = (diff_double_smoothed / abs_diff_double_smoothed) * 100
    signal = tsi.ewm(span = signal, adjust = False).mean()
    tsi = tsi[tsi.index >= '2020-01-01'].dropna()
    signal = signal[signal.index >= '2020-01-01'].dropna()
    
    return tsi, signal

aapl['tsi'], aapl['signal_line'] = get_tsi(aapl['close'], 25, 13, 12)
aapl = aapl[aapl.index >= '2020-01-01']
aapl.tail()

Вывод:

Пояснение к коду: Во-первых, мы определяем функцию с именем get_tsi, которая принимает данные о цене закрытия акции («close»), период ретроспективного анализа для длинной EMA («long»), период ретроспективного анализа. для короткой EMA ('short') и периода ретроспективного обзора для сигнальной линии ('signal') в качестве параметров. Внутри функции мы сначала вычисляем и сохраняем фактическое изменение цены («diff») и абсолютное изменение цены («abs_diff») в соответствующих переменных.

Затем, используя функцию 'ewm', предоставляемую пакетом Pandas для определения экспоненциальной скользящей средней, мы дважды сглаживаем ранее рассчитанные изменения цен, чтобы получить двойное сглаженное фактическое изменение цены ('diff_double_smoothed') и двойное сглаженное абсолютное изменение цены ( 'abs_diff_double_smoothed').

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

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

На этом этапе мы собираемся реализовать обсуждаемую торговую стратегию пересечения сигнальных линий True Strength Index на Python.

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

# TRUE STRENGTH INDEX STRATEGY

def implement_tsi_strategy(prices, tsi, signal_line):
    buy_price = []
    sell_price = []
    tsi_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if tsi[i-1] < signal_line[i-1] and tsi[i] > signal_line[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                tsi_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                tsi_signal.append(0)
        elif tsi[i-1] > signal_line[i-1] and tsi[i] < signal_line[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                tsi_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                tsi_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            tsi_signal.append(0)
            
    return buy_price, sell_price, tsi_signal

buy_price, sell_price, tsi_signal = implement_tsi_strategy(aapl['close'], aapl['tsi'], aapl['signal_line'])

Пояснение к коду. Во-первых, мы определяем функцию с именем «implementation_tsi_strategy», которая принимает цены акций («цены») и компоненты индекса истинной силы («tsi», «signal_line») как параметры.

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

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

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

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

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

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

# TRUE STRENGTH INDEX TRADING SIGNALS PLOT

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2)
ax1.plot(aapl.index, buy_price, marker = '^', markersize = 12, color = 'green', linewidth = 0, label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', markersize = 12, color = 'r', linewidth = 0, label = 'SELL SIGNAL')
ax1.legend()
ax1.set_title('AAPL TSI TRADING SIGNALS')
ax2.plot(aapl['tsi'], linewidth = 2, color = 'orange', label = 'TSI LINE')
ax2.plot(aapl['signal_line'], linewidth = 2, color = '#FF006E', label = 'SIGNAL LINE')
ax2.set_title('AAPL TSI 25,13,12')
ax2.legend()
plt.show()

Вывод:

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

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

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

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

# STOCK POSITION

position = []
for i in range(len(tsi_signal)):
    if tsi_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(aapl['close'])):
    if tsi_signal[i] == 1:
        position[i] = 1
    elif tsi_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
close_price = aapl['close']
tsi = aapl['tsi']
signal_line = aapl['signal_line']
tsi_signal = pd.DataFrame(tsi_signal).rename(columns = {0:'tsi_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'tsi_position'}).set_index(aapl.index)

frames = [close_price, tsi, signal_line, tsi_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy

Вывод:

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

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

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

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

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

# BACKTESTING

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

for i in range(len(aapl_ret)):
    returns = aapl_ret['returns'][i]*strategy['tsi_position'][i]
    tsi_strategy_ret.append(returns)
    
tsi_strategy_ret_df = pd.DataFrame(tsi_strategy_ret).rename(columns = {0:'tsi_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
tsi_investment_ret = []

for i in range(len(tsi_strategy_ret_df['tsi_returns'])):
    returns = number_of_stocks*tsi_strategy_ret_df['tsi_returns'][i]
    tsi_investment_ret.append(returns)

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

Вывод:

Profit gained from the tsi strategy by investing $100k in AAPL : 71095.33
Profit percentage of the tsi strategy : 71%

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

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

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

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

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

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

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

# SPY ETF COMPARISON

def get_benchmark(start_date, investment_value):
    spy = get_historical_data('SPY', start_date)['close']
    benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})
    
    investment_value = investment_value
    number_of_stocks = floor(investment_value/spy[-1])
    benchmark_investment_ret = []
    
    for i in range(len(benchmark['benchmark_returns'])):
        returns = number_of_stocks*benchmark['benchmark_returns'][i]
        benchmark_investment_ret.append(returns)

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

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

Вывод:

Benchmark profit by investing $100k : 23370.75
Benchmark Profit percentage : 23%
TSI Strategy profit is 48% higher than the Benchmark Profit

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

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

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

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

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

Полный код:

# IMPORTING PACKAGES

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

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

# EXTRACTING STOCK DATA

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

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

# TRUE STRENGTH INDEX CALCULATION 

def get_tsi(close, long, short, signal):
    diff = close - close.shift(1)
    abs_diff = abs(diff)
    
    diff_smoothed = diff.ewm(span = long, adjust = False).mean()
    diff_double_smoothed = diff_smoothed.ewm(span = short, adjust = False).mean()
    abs_diff_smoothed = abs_diff.ewm(span = long, adjust = False).mean()
    abs_diff_double_smoothed = abs_diff_smoothed.ewm(span = short, adjust = False).mean()
    
    tsi = (diff_double_smoothed / abs_diff_double_smoothed) * 100
    signal = tsi.ewm(span = signal, adjust = False).mean()
    tsi = tsi[tsi.index >= '2020-01-01'].dropna()
    signal = signal[signal.index >= '2020-01-01'].dropna()
    
    return tsi, signal

aapl['tsi'], aapl['signal_line'] = get_tsi(aapl['close'], 25, 13, 12)
aapl = aapl[aapl.index >= '2020-01-01']
aapl.tail()

# TRUE STRENGTH INDEX PLOT

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2.5)
ax1.set_title('AAPL CLOSING PRICE')
ax2.plot(aapl['tsi'], linewidth = 2, color = 'orange', label = 'TSI LINE')
ax2.plot(aapl['signal_line'], linewidth = 2, color = '#FF006E', label = 'SIGNAL LINE')
ax2.set_title('AAPL TSI 25,13,12')
ax2.legend()
plt.show()

# TRUE STRENGTH INDEX STRATEGY

def implement_tsi_strategy(prices, tsi, signal_line):
    buy_price = []
    sell_price = []
    tsi_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if tsi[i-1] < signal_line[i-1] and tsi[i] > signal_line[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                tsi_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                tsi_signal.append(0)
        elif tsi[i-1] > signal_line[i-1] and tsi[i] < signal_line[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                tsi_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                tsi_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            tsi_signal.append(0)
            
    return buy_price, sell_price, tsi_signal

buy_price, sell_price, tsi_signal = implement_tsi_strategy(aapl['close'], aapl['tsi'], aapl['signal_line'])

# TRUE STRENGTH INDEX TRADING SIGNALS PLOT

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2)
ax1.plot(aapl.index, buy_price, marker = '^', markersize = 12, color = 'green', linewidth = 0, label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', markersize = 12, color = 'r', linewidth = 0, label = 'SELL SIGNAL')
ax1.legend()
ax1.set_title('AAPL TSI TRADING SIGNALS')
ax2.plot(aapl['tsi'], linewidth = 2, color = 'orange', label = 'TSI LINE')
ax2.plot(aapl['signal_line'], linewidth = 2, color = '#FF006E', label = 'SIGNAL LINE')
ax2.set_title('AAPL TSI 25,13,12')
ax2.legend()
plt.show()

# STOCK POSITION

position = []
for i in range(len(tsi_signal)):
    if tsi_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(aapl['close'])):
    if tsi_signal[i] == 1:
        position[i] = 1
    elif tsi_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
close_price = aapl['close']
tsi = aapl['tsi']
signal_line = aapl['signal_line']
tsi_signal = pd.DataFrame(tsi_signal).rename(columns = {0:'tsi_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'tsi_position'}).set_index(aapl.index)

frames = [close_price, tsi, signal_line, tsi_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy
strategy[12:17]

# BACKTESTING

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

for i in range(len(aapl_ret)):
    returns = aapl_ret['returns'][i]*strategy['tsi_position'][i]
    tsi_strategy_ret.append(returns)
    
tsi_strategy_ret_df = pd.DataFrame(tsi_strategy_ret).rename(columns = {0:'tsi_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
tsi_investment_ret = []

for i in range(len(tsi_strategy_ret_df['tsi_returns'])):
    returns = number_of_stocks*tsi_strategy_ret_df['tsi_returns'][i]
    tsi_investment_ret.append(returns)

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

# SPY ETF COMPARISON

def get_benchmark(start_date, investment_value):
    spy = get_historical_data('SPY', start_date)['close']
    benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})
    
    investment_value = investment_value
    number_of_stocks = floor(investment_value/spy[-1])
    benchmark_investment_ret = []
    
    for i in range(len(benchmark['benchmark_returns'])):
        returns = number_of_stocks*benchmark['benchmark_returns'][i]
        benchmark_investment_ret.append(returns)

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

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