Эта серия статей посвящена пониманию AI/ML и тому, как это связано с торговлей валютой. Большинство статей в эфире сосредоточены на прогнозировании цены и почти бесполезны, когда речь идет о поиске прибыльных торговых стратегий, поэтому мы сосредоточимся на этом здесь.
Обо мне
Я торгую на Fx уже 20 лет, используя как традиционный статистический анализ, так и анализ графиков, а также AI/ML последние 5 лет или около того. Имея степень бакалавра технических наук, степень магистра и несколько сертификатов в области машинного обучения, я хотел поделиться некоторыми подводными камнями, на изучение которых у меня ушли годы, и объяснить, почему заставить систему работать сложно, но не невозможно.
Введение
В первых двух статьях мы:
1. Создали простейший пример «hello world». Мы собирались собрать данные, сгенерировать модель и измерить наш результат
2. Мы использовали его, чтобы получить «примерную оценку» и, возможно, немного лучше, чем угадывать, и улучшили наши измерения.
3. В в третьем мы заглянули под обложки логистической регрессии, чтобы найти ее ограничения.
4. В этой статье мы рассмотрим проблему нормализации.
Отказ от ответственности
Это никоим образом не является финансовым советом и не пропагандирует какую-либо конкретную торговую стратегию, а предназначено для того, чтобы помочь понять некоторые детали рынка Fx и то, как применять к нему методы машинного обучения.
Необходимость нормализации
Нормализация — это процесс, в котором мы помещаем все данные в одну шкалу. Рассмотрим цену AUDUSD за последние 7 лет.
Цена сместилась примерно с 0,55 до 0,90 . Помните из прошлой статьи, что в логистической регрессии используется «вес: (w), умноженный на функцию (x, которая в данном случае является ценой), и поэтому, если цена значительно меняется, значение веса теряет свою точность. Также при «обучении» вес будет масштабироваться по средней цене и терять практическую полезность, когда цена от него отклоняется.
Помните, что наша модель использует цену закрытия за последние 4 часа (на данный момент), чтобы предсказать, что цена вырастет (длинная сделка) на 200 пунктов. Итак, давайте посмотрим на 5 случайных случаев, которые произошли. Мы будем повторно использовать код из предыдущих статей, но с некоторыми изменениями.
Сначала импортируйте наши данные
# # IMPORT DATA From github # import pandas as pd from datetime import datetime url = 'https://raw.githubusercontent.com/the-ml-bull/Hello_World/main/Fx60.csv' dateparse = lambda x: datetime.strptime(x, '%d/%m/%Y %H:%M') df = pd.read_csv(url, parse_dates=['date'], date_parser=dateparse) df.head(n=10)
Затем рассчитайте изменения цен и т. д. Обратите внимание, что мы добавили каждый форвардный период для отдельного расчета. Это помогает при составлении графиков, но станет основой для улучшенных показателей результатов в следующей статье.
# # Create time shifted data as basis for model # import numpy as np df = df[['date', 'audusd_open', 'audusd_close']].copy() # x is the last 4 values so create x for each df['x_open'] = df['audusd_open'].shift(4) df['x_t-4'] = df['audusd_close'].shift(4) df['x_t-3'] = df['audusd_close'].shift(3) df['x_t-2'] = df['audusd_close'].shift(2) df['x_t-1'] = df['audusd_close'].shift(1) # add all future prices to measurement point df['y_t-0'] = df['audusd_close'] df['y_t-1'] = df['audusd_close'].shift(-1) df['y_t-2'] = df['audusd_close'].shift(-2) df['y_t-3'] = df['audusd_close'].shift(-3) # y is points 4 periods into the future - the open price now (not close) df['y_future'] = df['audusd_close'].shift(-3) df['y_change_price'] = df['y_future'] - df['audusd_open'] df['y_change_points'] = df['y_change_price'] * 100000 df['y'] = np.where(df['y_change_points'] >= 200, 1, 0)
Теперь давайте наметим 5 случайных случаев
# # Chart data # import random import matplotlib.pyplot as plt for chart_ix in range(5): random_ix = random.randint(0, len(true_events_df)) event = true_events_df.iloc[random_ix] x = [-4, -3, -2, -1, 0, 1, 2, 3] y = event[['x_t-4', 'x_t-3', 'x_t-2', 'x_t-1', 'y_t-0', 'y_t-1', 'y_t-2', 'y_t-3']] event_date = event['date'].strftime('%Y-%b-%d %H') plt.plot(x, y, label=event_date) #print('{:.5f}, {:.5f}, {:.5f}'.format(event['audusd_open'], event['y_t-3'], event['y_t-3']-event['audusd_open'])) plt.axvline(x = 0, color='black') plt.legend() plt.show()
Вы можете ясно видеть, что мы работаем в очень разных масштабах на каждом мероприятии. Однако в логистической регрессии наши веса фиксированы и одинаковы для каждого события. Следовательно, он просто не будет работать эффективно. Входные данные (переменные x) должны быть в одном масштабе.
Параметры нормализации
В Fx есть несколько методов, которые мы можем использовать для масштабирования данных.
Рынок Форекс работает и разговаривает в пунктах (точнее, в ПИПах — для другой статьи). Трейдеры часто устанавливают свои «тейк-профит» и «стоп-лосс» по цене или количеству пунктов. Иногда это делается с использованием процентов, но это не так часто. Следовательно, точки могут сделать вещи читабельными и потенциально обеспечить не менее хорошие результаты.
Используя точки, приведенная выше диаграмма становится:
# # Chart 5 occations where price went up 200 points using points instead of price # import random import matplotlib.pyplot as plt for chart_ix in range(5): random_ix = random.randint(0, len(true_events_df)) event = true_events_df.iloc[random_ix] x = [-4, -3, -2, -1, 0, 1, 2, 3] #y = event[['x_t-4', 'x_t-3', 'x_t-2', 'x_t-1', 'y_t-0', 'y_t-1', 'y_t-2', 'y_t-3']] y_points = (event[['x_t-4', 'x_t-3', 'x_t-2', 'x_t-1']] - event['x_open']) * 100000 y_points['y_t-0'] = (event['y_t-0'] - event['audusd_open']) * 100000 y_points['y_t-1'] = (event['y_t-1'] - event['audusd_open']) * 100000 y_points['y_t-2'] = (event['y_t-2'] - event['audusd_open']) * 100000 y_points['y_t-3'] = (event['y_t-3'] - event['audusd_open']) * 100000 event_date = event['date'].strftime('%Y-%b-%d %H') plt.plot(x, y_points, label=event_date) plt.axvline(x = 0, color='black') plt.legend() plt.show()
Сразу видно, что теперь все масштабируется в том же порядке. Но давайте сравним методы цены, пунктов, процентов, минимального максимума и стандартного отклонения друг с другом. Как всегда, давайте начнем с гипотезы (они примерно одинаковые, но точки более читабельны) и посмотрим, подтвердится ли она. Обратите внимание, что наша переменная y всегда равна 0 или 1, но мы нормализуем «диаграмму», чтобы она была в том же масштабе (использование цены или пунктов приведет к искажению шкалы и сделает ее нечитаемой).
Для этого нам нужно будет создать множество функций на основе работы, которую мы проделали ранее, поэтому мы собираемся собрать все вместе, чтобы создать итеративный подход к поиску различий между методами нормализации (ссылка на github внизу страницы). страница)
from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import MinMaxScaler, StandardScaler def normalize_data(df, method='price'): norm_df = df.copy() x_fields = ['x_t-4', 'x_t-3', 'x_t-2', 'x_t-1'] y_fields = ['y_t-0', 'y_t-1', 'y_t-2', 'y_t-3'] if method == 'price': for field in x_fields: norm_df[field + '_norm'] = df[field] for field in y_fields: norm_df[field + '_norm'] = df[field] if method == 'points': for field in x_fields: norm_df[field + '_norm'] = (df[field] - df['x_open']) * 100000 for field in y_fields: norm_df[field + '_norm'] = (df[field] - df['audusd_open']) * 100000 if method == 'percentage': for field in x_fields: norm_df[field + '_norm'] = (df[field] - df['x_open']) / df[field] * 100 for field in y_fields: norm_df[field + '_norm'] = (df[field] - df['audusd_open']) / df[field] * 100 if method == 'minmax': scaler = MinMaxScaler() scaled = scaler.fit_transform(df[x_fields + y_fields]) norm_field_names = [x + '_norm' for x in x_fields + y_fields] norm_df[norm_field_names] = scaled if method == 'stddev': scaler = StandardScaler() scaled = scaler.fit_transform(df[x_fields + y_fields]) norm_field_names = [x + '_norm' for x in x_fields + y_fields] norm_df[norm_field_names] = scaled return norm_df def get_class_weights(y_train, display=True): # # Create class weights # from sklearn.utils.class_weight import compute_class_weight num_ones = np.sum(y_train) num_zeros = len(y_train) - num_ones classes = np.unique(y_train) class_weights = compute_class_weight(class_weight='balanced', classes=classes, y=y_train) class_weights = dict(zip(classes, class_weights)) if display: print('In the training set we have 0s {} ({:.2f}%), 1s {} ({:.2f}%)'.format(num_zeros, num_zeros/len(y_train)*100, num_ones, num_ones/len(y_train)*100)) print('class weights {}'.format(class_weights)) return class_weights def get_train_val(df): # # Create Train and Val datasets # x = df[['x_t-4_norm', 'x_t-3_norm', 'x_t-2_norm', 'x_t-1_norm']] y = df['y'] y_points = df['y_change_points'] # Note Fx "follows" (time series) so randomization is NOT a good idea # create train and val datasets. no_train_samples = int(len(x) * 0.7) x_train = x[4:no_train_samples] y_train = y[4:no_train_samples] x_val = x[no_train_samples:-3] y_val = y[no_train_samples:-3] y_val_change_points = y_points[no_train_samples:-3] return x_train, y_train, x_val, y_val, y_val_change_points from sklearn.metrics import log_loss, confusion_matrix, precision_score, recall_score, f1_score def show_metrics(lr, x, y_true, y_change_points, display=True): # predict from teh val set meas we have predictions and true values as binaries y_pred = lr.predict(x) #basic error types log_loss_error = log_loss(y_true, y_pred) score = lr.score(x, y_true) # # Customized metrics # tp = np.where((y_pred == 1) & (y_change_points >= 0), 1, 0).sum() fp = np.where((y_pred == 1) & (y_change_points < 0), 1, 0).sum() tn = np.where((y_pred == 0) & (y_change_points < 0), 1, 0).sum() fn = np.where((y_pred == 0) & (y_change_points >= 0), 1, 0).sum() precision = 0 if (tp + fp) > 0: precision = tp / (tp + fp) recall = 0 if (tp + fn) > 0: recall = tp / (tp + fn) f1 = 0 if (precision + recall) > 0: f1 = 2 * precision * recall / (precision + recall) # output the errors if display: print('Errors Loss: {:.4f}'.format(log_loss_error)) print('Errors Score: {:.2f}%'.format(score*100)) print('Errors tp: {} ({:.2f}%)'.format(tp, tp/len(y_val)*100)) print('Errors fp: {} ({:.2f}%)'.format(fp, fp/len(y_val)*100)) print('Errors tn: {} ({:.2f}%)'.format(tn, tn/len(y_val)*100)) print('Errors fn: {} ({:.2f}%)'.format(fn, fn/len(y_val)*100)) print('Errors Precision: {:.2f}%'.format(precision*100)) print('Errors Recall: {:.2f}%'.format(recall*100)) print('Errors F1: {:.2f}'.format(f1*100)) errors = { 'loss': log_loss_error, 'score': score, 'tp': tp, 'fp': fp, 'tn': tn, 'fn': fn, 'precision': precision, 'recall': recall, 'f1': f1 } return errors import random import matplotlib.pyplot as plt def chart(norm_df, event_ix_to_plot, norm_method, errors): fig, ax = plt.subplots() for ix in event_ix_to_plot: event = norm_df.iloc[ix] x = [-4, -3, -2, -1, 0, 1, 2, 3] y = event[['x_t-4_norm', 'x_t-3_norm', 'x_t-2_norm', 'x_t-1_norm', 'y_t-0_norm', 'y_t-1_norm', 'y_t-2_norm', 'y_t-3_norm']] event_date = '{}'.format(ix) + ' - ' + event['date'].strftime('%Y-%b-%d %H') ax.plot(x, y, label=event_date) ax.axvline(x = 0, color='black') ax.legend(loc='lower right') ax.set_title('Method: {}'.format(norm_method)) textstr = 'loss: {:.2f}\nTP: {}\nFP: {}\nPrecision: {:.2f}%\nRecall: {:.2f}%\nF1: {:.2f}%'.format( errors['loss'], errors['tp'], errors['fp'], errors['precision']*100, errors['recall']*100, errors['f1']*100) props = dict(boxstyle='round', facecolor='wheat', alpha=0.5) ax.text(0.05, 0.95, textstr, transform=ax.transAxes, fontsize=8, verticalalignment='top', bbox=props) plt.show() import numpy as np import pandas as pd from datetime import datetime def load_data(): url = 'https://raw.githubusercontent.com/the-ml-bull/Hello_World/main/Fx60.csv' dateparse = lambda x: datetime.strptime(x, '%d/%m/%Y %H:%M') df = pd.read_csv(url, parse_dates=['date'], date_parser=dateparse) df = df[['date', 'audusd_open', 'audusd_close']].copy() # x is the last 4 values so create x for each df['x_open'] = df['audusd_open'].shift(4) df['x_t-4'] = df['audusd_close'].shift(4) df['x_t-3'] = df['audusd_close'].shift(3) df['x_t-2'] = df['audusd_close'].shift(2) df['x_t-1'] = df['audusd_close'].shift(1) # add all future prices to measurement point df['y_t-0'] = df['audusd_close'] df['y_t-1'] = df['audusd_close'].shift(-1) df['y_t-2'] = df['audusd_close'].shift(-2) df['y_t-3'] = df['audusd_close'].shift(-3) # y is points 4 periods into the future - the open price now (not close) df['y_future'] = df['audusd_close'].shift(-3) df['y_change_price'] = df['y_future'] - df['audusd_open'] df['y_change_points'] = df['y_change_price'] * 100000 df['y'] = np.where(df['y_change_points'] >= 200, 1, 0) return df event_ix_to_plot = None for norm_method in ['price', 'points', 'percentage', 'minmax', 'stddev']: df = load_data() norm_df = normalize_data(df, method=norm_method) x_train, y_train, x_val, y_val, y_val_change_points = get_train_val(norm_df) if event_ix_to_plot is None: valid_events = norm_df[norm_df == 1] events_to_plot = valid_events.sample(6) event_ix_to_plot = events_to_plot.index class_weights = get_class_weights(y_train, display=False) lr = LogisticRegression(class_weight=class_weights) lr.fit(x_train, y_train) errors = show_metrics(lr, x_val, y_val, y_val_change_points, display=False) chart(norm_df, event_ix_to_plot, norm_method, errors)
Результаты для каждого метода, отображающие одни и те же события, выглядят следующим образом.
Точность примерно такая же, но отзыв немного меняется. Однако статистика утверждает, что на самом деле нет разницы в точности (настолько близкой, что «статистическая» разница равна 0), что указывает на то, что нормализация не имеет значения. Однако это противоречит нашему пониманию математики (вес * характеристика) и большей части письменной работы, необходимой для этого.
Что происходит?
Принципиально есть две возможности.
а) большая часть научного и академического сообщества очень умных докторов наук ошибается в отношении необходимости нормализации.
б) наша первоначальная гипотеза (о том, что что-то в предыдущие 4 периода цены предсказывает внезапное изменение) неверна или плохо коррелирует .
Очевидно, что последнее гораздо более вероятно и в данном случае правильно. Мы еще не доказали эту гипотезу, и в этом основная проблема, и нам нужно рассмотреть другой подход (следующая статья).
Вам нужен этот тип «тупиковой» проверки гипотезы — это нормально.
- Разработайте гипотезу
- проверьте ее
- не подтвердите ее
- двигайтесь дальше
это типичный ход событий, и вы будете делать это много раз, прежде чем найдете что-то, что работает для вас.
Краткое содержание
Вывод здесь таков, что ни один из этих методов на самом деле не приводит нас туда, где мы должны быть, и лишь немного лучше (или то же самое), чем угадывание. Очень маловероятно, что какой-либо из этих вариантов может стать основой успешной стратегии.
Следующая статья
В следующей статье мы внесем некоторые коррективы в наше измерение (которое еще не совсем верно) и немного изучим работу Fx, прежде чем мы перейдем к добавлению дополнительных функций и начнем использовать различные методы машинного обучения.
Рекомендации
- Github
https://github.com/the-ml-bull/hello_world - Ютуб
https://youtu.be/WcYi0_H9OgI - Твиттер
@the_ml_bull - Часть 1 — Hello World
https://medium.com/@the.ml.ai.bull/artificial-intelligence-and-machine-learning-for-foreign-exchange- fx-трейдинг-f1e3c3efef78 - Часть 2. Расширение Hello World
https://medium.com/@the.ml.ai.bull/artificial-intelligence-and-machine-learning-for-foreign-exchange -fx-торговая-часть-2-расширение-4d93347064a2 - Часть 3. Логистическая регрессия
https://medium.com/@the.ml.ai.bull/artificial-intelligence-and-machine-learning-for-foreign-exchange- fx-трейдинг-часть-3-подъем-1b7c1a24ac1b