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

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

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

Окончательный результат торговой стратегии должен ответить на следующие вопросы:

  • НАПРАВЛЕНИЕ: определить, является ли актив дешевым / дорогим / справедливым.
  • ВХОДНАЯ ТОРГОВЛЯ: если актив дешевый / дорогой, следует ли вам его покупать / продавать
  • ВЫЙТИ ИЗ ТОРГОВЛИ: если актив имеет справедливую цену и если мы удерживаем позицию в этом активе (купили или продали его ранее), должны ли вы выйти из этой позиции
  • ДИАПАЗОН ЦЕН: по какой цене (или диапазону) совершить сделку
  • КОЛИЧЕСТВО: размер капитала для торговли (пример акций).

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

Стратегический подход

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

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

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

Вы можете выполнить шаги этой модели, используя этот блокнот IPython. В примерах кода используется бесплатный набор инструментов с открытым исходным кодом, основанный на питоне Auquan. Вы можете установить его через pip: `pip install -U auquan_toolbox`. Мы используем scikit learn для моделей машинного обучения. Установите его с помощью `pip install -U scikit-learn`.

Использование машинного обучения для создания сигнала торговой стратегии - интеллектуальный анализ данных

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

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

Шаг 1 - Настройте вашу проблему

Что вы пытаетесь предсказать? Что такое хороший прогноз? Как вы оцениваете

Что такое Y в нашей структуре выше?

Вы прогнозируете цену в будущем, будущую доходность / прибыль, сигнал покупки / продажи, оптимизацию распределения портфеля, пробуете эффективное исполнение и т. Д.?
Предположим, мы пытаемся предсказать цену на момент следующая отметка времени. В этом случае Y (t) = Цена (t + 1). Теперь мы можем дополнить наш фреймворк историческими данными.

Обратите внимание, что Y (t) будет известно только во время тестирования на истории, но при использовании нашей модели в реальном времени мы не узнаем цену (t + 1) в момент времени t. Мы делаем прогноз Y (Прогнозируемый, t), используя нашу модель, и сравниваем его с фактическим значением только в момент времени t + 1. Это означает, что вы не можете использовать Y в качестве функции в своей прогнозной модели.

Как только мы узнаем нашу цель, Y, мы также сможем решить, как оценивать наши прогнозы. Это важно, чтобы различать разные модели, которые мы будем опробовать на наших данных. Выберите показатель, который является хорошим индикатором эффективности нашей модели, исходя из решаемой проблемы. Например, если мы прогнозируем цену, мы можем использовать Среднеквадратичную ошибку в качестве метрики. Некоторые общие метрики (RMSE, лог-потери, оценка дисперсии и т. Д.) Предварительно закодированы в наборе инструментов Auquan и доступны в разделе Функции.

Для демонстрации воспользуемся задачей из QuantQuest (Задача 1). Мы собираемся создать модель прогнозирования, которая предсказывает будущее ожидаемое значение базиса, где:

база = Цена акций - Цена будущего

базис (t) = S (t) −F (t)

Y (t) = будущее ожидаемое значение базиса = Среднее (базис (t + 1), базис (t + 2), базис (t + 3), базис (t + 4), базис (t + 5))

Поскольку это проблема регрессии, мы будем оценивать модель на RMSE. Мы также будем использовать Total Pnl в качестве критерия оценки.

Наша цель: создать модель, чтобы прогнозируемое значение было как можно ближе к Y

Шаг 2. Соберите надежные данные

Собирайте и очищайте данные, которые помогут вам решить возникшую проблему

Вам нужно подумать о том, какие данные будут иметь предсказательную силу для целевой переменной Y? Если бы мы прогнозировали цену, вы могли бы использовать данные о ценах на акции, данные об объеме торговли акциями, фундаментальные данные, данные о ценах и объемах коррелированных акций, индикатор общего рынка, например, уровень индекса акций, цену других коррелированных активов и т. Д.

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

