В этой статье я попытаюсь воспроизвести предложенную структуру машинного обучения, используемую для прогнозирования движения цен на акции (Lin et al 2021). Эта структура использует технический анализ, пользовательские модели K-Line и исторические данные о ценах для прогнозного моделирования.
Код и модели доступны в этом репозитории GitHub.

Краткое содержание
Цель этого проекта — воспроизвести несколько алгоритмических торговых стратегий на основе машинного обучения, описанных в рецензируемых публикациях. В частности, я сосредоточился на анализе и реконструкции подхода, предложенного Lin et al. (2021) для предсказания тренда акций с использованием графика свечей. Для этого я использовал аналогичные технические индикаторы и стандартные модели машинного обучения, такие как Random Forest, Support Vector Machines, K-Nearest Neighbours, Gradient Boost и XGBoost, для прогнозирования движения цен на акции в течение 3–5 дней. Я также оценил производительность модели по следующим показателям: точность, точность, полнота, оценка F1.
Благодаря своей работе я обнаружил, что модели в этом проекте превосходят результаты, представленные Lin et al. (2021) в нескольких аспектах. Однако применимость в реальных условиях еще предстоит оценить (см. Результаты).
Примечание.Этот проект создан с чисто образовательной целью, я не получаю никаких прав ни на одну из его частей. Я уважаю академический процесс и права интеллектуальной собственности. Если у вас есть комментарии по этим вопросам, свяжитесь со мной по одному из контактов, указанных в конце статьи.
Введение
Цель этого проекта Python — проанализировать и потенциально превзойти исследовательскую статью «Прогнозирование тренда акций с использованием графика свечей», проведенную Лин и др. (2021). Вышеупомянутое исследование использовало графики свечей, технический анализ и ансамблевое обучение для прогнозирования тенденций движения цен на акции.
В этом проекте Python я использую такие модели, как KNN, SVM, XGBoost, RF и логистическая регрессия, чтобы учитывать исторические закономерности и точно прогнозировать тенденции акций. Результаты нашего исследовательского проекта показывают, что наши модели достигли значительно более высоких показателей точности, чем исходное исследование, с более поздними временными рамками и с использованием акций, котирующихся на NASDAQ. Это подтверждает, что простые статистические модели и технический анализ являются жизнеспособными инструментами для прогнозирования тенденций на фондовом рынке.
Исследовательская статья
Прогнозирование тренда акций с помощью свечных диаграмм (Лин и др., 2021 г.)
В своем исследовании «Прогнозирование тренда акций с использованием графика свечей» Лин и др. (2021) использовали модель с несколькими входными параметрами и сообщили о степени точности 60% своих выборочных данных. Авторы использовали ряд технических индикаторов, 8-триграммную схему для внутридневной классификации ценовых движений и предварительно выбранный набор моделей k-линий в качестве входных признаков.
Для обучения своих моделей Lin et al. предварительно выбрал множество алгоритмов, включая KNN, RF, LR и GBD, и обучил их различным входным функциям. Затем они использовали метод ансамбля, в котором была выбрана модель с наивысшим показателем точности, чтобы делать прогнозы на основе тестовых данных.
Файловая структура этого проекта выглядит следующим образом:

