В моей последней статье Алгоритмы классификации машинного обучения для прогнозирования движений рынка и тестирования на исторических данных мы использовали несколько алгоритмов классификации, основанных на scikit-learn, для прогнозирования направления движения акций. В этой статье давайте воспользуемся Keras и TensorFlow вместе с несколькими дополнительными функциями, чтобы посмотреть, сможем ли мы получить лучшие или сопоставимые результаты. Вы можете найти соответствующий блокнот Jupyter, использованный в этой статье, на моей странице Github. Наш подход будет следующим:

  1. Сбор исторических данных о ценах.
  2. Feature Engineering.
  3. Построение и применение модели глубокого обучения.
  4. Тестирование стратегии с использованием Backtrader.

Сбор исторических данных о ценах

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

Разработка функций

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

# define the number of lags
lags = [1, 2, 3, 4, 5]
# compute lagged log returns
cols = []
for lag in lags:
    col = f'rtn_lag{lag}'
    stock[col] = stock['returns'].shift(lag)
    cols.append(col)
stock.head(2)
# RSI - Relative Strenght Index
stock['rsi'] = RSI(stock.close)
# append to feature columns list
cols.append('rsi')
stock.tail(2)
# Compute Bollinger Bands
high, mid, low = BBANDS(stock.close, timeperiod=20)
stock = stock.join(pd.DataFrame({'bb_high': high, 'bb_low': low}, index=stock.index))
# append to feature columns list
cols.append('bb_high')
cols.append('bb_low')
# Compute Moving Average Convergence/ Divergence
stock['macd'] = MACD(stock.close)[0]
# append to feature columns list
cols.append('macd')

Комментарий к коду:

  1. Определите функцию для расчета доходности с задержкой за 5 дней вместе со списковой переменной -cols. Мы используем эту переменную для добавления всех имен столбцов функций, которые будут использоваться для модели.
  2. Вычислите индекс RSI как дополнительный столбец к нашему stock фрейму данных.
  3. Аналогичным образом добавьте столбцы для индикаторов «Полосы Боллинджера» и MACD.

Построение и применение модели глубокого обучения

Мы будем использовать модель глубокой нейронной сети с использованием API Keras и TensorFlow. Наш подход - это поиск API, который означает, что и как использовать API, а не математическое объяснение. Пожалуйста, обратитесь к {ссылке}, чтобы узнать больше о Keras и TensorFlow. Код на Python для этого раздела выглядит следующим образом:

# split the dataset in training and test datasets
train, test = train_test_split(stock.dropna(), test_size=0.4, shuffle=False)
# sort the data on date index
train = train.copy().sort_index()
test = test.copy().sort_index()
# define a function to create the deep neural network model
def create_model():
    np.random.seed(100)
    tf.random.set_seed(100)
    model = Sequential()
    model.add(Dense(64, activation='relu', input_dim=len(cols)))
    model.add(Dense(64, activation='relu'))
    model.add(Dense(1, activation='sigmoid'))
    model.compile(loss='binary_crossentropy', optimizer='adam', 
                  metrics=['accuracy'])
    return model
# normalized the training dataset
mu, std = train.mean(), train.std()
train_ = (train - mu) / mu.std()
# create the model
model = create_model()
# map market direction of (1,-1) to (1,0)
train['direction_'] = np.where(train['direction'] > 0, 1, 0)
%%time
# fit the model for training dataset
r = model.fit(train_[cols], train['direction_'], epochs=50, verbose=False)
# normalized the test dataset
mu, std = test.mean(), test.std()
test_ = (test - mu) / std
# map market direction of (1,-1) to (1,0)
test['direction_'] = np.where(test['direction'] > 0, 1, 0)
# evaluate the model with test dataset
model.evaluate(test_[cols], test['direction_'])
# predict the direction and map it (1,0)
pred = np.where(model.predict(test_[cols]) > 0.5, 1, 0) 
pred[:10].flatten()

Комментарий к коду:

  1. Разделите фрейм данныхstock, созданный в предыдущем разделе, на набор данных для обучения и тестирования. Я сохранил параметрshuffle=False и параметр test_size=0.4. Это означает, что мы собираемся использовать исходный набор данных 60% для обучения, а оставшиеся 40% - для тестирования.
  2. Отсортируйте наборы данных для обучения и тестирования с помощью индекса DateTime и определите функцию для построения модели. Параметр input_dim=len(cols) - это количество столбцов функций. Я использовал activation=’relu’ для ввода и плотных слоев, но вы можете изучить другие варианты. Функция активации для выходного слоя должна быть «сигмовидной», поскольку мы пытаемся решить проблему классификации. Поскольку это проблема классификации, функция потерь должна быть «binary_crossentropy», однако для оптимизатора вы можете поэкспериментировать.
  3. Затем мы нормализуем набор обучающих данных и создаем модель, вызывая функцию, созданную на предыдущем шаге.
  4. Сопоставьте направление рынка от (1, -1) до (1,0) для набора обучающих данных и поместите в модель x как нормализованные столбцы характеристик и y как направление рынка.
  5. Затем нормализуйте тестовый набор данных и сопоставьте направление рынка от (1, -1) до (1,0) для тестового набора данных и оцените модель с помощью тестового набора данных.
  6. Предскажите направление рынка с помощью столбцов характеристик нормализованного набора тестовых данных и сопоставьте прогнозируемое значение с (1,0) в зависимости от того, больше оно или меньше 0,5.

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

# based on prediction calculate the position for strategy
test['position_strategy'] = np.where(pred > 0, 1, -1)
# calculate daily returns for the strategy
test['strategy_return'] = test['position_strategy'] * test['returns']
# calculate total return and std. deviation of each strategy
print('\nTotal Returns:')
print(test[['returns', 'strategy_return']].sum().apply(np.exp))
print('\nAnnual Volatility:')
print(test[['returns', 'strategy_return']].std() * 252 ** 0.5)
# number of trades over time for the strategy
print('Number of trades = ', (test['position_strategy'].diff()!=0).sum())
# plot cumulative returns
fig, ax = plt.subplots(1, 1, sharex=True, figsize = (14,6))
ax.plot(test.returns.cumsum().apply(np.exp), label = 'Nifty 50 Buy and Hold')
ax.plot(test.strategy_return.cumsum().apply(np.exp), label = 'Strategy')
ax.set(title = 'Nifty 50 Buy and Hold vs. Strategy', ylabel = 'Cumulative Returns')
ax.grid(True)
ax.legend()
plt.savefig('images/chart2');

Комментарий к коду:

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

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

Тестирование стратегии с использованием Backtrader

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

  1. Мы начинаем с начального капитала 100 000 и торговой комиссии 0,1%.
  2. Мы покупаем, когда значение predicted равно +1, и продаем (только при наличии акций), когда прогнозируемое значение равно -1.
  3. Стратегия all-in - при создании заявки на покупку купите как можно больше акций.
  4. Короткие продажи не допускаются.

Давайте проанализируем эффективность нашей стратегии. Годовая доходность отрицательна, а совокупная доходность составляет -33,26% по сравнению с более чем 16-кратной доходностью, наблюдаемой во время векторизованного тестирования на исторических данных. Если мы визуализируем несколько других параметров, мы увидим, что наша стратегия не работает хорошо с добавленными ограничениями «без коротких продаж» и торговых комиссий. Опять же, в заключение, на бумаге все может выглядеть великолепно без ограничений, однако реальность может быть совершенно иной, если мы учитываем ограничения и осуществимость стратегии на реальном рынке.

Удачного инвестирования и оставляйте свои комментарии к статье!

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

Использованная литература:

  1. Python для финансов 2e: Освоение финансов, управляемых данными Ив Хилпиш
  2. yfinance2?dchild=1&keywords=Python+for+Finance+Cookbook&qid=1597938216&s=books&sr=1-2">Поваренная книга Python для финансов: более 50 рецептов применения современных библиотек Python для анализа финансовых данных Эрик Левинсон
  3. Машинное обучение для алгоритмической торговли Стефан Янсен
  4. Пожалуйста, ознакомьтесь с моими другими статьями / сообщениями о количественных финансах на моей странице Linkedin или на Medium.