
Análisis de múltiples símbolos con Python y MQL5 (Parte II): Análisis de componentes principales para la optimización de carteras
Gestionar el riesgo total al que está expuesta una cartera es una tarea bastante desafiante para todos los miembros de nuestra comunidad comercial. Considerando la gran cantidad de oportunidades de inversión que se ofrecen al inversor moderno, ¿cómo puede alguien analizar y decidir exhaustivamente las asignaciones de activos adecuadas en el mundo actual de mercados en constante expansión? En nuestra última discusión, demostré cómo se podría maximizar el rendimiento total de la cartera utilizando SciPy. Hoy me centraré en cómo controlar el riesgo/varianza de cualquier cartera que pueda tener a mano. Hay muchos modelos posibles que podemos utilizar para controlar el riesgo de nuestra cartera. Podemos utilizar una herramienta popular de la estadística, el Análisis de Componentes Principales (Principal Components Analysis, PCA), para gestionar eficazmente la varianza total de nuestra cartera.
Para los miembros de nuestra comunidad que buscan vender Asesores Expertos, este artículo demostrará cómo pueden crear una experiencia perfecta para sus usuarios finales. Nuestra aplicación comercial será flexible y robusta al mismo tiempo. Le mostraré cómo crear aplicaciones comerciales que permitirán a sus clientes cambiar fácilmente entre modos comerciales de riesgo alto, medio y bajo. Mientras que el algoritmo PCA se encargará del trabajo pesado para los usuarios finales en segundo plano.
Descripción general de la metodología
En este artículo gestionaremos una cartera de 10 criptomonedas. Las criptomonedas son activos digitales que existen en un tipo especial de red conocida como red blockchain. La tecnología blockchain es un protocolo de seguridad que hace prácticamente imposible que cualquier miembro de la red realice transacciones fraudulentas. Por lo tanto, dada la solidez de la red subyacente, uno de los primeros usos populares de la tecnología blockchain fue la moneda digital. Sin embargo, estos activos digitales son conocidos por su gran volatilidad, y a los inversores les puede resultar difícil gestionar adecuadamente sus niveles de riesgo al invertir en criptoactivos. Afortunadamente, podemos utilizar métodos estadísticos como el PCA para gestionar la cantidad total de varianza a la que deseamos exponernos al invertir en criptomonedas.El análisis de componentes principales es una técnica que proviene de una rama de la estadística multivariante llamada análisis factorial. El PCA se utiliza en muchos ámbitos, desde el análisis de imágenes hasta el aprendizaje automático. En el análisis de imágenes, el PCA se utiliza habitualmente para tareas como la compresión de datos, mientras que en el aprendizaje automático se emplea con mayor frecuencia para la reducción de la dimensionalidad.
La idea del PCA es encontrar un valor de coeficiente para cada columna del conjunto de datos. Si cada columna se multiplica por su coeficiente, las nuevas columnas que obtendremos deberían maximizar la varianza del conjunto de datos. Si hemos completado con éxito este procedimiento, habremos encontrado con éxito el primer componente principal.
El segundo componente principal será ortogonal al primero, lo que significa que no estarán correlacionados entre sí, al tiempo que maximizan la varianza total del conjunto de datos. Este patrón continúa hasta que hayamos creado suficientes componentes para explicar toda la varianza del conjunto de datos original.
Le mostraré cómo cada componente principal se asigna individualmente a niveles de riesgo discretos que podemos utilizar como ajustes para nuestras aplicaciones de trading. Esto le ayudará a gestionar de forma inteligente sus ajustes de riesgo en cuestión de minutos.
Primeros pasos
Creo que una demostración visual del funcionamiento del algoritmo sería muy útil para los lectores que se enfrentan al algoritmo por primera vez. Tomaré la imagen del logotipo de MQL5 de la figura 1 y primero la convertiré a blanco y negro. Este filtro en blanco y negro nos facilitará la aplicación y nos permitirá ver visualmente lo que el algoritmo PCA está haciendo con nuestros datos.
Fig. 1: Utilizaremos el logotipo de MQL5 que aparece arriba para demostrar el funcionamiento del algoritmo PCA.
Ahora que tenemos una imagen con la que trabajar, carguemos nuestras bibliotecas de 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
Lea la imagen y conviértala a blanco y negro.
# 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
Aplana la imagen a dos dimensiones y aplica el PCA apuntando a cinco niveles de componentes diferentes. La imagen original se encuentra en el extremo izquierdo de la figura 2. El primer componente principal maximiza la varianza de los datos de entrada. Como podemos ver en la imagen titulada «1 Components», la estructura general de la imagen original se conserva en cierta medida. Aunque el texto «MQL5» en el centro de la imagen ya no es legible, podemos deducir razonablemente a partir de la imagen que hay un fondo negro con una estructura blanca en el centro.
Cuando se utilizan 5 componentes principales, el texto de la imagen es legible. Sin embargo, se pierden detalles finos como los iconos de color gris claro que se integran en el fondo y es necesario recuperar más componentes.
Este ejercicio debería proporcionarle una comprensión intuitiva de que el algoritmo PCA intenta crear representaciones compactas y no correlacionadas de los datos de entrada originales. Esta tarea se logra creando combinaciones lineales sucesivas de los datos de entrada que maximizan la varianza de los datos de entrada.
# 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()
Fig. 2: Los dos primeros componentes principales, que resumen nuestro logotipo MQL5.
Fig. 3: Los tres últimos componentes principales, que resumen nuestro logotipo MQL5.
En lugar de una imagen, nos interesa más maximizar o minimizar la varianza de una cartera. Podemos lograrlo pasando un conjunto de datos que contenga los rendimientos de cada activo de la cartera a nuestro algoritmo PCA.
Obtención de nuestros datos de mercado
Para empezar, primero debemos asegurarnos de que nuestra terminal MetaTrader 5 esté activa.
mt5.initialize()
Ahora enumera todos los activos que mantendremos en nuestra cartera.
#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 ]
Queremos obtener los datos diarios del mercado de los últimos 6 años.
fetch = 365 * 6
Nuestro modelo realizará previsiones para los próximos 30 días.
look_ahead = 30
Crear el marco de datos que almacenará los rendimientos de nuestras criptomonedas.
data = pd.DataFrame(columns=crypto,index=range(fetch))
Obtener las cotizaciones de mercado para cada uno de los símbolos que tenemos en nuestra lista.
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"]
Obtener las cotizaciones de mercado para cada uno de los símbolos que tenemos en nuestra lista.
Datos
Fig. 4: Visualización de algunos de los datos de mercado que hemos recopilado, con el registro de seis años de precios históricos de una serie de criptomonedas.
Análisis exploratorio de datos
El comercio de criptomonedas puede ser complicado porque los valores son muy volátiles. La figura 5 nos ayuda a visualizar el rendimiento de una cesta de 10 criptomonedas durante un periodo de seis años, desde 2018 hasta 2024. El diferencial (spread) entre los activos evoluciona y puede que no resulte intuitivo para un ser humano calcularlo o utilizarlo de manera eficaz.
data_plotting = data.iloc[:,:]/data.iloc[0,:]
sns.lineplot(data_plotting)
Fig. 5: El rendimiento de 10 criptomonedas diferentes en las que podríamos haber invertido.
El riesgo que introduce cada criptomoneda en nuestra cartera puede visualizarse como la desviación estándar móvil de los rendimientos de cada símbolo. En la figura 6 podemos observar que los periodos de alta volatilidad parecen agruparse. Sin embargo, algunos mercados muestran un comportamiento más volátil que otros.
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")
Fig. 6: La desviación estándar de cada criptomoneda en nuestra cartera.
El algoritmo PCA nos ayudará a minimizar o maximizar intencionadamente nuestra exposición a la varianza total de la cartera que se visualiza en la figura 6. Intentar decidir manualmente qué activos maximizarán la varianza de nuestra cartera, dado que hay 10 activos, podría resultar imposible para un ser humano sin ayuda.Es de esperar que se observen fuertes niveles de correlación cuando analizamos activos de la misma clase de activos. Especialmente interesantes son los niveles de correlación entre:
- EOSUSD y XRPUSD
- DOGUSD y BCHUSD
#Correlation analysis
sns.heatmap(data.corr(),annot=True)
Fig. 7: Visualización de los niveles de correlación en nuestra cartera de 10 criptomonedas.
Calcula la variación en los niveles de precios durante dos semanas.
#Calculate the change over 2 weeks data = data.diff(14).dropna().reset_index(drop=True)
Ahora coloque el escalador.
scaler = RobustScaler() scaled_data = pd.DataFrame(scaler.fit_transform(data), columns=data.columns)
Ajustar el objeto PCA de Scikit learn a nuestros rendimientos diarios del mercado.
pca = PCA() pca.fit(scaled_data)
Analicemos ahora el primer componente principal. El signo del coeficiente nos indica si debemos ocupar posiciones largas o cortas en cada mercado. Los coeficientes positivos nos indican que debemos operar en largo plazo, y los coeficientes negativos nos indican que debemos operar en corto plazo. Por lo tanto, para maximizar la varianza de nuestra cartera, el algoritmo PCA sugiere operar en corto en todos los mercados que hemos seleccionado.
Para los traders que buscan desarrollar estrategias de scalping, este primer componente principal es el camino a seguir.
#High risk strategy pca.components_[0]
Además, cada componente también nos proporciona ratios óptimos de asignación de capital que maximizarán o minimizarán la varianza en consecuencia. Sin embargo, para obtener esta información, debemos aplicar algunas transformaciones al componente.
Dividiremos el componente por su Norma L1. La Norma L1 es la suma de los valores absolutos en el componente. Dividiendo cada puntuación de carga principal por su suma total, sabremos las proporciones óptimas que debemos asignar a cada activo para maximizar la varianza de nuestra cartera.
Después de calcular la proporción de cada criptomoneda en el componente, podemos multiplicar el ratio de asignación de activos por el número de posiciones que queremos abrir, para obtener una aproximación de cuántas posiciones debemos abrir en cada mercado. Calculamos la suma para demostrar que las posiciones totales sumarán 100 si multiplicamos el índice de asignación de activos por 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)
Por ejemplo, si quisiéramos abrir 10 posiciones siguiendo la estrategia de alto riesgo, nuestro componente principal sugiere que vendamos 1 posición en BCHUSD (Bitcoin Cash). La parte decimal del coeficiente se puede interpretar como una posición de un tamaño de lote ligeramente menor. Pero puede resultar que considerarlo con precisión en nuestra cartera consuma mucho tiempo. Quizás sea más sencillo confiar en la parte numérica completa de la asignación.
high_risk_asset_allocation * 10
Pasemos ahora a los componentes principales de riesgo medio.
#Mid risk strategy pca.components_[len(crypto)//2]
Y, por último, nuestros componentes principales de bajo riesgo sugieren una estrategia comercial completamente diferente.
#Low risk strategy pca.components_[len(crypto)-1]
Guardemos estos componentes principales en un archivo de texto, para que podamos utilizar los resultados en nuestra aplicación de trading 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])
Implementación en MQL5
Ya estamos listos para comenzar a implementar nuestra aplicación de trading en MetaTrader 5.
Nuestra aplicación comercial buscará seguir los hallazgos de nuestras asignaciones óptimas de activos. Abriremos solo 1 operación en cada mercado de acuerdo con la configuración de riesgo actual que haya seleccionado el usuario. La aplicación comercial cronometrará sus entradas utilizando el promedio móvil y el oscilador estocástico.
Si el componente principal sugiere que entremos en posiciones largas en el mercado, esperaremos a que los niveles de precios suban por encima de la lectura del promedio móvil en ese mercado y a que el Oscilador Estocástico esté por encima de 50. Una vez que se cumplan ambas condiciones, tomaremos nuestra posición larga y, en consecuencia, estableceremos nuestros niveles de take profit y stop loss utilizando el ATR. Esperamos que al programar nuestras entradas al mercado de esta manera obtengamos señales de entrada más estables y consistentes a lo largo del tiempo.
Para comenzar, primero definiremos el enumerador personalizado que le brinda a nuestro usuario final control sobre los parámetros de riesgo de nuestra aplicación comercial.
//+------------------------------------------------------------------+ //| 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 };
Cargue las bibliotecas que necesitamos.
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
Define valores constantes que no cambiarán en ningún momento.
//+------------------------------------------------------------------+ //| 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"};
Nuestras variables globales que utilizaremos a lo largo de nuestro programa.
//+------------------------------------------------------------------+ //| 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[];
Permitiremos al usuario final controlar dinámicamente la configuración de riesgo de la cuenta comercial y el tamaño de lote deseado.
//+------------------------------------------------------------------+ //| 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?
Cuando se carga nuestra aplicación comercial, primero cargaremos nuestros componentes principales de acuerdo con la configuración de riesgo que el usuario haya seleccionado. Todo esto lo gestionará la función "setup".
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our market data setup(); //--- return(INIT_SUCCEEDED); }
Si ya no utilizamos el Asesor Experto, liberemos los recursos que no estamos utilizando.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release the indicators IndicatorRelease(atr_handler); IndicatorRelease(ma_handler); IndicatorRelease(stoch_handler); }
Por último, cada vez que recibamos precios actualizados, mantengamos una cartera de 1 posición en cada mercado de criptomonedas. La posición que ocuparemos vendrá determinada por la configuración de riesgo que haya seleccionado el usuario. Dependiendo de la configuración de riesgo, puede que necesitemos ocupar una posición “buy” o “sell” para controlar la varianza en la cartera.
//+------------------------------------------------------------------+ //| 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); } } } }
La función de configuración determinará qué configuración de riesgo ha seleccionado el usuario final y luego cargará el componente principal correspondiente.
//+------------------------------------------------------------------+ //| 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; } } }
Dado que negociamos activamente 10 símbolos diferentes, necesitamos una función responsable de obtener datos de mercado relacionados con cada símbolo. La función "update" definida a continuación se encargará de esta tarea por nosotros. Cada vez que se llama a la función, cargará en la memoria el precio de oferta y demanda actual junto con otras lecturas de indicadores técnicos que se calculan a partir del mercado especificado. Una vez que hayamos utilizado los datos y decidido qué acción tomar, la función cargará los datos de mercado del próximo símbolo en la memoria.
//+------------------------------------------------------------------+ //| 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); //--- }
Por último, necesitamos una función que verifique si podemos abrir una posición alineada con la configuración de riesgo actual que el usuario ha especificado. De lo contrario, si no podemos, le daremos al usuario final una explicación significativa de por qué no podemos abrir una posición en ese mercado.
//+------------------------------------------------------------------+ //| 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; }; //+------------------------------------------------------------------+
Fig. 8: Visualización de nuestro asesor experto en MetaTrader 5.
Fig. 9: Ajuste de los parámetros de riesgo de nuestra aplicación de trading.
Fig. 10: Nuestra aplicación comercial se prueba en tiempo real con datos reales del mercado.
Conclusión
El PCA normalmente se considera un tema algo complicado, debido a la notación matemática requerida para aprender el concepto. Sin embargo, este artículo le ha mostrado cómo utilizar PCA de una manera fácil de usar, para que pueda comenzar a aplicarlo hoy mismo incluso si es la primera vez que aprende sobre el tema.
Gestionar el riesgo en la asignación de tu cartera no es una tarea sencilla, las herramientas y modelos matemáticos son la mejor forma de medir y gestionar el riesgo al que estás expuesto en los mercados financieros. Especialmente cuando el número de mercados en los que desea participar aumenta.
Ya sea que tengamos 10 símbolos o 100 símbolos, PCA siempre nos mostrará qué combinaciones maximizarán el riesgo en la cartera y qué combinaciones minimizarán el riesgo de nuestra cartera.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16273
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso