
Анализ нескольких символов с помощью Python и MQL5 (Часть II): Анализ главных компонентов для оптимизации портфеля
Управление общим риском, которому подвержен портфель, является достаточно сложной задачей, с которой сталкиваются все члены торгового сообщества. Как можно всесторонне проанализировать и принять решение о целесообразном распределении активов в современном мире постоянно расширяющихся рынков, учитывая огромный выбор инвестиционных возможностей, предлагаемых современному инвестору? В предыдущей статье я продемонстрировал, как можно максимизировать общую доходность портфеля с помощью SciPy. Сегодня я сосредоточусь на том, как контролировать риск/дисперсию любого портфеля. Существует множество возможных моделей, которые мы можем использовать для контроля риска нашего портфеля. Мы можем использовать популярный инструмент статистики — анализ главных компонентов (Principal Components Analysis, PCA) — для эффективного управления общей дисперсией нашего портфеля.
Для членов нашего сообщества, желающих продавать советников, эта статья продемонстрирует, как добиться бесперебойной работы для ваших конечных пользователей. Наше торговое приложение будет гибким и надежным одновременно. Я покажу вам, как создавать торговые приложения, которые позволят вашим клиентам легко переключаться между режимами торговли с высоким, средним и низким риском. При этом алгоритм PCA выполнит наиболее трудоемкую работу для ваших конечных пользователей в фоновом режиме.
Обзор методологии
В этой статье мы будем управлять портфелем из 10 криптовалют. Криптовалюты — это цифровые активы, которые существуют в особом типе сети, известном как сеть блокчейн. Технология блокчейн — это протокол безопасности, который делает практически невозможным совершение мошеннических транзакций любым участником сети. Таким образом, учитывая мощь базовой сети, одним из первых популярных применений технологии блокчейн стала цифровая валюта. Однако эти цифровые активы печально известны своей высокой волатильностью, и инвесторам может быть сложно адекватно управлять уровнем риска при инвестировании в криптоактивы. К счастью, мы можем использовать статистические методы, такие как PCA, чтобы управлять общим объемом дисперсии, которому мы готовы подвергаться при инвестировании в криптовалюты.Анализ главных компонентов — это метод из раздела многомерной статистики, называемого факторным анализом. PCA нашел применение во многих областях: от анализа изображений до машинного обучения. В анализе изображений PCA обычно используется для таких задач, как сжатие данных, тогда как в машинном обучении он чаще всего применяется для снижения размерности.
Идея PCA заключается в поиске одного значения коэффициента для каждого столбца в наборе данных. Если каждый столбец умножить на его коэффициент, то новые столбцы, которые мы получим, должны максимизировать дисперсию набора данных. Если мы успешно завершили эту процедуру, мы успешно нашли первый главный компонент.
Второй главный компонент будет ортогонален первому. Это означает, что они полностью некоррелированы, одновременно максимизируя общую дисперсию набора данных. Этот паттерн действует до тех пор, пока мы не создадим достаточно компонентов для учета всей дисперсии в исходном наборе данных.
Я продемонстрирую вам, как каждый основной компонент по отдельности сопоставляется с дискретными уровнями риска, которые мы можем использовать в качестве настроек для наших торговых приложений. Это поможет вам разумно управлять настройками рисков за считанные минуты.
Начало работы
Наглядная демонстрация работы алгоритма будет весьма полезна читателям, которые, возможно, сталкиваются с этим алгоритмом впервые. Я возьму логотип MQL5 на рис. 1 и сначала преобразую его в черно-белый вид. Этот черно-белый фильтр облегчит нам применение и визуальное наблюдение за тем, как алгоритм PCA действует на наши данные.
Рис. 1. Мы используем логотип MQL5, чтобы продемонстрировать работу алгоритма PCA
Теперь, когда у нас есть изображение, с которым можно работать, давайте загрузим наши библиотеки Python.
#Let's go import pandas as pd import numpy as np import MetaTrader5 as mt5 import seaborn as sns import matplotlib.pyplot as plt from sklearn.decomposition import PCA from skimage import io, color
Считаем изображение и преобразуем его в черно-белый вид.
# Load and preprocess the image image = io.imread('mql5.png') # Replace with your image path image_gray = image_gray = color.rgb2gray(image) # Convert to grayscale
Сведем изображение к двум измерениям и применим PCA, ориентируясь на 5 различных уровней компонентов. Исходное изображение находится слева на рис. 2. Первый главный компонент максимизирует дисперсию входных данных. Как мы видим на изображении 1 Components, общая структура исходного изображения в некоторой степени сохранилась. Хотя текст "MQL5" в центре изображения больше не читается, мы можем обоснованно заключить из изображения, что на черном фоне присутствует белая структура в середине.
При использовании 5 главных компонентов текст на изображении становится разборчивым. Однако мелкие детали, такие как светло-серые значки, стилизованные под фон, теряются и требуют восстановления дополнительных компонентов.
Это упражнение должно дать вам интуитивное понимание того, что алгоритм PCA пытается создать компактные и некоррелированные представления исходных входных данных. Эта задача решается путем создания последовательных линейных комбинаций входных данных, которые максимизируют дисперсию входных данных.
# Flatten the image h, w = image_gray.shape image_flattened = image_gray.reshape(h, w) # Apply PCA with different numbers of components n_components_list = [1,5,20,50,100] # Number of components to keep fig, axes = plt.subplots(1, len(n_components_list) + 1, figsize=(20, 10)) axes[0].imshow(image_gray, cmap='gray') axes[0].set_title("Original Image") axes[0].axis('off') for i, n_components in enumerate(n_components_list): # Initialize PCA and transform the flattened image pca = PCA(n_components=n_components) pca.fit(image_flattened) # Transform and inverse transform the image transformed_image = pca.transform(image_flattened) reconstructed_image = pca.inverse_transform(transformed_image).reshape(h, w) # Plot the reconstructed image axes[i + 1].imshow(reconstructed_image, cmap='gray') axes[i + 1].set_title(f"{n_components} Components") axes[i + 1].axis('off') plt.tight_layout() plt.show()
Рис. 2. Первые 2 главных компонента логотипа MQL5
Рис. 3. Последние 3 главных компонента логотипа MQL5
Вместо изображения нас больше интересует максимизация или минимизация дисперсии портфеля. Мы можем добиться этого, передав набор данных, содержащий доходность каждого актива в портфеле, в наш алгоритм PCA.
Получение рыночных данных
Для начала нужно убедиться, что наш терминал MetaTrader 5 запущен.
mt5.initialize()
Теперь перечислим все активы, которые будут в нашем портфеле.
#List of cryptocurrencies we wish to invest crypto = [ "BCHUSD", #BitcoinCash "EOSUSD", #EOS "BTCUSD", #Bitcoin "ETHUSD", #Etherum "ADAUSD", #Cardano "XRPUSD", #Ripple "UNIUSD", #Monero "DOGUSD", #Dogecoin "LTCUSD", #Litecoin "SOLUSD" #Solana ]
Мы хотим получить ежедневные рыночные данные за 6 лет.
fetch = 365 * 6
Наша модель будет делать прогнозы на 30 дней вперед.
look_ahead = 30
Создадим фрейм данных, в котором будет храниться наша криптовалютная доходность.
data = pd.DataFrame(columns=crypto,index=range(fetch))
Получим рыночные котировки для каждого из символов в нашем списке.
for i in range(0,len(crypto)): data[crypto[i]] = pd.DataFrame(mt5.copy_rates_from_pos(crypto[i],mt5.TIMEFRAME_M1,0,fetch)).loc[:,"close"]
Посмотрим на наши рыночные данные.
data
Рис. 4. Визуализация некоторых собранных нами рыночных данных, фиксирующих 6-летние исторические цены ряда криптовалют
Разведочный анализ данных
Торговля криптовалютами может быть сложной задачей, поскольку они очень волатильны. Рис. 5 помогает нам увидеть эффективность корзины из 10 криптовалют за 6-летний период с 2018 по 2024 год. Разница между активами меняется, и человеку может быть сложно ее рассчитать или эффективно использовать.
data_plotting = data.iloc[:,:]/data.iloc[0,:]
sns.lineplot(data_plotting)
Рис. 5. Эффективность 10 различных криптовалют, в которые мы могли бы инвестировать
Риск, привносимый в наш портфель каждой криптовалютой, можно визуализировать как скользящее стандартное отклонение доходности каждого символа. На рис. 6 видно, что периоды высокой волатильности, по-видимому, группируются вместе. Однако некоторые рынки демонстрируют более волатильное поведение, чем другие.
plt.plot(data_plotting.iloc[:,0].rolling(14).std()) plt.plot(data_plotting.iloc[:,1].rolling(14).std()) plt.plot(data_plotting.iloc[:,2].rolling(14).std()) plt.plot(data_plotting.iloc[:,3].rolling(14).std()) plt.plot(data_plotting.iloc[:,4].rolling(14).std()) plt.plot(data_plotting.iloc[:,5].rolling(14).std()) plt.plot(data_plotting.iloc[:,6].rolling(14).std()) plt.plot(data_plotting.iloc[:,7].rolling(14).std()) plt.plot(data_plotting.iloc[:,8].rolling(14).std()) plt.plot(data_plotting.iloc[:,9].rolling(14).std()) plt.legend(crypto) plt.title("The Risk Associated With Each of our Cryptocurrencies") plt.xlabel("Time") plt.ylabel("Standard Deviation")
Рис. 6. Стандартное отклонение каждой криптовалюты в нашем портфеле
Алгоритм PCA поможет нам намеренно минимизировать или максимизировать нашу подверженность общей дисперсии портфеля, визуализированного на рис. 6. Попытка вручную решить, какие активы максимизируют дисперсию нашего портфеля, учитывая, что в нем 10 активов, может оказаться для человека невыполнимой задачей без посторонней помощи.При анализе активов одного и того же класса вполне ожидаемо наличие сильной корреляции. Особый интерес представляют уровни корреляции между:
- EOSUSD и XRPUSD
- DOGUSD и BCHUSD
#Correlation analysis
sns.heatmap(data.corr(),annot=True)
Рис. 7: Визуализация уровней корреляции в нашем портфеле из 10 криптовалют
Рассчитаем изменение уровня цен за 2 недели.
#Calculate the change over 2 weeks data = data.diff(14).dropna().reset_index(drop=True)
Теперь установим масштабировщик.
scaler = RobustScaler() scaled_data = pd.DataFrame(scaler.fit_transform(data), columns=data.columns)
Подберем объект PCA из scikit learn на основе наших ежедневных рыночных доходов.
pca = PCA() pca.fit(scaled_data)
Давайте теперь проанализируем первый главный компонент. Знак коэффициента указывает нам, следует ли нам занимать длинные или короткие позиции на каждом рынке. Положительные коэффициенты говорят нам о необходимости покупки, а отрицательные — о необходимости продажи. Поэтому, чтобы максимизировать дисперсию нашего портфеля, алгоритм PCA предлагает открывать короткие позиции на всех выбранных нами рынках.
Для трейдеров, которые хотят построить стратегии скальпинга, использование этого первого главного компонента — правильное решение.
#High risk strategy pca.components_[0]
Кроме того, каждый компонент также предоставляет нам оптимальные коэффициенты распределения капитала, которые соответственно максимизируют или минимизируют дисперсию. Однако, чтобы получить эту информацию, нам необходимо применить к компоненту несколько преобразований.
Разделим компонент на его норму L1. Норма L1 (L1 Norm) представляет собой сумму абсолютных значений компонента. Разделив каждый показатель основной нагрузки на их общую сумму, мы узнаем оптимальные пропорции, которые следует выделить каждому активу, чтобы максимизировать дисперсию нашего портфеля.
Рассчитав долю каждой криптовалюты в компоненте, мы можем умножить коэффициент распределения активов на количество позиций, которые мы хотим открыть, чтобы получить приблизительное значение того, сколько позиций нам следует открыть на каждом рынке. Мы рассчитали сумму, чтобы доказать, что общая сумма позиций составит 100, если умножить коэффициент распределения активов на 100.
#High risk asset allocations can be estimated from the first principal component high_risk_asset_allocation = pca.components_[0] / np.linalg.norm(pca.components_[0],1) np.sum(high_risk_asset_allocation * 100)
Например, если мы хотим открыть 10 позиций, следуя высокорисковой стратегии, наш основной компонент предполагает, что мы продаем 1 позицию в BCHUSD (Bitcoin Cash). Десятичную часть коэффициента можно интерпретировать как позицию немного меньшего размера лота. Однако для точного учета этого фактора в нашем портфеле может потребоваться значительное количество времени. Возможно, проще будет положиться на целую часть распределения.
high_risk_asset_allocation * 10
Теперь перейдем к главным компонентам среднего риска.
#Mid risk strategy pca.components_[len(crypto)//2]
И, наконец, наши главные компоненты с низким уровнем риска в целом предполагают совершенно иную торговую стратегию.
#Low risk strategy pca.components_[len(crypto)-1]
Давайте сохраним эти основные компоненты в текстовом файле, чтобы мы могли использовать полученные результаты в нашем торговом приложении MetaTrader 5.
np.savetxt("LOW RISK COMPONENTS.txt",pca.components_[len(crypto)-1]) np.savetxt("MID RISK COMPONENTS.txt",pca.components_[len(crypto)//2]) np.savetxt("HIGH RISK COMPONENTS.txt",pca.components_[0])
Реализация средствами MQL5
Теперь мы готовы приступить к внедрению нашего торгового приложения для MetaTrader 5.
Наше приложение будет стремиться следовать результатам оптимального распределения активов. Мы будем открывать только одну сделку на каждом рынке в соответствии с текущими пользовательскими настройками риска. Торговое приложение будет рассчитывать время входа, используя скользящую среднюю и стохастический осциллятор.
Если главный компонент предполагает, что нам следует открывать длинные позиции на рынке, мы будем ждать, пока уровни цен поднимутся выше значения скользящей средней на этом рынке, а стохастический осциллятор поднимется выше 50. Как только оба условия будут выполнены, мы займем длинную позицию и соответствующим образом установим уровни тейк-профита и стоп-лосса с использованием ATR. Мы надеемся, что такой выбор времени выхода на рынок со временем даст нам более стабильные и последовательные сигналы для входа.
Для начала мы определим пользовательский перечислитель, который предоставит конечному пользователю контроль над параметрами риска нашего торгового приложения.
//+------------------------------------------------------------------+ //| PCA For Portfolio Optimization.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/en/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| Custom enumerations | //+------------------------------------------------------------------+ enum risk_level { High=0, Mid=1, Low=2 };
Загружаем нужные нам библиотеки.
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
Определим постоянные значения.
//+------------------------------------------------------------------+ //| Constants values | //+------------------------------------------------------------------+ const double high_risk_components[] = {#include "HIGH RISK COMPONENTS.txt"}; const double mid_risk_components[] = {#include "MID RISK COMPONENTS.txt"}; const double low_risk_components[] = {#include "LOW RISK COMPONENTS.txt"}; const string crypto[] = {"BCHUSD","EOSUSD","BTCUSD","ETHUSD","ADAUSD","XRPUSD","UNIUSD","DOGUSD","LTCUSD","SOLUSD"};
Наши глобальные переменные, которые мы будем использовать во всей нашей программе.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ double current_risk_settings[]; double vol,bid,ask; int atr_handler; int stoch_handler; int ma_handler; double atr_reading[],ma_reading[],stoch_reading[];
Мы позволим конечному пользователю динамически контролировать настройки риска торгового счета и желаемый размер лота.
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "Risk Levels" input risk_level user_risk = High; //Which risk level should we use? input group "Money Management" input int lot_multiple = 1; //How big should out lot size be?
При загрузке нашего торгового приложения мы сначала загрузим наши главные компоненты в соответствии с выбранной пользователем настройкой риска. Все это за нас сделает функция "настройки".
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our market data setup(); //--- return(INIT_SUCCEEDED); }
Если мы больше не используем советник, освободим ненужные ресурсы.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release the indicators IndicatorRelease(atr_handler); IndicatorRelease(ma_handler); IndicatorRelease(stoch_handler); }
Наконец, всякий раз, когда мы получаем обновленные цены, будем поддерживать портфель из одной позиции на каждом рынке криптовалют. Позиция, которую мы займем, будет определяться выбранными пользователем настройками риска. В зависимости от уровня риска нам может потребоваться занять позицию на покупку или продажу, чтобы контролировать дисперсию в портфеле.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Ensure we always have 10 positions if(PositionsTotal() < 10) { //--- Let's see which market we aren't in for(int i = 0; i < 10; i++) { //--- Check if we can now enter that market if(!PositionSelect(crypto[i])) { check_setup(i); } } } }
Функция настройки определит, какой параметр риска выбрал конечный пользователь, а затем загрузит соответствующий главный компонент.
//+------------------------------------------------------------------+ //| Setup our market data | //+------------------------------------------------------------------+ void setup(void) { //--- First let us define the current risk settings switch(user_risk) { //--- The user selected high risk case 0: { ArrayCopy(current_risk_settings,high_risk_components,0,0,WHOLE_ARRAY); Comment("EA in high risk mode"); break; } //--- The user selected mid risk case 1: { ArrayCopy(current_risk_settings,mid_risk_components,0,0,WHOLE_ARRAY); Comment("EA in mid risk mode"); break; } //--- The user selected low risk //--- Low risk is also the default setting for safety! default: { ArrayCopy(current_risk_settings,low_risk_components,0,0,WHOLE_ARRAY); Comment("EA in low risk mode"); break; } } }
Поскольку мы активно торгуем 10 различными символами, нам нужна функция, отвечающая за получение рыночных данных, связанных с каждым символом. Функция update, определенная ниже, выполнит эту задачу за нас. Всякий раз, когда вызывается функция, она загружает в память текущую цену bid и ask, а также показания других технических индикаторов, которые рассчитываются на основе указанного рынка. После того, как мы использовали данные и решили, какое действие предпринять, функция загрузит в память рыночные данные следующего символа.
//+------------------------------------------------------------------+ //| Update our system varaibles | //+------------------------------------------------------------------+ void update(string market) { //--- Get current prices bid = SymbolInfoDouble(market,SYMBOL_BID); ask = SymbolInfoDouble(market,SYMBOL_ASK); //--- Get current technical readings atr_handler = iATR(market,PERIOD_CURRENT,14); stoch_handler = iStochastic(market,PERIOD_CURRENT,5,3,3,MODE_EMA,STO_CLOSECLOSE); ma_handler = iMA(market,PERIOD_CURRENT,30,0,MODE_EMA,PRICE_CLOSE); //--- Copy buffer CopyBuffer(atr_handler,0,0,1,atr_reading); CopyBuffer(ma_handler,0,0,1,ma_reading); CopyBuffer(stoch_handler,0,0,1,stoch_reading); //--- }
Наконец, нам нужна функция, которая будет проверять, можем ли мы открыть позицию, соответствующую текущим настройкам риска, указанным пользователем. В противном случае, если мы не сможем этого сделать, мы дадим конечному пользователю содержательное объяснение, почему мы не можем открыть позицию на этом рынке.
//+------------------------------------------------------------------+ //| Open a position if we can | //+------------------------------------------------------------------+ void check_setup(int idx) { //--- The function takes the index of the symbol as its only parameter //--- It will look up the principal component loading of the symbol to determine whether it should buy or sell update(crypto[idx]); vol = lot_multiple * SymbolInfoDouble(crypto[idx],SYMBOL_VOLUME_MIN); if(current_risk_settings[idx] > 0) { if((iClose(crypto[idx],PERIOD_D1,0) > ma_reading[0]) && (stoch_reading[0] > 50)) { Comment("Analyzing: ",crypto[idx],"\nMA: ",ma_reading[0],"\nStoch: ",stoch_reading[0],"\nAction: Buy"); Trade.Buy(vol,crypto[idx],ask,(ask - (atr_reading[0] * 3)),(ask + (atr_reading[0] * 3)),"PCA Risk Optimization"); return; } else { Comment("Waiting for an oppurtunity to BUY: ",crypto[idx]); } } else if(current_risk_settings[idx] < 0) { if((iClose(crypto[idx],PERIOD_D1,0) < ma_reading[0]) && (stoch_reading[0] < 50)) { Comment("Analyzing: ",crypto[idx],"\nClose: ","\nMA: ",ma_reading[0],"\nStoch: ",stoch_reading[0],"\nAction: Sell"); Trade.Sell(vol,crypto[idx],bid,(bid + (atr_reading[0] * 3)),(bid - (atr_reading[0] * 3)),"PCA Risk Optimization"); return; } else { Comment("Waiting for an oppurtunity to SELL: ",crypto[idx]); return; } } Comment("Analyzing: ",crypto[idx],"\nMA: ",ma_reading[0],"\nStoch: ",stoch_reading[0],"\nAction: None"); return; }; //+------------------------------------------------------------------+
Рис. 8. Просмотр нашего советника в MetaTrader 5
Рис. 9. Настройка параметров риска нашего торгового приложения
Рис. 10. Наше торговое приложение тестируется в реальном времени на реальных рыночных данных
Заключение
PCA обычно рассматривается как довольно сложная тема из-за математической нотации, необходимой для изучения этой концепции. Надеюсь, эта статья показала вам, как использовать PCA простым и понятным способом, который вы можете начать применять уже сегодня, даже если вы новичок в этой теме.
Управление рисками при распределении вашего портфеля — непростая задача. Математические инструменты и модели — лучший способ измерения и управления рисками, которым вы подвергаетесь на финансовых рынках. Особенно, когда количество рынков, на которых вы хотите работать, увеличивается.
Независимо от того, сколько у нас символов — 10 или 100, PCA всегда покажет нам, какие комбинации максимизируют риск портфеля, а какие — минимизируют.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16273
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования