¡Bienvenido al Tutorial de MQL5!
Este material está diseñado para traders y desarrolladores que ya tienen nociones básicas de programación (variables, bucles, condicionales, etc.). Aunque repasaremos los comandos y estructuras principales de MQL5, el enfoque está en aplicar conceptos prácticos para desarrollar Expert Advisors (EAs), indicadores y scripts.
Si eres completamente nuevo en programación, te recomendamos:
- Consultar la documentación oficial de MQL5 para comprender los fundamentos del lenguaje.
- Practicar con los 60+ ejercicios y ejemplos resueltos de este tutorial, que van desde lo básico hasta lo avanzado.
- Empezar con proyectos simples (como indicadores básicos o scripts de alertas) antes de abordar EAs complejos.
Este tutorial no solo te enseñará a codificar, sino que también te guiará en la implementación de estrategias reales, gestión de riesgos y optimización. ¡Manos a la obra!
¡Importante!
Si en algún momento te sientes perdido, recuerda que la práctica constante y la experimentación son clave en el aprendizaje de MQL5. No dudes en revisar los ejemplos paso a paso y adaptarlos a tus propias estrategias.
¡Esperamos que disfrutes este tutorial y que te conviertas en un experto en MQL5! 🚀
Sumario:
- Sección 1: Introducción a MQL5
- ¿Qué es MQL5?
- Configuración del Entorno de Desarrollo
- Conceptos Fundamentales de Programación
- Sección 2: Fundamentos de Programación en MQL5
- Estructura Básica de un Script en MQL5
- Trabajando con Datos del Mercado
- Gestión de Posiciones y Órdenes
- Manipulación de Símbolos y Cuentas
- Sección 3: Construcción de Estrategias Automatizadas
- Creación de un Expert Advisor Básico
- Optimización de Código
- Pruebas y Backtesting
- Sección 4: Temas Intermedios
- Indicadores Personalizados
- Gestión Avanzada de Riesgos
- Notificaciones y Logs
- Trabajo con Arrays y Matrices
- Sección 5: Mejoras y Herramientas Adicionales
- Uso de Bibliotecas y Scripts Externos
- Integración con Servicios Externos
- Depuración y Mantenimiento
- Sección 6: Caso Práctico Completo
- Desarrollo de un EA Completo
- Publicación y Comercialización
- Anexos
- Recursos Adicionales
- Referencia de Funciones
- Glosario de Términos
Sección 3: Construcción de Estrategias Automatizadas
Capítulo 8: Creación de un Expert Advisor Básico
En este capítulo, crearemos un Expert Advisor (EA) básico utilizando una estrategia técnica simple: el cruce de medias móviles. Este EA abrirá y cerrará posiciones automáticamente basándose en señales generadas por esta estrategia. Además, implementaremos una gestión de riesgos básica para calcular el tamaño de la posición y establecer niveles adecuados de stop loss y take profit.
8.1. Diseño de una Estrategia Simple (Cruce de Medias Móviles)
El cruce de medias móviles es una estrategia popular que genera señales de compra cuando la media móvil rápida cruza por encima de la lenta, y señales de venta cuando la media móvil rápida cruza por debajo de la lenta.
Ejemplo Práctico: Implementación del Cruce de Medias Móviles
//+------------------------------------------------------------------+
//| Expert Advisor basado en cruce de medias móviles |
//+------------------------------------------------------------------+
#property strict
// Parámetros de entrada
input int FastMAPeriod = 10; // Período de la media móvil rápida
input int SlowMAPeriod = 50; // Período de la media móvil lenta
input double RiskPercentage = 1.0; // Porcentaje de riesgo por operación (%)
input int StopLossPips = 50; // Distancia del stop loss en pips
input int TakeProfitPips = 100; // Distancia del take profit en pips
// Variables globales
double fastMA, slowMA;
// Función de inicialización
int OnInit() {
Print("EA iniciado con los siguientes parámetros:");
Print("Período rápido: ", FastMAPeriod);
Print("Período lento: ", SlowMAPeriod);
Print("Porcentaje de riesgo: ", RiskPercentage, "%");
Print("Stop Loss: ", StopLossPips, " pips");
Print("Take Profit: ", TakeProfitPips, " pips");
return(INIT_SUCCEEDED);
}
// Función principal que se ejecuta en cada tick
void OnTick() {
// Calcular las medias móviles
fastMA = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
slowMA = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
// Verificar si hay posiciones abiertas
if (PositionsTotal() == 0) {
// Generar señal de compra
if (fastMA > slowMA) {
OpenBuyPosition();
}
// Generar señal de venta
else if (fastMA < slowMA) {
OpenSellPosition();
}
} else {
// Cerrar posición si las medias móviles cruzan en sentido contrario
CloseOpenPosition();
}
}
// Función para abrir una posición larga
void OpenBuyPosition() {
double lotSize = CalculateLotSize(RiskPercentage); // Calcular tamaño de lote
double stopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) - StopLossPips * Point(), _Digits);
double takeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + TakeProfitPips * Point(), _Digits);
if (OrderSend(_Symbol, OP_BUY, lotSize, Ask, 10, stopLoss, takeProfit)) {
Print("Posición larga abierta. Tamaño: ", DoubleToString(lotSize, 2), " lote(s).");
} else {
Print("Error al abrir posición larga. Código de error: ", GetLastError());
}
}
// Función para abrir una posición corta
void OpenSellPosition() {
double lotSize = CalculateLotSize(RiskPercentage); // Calcular tamaño de lote
double stopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) + StopLossPips * Point(), _Digits);
double takeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - TakeProfitPips * Point(), _Digits);
if (OrderSend(_Symbol, OP_SELL, lotSize, Bid, 10, stopLoss, takeProfit)) {
Print("Posición corta abierta. Tamaño: ", DoubleToString(lotSize, 2), " lote(s).");
} else {
Print("Error al abrir posición corta. Código de error: ", GetLastError());
}
}
// Función para cerrar la posición abierta
void CloseOpenPosition() {
for (int i = 0; i < PositionsTotal(); i++) {
uint ticket = PositionGetTicket(i);
int type = PositionGetInteger(POSITION_TYPE);
if (type == POSITION_TYPE_BUY && fastMA < slowMA) {
OrderClose(ticket, PositionGetDouble(POSITION_VOLUME), Bid, 10);
Print("Posición larga cerrada.");
} else if (type == POSITION_TYPE_SELL && fastMA > slowMA) {
OrderClose(ticket, PositionGetDouble(POSITION_VOLUME), Ask, 10);
Print("Posición corta cerrada.");
}
}
}
// Función para calcular el tamaño de la posición basado en el riesgo
double CalculateLotSize(double riskPercentage) {
double balance = AccountInfoDouble(ACCOUNT_BALANCE); // Saldo de la cuenta
double riskAmount = (riskPercentage / 100) * balance; // Monto a arriesgar
double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); // Tamaño del contrato
double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // Precio actual
// Calcular el tamaño de lote: (monto a arriesgar / valor de punto) / tamaño del contrato
double lotSize = MathFloor((riskAmount / (StopLossPips * Point())) / contractSize);
return lotSize > 0 ? lotSize : 0.01; // Asegurar un mínimo de 0.01 lote
}
Explicación del Código
- Parámetros de Entrada:
FastMAPeriodySlowMAPeriod: Períodos para las medias móviles rápida y lenta.RiskPercentage: Porcentaje de saldo de la cuenta que se arriesga por operación.StopLossPipsyTakeProfitPips: Distancias para stop loss y take profit en pips.
- Cálculo de Medias Móviles:
- Usamos la función
iMApara calcular las medias móviles rápidas y lentas.
- Usamos la función
- Generación de Señales:
- Si la media móvil rápida (
fastMA) cruza por encima de la lenta (slowMA), generamos una señal de compra. - Si la media móvil rápida cruza por debajo de la lenta, generamos una señal de venta.
- Si la media móvil rápida (
- Gestión de Posiciones:
- Si no hay posiciones abiertas, intentamos abrir una posición según la señal generada.
- Si ya hay una posición abierta, la cerramos si las medias móviles cruzan en sentido contrario.
- Gestión de Riesgos:
- La función
CalculateLotSizecalcula el tamaño de lote basado en el porcentaje de riesgo y el stop loss en pips. - Ajustamos el stop loss y take profit dinámicamente según el precio actual.
- La función
8.2. Implementación de Señales de Compra y Venta
Las señales de compra y venta se generan automáticamente mediante el cruce de medias móviles. El EA abre posiciones largas o cortas dependiendo de la dirección del cruce.
8.3. Gestión de Riesgos Básicos
La gestión de riesgos es crucial para proteger tu capital. En este EA, implementamos las siguientes prácticas:
- Cálculo del Tamaño de Lote:
- Calculamos el tamaño de lote basándonos en el porcentaje de riesgo y el stop loss en pips. Esto asegura que nunca arriesgues más del porcentaje especificado por operación.
- Stop Loss y Take Profit:
- Establecemos niveles fijos de stop loss y take profit para limitar pérdidas y asegurar ganancias.
Interactividad: Ejercicios Prácticos
Ejercicio 1: Modifica elSi se genera una señal de venta: EA para que use medias móviles exponenciales (MODE_EMA) en lugar de simples (MODE_SMA).
//+------------------------------------------------------------------+
//| Expert Advisor basado en cruce de medias móviles exponenciales |
//+------------------------------------------------------------------+
#property strict
// Parámetros de entrada
input int FastMAPeriod = 10; // Período de la media móvil rápida
input int SlowMAPeriod = 50; // Período de la media móvil lenta
input double RiskPercentage = 1.0; // Porcentaje de riesgo por operación (%)
input int StopLossPips = 50; // Distancia del stop loss en pips
input int TakeProfitPips = 100; // Distancia del take profit en pips
// Variables globales
double fastMA, slowMA;
// Función de inicialización
int OnInit() {
Print("EA iniciado con los siguientes parámetros:");
Print("Período rápido: ", FastMAPeriod);
Print("Período lento: ", SlowMAPeriod);
Print("Porcentaje de riesgo: ", RiskPercentage, "%");
Print("Stop Loss: ", StopLossPips, " pips");
Print("Take Profit: ", TakeProfitPips, " pips");
return(INIT_SUCCEEDED);
}
// Función principal que se ejecuta en cada tick
void OnTick() {
// Calcular las medias móviles exponenciales
fastMA = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0); // Media móvil rápida (EMA)
slowMA = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0); // Media móvil lenta (EMA)
// Verificar si hay posiciones abiertas
if (PositionsTotal() == 0) {
// Generar señal de compra
if (fastMA > slowMA) {
OpenBuyPosition();
}
// Generar señal de venta
else if (fastMA < slowMA) {
OpenSellPosition();
}
} else {
// Cerrar posición si las medias móviles cruzan en sentido contrario
CloseOpenPosition();
}
}
// Función para abrir una posición larga
void OpenBuyPosition() {
double lotSize = CalculateLotSize(RiskPercentage); // Calcular tamaño de lote
double stopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) - StopLossPips * Point(), _Digits);
double takeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + TakeProfitPips * Point(), _Digits);
if (OrderSend(_Symbol, OP_BUY, lotSize, Ask, 10, stopLoss, takeProfit)) {
Print("Posición larga abierta. Tamaño: ", DoubleToString(lotSize, 2), " lote(s).");
} else {
Print("Error al abrir posición larga. Código de error: ", GetLastError());
}
}
// Función para abrir una posición corta
void OpenSellPosition() {
double lotSize = CalculateLotSize(RiskPercentage); // Calcular tamaño de lote
double stopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) + StopLossPips * Point(), _Digits);
double takeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - TakeProfitPips * Point(), _Digits);
if (OrderSend(_Symbol, OP_SELL, lotSize, Bid, 10, stopLoss, takeProfit)) {
Print("Posición corta abierta. Tamaño: ", DoubleToString(lotSize, 2), " lote(s).");
} else {
Print("Error al abrir posición corta. Código de error: ", GetLastError());
}
}
// Función para cerrar la posición abierta
void CloseOpenPosition() {
for (int i = 0; i < PositionsTotal(); i++) {
uint ticket = PositionGetTicket(i);
int type = PositionGetInteger(POSITION_TYPE);
if (type == POSITION_TYPE_BUY && fastMA < slowMA) {
OrderClose(ticket, PositionGetDouble(POSITION_VOLUME), Bid, 10);
Print("Posición larga cerrada.");
} else if (type == POSITION_TYPE_SELL && fastMA > slowMA) {
OrderClose(ticket, PositionGetDouble(POSITION_VOLUME), Ask, 10);
Print("Posición corta cerrada.");
}
}
}
// Función para calcular el tamaño de la posición basado en el riesgo
double CalculateLotSize(double riskPercentage) {
double balance = AccountInfoDouble(ACCOUNT_BALANCE); // Saldo de la cuenta
double riskAmount = (riskPercentage / 100) * balance; // Monto a arriesgar
double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); // Tamaño del contrato
double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // Precio actual
// Calcular el tamaño de lote: (monto a arriesgar / valor de punto) / tamaño del contrato
double lotSize = MathFloor((riskAmount / (StopLossPips * Point())) / contractSize);
return lotSize > 0 ? lotSize : 0.01; // Asegurar un mínimo de 0.01 lote
}
Explicación del Código
- Cambio de Medias Móviles Simples a Exponenciales:
- En el cálculo de las medias móviles, hemos reemplazado
MODE_SMAporMODE_EMAen las llamadas a la funcióniMA. Esto cambia el tipo de media móvil utilizada de simple (SMA) a exponencial (EMA). - Las medias móviles exponenciales dan más peso a los datos recientes, lo que puede hacer que el EA sea más sensible a cambios recientes en el precio.
- En el cálculo de las medias móviles, hemos reemplazado
- Impacto en el Comportamiento del EA:
- Al usar medias móviles exponenciales, las señales de cruce pueden generarse más rápidamente en comparación con las medias móviles simples. Esto puede resultar en entradas y salidas más rápidas del mercado.
- Sin embargo, también puede aumentar el riesgo de falsos cruces debido a la mayor sensibilidad a movimientos de precios pequeños.
- Comentarios Adicionales:
- El resto del código permanece igual, ya que la lógica de gestión de posiciones y riesgos no depende del tipo de media móvil utilizado.
Resultado Esperado
Cuando ejecutes este EA modificado, verás mensajes similares a los siguientes en la ventana de resultados:
- Si se genera una señal de compra:
Posición larga abierta. Tamaño: 0.10 lote(s).
- Si se genera una señal de venta:
Posición corta abierta. Tamaño: 0.10 lote(s).
- Si se cierra una posición debido a un cruce inverso:
Posición larga cerrada.
Caso Práctico
Este tipo de modificación es útil para traders que prefieren estrategias más reactivas al movimiento reciente del precio. Las medias móviles exponenciales son ideales para mercados volátiles o cuando se busca capturar tendencias emergentes rápidamente.
¡Espero que este ejemplo te ayude a entender cómo adaptar el tipo de indicador técnico en un EA para ajustarlo a tus necesidades!
Ejercicio 2: Añade una condición adicional para abrir posiciones solo si el spread es menor a 2 pips.
Ver solución:Ocultar solución//+-----------------------------------------------------------------------+
//| Expert Advisor basado en cruce de medias móviles con filtro de spread |
//+-----------------------------------------------------------------------+
#property strict
// Parámetros de entrada
input int FastMAPeriod = 10; // Período de la media móvil rápida
input int SlowMAPeriod = 50; // Período de la media móvil lenta
input double RiskPercentage = 1.0; // Porcentaje de riesgo por operación (%)
input int StopLossPips = 50; // Distancia del stop loss en pips
input int TakeProfitPips = 100; // Distancia del take profit en pips
input double MaxSpreadPips = 2.0; // Máximo spread permitido en pips
// Variables globales
double fastMA, slowMA;
// Función de inicialización
int OnInit() {
Print("EA iniciado con los siguientes parámetros:");
Print("Período rápido: ", FastMAPeriod);
Print("Período lento: ", SlowMAPeriod);
Print("Porcentaje de riesgo: ", RiskPercentage, "%");
Print("Stop Loss: ", StopLossPips, " pips");
Print("Take Profit: ", TakeProfitPips, " pips");
Print("Máximo spread permitido: ", MaxSpreadPips, " pips");
return(INIT_SUCCEEDED);
}
// Función principal que se ejecuta en cada tick
void OnTick() {
// Calcular las medias móviles exponenciales
fastMA = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0); // Media móvil rápida (EMA)
slowMA = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0); // Media móvil lenta (EMA)
// Verificar si hay posiciones abiertas
if (PositionsTotal() == 0) {
// Verificar si el spread es menor al máximo permitido
double currentSpread = (SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID)) / Point();
if (currentSpread < MaxSpreadPips) {
// Generar señal de compra
if (fastMA > slowMA) {
OpenBuyPosition();
}
// Generar señal de venta
else if (fastMA < slowMA) {
OpenSellPosition();
}
} else {
Print("El spread actual (", DoubleToString(currentSpread, 1), " pips) es mayor al máximo permitido (", MaxSpreadPips, " pips). No se abrirá posición.");
}
} else {
// Cerrar posición si las medias móviles cruzan en sentido contrario
CloseOpenPosition();
}
}
// Función para abrir una posición larga
void OpenBuyPosition() {
double lotSize = CalculateLotSize(RiskPercentage); // Calcular tamaño de lote
double stopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) - StopLossPips * Point(), _Digits);
double takeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + TakeProfitPips * Point(), _Digits);
if (OrderSend(_Symbol, OP_BUY, lotSize, Ask, 10, stopLoss, takeProfit)) {
Print("Posición larga abierta. Tamaño: ", DoubleToString(lotSize, 2), " lote(s).");
} else {
Print("Error al abrir posición larga. Código de error: ", GetLastError());
}
}
// Función para abrir una posición corta
void OpenSellPosition() {
double lotSize = CalculateLotSize(RiskPercentage); // Calcular tamaño de lote
double stopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) + StopLossPips * Point(), _Digits);
double takeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - TakeProfitPips * Point(), _Digits);
if (OrderSend(_Symbol, OP_SELL, lotSize, Bid, 10, stopLoss, takeProfit)) {
Print("Posición corta abierta. Tamaño: ", DoubleToString(lotSize, 2), " lote(s).");
} else {
Print("Error al abrir posición corta. Código de error: ", GetLastError());
}
}
// Función para cerrar la posición abierta
void CloseOpenPosition() {
for (int i = 0; i < PositionsTotal(); i++) {
uint ticket = PositionGetTicket(i);
int type = PositionGetInteger(POSITION_TYPE);
if (type == POSITION_TYPE_BUY && fastMA < slowMA) {
OrderClose(ticket, PositionGetDouble(POSITION_VOLUME), Bid, 10);
Print("Posición larga cerrada.");
} else if (type == POSITION_TYPE_SELL && fastMA > slowMA) {
OrderClose(ticket, PositionGetDouble(POSITION_VOLUME), Ask, 10);
Print("Posición corta cerrada.");
}
}
}
// Función para calcular el tamaño de la posición basado en el riesgo
double CalculateLotSize(double riskPercentage) {
double balance = AccountInfoDouble(ACCOUNT_BALANCE); // Saldo de la cuenta
double riskAmount = (riskPercentage / 100) * balance; // Monto a arriesgar
double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); // Tamaño del contrato
double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // Precio actual
// Calcular el tamaño de lote: (monto a arriesgar / valor de punto) / tamaño del contrato
double lotSize = MathFloor((riskAmount / (StopLossPips * Point())) / contractSize);
return lotSize > 0 ? lotSize : 0.01; // Asegurar un mínimo de 0.01 lote
}
Explicación del Código
- Parámetro
MaxSpreadPips:- Se ha añadido un nuevo parámetro de entrada llamado
MaxSpreadPipsque define el máximo spread permitido (en este caso, 2 pips).
- Se ha añadido un nuevo parámetro de entrada llamado
- Cálculo del Spread Actual:
- En la función
OnTick, calculamos el spread actual usando la diferencia entre el precio de compra (SYMBOL_ASK) y el precio de venta (SYMBOL_BID). Dividimos esta diferencia porPoint()para convertirla en pips.
- En la función
- Condición de Filtro de Spread:
- Antes de generar señales de compra o venta, verificamos si el spread actual es menor al máximo permitido (
MaxSpreadPips). - Si el spread es demasiado alto, mostramos un mensaje indicando que no se abrirá ninguna posición debido al spread elevado.
- Antes de generar señales de compra o venta, verificamos si el spread actual es menor al máximo permitido (
- Impacto en el Comportamiento del EA:
- Esta condición evita abrir posiciones en momentos de alta volatilidad o liquidez reducida, donde el spread puede ser excesivo y aumentar los costos de trading.
- Esto mejora la eficiencia del EA al asegurarse de que solo opera en condiciones favorables.
Resultado Esperado
Cuando ejecutes este EA modificado, verás mensajes similares a los siguientes en la ventana de resultados:
- Si el spread es bajo y se genera una señal de compra:
Posición larga abierta. Tamaño: 0.10 lote(s).
- Si el spread es alto:
El spread actual (2.5 pips) es mayor al máximo permitido (2.0 pips). No se abrirá posición.
- Si se cierra una posición debido a un cruce inverso:
Posición larga cerrada.
Caso Práctico
Este tipo de filtro es especialmente útil para traders que operan en mercados con spreads variables, como Forex. Al evitar abrir posiciones cuando el spread es alto, reduces los costos iniciales de trading y minimizas el impacto negativo de la falta de liquidez en tus operaciones.
¡Espero que este ejemplo te ayude a entender cómo implementar filtros adicionales para mejorar la robustez de tu EA!
Ejercicio 3: Implementa un trailing stop para ajustar dinámicamente el stop loss según el movimiento del precio.
Ver solución:Ocultar solución//+------------------------------------------------------------------+
//| Expert Advisor con trailing stop |
//+------------------------------------------------------------------+
#property strict
// Parámetros de entrada
input int FastMAPeriod = 10; // Período de la media móvil rápida
input int SlowMAPeriod = 50; // Período de la media móvil lenta
input double RiskPercentage = 1.0; // Porcentaje de riesgo por operación (%)
input int InitialStopLossPips = 50; // Distancia inicial del stop loss en pips
input int TakeProfitPips = 100; // Distancia del take profit en pips
input int TrailingStopPips = 30; // Distancia del trailing stop en pips
// Variables globales
double fastMA, slowMA;
// Función de inicialización
int OnInit() {
Print("EA iniciado con los siguientes parámetros:");
Print("Período rápido: ", FastMAPeriod);
Print("Período lento: ", SlowMAPeriod);
Print("Porcentaje de riesgo: ", RiskPercentage, "%");
Print("Stop Loss Inicial: ", InitialStopLossPips, " pips");
Print("Take Profit: ", TakeProfitPips, " pips");
Print("Trailing Stop: ", TrailingStopPips, " pips");
return(INIT_SUCCEEDED);
}
// Función principal que se ejecuta en cada tick
void OnTick() {
// Calcular las medias móviles exponenciales
fastMA = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0); // Media móvil rápida (EMA)
slowMA = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0); // Media móvil lenta (EMA)
// Verificar si hay posiciones abiertas
if (PositionsTotal() == 0) {
// Generar señal de compra
if (fastMA > slowMA) {
OpenBuyPosition();
}
// Generar señal de venta
else if (fastMA < slowMA) {
OpenSellPosition();
}
} else {
// Ajustar el trailing stop para posiciones abiertas
AdjustTrailingStop();
}
}
// Función para abrir una posición larga
void OpenBuyPosition() {
double lotSize = CalculateLotSize(RiskPercentage); // Calcular tamaño de lote
double stopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) - InitialStopLossPips * Point(), _Digits);
double takeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + TakeProfitPips * Point(), _Digits);
if (OrderSend(_Symbol, OP_BUY, lotSize, Ask, 10, stopLoss, takeProfit)) {
Print("Posición larga abierta. Tamaño: ", DoubleToString(lotSize, 2), " lote(s).");
} else {
Print("Error al abrir posición larga. Código de error: ", GetLastError());
}
}
// Función para abrir una posición corta
void OpenSellPosition() {
double lotSize = CalculateLotSize(RiskPercentage); // Calcular tamaño de lote
double stopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) + InitialStopLossPips * Point(), _Digits);
double takeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - TakeProfitPips * Point(), _Digits);
if (OrderSend(_Symbol, OP_SELL, lotSize, Bid, 10, stopLoss, takeProfit)) {
Print("Posición corta abierta. Tamaño: ", DoubleToString(lotSize, 2), " lote(s).");
} else {
Print("Error al abrir posición corta. Código de error: ", GetLastError());
}
}
// Función para ajustar el trailing stop
void AdjustTrailingStop() {
for (int i = 0; i < PositionsTotal(); i++) {
uint ticket = PositionGetTicket(i);
string symbol = PositionGetString(POSITION_SYMBOL);
int type = PositionGetInteger(POSITION_TYPE);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentStopLoss = PositionGetDouble(POSITION_SL);
double currentPrice = type == POSITION_TYPE_BUY ? SymbolInfoDouble(symbol, SYMBOL_BID) : SymbolInfoDouble(symbol, SYMBOL_ASK);
// Calcular el nuevo stop loss basado en el trailing stop
double newStopLoss;
if (type == POSITION_TYPE_BUY) {
newStopLoss = currentPrice - TrailingStopPips * Point();
} else if (type == POSITION_TYPE_SELL) {
newStopLoss = currentPrice + TrailingStopPips * Point();
}
// Ajustar el stop loss solo si mejora el nivel actual
if ((type == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) ||
(type == POSITION_TYPE_SELL && newStopLoss < currentStopLoss)) {
if (OrderModify(ticket, openPrice, newStopLoss, PositionGetDouble(POSITION_TP), 0)) {
Print("Trailing stop ajustado para la posición. Nuevo SL: ", DoubleToString(newStopLoss, _Digits));
} else {
Print("Error al ajustar el trailing stop. Código de error: ", GetLastError());
}
}
}
}
// Función para calcular el tamaño de la posición basado en el riesgo
double CalculateLotSize(double riskPercentage) {
double balance = AccountInfoDouble(ACCOUNT_BALANCE); // Saldo de la cuenta
double riskAmount = (riskPercentage / 100) * balance; // Monto a arriesgar
double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); // Tamaño del contrato
double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // Precio actual
// Calcular el tamaño de lote: (monto a arriesgar / valor de punto) / tamaño del contrato
double lotSize = MathFloor((riskAmount / (InitialStopLossPips * Point())) / contractSize);
return lotSize > 0 ? lotSize : 0.01; // Asegurar un mínimo de 0.01 lote
}
Explicación del Código
- Parámetro
TrailingStopPips:- Se ha añadido un nuevo parámetro de entrada llamado
TrailingStopPipsque define la distancia mínima entre el precio actual y el stop loss dinámico.
- Se ha añadido un nuevo parámetro de entrada llamado
- Función
AdjustTrailingStop:- Esta función recorre todas las posiciones abiertas y ajusta el stop loss dinámicamente según el movimiento del precio.
- Para posiciones largas (
BUY), el nuevo stop loss se calcula como el precio actual menos la distancia del trailing stop. - Para posiciones cortas (
SELL), el nuevo stop loss se calcula como el precio actual más la distancia del trailing stop.
- Condición de Mejora del Stop Loss:
- El stop loss solo se ajusta si el nuevo nivel mejora el stop loss actual (es decir, si está más cerca del precio de entrada).
- Esto asegura que el stop loss nunca retroceda, lo que podría aumentar el riesgo innecesariamente.
- Impacto en el Comportamiento del EA:
- El trailing stop protege las ganancias al mover el stop loss hacia el precio actual a medida que este se mueve en dirección favorable.
- Esto permite que las posiciones permanezcan abiertas mientras el mercado sigue una tendencia, pero cierra automáticamente la posición si el mercado retrocede.
Resultado Esperado
Cuando ejecutes este EA modificado, verás mensajes similares a los siguientes en la ventana de resultados:
- Si el trailing stop se ajusta:
Trailing stop ajustado para la posición. Nuevo SL: 1.23456
- Si no se puede ajustar el trailing stop:
Error al ajustar el trailing stop. Código de error: 129
- Si se abre una posición:
Posición larga abierta. Tamaño: 0.10 lote(s).
Caso Práctico
El trailing stop es una herramienta poderosa para proteger ganancias y minimizar pérdidas en mercados volátiles. Al ajustar dinámicamente el stop loss, puedes seguir una tendencia mientras reduces el riesgo de reversión repentina del mercado.
¡Espero que este ejemplo te ayude a entender cómo implementar un trailing stop en tu EA!
Conclusión: En este capítulo, hemos creado un Expert Advisor básico basado en el cruce de medias móviles. Hemos implementado señales de compra y venta automáticas y añadido una gestión de riesgos básica para proteger el capital. Este EA puede ser una base sólida para desarrollar estrategias más avanzadas.
En el próximo capítulo, exploraremos cómo optimizar y mejorar este EA mediante pruebas y ajustes finos.
¡Sigue practicando y perfeccionando tus habilidades en MQL5!