Входные переменные:
- Технические индикаторы. В документе использовалось около 19 индикаторов технического анализа. В своей репликации я использовал следующее:
- Индикаторы перекрытия: скользящая средняя, экспоненциальная скользящая средняя, двойная экспоненциальная скользящая средняя, адаптивная скользящая средняя Кауфмана, простая скользящая средняя, параболический SAR.
- Индикаторы импульса: индекс среднего направления движения, абсолютное колебание цены, индекс товарного канала, схождение/расхождение скользящих средних, индекс денежного потока, импульс, индекс относительной силы
- Индикаторы объема: линия Чайкина A/D (AD), осциллятор Чайкина, балансовый объем
- Индикаторы волатильности: Истинный диапазон, Средний истинный диапазон, Нормализованный средний истинный диапазон
2. Схема с 8 триграммами. В статье использовался китайский метод классификации движений цен акций в течение дня на 8 различных подклассов.
3. К-образные паттерны. Из-за отсутствия технического описания паттернов свечей, использованных в статье, я использовал наиболее популярные индикаторы паттернов ta-lib в качестве входных данных.
Целевые ярлыки
Целевые метки, используемые в этом проекте, были определены мной. В большинстве случаев 5-дневное и 3-дневное среднее процентных изменений исторической цены классифицировалось как положительное (1) или отрицательное (0). Принимались во внимание и другие варианты целевых меток.
Что касается исторических данных об акциях, в исходной статье их модель тестировалась на 3455 акциях на китайском фондовом рынке в период с 2000 по 2017 год. В отличие от этого, наш анализ будет проверять модель на репрезентативной выборке, взятой из NASDAQ за периоды 2015–2023 годов.
Модели
В этом проекте я буду использовать следующие модели:
- Случайный лесной классификатор
- K-классификатор ближайших соседей
- Машина опорных векторов
- Повышение градиента
- XGBoost
Воссоздание стратегии
Чтобы протестировать стратегию, изложенную в этом проекте, нам потребуются следующие модули:
Модуль 1. Этот модуль извлекает обучающие и тестовые данные, генерирует все необходимые входные сигналы и подготавливает полученные потоки данных для прогнозирования модели.
Модуль 2. Этот модуль создает ряд моделей машинного обучения, использованных в исходной статье, инициализирует их оптимальными параметрами (достигнутыми путем настройки гиперпараметров) и обучает модели на обучающем наборе.
Модуль 3. Этот модуль проверяет точность предиктора с использованием тестовых данных и создает показатели для оценки производительности модели, включая точность, точность, полноту и показатель F2.
Кроме того, в Модуле 1 есть подмодуль, который анализирует исторические ценовые данные и добавляет самые популярные паттерны K-линии в каждую строку через TaLib.

Данные
Данные, используемые в этом проекте, были получены из Yahoo Finance API и включали 1760 компаний, зарегистрированных на NASDAQ.
Обучающие и тестовые наборы были распределены по отраслям следующим образом:

Целевая метка
Пусть Pᵢ обозначает цену закрытия акции в день i, где i=1,2,…,n. Мы хотим предсказать, будет ли акция в восходящем или нисходящем тренде в течение следующих x дней, основываясь на среднем процентном изменении цены за этот период.
Чтобы вычислить среднее процентное изменение цены за следующие $x$ дней, мы можем использовать формулу:

Это представляет собой среднее дневное процентное изменение цены за период с дня i+1 до дня i+x относительно цены в день i.
Чтобы определить целевую метку y_i, мы можем установить:

Это устанавливает y_i равным 1, если среднее дневное процентное изменение в течение следующих x дней положительно, и 0 в противном случае.
В этом примере я использовал x=5 и x=3 для проверки разных временных окон.
Настройка гиперпараметров
Чтобы найти оптимальные гиперпараметры для каждой модели, использовалась рандомизированная перекрестная проверка SciKit-Learn. В следующей таблице приведены оптимальные параметры для каждой модели:

Код
В следующем разделе я шаг за шагом объясню, как работает конвейер и каковы обязанности каждого модуля. Этот раздел очень технический и зависит от языка, поэтому, если он кажется сложным, вы можете перейти к следующим разделам.
Для запуска кода вам потребуется импортировать следующие библиотеки: [Пожалуйста, предоставьте список необходимых библиотек.]
import random from tkinter import END from sklearn import preprocessing from sklearn.ensemble import GradientBoostingClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn import metrics from sklearn.model_selection import GridSearchCV from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.svm import SVC import talib as tb import os import joblib from collections import Counter import pandas as pd from sklearn.pipeline import Pipeline import numpy as np from sklearn import model_selection import xgboost as xgb from ta_patterns import create_signals import matplotlib.pyplot as plt import warnings from sklearn import utils import yfinance as yf from collections import Counter import pickle
Модуль 1
Модуль 1 отвечает за выборку обучающих и тестовых данных, генерацию всех необходимых входных сигналов и подготовку результирующих потоков данных для предсказаний модели. Этот модуль также анализирует исторические ценовые данные и добавляет самые популярные модели K-линий в каждую строку через TaLib.
Кроме того, этот модуль воспроизводит технические индикаторы, использованные в исходном исследовании, такие как модели 8-триграмм, и создает целевую метку. Целевая метка создается путем классификации 5-дневного и 3-дневного среднего процентного изменения исторических изменений цен как положительное (1) или отрицательное (0) в большинстве случаев. Также рассматриваются другие формы целевых меток.
В целом модуль 1 подготавливает данные для использования в моделях машинного обучения.
model_names = ["svm", "knn", "rf", "gb", "xgb_model"]
def format_data(df, start_time, end_time,custom=True,trigrams=True,patterns=True, avg_days=5, additional=["O", "M", "V"]):
"""Takes input features and creates TA indicators, the 8-trigram scheme and Target labels.
Parameters
----------
df:
Pandas DataFrame containing Open, High, Low, Close, Volume and Date columns.
start_time:
Start time in datetime format when the stock is purchased
end_time:
End time in datetime format when the stock is sold
custom:
Boolean Value to specify whether to create custom signals or not
trigrams:
Boolean value to specify whether to calculate 8 Trigrams or not
patterns:
Boolean Value to specify whether to calculate typical candlestick patterns or not
avg_days:
Integer denoting the number of days for which rolling average has to be calculated
additional:
List containing values O,M,V which specify which additional stock market indicators are to be calculated
Returns
----------
Pandas DataFrame with columns -
Updated Open, High, Low and Closing Prices, Volume, Trigrams, Target and optionally Short Line Cdl, Long Line Cdl, Spinning Top and Closing Marubozu (if custom signals are required)
"""
date_mask = (df["Date"] > start_time) & (df["Date"] <= end_time)
df = df.loc[date_mask]
short_ind = 5
long_ind = 10
# OVERLAP INDICATORS
df["ma"] = tb.MA(df["Close"], timeperiod=short_ind)
df["ema"] = tb.EMA(df["Close"], timeperiod=long_ind)
df["dema"] = tb.DEMA(df["Close"], timeperiod=short_ind)
df["kama"] = tb.KAMA(df["Close"], timeperiod=short_ind)
df["sma"] = tb.SMA(df["Close"], timeperiod=long_ind)
df["sar"] = tb.SAR(df["High"], df["Low"])
# MOMENTUM INDICATORS
df["adx"] = tb.ADX(df["High"], df["Low"],
df["Close"], timeperiod=long_ind)
df["cci"] = tb.CCI(df["High"], df["Low"],
df["Close"], timeperiod=long_ind)
df["apo"] = tb.APO(df["Close"], fastperiod=long_ind,
slowperiod=short_ind)
df["bop"] = tb.BOP(df["Open"], df["High"], df["Low"], df["Close"])
df["macd"], df["macdsignal"], df["macdhist"] = tb.MACD(
df["Close"], fastperiod=12, slowperiod=26, signalperiod=9)
df["mfi"] = tb.MFI(df["High"], df["Low"], df["Close"],
df["Volume"], timeperiod=long_ind)
df["mom"] = tb.MOM(df["Close"], timeperiod=long_ind)
df["rsi"] = tb.RSI(df["Close"], timeperiod=long_ind)
# VOLUME INDICATORS
df["ad"] = tb.AD(df["High"], df["Low"], df["Close"], df["Volume"])
df["adosc"] = tb.ADOSC(df["High"], df["Low"], df["Close"],
df["Volume"], fastperiod=short_ind, slowperiod=long_ind)
df["obv"] = tb.OBV(df["Close"], df["Volume"])
df["trange"] = tb.TRANGE(df["High"], df["Low"], df["Close"])
df["atr"] = tb.ATR(df["High"], df["Low"],
df["Close"], timeperiod=long_ind)
df["natr"] = tb.NATR(df["High"], df["Low"],
df["Close"], timeperiod=long_ind)
df.reset_index(drop=True, inplace=True)
# 8 TRIGRAMS
if trigrams == True:
trigrams = []
for i in range(1, len(df)):
if (df.loc[i, "High"] > df.loc[i-1, "High"]) & (df.loc[i, "Low"] < df.loc[i-1, "Low"]) & (df.loc[i, "Close"] > df.loc[i-1, "Close"]):
signal = 100 # "BullishHorn"
elif (df.loc[i, "High"] > df.loc[i-1, "High"]) & (df.loc[i, "Low"] < df.loc[i-1, "Low"]) & (df.loc[i, "Close"] < df.loc[i-1, "Close"]):
signal = -100 # "BearHorn"
elif (df.loc[i, "High"] > df.loc[i-1, "High"]) & (df.loc[i, "Low"] > df.loc[i-1, "Low"]) & (df.loc[i, "Close"] > df.loc[i-1, "Close"]):
signal = 100 # "BullishHigh"
elif (df.loc[i, "High"] > df.loc[i-1, "High"]) & (df.loc[i, "Low"] > df.loc[i-1, "Low"]) & (df.loc[i, "Close"] < df.loc[i-1, "Close"]):
signal = -100 # "BearHigh"
elif (df.loc[i, "High"] < df.loc[i-1, "High"]) & (df.loc[i, "Low"] < df.loc[i-1, "Low"]) & (df.loc[i, "Close"] > df.loc[i-1, "Close"]):
signal = 100 # "BullishLow"
elif (df.loc[i, "High"] < df.loc[i-1, "High"]) & (df.loc[i, "Low"] < df.loc[i-1, "Low"]) & (df.loc[i, "Close"] < df.loc[i-1, "Close"]):
signal = -100 # "BearLow"
elif (df.loc[i, "High"] < df.loc[i-1, "High"]) & (df.loc[i, "Low"] > df.loc[i-1, "Low"]) & (df.loc[i, "Close"] > df.loc[i-1, "Close"]):
signal = 100 # "BullishHarami"
elif (df.loc[i, "High"] < df.loc[i-1, "High"]) & (df.loc[i, "Low"] > df.loc[i-1, "Low"]) & (df.loc[i, "Close"] < df.loc[i-1, "Close"]):
signal = -100 # "BearHarami"
else:
signal = 0
trigrams.append(signal)
else:
trigrams = [0]*(len(df.index)-1)
df.drop(df.index[0], inplace=True)
df["trigrams"] = trigrams
# TARGET
df["target"] = df["Close"].pct_change().rolling(
avg_days).mean().shift(avg_days)
df.dropna(inplace=True)
columns = ["Open", "High", "Volume", "Low",
"trigrams", "target"]
if custom == True:
df = create_signals( data=df)
columns = columns + ["shortLineCdl",
"longLineCdl", "spinningTop", "closingMarubozu"]
if "O" in additional:
columns = columns + ["ma", "ema", "dema", "kama", "sma", "sar"]
if "M" in additional:
columns = columns + ["adx", "cci", "apo",
"bop", "macd", "mfi", "mom", "rsi"]
if "V" in additional:
columns = columns + ["ad", "adosc", "obv", "trange", "atr", "natr"]
df = df[columns]
return df
Многоуровневая классификация (необязательно)
Дополнительной функцией в этом проекте является реализация генератора меток, который может создавать несколько меток на основе коэффициента стандартного отклонения, что позволяет сравнивать эффективность стратегии в разных условиях.
Примечание. Этот код не соответствует производственным стандартам и использовался только экспериментально:
def create_target(x,classes,st_dev):
"""
Returns the target label based on the value of x, the number of classes to be generated, and the standard deviation factor.
Args:
x (float): A numeric value representing the price movement.
classes (int): An integer representing the number of target classes to be generated. Valid options are 2, 3, or 5.
st_dev (float): A numeric value representing the standard deviation factor.
Returns:
int: The target label. The value of the label depends on the number of classes specified. If classes is 2, the function returns 1 if x > 0, and 0 otherwise. If classes is 3, the function returns 0 if x < -st_dev, 1 if -st_dev < x < st_dev, and 2 otherwise. If classes is 5, the function returns 0 if x < -2*st_dev, 1 if -2*st_dev < x < -st_dev, 2 if -st_dev < x < st_dev, 3 if st_dev < x < 2*st_dev, and 4 otherwise.
"""
if classes ==2:
if x > 0:
return 1
else:
return 0
if classes == 3:
DEV_FACTOR = 0.7
st_dev = st_dev*DEV_FACTOR
if x < -st_dev:
return 0
elif x < st_dev and x >- st_dev:
return 1
else:
return 2
if classes == 5:
DEV_FACTOR = 0.7
st_dev = st_dev*DEV_FACTOR
if x < -st_dev*2:
return 0
elif x < -st_dev and x > -st_dev*2:
return 1
elif x < st_dev and x > -st_dev:
return 2
elif x > st_dev and x < st_dev*2:
return 3
else:
return 4
Подмодуль для создания паттернов K-line использует TaLib Python следующим образом:
import os
import pandas as pd
import numpy as np
import talib as ta
from candle_patterns import cs_patterns_rest
def create_signals(data):
"""
Creates technical trading signals based on candlestick charting patterns.
Args:
data (pandas.DataFrame): A dataframe of OHLCV (Open, High, Low, Close, Volume) data.
Returns:
pandas.DataFrame: A dataframe of OHLCV data with additional columns for each signal generated.
"""
for signal in cs_patterns_rest:
try:
values = cs_patterns_rest[signal](
data.Open, data.High, data.Low, data.Close)
data[signal] = values
except Exception as e:
print(str(e))
data = data.reset_index()
return data
Где популярные паттерны K-Line определены в файле Candle_patterns.py как словарь с лямбда-функциями.
Модуль 2
Модуль 2 отвечает за создание моделей машинного обучения, их инициализацию с оптимальными гиперпараметрами и их обучение на предварительно обработанных обучающих данных.
Сначала мы импортируем необходимые библиотеки для моделей машинного обучения и определяем функцию, которая инициализирует каждую модель оптимальными гиперпараметрами. Затем мы создаем экземпляр каждой модели и обучаем его на обучающих данных с помощью метода fit(). Наконец, мы сохраняем обученные модели в словаре для использования в будущем.
Вот код модуля 2:
def fit_models(sample, models, cv=0, classes=2,fit=False):
"""
Function used to fit models and evaluate their performance on a given dataset.
Parameters
----------
sample: pandas dataframe
Pandas dataframe with the necessary features
models: list
List of ML models
cv: int
Number of cross validations
classes: int
Number of target classes
fit: Bool
Flag to indicate if the model has to be fitted or not
Returns
-------
fitted_models : list
List of fitted ML models
X_test : year
A feature matrix for Test set
y_test : year
Labels for Test set
"""
ITER_SIZE = 5
_df = pd.DataFrame()
for col in sample.columns:
_df[col] = sample[col]
for i in range(ITER_SIZE):
_df[f"{col}_{i}"] = sample[col].shift(periods=i+1)
if _df.shape[0] > 8000:
raise Exception("Weird stuff going on")
_df = _df.merge(_df, how="right")
sample = _df
X = sample.dropna().drop(["target"], axis=1)
X = X.dropna().drop([f"target_{i}" for i in range(ITER_SIZE)], axis=1)
sample.dropna(inplace=True)
y = sample[
"target"].shift(-1).apply(lambda x: create_target(x,classes=classes,st_dev=sample["target"].std()))
scaler = StandardScaler()
pipeline = Pipeline(steps=[("scaler", scaler), ])
X = pipeline.fit_transform(X)
split_size = int(len(X) * 0.8)
X_train, y_train = X[:split_size], y[:split_size]
X_test, y_test = X[split_size:], y[split_size:]
y_train = np.stack(y_train.values.tolist(), axis=0)
y_test = np.stack(y_test.values.tolist(), axis=0)
X_train = np.asarray(X_train).astype(np.float32)
y_train = np.asarray(y_train).astype(np.float32)
X_test = np.asarray(X_test).astype(np.float32)
y_test = np.asarray(y_test).astype(np.float32)
X_train_lstm = X_train.reshape(X_train.shape[0], -1, ITER_SIZE+1)
X_test_lstm = X_test.reshape(X_test.shape[0], -1, ITER_SIZE+1)
if fit ==True:
fitted_models = []
if len(models) == 0:
svm_params = {
"svc__C": [1],
"svc__gamma": [0.1]
}
knn_params = {
"knn__n_neighbors": [150],
"knn__weights": ["distance"],
"knn__algorithm": ["auto"],
"knn__leaf_size": [1]
}
rf_params = {
"rf__n_estimators": [9],
"rf__criterion": ["gini"],
"rf__min_samples_leaf": [5],
"rf__max_depth": [1]
}
gb_params = {
"gb__n_estimators": [1],
"gb__max_features": [7],
"gb__max_depth": [1]
}
xgb_params = {
"xgb__n_estimators": [10],
"xgb__max_depth": [3],
"xgb__min_child_weight": [10],
"xgb__gamma": [0],
"xgb__learning_rate": [0.1],
"xgb__seed": [27],
"xgb__subsample": [0.65],
}
print("\tFitting Models...")
svm, svm_best_params = iterate_models(
SVC(), X_train, y_train, svm_params, cv)
knn, knn_best_params = iterate_models(KNeighborsClassifier(), X_train,
y_train, knn_params, cv)
rf, rf_best_params = iterate_models(RandomForestClassifier(),
X_train, y_train, rf_params, cv)
gb, gb_best_params = iterate_models(GradientBoostingClassifier(),
X_train, y_train, gb_params, cv)
xgb_model, xgb_best_params = iterate_models(xgb.XGBClassifier(),
X_train, y_train, xgb_params, cv)
if cv != 0:
print("SVM: ")
print(svm_best_params)
print("KNN: ")
print(knn_best_params)
print("RF: ")
print(rf_best_params)
print("GB: ")
print(gb_best_params)
print("XGB: ")
print(xgb_best_params)
fitted_models = [svm, knn, rf, gb, xgb_model]
else:
for i, model in enumerate(models):
print(f"\tFitting Model_{model_names[i]}")
if i == 5:
model.fit(X_train_lstm, y_train)
else:
model.fit(X_train, y_train)
fitted_models.append(model)
filename = model_names[i]+".sav"
pickle.dump(model, open(
f"Lin_et_al_2021//ensemble_models//{classes}class//"+filename, 'wb'))
else:
fitted_models = [joblib.load(f"Lin_et_al_2021//ensemble_models//{classes}class//"+model) for model in os.listdir(f"Lin_et_al_2021//ensemble_models//{classes}class//")]
return fitted_models, X_test, y_test
Модуль 3
Модуль 3 отвечает за оценку производительности конвейера на тестовом наборе. Он выполняет итерацию по конвейеру, определенному в Модуле 2, и использует обученные модели для прогнозирования набора тестов. Затем прогнозы оцениваются по показателям производительности, определенным в начале проекта, включая точность, достоверность, полноту и оценку F1.
Код модуля 3 выглядит следующим образом:
def predict_models(models, X_test, y_test):
"""
Predicts the target values of a given list of models on a test set
Args:
models (list): A list of models to be used for prediction.
X_test (numpy array or pandas dataframe): The test set input features.
y_test (numpy array or pandas dataframe): The test set target values.
Returns:
y_preds (list): A list of target value predictions for each model.
"""
y_preds = []
for model in models:
try:
y_pred = model.predict(X_test)
y_preds.append(y_pred)
except Exception as e:
print(str(e))
break
return y_preds
def score_predictions( models,ticker, X_test,y_test,average):
"""
Calculates the evaluation metrics for multiple classification models.
Parameters:
models (list): List of trained models for prediction.
ticker (str): Ticker symbol of the stock.
X_test (array-like): Test dataset of features.
y_test (array-like): Test dataset of target labels.
average (str): Type of averaging to be used for the metrics calculation.
Possible values are: "micro", "macro" and "weighted".
Returns:
pandas.DataFrame: DataFrame with evaluation metrics for each model.
"""
scaler = StandardScaler()
pipeline = Pipeline(
steps=[("scaler", scaler)])
X_test = pipeline.fit_transform(X_test)
y_pred = predict_models(models, X_test, y_test
df = pd.DataFrame({"ticker": ticker,
"svm_acc": metrics.accuracy_score(y_test, y_pred[0]),
"svm_prec": metrics.precision_score(y_test, y_pred[0], average=average),
"svm_recall": metrics.recall_score(y_test, y_pred[0], average=average),
"svm_f1": metrics.f1_score(y_test, y_pred[0], average=average),
"knn_acc": metrics.accuracy_score(y_test, y_pred[1]),
"knn_prec": metrics.precision_score(y_test, y_pred[1], average=average),
"knn_recall": metrics.recall_score(y_test, y_pred[1], average=average),
"knn_f1": metrics.f1_score(y_test, y_pred[1], average=average),
"rf_acc": metrics.accuracy_score(y_test, y_pred[2]),
"rf_prec": metrics.precision_score(y_test, y_pred[2], average=average),
"rf_recall": metrics.recall_score(y_test, y_pred[2], average=average),
"rf_f1": metrics.f1_score(y_test, y_pred[2], average=average),
"gb_acc": metrics.accuracy_score(y_test, y_pred[3]),
"gb_prec": metrics.precision_score(y_test, y_pred[3], average=average),
"gb_recall": metrics.recall_score(y_test, y_pred[3], average=average),
"gb_f1": metrics.f1_score(y_test, y_pred[3], average=average),
"xgb_acc": metrics.accuracy_score(y_test, y_pred[4]),
"xgb_prec": metrics.precision_score(y_test, y_pred[4], average=average),
"xgb_recall": metrics.recall_score(y_test, y_pred[4], average=average),
"xgb_f1": metrics.f1_score(y_test, y_pred[4], average=average), }, index=[0])
return df
Запуск полного конвейера
Следующий сценарий объединяет все методы вместе, чтобы создать гладкую среду тестирования, и сохраняет каждый тест с гиперпараметрами, закодированными в имени файла, в следующем формате:
N[Размер выборки]_[Начальный год]-[Конечный год]CL[Выходные классы]([Используемые индикаторы])D[Временной интервал]
Например, N1760_15–23CL2(OMV)D3.csv содержит результаты тестирования выборки из 1760 акций с 2015 по 2023 год с выходными данными бинарного класса с использованием индикаторов осцилляции, импульса и волатильности в трехдневном временном окне.
def run_simulation(N,START_TIME,END_TIME,AVG_DAYS,TRIGRAMS,ADDITIONAL,CLASSES,symbols):
"""
Runs a simulation on a subset of stocks using various machine learning models.
Args:
- N (int): the number of stocks to include in the simulation
- START_TIME (str): the start date for the simulation (in YYYY-MM-DD format)
- END_TIME (str): the end date for the simulation (in YYYY-MM-DD format)
- AVG_DAYS (int): the number of days to average the stock data over
- TRIGRAMS (bool): whether to include trigram features in the model
- ADDITIONAL (list): a list of additional features to include in the model
- CLASSES (int): the number of classes for the classification problem (2 for binary classification, more than 2 for multiclass)
- symbols (list): a list of stock symbols to include in the simulation
Returns:
- None
Saves the results of the simulation to a CSV file with a unique ID based on the simulation parameters.
Example:
run_simulation(10, "2015-01-01", "2022-01-01", 30, True, ["rsi", "macd"], 2, ["AAPL", "GOOGL", "TSLA", "MSFT", "AMZN"])
"""
scores = pd.DataFrame(columns=["ticker", "svm_acc", "svm_prec", "svm_recall", "svm_f1",
"knn_acc", "knn_prec", "knn_recall", "knn_f1",
"rf_acc", "rf_prec", "rf_recall", "rf_f1",
"gb_acc", "gb_prec", "gb_recall", "gb_f1"])
TEST_ID = ["N", str(N), "_", START_TIME[2:4], "-",
END_TIME[2:4], "CL", str(CLASSES), "(", "".join(ADDITIONAL), ")", "D", str(AVG_DAYS)]
if CUSTOM == True:
TEST_ID.append("_C")
if TRIGRAMS == True:
TEST_ID.append("_T")
TEST_ID = "".join(TEST_ID)
print(TEST_ID)
if CLASSES > 2:
average = "weighted"
else:
average = "binary"
prob_tickers = []
models = []
No_POS = 0
No_NEG = 0
for i,f in enumerate(symbols[:N]):
print(f)
print(f"{i+1} out of {N} ")
try:
if f+".csv" not in os.listdir("Lin_et_al_2021//data//stocks//"):
df = yf.download(f, start=START_TIME, end=END_TIME)
df.reset_index(inplace=True)
df = format_data(df, start_time=START_TIME,
end_time=END_TIME, avg_days=AVG_DAYS,
trigrams=TRIGRAMS, additional=ADDITIONAL)
df.to_csv("Lin_et_al_2021//data//stocks//"+f+".csv")
else:
df = pd.read_csv("Lin_et_al_2021//data//stocks//"+f+".csv",index_col=0)
models, X_test, y_test = fit_models(df, models, classes=CLASSES,fit=False)
if CLASSES == 2:
No_POS += Counter(y_test)[0]
No_NEG += Counter(y_test)[1]
score_df = score_predictions(models,f,X_test,y_test,average)
scores = pd.concat([scores, score_df], ignore_index=True)
scores.to_csv(f"Lin_et_al_2021//ensemble_results//{TEST_ID}.csv")
except Exception as e:
print(str(e))
prob_tickers.append(f)
i += 1
print(No_POS)
print(No_NEG)
print(prob_tickers)
Полученные результаты
Конвейер был протестирован с различными гиперпараметрами, и результаты были многообещающими. Например, применительно к 1760 компаниям, использующим двоичные целевые метки, 3-дневное временное окно, настраиваемые шаблоны K-линии и 8-триграммы, система продемонстрировала сильную прогностическую способность, о чем свидетельствуют показатели оценки.

Примечательно, что производительность моделей в этом пайплайне оказалась даже лучше, чем та, о которой сообщили Lin et al. (2021) по большинству показателей.

Спасибо за прочтение и следите за обновлениями во второй части, где я попытаюсь протестировать торговые сигналы через Backtrader и посмотреть, можно ли построить прибыльную торговую стратегию на этих моделях!
Для любого комментария, отзыва или замечания, не стесняйтесь обращаться ко мне на мой LinkedIn или Электронная почта.
Рекомендации
Лин, Яоху и Лин, Шанцун и Ян, Хайцзюнь и Ву, Харрис. (2021). Прогнозирование тренда акций с использованием свечных графиков и методов машинного обучения в сочетании с новой инженерной схемой. IEEE-доступ. 9. 101433–101446. 10.1109/ДОСТУП.2021.3096825.