Эта серия статей посвящена пониманию AI/ML и тому, как это связано с торговлей валютой. Большинство статей в эфире сосредоточены на прогнозировании цены и почти бесполезны, когда речь идет о поиске прибыльных торговых стратегий, поэтому здесь основное внимание уделяется именно этому.
Обо мне
Я торгую на Fx уже 20 лет, используя как традиционный статистический анализ, так и анализ графиков, а также AI/ML последние 5 лет или около того. Имея степень бакалавра технических наук, степень магистра и несколько сертификатов в области машинного обучения, я расскажу о некоторых ловушках, на изучение которых у меня ушли годы, и объясню, почему заставить систему работать сложно, но не невозможно.
Введение
В первых статьях мы:
1. Создали самый простой пример «hello world». Мы собрали данные, создали модель и измерили наш результат. Это послужило основой для дальнейшей работы.
2. Мы используем весовые коэффициенты классов, чтобы получить нашу модель «в поле зрения» и, возможно, «немного лучше, чем угадывать», а также улучшить наши измерения.
3. В 3-м В этой статье мы углубились под обложки логистической регрессии, чтобы найти ее ограничения и понять, куда двигаться дальше.
4. Мы рассмотрели нормализацию и ее влияние, понимая, что наша гипотеза может быть неверной.
5. Краткое введение в использование других или новых функций и их влияние.
6. Обзоры этой статьи представляют собой основу для измерения. Это наша основа для сравнения, поэтому она должна быть правильной, чтобы мы могли сравнивать модели и комбинации функций. Мы изучаем некоторые подводные камни, так как это может быть сложно
Отказ от ответственности
Это никоим образом не является финансовым советом и не пропагандирует какую-либо конкретную торговую стратегию или предполагает возможность прибыльной стратегии, а вместо этого предназначено для того, чтобы помочь понять некоторые детали рынка Fx и то, как применять к нему методы ML.
Резюме модели
Во-первых, давайте подытожим нашу модель.
Наша гипотеза состоит в том, что что-то в данных OHLC за предыдущие 4 периода предсказывает внезапное изменение на 200 или более пунктов (для упрощения мы измеряем только «вверх» или «вверх») в следующие 4 периода. Мы добавили веса классов, различные методы нормализации и множество функций, но все без особого успеха.
Текущее измерение
В наших текущих измерениях используется типичная модель логистической регрессии (tp, fp, точность, полнота, f1 и т. д.), и впоследствии мы обнаружили, что она не подходит (см. предыдущие статьи).
Хотя метрики хорошие, они измеряют только движение выше 200 пунктов. Однако движение выше 0 означает прибыль. Мы прогнозируем изменение цены на 200 пунктов (в нашем обучении), но если цена движется на 100 пунктов, это все равно выигрыш. Поэтому в статье 2 (опираясь на привет, мир) мы модифицировали наш алгоритм измерения, чтобы учесть это.
Однако это тоже не отражает того, как на самом деле работает торговля на валютном рынке.
Реалии торговли на Форекс
Я много лет знаком с профессиональными трейдерами, и почти все они используют тейк-профит и стоп-лосс. Это означает, что они торгуют с расчетом на прибыль и выходят, как только достигают ее. У них также есть максимальный убыток, который они примут и выйдут, когда он будет достигнут. Это может произойти в любое время, даже если их гипотеза движения на четыре часа вперед, это может произойти в первый, второй или третий период.
На приведенном выше графике выше
Период 0 — цена движется вверх › 200 пунктов (выигрыш с учетом тейк-профита)
Точка 1 — цена идет вниз, примерно туда, где она началась
Период 2 — цена продолжает двигаться вниз
> Период 3 — Цена ‹ -100 пунктов (убыток)
Очень немногие трейдеры работают без стоп-лосса, поскольку он защищает наш счет от массовых падений, автоматически торгуя, поэтому TP и SL необходимо учитывать при оценке нашего успеха.
Предположим (позже они станут мета-параметрами):
- Мы выходим максимум через 4 периода (наш прогноз был 200 пунктов после четырех периодов, а затем выходим по любой цене, это период 0, 1, 2, 3)
- Тейк-профит 200 пунктов. Если цена поднимается на 200 пунктов в любой момент, мы выходим и фиксируем прибыль
- Стоп-лосс -150 пунктов. Если цена упадет на 150 пунктов в любой момент, мы выйдем из системы, приняв убыток (для защиты от того, что цена может упасть еще ниже)
У каждого трейдера есть свой собственный метод определения прибыли и убытков, но общее правило заключается в том, что, поскольку существуют риски, потенциал роста должен быть выше, чем потенциал снижения. Существуют эмпирические правила относительно «верхней и нижней стороны», но мы рассмотрим их в следующей статье.
Это влияет на две части нашего алгоритма: измерение И определение y_true, и нам нужно обновить обе.
Определение успеха (у)
Это наше самое большое изменение в этой статье. Мы обновляем способ измерения y, чтобы включить стоп-лосс и тейк-профит. Затем это означает изменение нашего кода, чтобы он перебирал каждый форвардный период в «диапазоне y» (4 периода в нашей текущей гипотезе), проверяя стоп-лосс или тейк-профит. Обратите внимание, что период может указывать как стоп-лосс, так и тейк-профит, поэтому мы предполагаем стоп-лосс (наихудший случай для прибыли). Кроме того, теперь мы знаем «точки», которые фактически перемещаются, ограниченные стоп-лоссом, тейк-профитом или тем, что он закончил в конце, что позволит нам добавить показатель прибыли.
Обратите внимание, что:
– Мы добавили дату к нашим функциям. Мы не будем использовать это как функцию (пока), но для построения графика ниже.
- При рассмотрении «тейк-профита» мы снова сравниваем «максимальную» цену за период.
- Мы рассматриваем «стоп-лосс». сравниваем с «минимальной» ценой в период
- Теперь у нас есть «пройденные точки (для определения прибыли) и период выхода (0, 1, 2 или 3 — для построения графика)
# create new holder for all values x_values_df = pd.DataFrame() # # X values look back # # loop thorugh feature name and "back periods" to go back x_feature_names = [] for feature in feature_names: for period in [1,2,3,4]: # create the name (eg 'x_audusd_close_t-1') feature_name = 'x_' + feature + '_t-' + str(period) x_feature_names.append(feature_name) x_values_df[feature_name] = df[feature].shift(period) # Add "starting" values when used in normalization and date for reference x_values_df['date'] = df['date'] x_values_df['x_audusd_open'] = df['audusd_open'].shift(4) x_values_df['x_eurusd_open'] = df['eurusd_open'].shift(4) x_values_df['audusd_open'] = df['audusd_open'] x_values_df['eurusd_open'] = df['eurusd_open'] # # Y values look forward # # add all future y values for future periods # set y=1 for >= 200 points # set y=-1 for <= -150 (for later use) # set y=0 for all else # set y_points to actual points finished with (for later calculation of profit) x_values_df['y'] = -2 # start wtih -2 to search for not -1,0,1 x_values_df['y_points'] = 0 # start with no point movement for period in [0,1,2,3]: # loop thorugh each forward period in y (4 forward periods) # names to store future price and change points name = 'y_t-' + str(period) #name of future y value price_name = 'y_change_price_' + str(period) # name of future y points change points_name = 'y_change_points_' + str(period) # name of future y points change # add important future values to spreadsheet x_values_df[name] = df['audusd_close'].shift(-period) x_values_df[name + '_low'] = df['audusd_low'].shift(-period) x_values_df[name + '_high'] = df['audusd_high'].shift(-period) # calculate change in points x_values_df[price_name] = x_values_df[name] - df['audusd_open'] x_values_df[points_name] = x_values_df[price_name] * 100000 x_values_df[price_name + '_low'] = x_values_df[name + '_low'] - df['audusd_open'] x_values_df[points_name + '_low'] = x_values_df[price_name + '_low'] * 100000 x_values_df[price_name + '_high'] = x_values_df[name + '_high'] - df['audusd_open'] x_values_df[points_name + '_high'] = x_values_df[price_name + '_high'] * 100000 # get and calculate all "down" values where y isnt already set # down "down" first, for case where bar goes both up and down in the same period assume down first (worst case for profit) down_df = x_values_df[(x_values_df['y'] == -2) & (x_values_df[points_name + '_low'] <= -150)] x_values_df.loc[down_df.index, 'y'] = -1 x_values_df.loc[down_df.index, 'y_points'] = -150 x_values_df.loc[down_df.index, 'y_finish_period'] = period # get and calculate all "up" vales where y isnt already set up_df = x_values_df[(x_values_df['y'] == -2) & (x_values_df[points_name + '_high'] >= 200)] x_values_df.loc[up_df.index, 'y'] = 1 x_values_df.loc[up_df.index, 'y_points'] = 200 x_values_df.loc[up_df.index, 'y_finish_period'] = period # if no period triggered tp/sl then no movement (y=0) and points are whatever it is at the end of the period none_df = x_values_df[x_values_df['y'] == -2] x_values_df.loc[none_df.index, 'y'] = 0 x_values_df.loc[none_df.index, 'y_points'] = x_values_df[points_name] x_values_df.loc[none_df.index, 'y_finish_period'] = 3 # set down (currently -1) to 0 since we arent using it (yet - we will later) x_values_df.loc[x_values_df[x_values_df['y'] == -1].index, 'y'] = 0 # if points exceeds tp or sl then reset to sl/tp since these limits are fixed in trading x_values_df.loc[x_values_df['y_points'] < -150, 'y_points'] = -150 x_values_df.loc[x_values_df['y_points'] > 200, 'y_points'] = 200 # and reset df (avoids indexing complications later) and done x_values_df = x_values_df.copy() return x_values_df, x_feature_names
Обновленное измерение
Наша переменная y обновляется выше и проходит через уже существующий код, поэтому алгоритм измерения будет работать без изменений. Однако, поскольку теперь у нас есть метрика «баллы», мы можем проверить нашу эффективную прибыль. Это довольно простое изменение нашего алгоритма измерения.
Несколько заметок:
- Мы упростили наше деление, используя функцию деления numpy, которая автоматически заботится о делении на 0.
- Мы добавили показатель «прибыль», который представляет собой накопленные баллы. Помните, что у нас есть тейк-профит в 200 пунктов и стоп-лосс в 150 пунктов. (поэтому наша потенциальная прибыль выше, чем наши потенциальные убытки).
def divide(a, b): a = np.asarray(a).astype(float) b = np.asarray(b).astype(float) result = np.divide(a, b, out=np.zeros_like(a), where=b != 0) return result def show_metrics(lr, x, y_true, y_points, display=True): x = x.to_numpy() y_true = y_true.to_numpy() y_points = y_points.to_numpy() # 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 to confusion matrix # tp = np.where((y_pred == 1) & (y_points >= 0), 1, 0).sum() fp = np.where((y_pred == 1) & (y_points < 0), 1, 0).sum() tn = np.where((y_pred == 0) & (y_points < 0), 1, 0).sum() fn = np.where((y_pred == 0) & (y_points >= 0), 1, 0).sum() # derived from confusion matrix precision = float(divide(tp, (tp+fp))) recall = float(divide(tp, (tp + fn))) f1 = float(divide((precision*recall), (precision + recall))) # profit calculation (if predicted use points, otherwise 0) profit = np.where(y_pred==1, y_points, 0).sum() # 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)) print('profit: {:.2f} points'.format(profit)) errors = { 'loss': log_loss_error, 'score': score, 'tp': tp, 'fp': fp, 'tn': tn, 'fn': fn, 'precision': precision, 'recall': recall, 'f1': f1, 'profit': profit } return errors
Полученные результаты
Во-первых, давайте добавим некоторый код для расчета «прибыли» (по сути, расчет сделки за сделкой), а затем для построения графика каждой сделки, чтобы мы могли визуализировать то, что происходит, и проверить, имеет ли смысл.
def get_trades(lr, x, y_change_points): y_pred = lr.predict(x) trades = [] for ix in range(len(y_pred)): if y_pred[ix] == 1: won = False profit = y_change_points.iloc[ix] if profit > 0: won = True trades.append([ix, won, profit]) trades_df = pd.DataFrame(trades, columns=['ix', 'won', 'profit']) return trades_df def chart_trades(trades, features_df, raw_df, number_of_charts): rows = math.ceil(number_of_charts / 3) fig, axes = plt.subplots(nrows=rows, ncols=3) for chart_ix in range(number_of_charts): # get indexes and ate for chart trade_ix = random.randint(0, len(trades)) data_ix = trades['ix'].iloc[trade_ix] + 35373 date = features_df['date'].iloc[data_ix] # prepard data for OHLC candles mpf_df = pd.DataFrame() mpf_df[['date', 'Open', 'High', 'Low', 'Close', 'Volume']] = \ raw_df[['date', 'audusd_open', 'audusd_high', 'audusd_low', 'audusd_close', 'audusd_volume']] \ .iloc[data_ix-4:data_ix+4].to_numpy() mpf_df['date'] = pd.to_datetime(mpf_df['date']) mpf_df = mpf_df.set_index('date') mpf_df = mpf_df[['Open', 'High', 'Low', 'Close', 'Volume']].astype(float) # plot the chart ax = axes.flatten()[chart_ix] mpf.plot(mpf_df, ax=ax, volume=False, datetime_format='', type='candle') # add title ax.set_title(date.strftime('%d-%m-%Y %H:%M')) # add marker to seperate history and future ax.plot([4, 4], [mpf_df['High'].iloc[4], ax.get_ylim()[1]], color='y', marker='o', linewidth=3.0) ax.plot([4, 4], [ax.get_ylim()[0], mpf_df['Low'].iloc[4]], color='y', marker='o', linewidth=3.0) # horizontal lines indicator tp and sl open_price = mpf_df['Open'].iloc[4] ax.axhline(open_price+(200/100000), color='green', linewidth=1.5) ax.axhline(open_price-(150/100000), color='red', linewidth=1.5) # plot line from start to close position close_period = int(features_df['y_finish_period'].iloc[data_ix]) profit = features_df['y_points'].iloc[data_ix] close_price = open_price + (profit / 100000) ax.plot([4,4+close_period], [mpf_df['Open'].iloc[4], close_price], color='r', marker='x', linewidth=1.5) # text box indicating result txt = 'Win: {}\nProfit: {:.2f}\nExit: {}'.format(trades.iloc[trade_ix]['won'], trades.iloc[trade_ix]['profit'], close_period) props = dict(boxstyle='round', facecolor='wheat', alpha=0.5) ax.text(0.05, 0.75, txt, style='italic', transform=ax.transAxes, size=10, bbox=props) plt.show() return
Этот тип упражнений по визуализации очень полезен, и я всегда рекомендую его. Это поможет вам «увидеть», что происходит, внимательно проверить ваши графики и, если что-то не имеет смысла, вернуться назад и выяснить, почему, скорее всего, у вас где-то есть ошибки.
Теперь мы можем запустить наш алгоритм тестирования и обновить метрики.
# # Main loop to test different normalization techniques # for norm_method in ['price', 'points', 'percentage', 'minmax', 'stddev']: # load raw data raw_df = load_data() # create features feature_names =['audusd_open', 'audusd_close', 'audusd_high', 'audusd_low', 'audusd_volume', \ 'eurusd_open', 'eurusd_close', 'eurusd_high', 'eurusd_low', 'eurusd_volume'] df, x_feature_names = create_xy_values(raw_df, feature_names) # prepare data for learning (normalize, split and class weights) norm_df, x_feature_names_norm = normalize_data(df, x_feature_names, method=norm_method) x_train, y_train, x_val, y_val, y_val_change_points, no_train_samples = get_train_val(norm_df, x_feature_names_norm) class_weights = get_class_weights(y_train, display=False) # train the model lr = LogisticRegression(class_weight=class_weights, max_iter=1000) lr.fit(x_train, y_train) # if we want to see all actual trades and chart them trades_df = get_trades(lr, x_val, y_val_change_points) print('trades {}, wins {}, profit {:.2f}'.format(len(trades_df), trades_df['won'].sum(), trades_df['profit'].sum())) # if we want to chart trades chart_trades(trades_df, df, raw_df, number_of_charts=9) # to show standard errors print('Errrors for method {}'.format(norm_method)) errors = show_metrics(lr, x_val, y_val, y_val_change_points, display=True)
Мы сразу видим, что производительность модели ухудшилась, и торговые потери будут значительными. Это вызывает несколько вопросов:
1. Почему производительность модели ухудшилась?
Точность не совсем низкая (около 43%), что значительно ниже предположений. Мы сделали две вещи. Рассматривая TP/SL, мы добавили еще один шаг, отделяющий результат Y от того, что происходит на самом деле. У нас также есть большое количество функций, с которыми логистическая регрессия не справится.
2. Почему торговые убытки такие большие?
Это легче увидеть, посчитав, сколько баров идет вверх, а сколько вниз.
def check_simultaneous_sl_tp(trades_df, features_df, raw_df): trade_ixs = trades_df['ix'].tolist() results = [] for ix in range(35373, len(features_df)): low = raw_df['audusd_low'].iloc[ix] high = raw_df['audusd_high'].iloc[ix] open = raw_df['audusd_open'].iloc[ix] points_high = round((high - open) * 100000, 2) points_low = round((low - open) * 100000, 2) gone_high, gone_low, during_trade = False, False, False if points_high > 200: gone_high = True if points_low < -150: gone_low = True if (ix-35373) in trade_ixs: during_trade = True results.append([ix, gone_high, gone_low, during_trade]) results_df = pd.DataFrame(results, columns=['ix', 'high', 'low', 'trade']) print('Total Periods {}'.format(len(results_df))) print('Highs {}'.format(len(results_df[results_df['high'] == True]))) print('Lows {}'.format(len(results_df[results_df['low'] == True]))) print('Trades {}'.format(len(results_df[results_df['trade'] == True]))) print('Not In Trade Highs AND Lows together {}'.format(len(results_df[(results_df['high'] == True) & (results_df['low'] == True)]))) print('In Trade Highs AND Lows together {}'.format(len(results_df[(results_df['high'] == True) & (results_df['low'] == True) & (results_df['trade'] == True)]))) return results_df
Всего периодов 15161
Максимумы 787
Минимумы 1743
Сделки 5087
Не в сделке Максимумы и минимумы вместе 43
В торговле максимумы и минимумы вместе 29
Мы видим, что движения вниз гораздо чаще, чем вверх, и 29 сделок имеют TP и ‹ SL за один и тот же период (мы предполагаем SL, но на самом деле не знаем, куда он пошел).
Примечание о кодировании
Отладка становится немного сложнее, поэтому я фактически перешел на VS Code, который, на мой взгляд, предоставляет более простые возможности отладки для совместной работы. Я советую вам проверить это, но я буду продолжать размещать обновления в colab.
Крайне важно, чтобы вы проверяли и перепроверяли свои расчеты. Помните: мусор на входе — мусор на выходе! Проверка, двойная проверка и тройная проверка. Если вы ошибетесь в своих входных данных (x или y), вы никогда не заставите их работать, или вы обнаружите, что они работают во время обучения, но не на практике, что приводит к убыткам при торговле.
Краткое содержание
Обновление измерения для включения стоп-лосса и тейк-профита ухудшило нашу модель, но нам нужно будет включить его, поскольку это важная часть торговли на валютном рынке.
Если использовать модель «как есть», наши торговые потери будут значительными.
Куда перейти к следующей статье
Есть несколько направлений, по которым мы можем пойти. Мы можем работать над оптимизацией наших мета-параметров (таких как стоп-лосс, тейк-профит, метод нормализации и т. д.) или мы можем работать над нашей точностью, что хуже, чем угадывать. Это то направление, в котором мы будем двигаться (обратите внимание, я говорю о метрике, которую мы пытаемся улучшить, а не о конкретной методике для этого).
В следующей статье мы сосредоточимся на повышении нашей точности с помощью новых производных функций и некоторых дополнительных функций.
Рекомендации
- Github
https://github.com/the-ml-bull/hello_world - YouTube
Скоро появится - Твиттер
@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 - Часть 4. Нормализация
https://medium.com/@the.ml.ai.bull/artificial-intelligence-and-machine-learning-for-foreign-exchange-fx -торговая-часть-4-3009349dac13 - Часть 5. Особенности
https://medium.com/@the.ml.ai.bull/artificial-intelligence-and-machine-learning-for-foreign-exchange-fx -торговая-часть-5-функций-58302743ec2d