Características del Wizard MQL5 que debe conocer (Parte 62): Uso de patrones del ADX y el CCI con aprendizaje por refuerzo TRPO
Introducción
Continuamos analizando cómo se pueden combinar en el aprendizaje automático los indicadores técnicos que rastrean diferentes partes de la acción del precio. En el artículo anterior, vimos cómo el aprendizaje supervisado en un perceptrón multicapa (MLP) sienta las bases para la predicción de la evolución de los precios. Nos referimos a las entradas de la MLP como características y a sus salidas de pronóstico como estados. Desde la forma en que definimos nuestras características en el artículo anterior, que era ligeramente diferente de nuestro enfoque en los artículos 57 a 60, nuestro objetivo era tener un vector de entrada más continuo en contraposición a la opción discreta que habíamos utilizado. El cambio hacia los datos continuos y la regresión, y el alejamiento de los datos discretos y la clasificación, se puede argumentar mejor si observamos las tendencias de la IA.
Antes, para que un programa informático proporcionara una respuesta útil o práctica, esta respuesta debía introducirse manualmente en el programa mediante código. Básicamente, la cláusula if era fundamental para programar la mayoría de los programas. Y si lo piensas bien, la dependencia de las cláusulas if implicaba que los datos introducidos por el usuario o los datos procesados por el programa tenían que pertenecer a ciertas categorías. Tenía que ser discreto. Por lo tanto, se puede argumentar, en su mayor parte, que nuestro desarrollo y uso de datos discretos fue una respuesta a las limitaciones de programación y no fue pertinente a los datos o al problema que se estaba resolviendo.
Y entonces llegó OpenAI en otoño de 2023 con su primer GPT público, y todo cambió. El desarrollo de las redes de transformadores y los GPT no se produjo de la noche a la mañana, ya que los primeros perceptrones se desarrollaron a finales de los años 60, pero se puede afirmar con seguridad que el lanzamiento de ChatGPT fue un hito importante. Con la amplia adopción de modelos de lenguaje a gran escala, ha quedado muy claro que la tokenización, la incrustación de palabras y, por supuesto, la autoatención son componentes críticos para permitir que los modelos escalen en función de lo que pueden procesar. No más cláusulas condicionales. Es teniendo en cuenta este contexto de uso de tokenización e incrustación de palabras para lograr que las entradas de la red sean lo más continuas posible, que también hicimos que las entradas de nuestra MLP de aprendizaje supervisado fueran "más continuas".
Para ilustrar esto, nuestra segunda característica, característica-1, se representa de la siguiente manera en la MLP desde Python:
def feature_1(adx_df, cci_df): """ Creates a modified 3D signal array with: 1. ADX > 25 (1 when above 25, else 0) 2. CCI crosses from below 0 to above +50 (1 when condition met, else 0) 3. CCI crosses from above 0 to below -50 (1 when condition met, else 0) """ # Initialize empty array with 3 dimensions feature = np.zeros((len(adx_df), 5)) # Dimension 1: ADX above 25 (continuous, not just crossover) feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: CCI crosses from <0 to >+50 feature[:, 1] = (cci_df['cci'] > 50).astype(int) feature[:, 2] = (cci_df['cci'].shift(1) < 0).astype(int) # Dimension 3: CCI crosses from >0 to <-50 feature[:, 3] = (cci_df['cci'] < -50).astype(int) feature[:, 4] = (cci_df['cci'].shift(1) > 0).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
Si hubiéramos seguido el método que habíamos estado utilizando para los artículos del 57 al 60, se habría procesado de la siguiente manera:
def feature_1(adx_df, cci_df): """ """ # Initialize empty array with 3 dimensions and same length as input feature = np.zeros((len(dem_df), 3)) # Dimension 1: feature[:, 0] = (adx_df['adx'] > 25).astype(int) feature[:, 1] = ((cci_df['cci'] > 50) & (cci_df['cci'].shift(1) < 0)).astype(int) feature[:, 2] = ((cci_df['cci'] < -50) & (cci_df['cci'].shift(1) > 0)).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
Este enfoque tiende a clasificar las señales de acuerdo con los patrones típicos esperados para las tendencias alcistas y bajistas, ya que el segundo elemento del vector de salida captura únicamente las características de una señal alcista. El tercer elemento solo refleja rasgos bajistas. Al ceñirse a patrones ya definidos como alcistas o bajistas, este enfoque tiende a ser más un clasificador y, por lo tanto, discreto. Dicho esto, los resultados de nuestras pruebas mostraron que solo 3 de los 10 patrones probados pudieron superar la prueba walk-forward desde el 01/01/2024 hasta el 01/01/2025, habiendo sido probados/entrenados desde el 01/01/2020 hasta el 01/01/2024. El símbolo utilizado fue EUR/USD y el marco temporal fue el diario.
Por lo tanto, parece que, dado el amplio período de tiempo y la ventana de entrenamiento relativamente larga que utilizamos, podría ser conveniente seguir utilizando datos de entrada más discretos para nuestra MLP inicial. Podríamos argumentar a favor de esto si analizamos los datos de entrada del modelo LLM. Sí, la tokenización y la incrustación de palabras hacen que los datos de entrada sean más continuos, sin embargo, el "ingrediente secreto" del modelo LLM de autoatención es inherentemente discreto. Esto se debe a que busca asignar un peso de importancia relativa a cada una de las palabras proporcionadas en el texto de entrada.
No estamos haciendo nada similar, y por lo tanto, esa podría ser una explicación. Por lo tanto, los lectores son libres de modificar y probar diferentes formatos de entrada, ya que se adjunta todo el código fuente de MQL5. Por nuestra parte, mantendremos este enfoque y veremos qué resultados obtenemos con el aprendizaje por refuerzo.

Aprendizaje por refuerzo
Partiendo del modelo de aprendizaje supervisado de nuestro último artículo, introducimos acciones y recompensas. Recordemos que teníamos características como entradas para nuestra red neuronal multicapa (MLP) y estados (cambios previstos en el precio) como salidas. Las acciones en esta etapa representan el cómo de lo que debemos hacer cuando sabemos lo que pronostica nuestra MLP. Por ejemplo, si la previsión apunta a una caída de los precios, podríamos realizar una orden de venta limitada, una orden de venta con stop o una venta instantánea a precio de mercado. Desarrollar y entrenar una red de políticas puede ayudar a perfeccionar esta decisión.
Normalmente, en un escenario como el del ejemplo anterior, en el que se realizan diferentes tipos de órdenes de venta, el tamaño del vector de salida de la red de políticas coincidiría con el número de acciones posibles. En este caso, serían 3, por las 3 opciones: orden limitada, orden de stop y orden de mercado. El entrenamiento en estas condiciones debería generar diferencias en el rendimiento, y el lector está invitado a explorar este aspecto. Por nuestra parte, mantenemos la idea de que las acciones son un vector de una sola dimensión que, en esencia, es una réplica de nuestro vector de salida de estados del MLP del artículo anterior. ¿Qué propósito tiene esto entonces? Sirve como confirmación de la previsión a largo o corto plazo realizada por la red de aprendizaje supervisado.
Además, se utilizan recompensas para dimensionar la cantidad de ganancias obtenidas en cada operación realizada. Las recompensas son el resultado de la red de valor y, aunque las estamos midiendo nuevamente como un vector unidimensional, también pueden ser multidimensionales. Esto se debe a que el análisis posterior a la operación puede considerar más que solo las ganancias o las pérdidas, sino que también puede incluir las fluctuaciones. Estas excursiones pueden ser favorables o adversas, por lo tanto, el vector de recompensas también puede tener un tamaño tridimensional al incluir la excursión adversa, la excursión favorable y la ganancia neta.
Optimización de la política de la región de confianza
La optimización de políticas por regiones de confianza (Trust Region Policy Optimization, TRPO) es un algoritmo de aprendizaje por refuerzo cuyo objetivo principal es mejorar la política. Lo hace de forma iterativa, actualizando los pesos y sesgos de la red de políticas, al tiempo que los mantiene dentro de una «región de confianza» de la política actual.
Los componentes clave para su implementación son la red de políticas, la región de confianza y la divergencia KL. La red de políticas es una red neuronal que representa la selección de acciones mediante la asignación de estados a una distribución de probabilidad sobre las posibles acciones. La región de confianza es una restricción que limita el cambio en la política en cada iteración. Garantiza que la nueva política no se desvíe demasiado de la anterior, evitando así la inestabilidad. Finalmente, la divergencia KL mide la diferencia entre las distribuciones de probabilidad previstas y las de referencia. En esencia, define la restricción de la región de confianza.
El proceso de entrenamiento implica: recopilar datos, idealmente en lotes de trayectorias, utilizando la política actual; estimar la función de ventaja para cada par estado-acción en los datos recopilados para tener una idea de cuán mejor es una acción en comparación con la acción promedio; formular el problema de optimización para encontrar pesos y sesgos adecuados de las redes de política y valor que maximicen las recompensas sujetas a restricciones de divergencia KL que mantengan la divergencia entre políticas nuevas y antiguas dentro de un umbral específico; resolver el problema de optimización mediante técnicas como el descenso de gradiente; y finalmente actualizar los pesos de las redes de política y valor.
Las principales ventajas de TRPO son: una mejora monótona, en la que se garantiza la mejora de la política; la estabilidad, ya que la región de confianza evita que la política realice actualizaciones innecesariamente grandes que podrían resultar inestables; y la eficiencia, ya que TRPO tiende a aprender de forma eficaz con menos muestras que otros métodos de gradiente de políticas. En resumen, la idea central de TRPO es maximizar la ventaja esperada de una nueva política sobre una política antigua, sujeta a una restricción sobre cuánto se le permite cambiar a dicha política. Esto se refleja en las siguientes ecuaciones;

Donde:
- θ: Nuevos parámetros de política.
- θold: Parámetros de política antiguos antes de la actualización.
- πθ(a∣s): Probabilidad de la acción a bajo la nueva política πθ
- πθold(a∣s): Probabilidad de la acción a bajo la política antigua πθold
- Aπθold(s,a): Función de ventaja, que estima cuánto mejor es a que la acción promedio en el estado s.
- ρθold (s): Distribución de visita de estados bajo la política anterior.
- DKL: Divergencia de Kullback-Leibler (KL), que mide la diferencia entre las políticas antiguas y las nuevas.
- δ: Restricción de la región de confianza (valor positivo pequeño).
La red de políticas
Implementamos nuestras redes de políticas y valores en Python de la siguiente manera:
class PolicyNetwork(nn.Module): def __init__(self, state_dim, action_dim, hidden_size=64, discrete=False): super(PolicyNetwork, self).__init__() self.discrete = discrete self.fc1 = nn.Linear(state_dim, hidden_size) self.fc2 = nn.Linear(hidden_size, hidden_size) self.export_mode = False if self.discrete: self.fc3 = nn.Linear(hidden_size, action_dim) else: self.mean = nn.Linear(hidden_size, action_dim) self.log_std = nn.Parameter(torch.zeros(action_dim)) def forward(self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) if self.discrete: action_probs = F.softmax(self.fc3(x), dim=-1) dist = Categorical(action_probs) else: mean = self.mean(x) std = torch.exp(self.log_std) if self.export_mode: return mean, std # return raw tensors cov_mat = torch.diag_embed(std).unsqueeze(dim=0) dist = MultivariateNormal(mean, cov_mat) return dist
La red de políticas hereda del módulo nn.module, lo que la convierte en un módulo de red de PyTorch. Toma como parámetros state-dimension, el tamaño del espacio de estados de entrada; action-dimension, el tamaño del espacio de acciones; hidden-size, el tamaño de las capas ocultas, para el cual establecemos por defecto 64. También tomamos como entrada un indicador booleano etiquetado como "discreto" que establece si el espacio de acción es discreto o continuo. Este indicador discreto termina por determinar la estructura de la capa de salida y el tipo de distribución utilizada.
Esta configuración hace que nuestra red sea versátil a la hora de gestionar espacios de acción tanto discretos como continuos en entornos de aprendizaje por refuerzo. Así, juegos como CartPole pueden usar la opción discreta en True, mientras que en nuestro caso de comercio o incluso en casos de robótica, esto se establecería en False. En TRPO, la red de políticas define la política del agente, que es una asignación de estados a acciones. Por lo tanto, la flexibilidad para manejar diferentes tipos de espacios de acción puede resultar importante para la aplicabilidad general. Para los operadores, podríamos querer limitar nuestras acciones a los 3 tipos de órdenes: límites, stops y órdenes de mercado, como se mencionó anteriormente, y en ese caso se asignaría el valor True a discrete.
Al implementar esto, es importante asegurarse de que la dimensión de estado y la dimensión de acción coincidan con las especificaciones establecidas en el entorno o los conjuntos de datos utilizados. El indicador discreto también debe coincidir con el tipo de espacio de acción del entorno. El tamaño oculto de 64 es un hiperparámetro ajustable que podría justificar su aumento o incluso la adición de más capas ocultas dependiendo de la complejidad del entorno/conjunto de datos utilizado.
La arquitectura de red presenta 2 capas lineales totalmente conectadas, donde fc1 asigna el estado de entrada a una capa oculta y fc2 lo asigna a otra capa oculta del mismo tamaño. El "Modo de exportación" es un indicador que se utiliza para controlar si la red devuelve tensores sin procesar para su exportación o una distribución para entrenamiento/muestreo.
Estas capas constituyen la base de la red de políticas, ya que, en efecto, transforman las entradas de estado sin procesar en una representación de nivel superior de una acción adecuada para su selección. Al ser una arquitectura simple de dos capas con funciones de activación ReLU, obtenemos suficiente expresividad a la vez que mantenemos el modelo ligero. En TRPO, la red de políticas debe ser diferenciable. Esto es importante para calcular los gradientes de las actualizaciones de la política. Con estos dos sistemas lineales, se cumple este requisito.
La elección de dos capas ocultas de tamaño 64 es razonable como configuración predeterminada, pero a menudo puede requerir ajustes a medida que el entorno o los conjuntos de datos probados se vuelven más complejos o grandes. En los casos en que se combinen más de 2 indicadores para obtener patrones de características o se introduzcan estados más elaborados en la red de políticas, entonces sería necesario aumentar este número.
Finalmente, dependiendo de si nuestra red está funcionando en modo discreto o no, nuestra capa de salida final contará con una o dos redes. Para espacios discretos, la salida de la capa lineal fc3 es una representación de los logits de cada acción. Si, por otro lado, la opción "discreto" se establece en falso, entonces estaríamos trabajando con espacios continuos, en cuyo caso dos redes generarían un vector separado cada una. En primer lugar, se calcula la media de una distribución gaussiana de cada dimensión de acción. En segundo lugar, tenemos un vector de desviación estándar logarítmica de la distribución gaussiana de cada dimensión de acción.
Esto es importante porque la bifurcación permite modelar en diferentes tipos de espacios de acción. La opción discreta genera una distribución de probabilidad sobre un número preestablecido de acciones. La alternativa continua, es decir, no discreta, produce una distribución gaussiana multivariada. Se trata simplemente de dos vectores, donde uno de ellos, la media, proporciona una media indicativa y, por lo tanto, una ponderación para cada acción; mientras que el otro vector de desviaciones estándar logarítmicas proporciona una medida de dispersión, o de confianza, para cada una de las predicciones de la media.
En TRPO, la distribución de políticas se utiliza para muestrear acciones y calcular probabilidades logarítmicas para las actualizaciones del gradiente de la política. Por lo tanto, la elección entre métodos discretos y continuos afecta la cantidad de cálculos necesarios y la eficiencia de la red. La desviación estándar en escala logarítmica es un parámetro que se puede aprender y que permite a la red adaptar el nivel de exploración (varianza) durante el entrenamiento. Esto es importante para equilibrar la exploración y la explotación.
Para acciones discretas, la dimensión de la acción debe coincidir con el número de acciones posibles. Nos ceñiremos a una sola dimensión, ya que se trata de una variable continua, pero retomaremos las opciones de acción discretas en artículos posteriores. Sin embargo, para nuestras acciones continuas actuales, siempre es esencial inicializar log_std con cuidado. Comenzar en Torch.zeros(action_dim) significa que las desviaciones estándar iniciales son exp(0)=1, lo que puede ser demasiado amplio o demasiado estrecho dependiendo de la escala de acción. Debe existir un método de escalado específico para cada entorno.
Asimismo, en TRPO, las probabilidades logarítmicas de la política se utilizan en la función objetivo y en las restricciones de divergencia KL. Esto significa que es vital garantizar la estabilidad numérica dentro de la distribución. Finalmente, en los casos en que el entorno utiliza acciones acotadas, es probable que las salidas de la red queden fuera del alcance en ocasiones. Por lo tanto, esto requeriría recortar o escalar los valores medios de salida para asegurar que se ajusten al rango previsto.
La pasada hacia delante de la red de políticas realiza un procesamiento común donde el estado de entrada x se pasa a través de fc1 y fc2 con activaciones ReLU aplicadas para agregar no linealidad. En caso de que las acciones sean discretas, la salida de fc3 pasa por una función softmax para producir probabilidades de acción. Si es continua, por otro lado, la media se calcula a través de la capa de media; la desviación estándar se obtiene como exp(log_std) para asegurar que sea positiva; si export_mode se establece en verdadero, las desviaciones estándar se devuelven como tensores sin procesar. Si export_mode es falso, entonces se procederá a la construcción de una matriz de covarianza diagonal (cov_mat) y a la creación de una distribución normal multivariada para el muestreo.
El paso hacia adelante define la manera en que la política asigna estados a distribuciones de acciones, y este es el núcleo de la toma de decisiones del agente de aprendizaje por refuerzo. En TRPO, la distribución de políticas: muestrea acciones durante la interacción con el entorno, calcula probabilidades logarítmicas para el objetivo de gradiente de política y evalúa la restricción de la región de confianza. El uso de distribuciones categóricas y normales multivariadas garantiza la compatibilidad con bibliotecas de aprendizaje por refuerzo estándar como torch.distributions de PyTorch. La opción de modo de exportación permite una implementación práctica, ya que se utilizan resultados sin procesar, que luego pueden ser procesados según sea necesario.
La función softmax en el caso discreto garantiza que las probabilidades sumen 1. La inestabilidad numérica tiende a ser frecuente dentro de los logits y, por lo tanto, se debe aplicar un monitoreo de NaN y utilizar Torch.Clamp según sea necesario. Para acciones continuas, la matriz cov-mat diagonal asume dimensiones de acción independientes. Si, por el contrario, las acciones están correlacionadas, entonces se debe aplicar una matriz de covarianza completa. Esto aumentará el costo computacional. En TRPO, las probabilidades logarítmicas de la política deben calcularse de manera eficiente y precisa, ya que se utilizan en los pasos de gradiente conjugado y búsqueda lineal.
La red de valor
Implementamos nuestra red de valor de la siguiente manera:
class ValueNetwork(nn.Module): def __init__(self, state_dim, hidden_size=64): super(ValueNetwork, self).__init__() self.fc1 = nn.Linear(state_dim, hidden_size) self.fc2 = nn.Linear(hidden_size, hidden_size) self.fc3 = nn.Linear(hidden_size, 1) def forward(self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x
Existe una considerable superposición entre el diseño y la implementación de la red de valor y la red de políticas. Por lo tanto, pasaré por alto la mayor parte. Sin embargo, en principio, la red de valor estima el valor del estado para el cálculo de la ventaja (o recompensa) y la reducción de la varianza. También estamos utilizando una arquitectura simple, como con la red de políticas, y nuestra salida es un único escalar. Esta red es vital para la actualización estable de las políticas de TRPO mediante estimaciones precisas de las recompensas.
Agente TRPO
Implementamos nuestra clase de agente TRPO en Python de la siguiente manera:
class TRPO_Agent: def __init__(self, state_dim, action_dim, discrete=False, hidden_size=64, lr_v=0.001, gamma=0.99, delta=0.01, lambda_=0.97, max_kl=0.01, cg_damping=0.1, cg_iters=10, device='cpu'): self.policy = PolicyNetwork(state_dim, action_dim, hidden_size, discrete).to(device) self.value_net = ValueNetwork(state_dim, hidden_size).to(device) self.value_optimizer = optim.Adam(self.value_net.parameters(), lr=lr_v) self.gamma = gamma self.delta = delta self.lambda_ = lambda_ self.max_kl = max_kl self.cg_damping = cg_damping self.cg_iters = cg_iters self.discrete = discrete self.device = device self.state_dim = state_dim def get_action(self, state): # Convert state to tensor and add batch dimension state = torch.FloatTensor(state).unsqueeze(0).to(self.device) # Get action distribution from policy dist = self.policy(state) # Sample action from distribution action = dist.sample() # Get log probability BEFORE converting to numpy/item log_prob = dist.log_prob(action) # Convert action to appropriate format if self.discrete: action = action.item() # For discrete actions else: action = action.detach().cpu().numpy()[0] # For continuous actions # Clip continuous actions to [-1, 1] range (optional for discrete) if not self.discrete: action = np.clip(action, -1, 1) return action, log_prob def update_value_net(self, states, targets): # Convert inputs to proper tensor format if torch.is_tensor(states): states = states.detach().cpu().numpy() if torch.is_tensor(targets): targets = targets.detach().cpu().numpy() states = np.array(states, dtype=np.float32) targets = np.array(targets, dtype=np.float32) # Ensure proper shapes if len(states.shape) == 1: states = np.expand_dims(states, 0) if len(targets.shape) == 0: targets = np.expand_dims(targets, 0) states_tensor = torch.FloatTensor(states).to(self.device) targets_tensor = torch.FloatTensor(targets).to(self.device) # Forward pass self.value_optimizer.zero_grad() values = self.value_net(states_tensor) # Ensure matching shapes for loss calculation values = values.view(-1) targets_tensor = targets_tensor.view(-1) loss = F.mse_loss(values, targets_tensor) loss.backward() self.value_optimizer.step() def update_policy(self, states, actions, old_log_probs, advantages): # Handle tensor conversion safely def safe_convert(x): if torch.is_tensor(x): return x.detach().cpu().numpy() return np.array(x, dtype=np.float32) states = safe_convert(states) actions = safe_convert(actions) old_log_probs = safe_convert(old_log_probs) advantages = safe_convert(advantages) # Convert to tensors with proper shapes states_tensor = torch.FloatTensor(states).to(self.device) actions_tensor = torch.FloatTensor(actions).to(self.device) old_log_probs_tensor = torch.FloatTensor(old_log_probs).to(self.device) advantages_tensor = torch.FloatTensor(advantages).to(self.device) # Get old distribution with torch.no_grad(): old_dist = self.policy(states_tensor) # Compute gradient of surrogate loss def get_loss(): dist = self.policy(states_tensor) if self.discrete: log_probs = dist.log_prob(actions_tensor.long()) else: log_probs = dist.log_prob(actions_tensor) return -self.surrogate_loss(log_probs, old_log_probs_tensor, advantages_tensor) # Rest of the TRPO update remains the same... loss = get_loss() grads = torch.autograd.grad(loss, self.policy.parameters(), create_graph=True) flat_grad = torch.cat([grad.view(-1) for grad in grads]).detach() step_dir = self.conjugate_gradient(states_tensor, old_dist, flat_grad, nsteps=self.cg_iters) shs = 0.5 * torch.dot(step_dir, self.hessian_vector_product(states_tensor, old_dist, step_dir)) step_size = torch.sqrt(self.max_kl / (shs + 1e-8)) full_step = step_size * step_dir old_params = torch.cat([param.view(-1) for param in self.policy.parameters()]) def line_search(): for alpha in [0.5**x for x in range(10)]: new_params = old_params + alpha * full_step self.set_policy_params(new_params) with torch.no_grad(): new_dist = self.policy(states_tensor) new_loss = get_loss() kl = self.kl_divergence(old_dist, new_dist) if kl <= self.max_kl and new_loss < loss: return True return False if not line_search(): self.set_policy_params(old_params) def set_policy_params(self, flat_params): prev_idx = 0 for param in self.policy.parameters(): flat_size = param.numel() param.data.copy_(flat_params[prev_idx:prev_idx + flat_size].view(param.size())) prev_idx += flat_size def compute_advantages(self, rewards, values, dones): advantages = np.zeros_like(rewards) last_advantage = 0 for t in reversed(range(len(rewards))): if dones[t]: delta = rewards[t] - values[t] last_advantage = delta else: delta = rewards[t] + self.gamma * values[t+1] - values[t] last_advantage = delta + self.gamma * self.lambda_ * last_advantage advantages[t] = last_advantage advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8) return advantages def surrogate_loss(self, new_probs, old_probs, advantages): ratio = torch.exp(new_probs - old_probs) return torch.mean(ratio * advantages) def kl_divergence(self, old_dist, new_dist): if self.discrete: return torch.mean(torch.sum(old_dist.probs * (torch.log(old_dist.probs) - torch.log(new_dist.probs)), dim=1)) else: return torch.distributions.kl.kl_divergence(old_dist, new_dist).mean() def hessian_vector_product(self, states, old_dist, vector): kl = self.kl_divergence(old_dist, self.policy(states)) # First compute gradient of KL grads = torch.autograd.grad(kl, self.policy.parameters(), create_graph=True) flat_grad_kl = torch.cat([grad.view(-1) for grad in grads]) # Compute gradient of (grad_KL * vector) grad_vector_product = torch.sum(flat_grad_kl * vector) grad_grad = torch.autograd.grad(grad_vector_product, self.policy.parameters(), retain_graph=True) flat_grad_grad = torch.cat([grad.contiguous().view(-1) for grad in grad_grad]) return flat_grad_grad + self.cg_damping * vector def conjugate_gradient(self, states, old_dist, b, nsteps=10, residual_tol=1e-10): x = torch.zeros_like(b) r = b.clone() p = b.clone() rdotr = torch.dot(r, r) for i in range(nsteps): Avp = self.hessian_vector_product(states, old_dist, p) alpha = rdotr / torch.dot(p, Avp) x += alpha * p r -= alpha * Avp new_rdotr = torch.dot(r, r) if new_rdotr < residual_tol: break beta = new_rdotr / rdotr p = r + beta * p rdotr = new_rdotr return x
La clase de agente TRPO funciona tanto en espacios de acción continuos como en espacios discretos, utilizando una red de políticas para elegir acciones y una red de valores para estimar las recompensas para cada estado. Una distinción clave en las redes de valor TRPO es que las entradas son solo estados y no incluyen acciones, como suele ocurrir con otros algoritmos de aprendizaje por refuerzo. TRPO optimiza la política maximizando un objetivo sustituto, al tiempo que restringe las actualizaciones de la política para que se mantengan dentro de una región de confianza definida por la divergencia KL. Esta clase incluye métodos para elegir acciones, actualizar funciones de valor, optimizar políticas, estimar recompensas y realizar otros cálculos.
La función def __init__() inicia el agente TRPO con redes de políticas y valores, optimizadores e hiperparámetros para la optimización de la región de confianza de TRPO. Las entradas incluyen hiperparámetros, algunos de los cuales son específicos de TRPO como 'max_kl' y 'cg_damping', otros son específicos de RL como gamma y lambda. Al ajustar estos hiperparámetros, los valores predeterminados de max_kl=0,01, cg_damping=0,1 y lambda=0,97 son razonables, pero dependen del entorno y del conjunto de datos.
Para entornos más complejos, como conjuntos de datos de alta dimensión, un valor de max_kl menor, de aproximadamente 0,005, para restricciones más estrictas, o un valor de cg_iters mayor, de aproximadamente 20, podría ser mejor para la convergencia del gradiente conjugado. La elección del optimizador Adam para la red de valor es estándar; sin embargo, la red de políticas depende de la actualización personalizada de TRPO sin el optimizador. También es una buena idea asegurarse de que la tasa de aprendizaje de la red de valores sea suficientemente pequeña para una mejor estabilidad del aprendizaje de la red.
La función de acción `get` convierte el estado de entrada en un tensor de PyTorch. El estado se procesa a través de la red de políticas para obtener una distribución de acciones, se toma una muestra de la distribución para calcular su probabilidad logarítmica y, finalmente, se convierte la acción a un formato compatible con el entorno. Este formato es un escalar para acciones discretas y una matriz NumPy recortada para acciones continuas.
Esta función representa la interfaz del agente con el entorno, ya que permite la selección de acciones en función de la política establecida. La probabilidad logarítmica es fundamental para los cálculos de gradiente de TRPO, ya que se utiliza en la pérdida sustituta para evaluar el rendimiento de la política. Limitar las acciones continuas a un rango de [-1,1] puede garantizar que las salidas de la red de políticas sean compatibles con lo que el entorno espera en lo que respecta a las acciones acotadas. En acciones continuas, se debe realizar un seguimiento de la desviación estándar para evitar distribuciones degeneradas. El método de procesamiento por lotes utilizado presupone entradas de estado único; sin embargo, en estados vectorizados o multidimensionales, esto se puede extender para manejar los estados de manera más eficiente.
La actualización de la red de valor convierte los estados de entrada en el valor Q de las predicciones de la red de políticas, también conocido como recompensas. Calcula la pérdida de error cuadrático medio frente a los objetivos y actualiza la red de valores mediante retropropagación con el optimizador Adam. Las estimaciones de valor precisas reducen la varianza en los gradientes de las políticas y, por lo tanto, mejoran la estabilidad de TRPO. La función de pérdida MSE garantiza que la red de valor aprenda a predecir el rendimiento descontado esperado y, por lo tanto, se alinee con el objetivo de aprendizaje por refuerzo. Estamos utilizando objetivos de diferencia temporal para calcular los objetivos de entrenamiento de la red de valor. Es necesario calcularlos con precisión, ya que las estimaciones inexactas pueden dar lugar a actualizaciones de políticas inestables.
La función de pérdida es el MSE estándar; sin embargo, también se podría considerar la función de pérdida de Huber para obtener algo más robusto frente a valores atípicos en entornos/conjuntos de datos con alta varianza. La lógica de corrección de forma también parece ser eficaz, sin embargo, en situaciones con grandes conjuntos de datos puede presentar dificultades. Esto puede requerir una preoptimización de las formas de las entradas para garantizar que se procesen previamente con las formas correctas. Además, se puede incorporar el recorte de gradiente mediante módulos como Torch.nn.utils.clip_grd_norm_ para limitar las actualizaciones excesivas y, por lo tanto, estabilizar el entrenamiento de la red.
La función de actualización de políticas transforma los estados, las acciones, las probabilidades de registro antiguas y las recompensas en tensores con las formas adecuadas. También calcula la distribución de la política anterior para los cálculos de divergencia KL y define la función de pérdida sustituta, que es una forma de medir la recompensa esperada bajo la nueva política en comparación con la política anterior. Además, la función de actualización de políticas calcula el gradiente de la política, utiliza el gradiente conjugado para encontrar la dirección de búsqueda y determina el tamaño del paso en función de la restricción de la región de confianza, 'max-kl'. Realiza una búsqueda lineal que garantiza que la nueva política cumpla con las restricciones de divergencia KL, y también mejora la pérdida sustituta o vuelve a los parámetros antiguos si la búsqueda falla.
En muchos sentidos, este es el núcleo de TRPO, dado que implementa la optimización de la región de confianza, que equilibra la mejora de las políticas con la estabilidad. La función de pérdida sustituta estima el objetivo del gradiente de la política, mientras que la restricción de divergencia KL garantiza que no tengamos grandes cambios de política que puedan degradar el rendimiento. En otras palabras, el método del gradiente conjugado resuelve de manera eficiente la dirección de búsqueda, mientras que la búsqueda lineal garantiza actualizaciones robustas.
En TRPO, el parámetro max-kl es fundamental. Un valor demasiado pequeño, como por ejemplo inferior a 0,005, puede restringir excesivamente las actualizaciones, lo que puede resultar en un aprendizaje muy lento. Por el contrario, un valor demasiado elevado, como por ejemplo superior a 0,05, puede provocar actualizaciones desestabilizadoras, precisamente el problema que TRPO pretende mitigar. El parámetro 'cg-iters' (iteraciones de gradiente conjugado) debe tener un tamaño suficiente para que se produzca la convergencia. También se debe controlar el residuo para verificar la precisión de la solución.
La función de configuración de parámetros de política actualiza los parámetros de la red de políticas creando una copia de los valores de un vector aplanado y, a continuación, redimensionándolos para que coincidan con el tamaño de cada parámetro. Esto permite actualizar los parámetros personalizados de TRPO, que se calculan como un vector plano durante la búsqueda lineal del gradiente conjugado. Por lo tanto, esta función garantiza que la red de políticas refleje parámetros optimizados después de cada actualización.
El cálculo de las recompensas, al que se hace referencia en el código como compute-advantage, determina las recompensas al utilizar la Estimación de Ventaja Generalizada o GAE. Esto implica calcular el error de diferencia temporal para cada paso de tiempo. La combinación de errores TD y lambda_ ayuda a equilibrar el sesgo y la varianza. También restablece la recompensa/ventaja al final de cada episodio según lo registrado por el parámetro dones[t] y normaliza estas recompensas para que tengan una media cero y una varianza unitaria.
La función de pérdida sustituta calcula la pérdida sustituta como el valor anticipado de la razón de probabilidad πnew(a∣s)/πold(a∣s) multiplicado por las ventajas. La función de pérdida sustituta estima el objetivo del gradiente de política midiendo cómo los cambios de política afectan las recompensas esperadas. En TRPO, esta pérdida se maximiza y, por lo tanto, se anula en get-loss, dentro de la restricción de la región de confianza.
La función de divergencia kl establece una magnitud que indica cuán separadas están las distribuciones de políticas antiguas de las distribuciones de políticas nuevas. Cuando las acciones son discretas, se utiliza una fórmula analítica de distribuciones categóricas. Para acciones continuas, PyTorch utiliza distribuciones normales multivariadas. Estas mediciones ayudan a reforzar la restricción de la región de confianza de TRPO.
La función de producto vectorial hessiano, como su nombre indica, calcula el producto hessiano utilizado para la divergencia KL en el método del gradiente conjugado. Los cálculos obtienen el gradiente de la divergencia KL, lo multiplican por el vector de entrada y luego calculan el gradiente de segundo orden. Añade amortiguación para mejorar la estabilidad numérica. Al aproximar la acción de la matriz de información de Fisher sobre un vector, permitimos el cálculo eficiente de la dirección de búsqueda en TRPO. El término de amortiguación garantiza que la matriz hessiana sea definida positiva y que el gradiente conjugado converja.
Finalmente, la función de gradiente conjugado implementa el método para resolver Hx = g, donde H es la matriz de Fisher que se aproxima mediante la función de producto vectorial hessiano y g es el gradiente de la política. Refina iterativamente su solución x o la dirección de búsqueda, hasta que se produce la convergencia o se realizan n pasos de iteraciones.
Pruebas de funcionamiento
Si realizamos pruebas walk-forward solo para los 3 patrones de características que pudieron superarlas en el artículo anterior, las características 2, 3 y 4, se nos presentan los informes a continuación. Estamos probando el par EUR/USD desde el 1 de enero de 2020 hasta el 1 de enero de 2025. El entrenamiento se realizó en Python con datos correspondientes al 80% de ese período, es decir, desde el 1 de enero de 2020 hasta el 1 de enero de 2024.






Si consideramos que el período walk-forward es solo el año 2024, entonces parece que solo los patrones 2 y 3 lograron superarlo. Como siempre, intervienen muchos factores y se recomienda realizar una investigación independiente antes de utilizar cualquier código o material compartido en estos artículos. Para ensamblar y utilizar Asesores Expertos como el utilizado en las pruebas anteriores, es necesario usar los archivos del código adjunto con el asistente MQL5. Para los nuevos lectores, encontrarán instrucciones aquí y aquí sobre cómo hacerlo.
Conclusión
Hemos publicado un segundo artículo que complementa nuestro último trabajo sobre cómo se puede desarrollar un modelo de aprendizaje supervisado que utiliza como datos de entrada los patrones del ADX y el CCI para convertirlo en un Asesor Experto. Este artículo utiliza los mismos indicadores, pero en el aprendizaje por refuerzo. El aprendizaje por refuerzo (RL) tiene como objetivo hacer que el Asesor Experto desarrollado previamente sea más robusto, extendiendo con cautela su ventana de aprendizaje.
El artículo resumen que se iba a publicar a continuación, en relación con estos patrones indicadores, tenía como objetivo analizar la inferencia. Aquí aplicamos la inferencia como un medio para resumir y "archivar" lo que se aprende en el aprendizaje supervisado y el aprendizaje por refuerzo. En este artículo se ofrecen ejemplos de este enfoque. Sin embargo, dejaremos la interpretación de las inferencias al lector, ya que volveremos a un formato de artículo más sencillo que incluirá, de forma alternada, algunas ideas sobre aprendizaje automático.
| Nombre | Descripción |
|---|---|
| wz_62.mq5 | Asesor experto ensamblado con el Asistente MQL5 (Wizard MQL5) para mostrar los archivos incluidos |
| SignalWZ_62.mqh | Archivo de clase de señal |
| 61_2.onnx | Característica 2: Modelo de aprendizaje supervisado ONNX |
| 61_3.onnx | Característica 3: Modelo de aprendizaje supervisado ONNX |
| 61_4.onnx | Característica 4: Modelo de aprendizaje supervisado ONNX |
| 62_policy_2.onnx | Característica 2: Actor de aprendizaje por refuerzo |
| 62_policy_3.onnx | Característica 3: Actor de aprendizaje por refuerzo |
| 62_policy_4.onnx | Característica 4: Actor de aprendizaje por refuerzo |
| 62_value_2.onnx | Característica 2: Crítico de aprendizaje por refuerzo |
| 62_value_3.onnx | Característica 3: Crítico de aprendizaje por refuerzo |
| 62_value_4.onnx | Característica 4: Crítico de aprendizaje por refuerzo |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17938
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.
Descarga de datos del Fondo Monetario Internacional en Python
Optimización basada en biogeografía — Biogeography-Based Optimization (BBO)
Simulación de mercado (Parte 24): Iniciando SQL (VII)
Del básico al intermedio: Puntero a función
- 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