Если вы используете Auquan's Toolbox, мы предоставляем доступ к бесплатным данным из Google, Yahoo, NSE и Quandl. Мы также предварительно очищаем данные для дивидендов, разделения акций и рулонов и загружаем их в формате, понятном остальной части набора инструментов.

Для нашей демонстрационной задачи мы используем следующие данные для фиктивной акции MQK с минутными интервалами для торговых дней в течение одного месяца (~ 8000 точек данных): Цена предложения акции, Цена предложения, Объем предложения, Будущий объем спроса Цена Bid, Цена Ask, Объем Bid, Объем Ask, StockVWAP, Future VWAP. Эти данные уже очищены для дивидендов, сплитов, бросков.

# Load the data
from backtester.dataSource.quant_quest_data_source import QuantQuestDataSource
cachedFolderName = '/Users/chandinijain/Auquan/qq2solver-data/historicalData/'
dataSetId = 'trainingData1'
instrumentIds = ['MQK']
ds = QuantQuestDataSource(cachedFolderName=cachedFolderName,
                                    dataSetId=dataSetId,
                                    instrumentIds=instrumentIds)
def loadData(ds):
    data = None
    for key in ds.getBookDataByFeature().keys():
        if data is None:
            data = pd.DataFrame(np.nan, index = ds.getBookDataByFeature()[key].index, columns=[])
        data[key] = ds.getBookDataByFeature()[key]
    data['Stock Price'] =  ds.getBookDataByFeature()['stockTopBidPrice'] + ds.getBookDataByFeature()['stockTopAskPrice'] / 2.0
    data['Future Price'] = ds.getBookDataByFeature()['futureTopBidPrice'] + ds.getBookDataByFeature()['futureTopAskPrice'] / 2.0
    data['Y(Target)'] = ds.getBookDataByFeature()['basis'].shift(-5)
    del data['benchmark_score']
    del data['FairValue']
    return data
data = loadData(ds)

Инструментальная панель Auquan скачала и загрузила данные в словарь фреймов данных за вас. Теперь нам нужно подготовить данные в том формате, который нам нравится. Функция ds.getBookDataByFeature() возвращает словарь фреймов данных, по одному фрейму данных для каждой функции. Мы создаем новый data фрейм данных для акции со всеми функциями.

Шаг 3. Разделение данных

Создание наборов данных для обучения, перекрестной проверки и тестирования на основе данных

Это чрезвычайно важный шаг! Прежде чем мы продолжим, мы должны разделить наши данные на обучающие данные для обучения вашей модели и тестовые данные для оценки производительности модели. Рекомендуемый сплит: 60–70% тренировка и 30-40% тест.

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

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

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

Помните, что после того, как вы проверите производительность на тестовых данных, не возвращайтесь и не пытайтесь оптимизировать свою модель дальше. Если вы обнаружите, что ваша модель не дает хороших результатов, откажитесь от нее и начните заново. Рекомендуемое разделение может включать 60% данных обучения, 20% данных проверки и 20% данных тестирования.

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

# Training Data
dataSetId =  'trainingData1'
ds_training = QuantQuestDataSource(cachedFolderName=cachedFolderName,
                                    dataSetId=dataSetId,
                                    instrumentIds=instrumentIds)
training_data = loadData(ds_training)
# Validation Data
dataSetId =  'trainingData2'
ds_validation = QuantQuestDataSource(cachedFolderName=cachedFolderName,
                                    dataSetId=dataSetId,
                                    instrumentIds=instrumentIds)
validation_data = loadData(ds_validation)
# Test Data
dataSetId =  'trainingData3'
ds_test = QuantQuestDataSource(cachedFolderName=cachedFolderName,
                                    dataSetId=dataSetId,
                                    instrumentIds=instrumentIds)
out_of_sample_test_data = loadData(ds_test)

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

def prepareData(data, period):
    data['Y(Target)'] = data['basis'].rolling(period).mean().shift(-period)
    if 'FairValue' in data.columns:
        del data['FairValue']
    data.dropna(inplace=True)
period = 5
prepareData(training_data, period)
prepareData(validation_data, period)
prepareData(out_of_sample_test_data, period)

Этап 4. Разработка функций

Анализируйте поведение ваших данных и создавайте функции, обладающие предсказательной силой.

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

  • Не выбирайте произвольно очень большой набор функций, не изучив взаимосвязь с целевой переменной.
  • Небольшая связь с целевой переменной или ее отсутствие, скорее всего, приведет к переобучению
  • Ваши функции могут быть сильно коррелированы друг с другом, в этом случае меньшее количество функций также объяснит цель.
  • Я обычно создаю несколько функций, которые имеют интуитивный смысл, смотрю на корреляцию целевой переменной с этими функциями, а также на их взаимосвязь, чтобы решить, что использовать.
  • Вы также можете попробовать ранжировать функции-кандидаты по Максимальному информационному коэффициенту (MIC), выполнив Анализ главных компонентов (PCA) и другие методы.

Преобразование / нормализация функций:

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

  • Масштабирование: разделите объекты на стандартное отклонение или межквартильный диапазон
  • Центрирование: вычесть историческое среднее значение из текущего значения
  • Нормализация: оба указанных выше значения (x - среднее) / стандартное отклонение за период ретроспективного анализа
  • Регулярная нормализация: стандартизируйте данные до диапазона от -1 до +1 за период ретроспективного анализа (x-min) / (max-min) и повторно центрируйте

Обратите внимание, поскольку мы используем историческое скользящее среднее, стандартное отклонение, максимальное или минимальное значение за период ретроспективного анализа, одно и то же нормализованное значение функции будет означать разное фактическое значение в разное время. Например, если текущее значение признака равно 5 при скользящем среднем 30-периодном значении 4,5, оно преобразуется в 0,5 после центрирования. Позже, если скользящее 30-периодное среднее значение изменится на 3, значение 3,5 превратится в 0,5. Это может быть причиной ошибок в вашей модели; следовательно, нормализация сложна, и вам нужно выяснить, что на самом деле улучшает производительность вашей модели (если вообще).

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

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

def difference(dataDf, period):
    return dataDf.sub(dataDf.shift(period), fill_value=0)
def ewm(dataDf, halflife):
    return dataDf.ewm(halflife=halflife, ignore_na=False,
                      min_periods=0, adjust=True).mean()
def rsi(data, period):
    data_upside = data.sub(data.shift(1), fill_value=0)
    data_downside = data_upside.copy()
    data_downside[data_upside > 0] = 0
    data_upside[data_upside < 0] = 0
    avg_upside = data_upside.rolling(period).mean()
    avg_downside = - data_downside.rolling(period).mean()
    rsi = 100 - (100 * avg_downside / (avg_downside + avg_upside))
    rsi[avg_downside == 0] = 100
    rsi[(avg_downside == 0) & (avg_upside == 0)] = 0
return rsi
def create_features(data):
    basis_X = pd.DataFrame(index = data.index, columns =  [])
    
    basis_X['mom3'] = difference(data['basis'],4)
    basis_X['mom5'] = difference(data['basis'],6)
    basis_X['mom10'] = difference(data['basis'],11)
    
    basis_X['rsi15'] = rsi(data['basis'],15)
    basis_X['rsi10'] = rsi(data['basis'],10)
    
    basis_X['emabasis3'] = ewm(data['basis'],3)
    basis_X['emabasis5'] = ewm(data['basis'],5)
    basis_X['emabasis7'] = ewm(data['basis'],7)
    basis_X['emabasis10'] = ewm(data['basis'],10)
    basis_X['basis'] = data['basis']
    basis_X['vwapbasis'] = data['stockVWAP']-data['futureVWAP']
    
    basis_X['swidth'] = data['stockTopAskPrice'] -
                        data['stockTopBidPrice']
    basis_X['fwidth'] = data['futureTopAskPrice'] -
                        data['futureTopBidPrice']
    
    basis_X['btopask'] = data['stockTopAskPrice'] -
                         data['futureTopAskPrice']
    basis_X['btopbid'] = data['stockTopBidPrice'] -
                         data['futureTopBidPrice']

    basis_X['totalaskvol'] = data['stockTotalAskVol'] -
                             data['futureTotalAskVol']
    basis_X['totalbidvol'] = data['stockTotalBidVol'] -
                             data['futureTotalBidVol']
    
    basis_X['emabasisdi7'] = basis_X['emabasis7'] -
                             basis_X['emabasis5'] + 
                             basis_X['emabasis3']
    
    basis_X = basis_X.fillna(0)
    
    basis_y = data['Y(Target)']
    basis_y.dropna(inplace=True)
    
    print("Any null data in y: %s, X: %s"
            %(basis_y.isnull().values.any(), 
             basis_X.isnull().values.any()))
    print("Length y: %s, X: %s"
            %(len(basis_y.index), len(basis_X.index)))
    
    return basis_X, basis_y
basis_X_train, basis_y_train = create_features(training_data)
basis_X_test, basis_y_test = create_features(validation_data)

Шаг 5. Выбор модели

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

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

Вот некоторые распространенные алгоритмы контролируемого обучения, которые помогут вам начать работу:

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

Шаг 6: обучение, проверка и оптимизация (повторите шаги 4–6)

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

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

Для нашей демонстрационной задачи давайте начнем с простой линейной регрессии

from sklearn import linear_model
from sklearn.metrics import mean_squared_error, r2_score
def linear_regression(basis_X_train, basis_y_train,
                      basis_X_test,basis_y_test):
    
    regr = linear_model.LinearRegression()
    # Train the model using the training sets
    regr.fit(basis_X_train, basis_y_train)
    # Make predictions using the testing set
    basis_y_pred = regr.predict(basis_X_test)
    # The coefficients
    print('Coefficients: \n', regr.coef_)
    
    # The mean squared error
    print("Mean squared error: %.2f"
          % mean_squared_error(basis_y_test, basis_y_pred))
    
    # Explained variance score: 1 is perfect prediction
    print('Variance score: %.2f' % r2_score(basis_y_test,
                                            basis_y_pred))
    # Plot outputs
    plt.scatter(basis_y_pred, basis_y_test,  color='black')
    plt.plot(basis_y_test, basis_y_test, color='blue', linewidth=3)
    plt.xlabel('Y(actual)')
    plt.ylabel('Y(Predicted)')
    plt.show()
    
    return regr, basis_y_pred
_, basis_y_pred = linear_regression(basis_X_train, basis_y_train, 
                                    basis_X_test,basis_y_test)

('Coefficients: \n', array([ -1.0929e+08, 4.1621e+07, 1.4755e+07, 5.6988e+06, -5.656e+01, -6.18e-04, -8.2541e-05,4.3606e-02, -3.0647e-02, 1.8826e+07, 8.3561e-02, 3.723e-03, -6.2637e-03, 1.8826e+07, 1.8826e+07, 6.4277e-02, 5.7254e-02, 3.3435e-03, 1.6376e-02, -7.3588e-03, -8.1531e-04, -3.9095e-02, 3.1418e-02, 3.3321e-03, -1.3262e-06, -1.3433e+07, 3.5821e+07, 2.6764e+07, -8.0394e+06, -2.2388e+06, -1.7096e+07]))
Mean squared error: 0.02
Variance score: 0.96

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

def normalize(basis_X, basis_y, period):
    basis_X_norm = (basis_X - basis_X.rolling(period).mean())/
                    basis_X.rolling(period).std()
    basis_X_norm.dropna(inplace=True)
    basis_y_norm = (basis_y - 
                    basis_X['basis'].rolling(period).mean())/
                    basis_X['basis'].rolling(period).std()
    basis_y_norm = basis_y_norm[basis_X_norm.index]
    
    return basis_X_norm, basis_y_norm
norm_period = 375
basis_X_norm_test, basis_y_norm_test = normalize(basis_X_test,basis_y_test, norm_period)
basis_X_norm_train, basis_y_norm_train = normalize(basis_X_train, basis_y_train, norm_period)
regr_norm, basis_y_pred = linear_regression(basis_X_norm_train, basis_y_norm_train, basis_X_norm_test, basis_y_norm_test)
basis_y_pred = basis_y_pred * basis_X_test['basis'].rolling(period).std()[basis_y_norm_test.index] + basis_X_test['basis'].rolling(period).mean()[basis_y_norm_test.index]

Mean squared error: 0.05
Variance score: 0.90

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

Посмотрим на коэффициенты

for i in range(len(basis_X_train.columns)):
    print('%.4f, %s'%(regr_norm.coef_[i], basis_X_train.columns[i]))

19.8727, emabasis4
-9.2015, emabasis5
8.8981, emabasis7
-5.5692, emabasis10
-0.0036, rsi15
-0.0146, rsi10
0,0196, mom10 < br /> -0.0035, mom5
-7.9138, base
0.0062, swidth
0.0117, fwidth
2.0883, btopask
2.0311, btopbid
0,0974, bavgask
0,0611, bavgbid
0,0007, topaskvolratio
0,0113, topbidvolratio
-0,0220, totalaskvolratio
0,0231, totalbidvolratio

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

Давайте также посмотрим на корреляцию между различными функциями.

import seaborn
c = basis_X_train.corr()
plt.figure(figsize=(10,10))
seaborn.heatmap(c, cmap='RdYlGn_r', mask = (np.abs(c) <= 0.8))
plt.show()

Темно-красные области указывают на сильно коррелированные переменные. Давайте снова создадим / изменим некоторые функции и попробуем улучшить нашу модель.

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

def create_features_again(data):
    basis_X = pd.DataFrame(index = data.index, columns =  [])
    basis_X['mom10'] = difference(data['basis'],11)
    basis_X['emabasis2'] = ewm(data['basis'],2)
    basis_X['emabasis5'] = ewm(data['basis'],5)
    basis_X['emabasis10'] = ewm(data['basis'],10)
    basis_X['basis'] = data['basis']
    basis_X['totalaskvolratio'] = (data['stockTotalAskVol']
                                 - data['futureTotalAskVol'])/
                                   100000
    basis_X['totalbidvolratio'] = (data['stockTotalBidVol']
                                 - data['futureTotalBidVol'])/
                                   100000
    basis_X = basis_X.fillna(0)
    
    basis_y = data['Y(Target)']
    basis_y.dropna(inplace=True)
    return basis_X, basis_y
basis_X_test, basis_y_test = create_features_again(validation_data)
basis_X_train, basis_y_train = create_features_again(training_data)
_, basis_y_pred = linear_regression(basis_X_train, basis_y_train, basis_X_test,basis_y_test)
basis_y_regr = basis_y_pred.copy()

('Coefficients: ', array([ 0.03246139,
0.49780982, -0.22367172,  0.20275786,  0.50758852,
-0.21510795, 0.17153884]))
Mean squared error: 0.02
Variance score: 0.96

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

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

K Ближайшие соседи

from sklearn import neighbors
n_neighbors = 5
model = neighbors.KNeighborsRegressor(n_neighbors, weights='distance')
model.fit(basis_X_train, basis_y_train)
basis_y_pred = model.predict(basis_X_test)
basis_y_knn = basis_y_pred.copy()

СВР

from sklearn.svm import SVR
model = SVR(kernel='rbf', C=1e3, gamma=0.1)
model.fit(basis_X_train, basis_y_train)
basis_y_pred = model.predict(basis_X_test)
basis_y_svr = basis_y_pred.copy()

Деревья решений

model=ensemble.ExtraTreesRegressor()
model.fit(basis_X_train, basis_y_train)
basis_y_pred = model.predict(basis_X_test)
basis_y_trees = basis_y_pred.copy()

Шаг 7: Бэктест на тестовых данных

Проверка производительности реальных данных вне выборки

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

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

Если вам не нравятся результаты бэктеста на тестовых данных, откажитесь от модели и начните заново. НЕ возвращайтесь и повторно оптимизируйте свою модель, это приведет к чрезмерной подгонке! (Также рекомендуется создать новый набор тестовых данных, поскольку он теперь испорчен; отбрасывая модель, мы неявно знаем что-то о наборе данных).

Для тестирования на истории мы используем набор инструментов Auquan.

import backtester
from backtester.features.feature import Feature
from backtester.trading_system import TradingSystem
from backtester.sample_scripts.fair_value_params import FairValueTradingParams
class Problem1Solver():
def getTrainingDataSet(self):
        return "trainingData1"
def getSymbolsToTrade(self):
        return ['MQK']
def getCustomFeatures(self):
        return {'my_custom_feature': MyCustomFeature}
def getFeatureConfigDicts(self):
                            
        expma5dic = {'featureKey': 'emabasis5',
                 'featureId': 'exponential_moving_average',
                 'params': {'period': 5,
                              'featureName': 'basis'}}
        expma10dic = {'featureKey': 'emabasis10',
                 'featureId': 'exponential_moving_average',
                 'params': {'period': 10,
                              'featureName': 'basis'}}                     
        expma2dic = {'featureKey': 'emabasis3',
                 'featureId': 'exponential_moving_average',
                 'params': {'period': 3,
                              'featureName': 'basis'}}
        mom10dic = {'featureKey': 'mom10',
                 'featureId': 'difference',
                 'params': {'period': 11,
                              'featureName': 'basis'}}
        
        return [expma5dic,expma2dic,expma10dic,mom10dic]    
    
    def getFairValue(self, updateNum, time, instrumentManager):
        # holder for all the instrument features
        lbInstF = instrumentManager.getlookbackInstrumentFeatures()
        mom10 = lbInstF.getFeatureDf('mom10').iloc[-1]
        emabasis2 = lbInstF.getFeatureDf('emabasis2').iloc[-1]
        emabasis5 = lbInstF.getFeatureDf('emabasis5').iloc[-1]
        emabasis10 = lbInstF.getFeatureDf('emabasis10').iloc[-1] 
        basis = lbInstF.getFeatureDf('basis').iloc[-1]
        totalaskvol = lbInstF.getFeatureDf('stockTotalAskVol').iloc[-1] - lbInstF.getFeatureDf('futureTotalAskVol').iloc[-1]
        totalbidvol = lbInstF.getFeatureDf('stockTotalBidVol').iloc[-1] - lbInstF.getFeatureDf('futureTotalBidVol').iloc[-1]
        
        coeff = [ 0.03249183, 0.49675487, -0.22289464, 0.2025182, 0.5080227, -0.21557005, 0.17128488]
        newdf['MQK'] = coeff[0] * mom10['MQK'] + coeff[1] * emabasis2['MQK'] +\
                      coeff[2] * emabasis5['MQK'] + coeff[3] * emabasis10['MQK'] +\
                      coeff[4] * basis['MQK'] + coeff[5] * totalaskvol['MQK']+\
                      coeff[6] * totalbidvol['MQK']
                    
        newdf.fillna(emabasis5,inplace=True)
        return newdf
problem1Solver = Problem1Solver()
tsParams = FairValueTradingParams(problem1Solver)
tradingSystem = TradingSystem(tsParams)
tradingSystem.startTrading(onlyAnalyze=False, 
                           shouldPlot=True,
                           makeInstrumentCsvs=False)

Шаг 8: другие способы улучшения модели

Прокручивающаяся проверка, ансамблевое обучение, упаковка, повышение

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

1. Скользящая проверка

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

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

2. Ансамблевое обучение

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

Чтобы этот пост был кратким, я пропущу эти методы, но вы можете узнать больше о них здесь.

Давайте попробуем метод ансамбля для нашей задачи

basis_y_pred_ensemble = (basis_y_trees + basis_y_svr +
                         basis_y_knn + basis_y_regr)/4

Среднеквадратичная ошибка: 0,02
Оценка дисперсии: 0,95

Весь код для вышеперечисленных шагов доступен в этом блокноте IPython. Подробнее читайте ниже:

Это было довольно много информации. Давайте сделаем краткий обзор:

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

Уф! Но дело не в этом. Теперь у вас есть только надежная модель прогноза. Помните, чего мы на самом деле хотели от нашей стратегии? Вам по-прежнему необходимо:

  • Разработайте сигнал для определения направления торговли на основе модели прогнозирования
  • Разработайте стратегию определения точек входа / выхода
  • Система исполнения для определения размеров и цены

И затем вы, наконец, можете отправить этот приказ своему брокеру и совершить автоматическую торговлю!

Важное примечание о транзакционных расходах. Почему так важны следующие шаги? Ваша модель сообщает вам, когда выбранный вами актив является покупкой или продажей. Однако он не принимает во внимание комиссии / транзакционные издержки / доступные объемы торгов / стопы и т. Д. Транзакционные издержки очень часто превращают прибыльные сделки в убыточные. Например, актив с ожидаемым увеличением цены на 0,05 доллара является покупкой, но если вам придется заплатить 0,10 доллара, чтобы совершить эту сделку, вы получите чистый убыток в размере -0,05 доллара. Наша собственная великолепно выглядящая диаграмма прибыли, приведенная выше, на самом деле выглядит так после того, как вы учтете комиссии брокера, биржевые сборы и спреды:

Комиссии за транзакции и спреды составляют более 90% нашего Pnl! Мы обсудим их подробно в следующем посте.

Наконец, давайте рассмотрим несколько распространенных ошибок.

ДЕЛАТЬ и НЕ НУЖНО

  • ИЗБЕГАЙТЕ ПЕРЕОБОРУДОВКИ ЛЮБОЙ ЗАТРАТОЙ!
  • Не переучивайтесь после каждого сбора данных: это была распространенная ошибка, которую люди допускали в QuantQuest. Если ваша модель требует повторного обучения после каждого набора данных, вероятно, это не очень хорошая модель. Тем не менее, его нужно будет периодически переобучать, только с разумной частотой (например, переобучение в конце каждой недели, если вы делаете внутридневные прогнозы).
  • Избегайте предвзятости, особенно предвзятости. Это еще одна причина, по которой модели не работают. Убедитесь, что вы не используете информацию из будущего. В основном это означает, что не используйте целевую переменную Y как функцию в вашей модели. Это доступно вам во время тестирования на истории, но не будет доступно, когда вы запустите свою модель вживую, что сделает вашу модель бесполезной.
  • Будьте осторожны с предвзятостью интеллектуального анализа данных: поскольку мы пробуем несколько моделей на наших данных, чтобы увидеть, подходит ли что-либо, без какой-либо внутренней причины, убедитесь, что вы выполняете строгие тесты, чтобы отделить случайные шаблоны от реальных шаблонов, которые могут возникнуть. в будущем. Например, то, что может показаться паттерном восходящего тренда, хорошо объясняемым линейной регрессией, может оказаться небольшой частью более крупного случайного блуждания!

Избегайте переобучения

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

  • Переобучение - самая опасная ловушка торговой стратегии
  • Сложный алгоритм может прекрасно работать при тестировании на исторических данных, но совершенно не работает на новых невидимых данных - этот алгоритм на самом деле не выявил никаких тенденций в данных и не обнаружил реальной предсказательной силы. Он просто очень хорошо соответствует данным, которые он видел.
  • Делайте ваши системы максимально простыми. Если вам нужно большое количество сложных функций для объяснения ваших данных, вы, вероятно, переборщите.
  • Разделите доступные данные на данные для обучения и тестирования и всегда проверяйте производительность на реальных данных вне выборки, прежде чем использовать свою модель для торговли в реальном времени.

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