
Paradigmas de programación (Parte 1): Enfoque procedimental para el desarrollo de un asesor basado en la dinámica de precios
Introducción
En el mundo del desarrollo de software, los paradigmas de programación suponen los principios rectores para escribir y organizar el código. Al igual que elegimos diferentes rutas para llegar a un destino, existen diferentes enfoques o paradigmas de programación para realizar tareas utilizando MQL5.
En una serie de dos artículos, revisaremos los paradigmas básicos de programación necesarios para crear herramientas comerciales utilizando MQL5. Nuestro objetivo será compartir prácticas eficaces y recomendadas que generen grandes resultados con un código breve y eficiente. Además, explicaremos cada estilo de programación y lo demostraremos creando un asesor totalmente funcional.Tipos de paradigmas de programación
Existen tres paradigmas principales de programación que todo desarrollador MQL5 debe conocer:
- Programación procedimental: Este artículo se centrará en este paradigma concreto.
- Programación funcional: Este paradigma también se tratará en el artículo, ya que resulta muy similar a la programación procedimental.
- Programación orientada a objetos (POO): Este paradigma lo analizaremos en el próximo artículo.
Programación procedimental
La programación procedimental supone un enfoque sistemático, paso a paso, de la escritura de código. Consiste en descomponer cualquier problema en una secuencia de instrucciones precisas, como cuando preparamos una comida siguiendo una receta. Los programadores crean un camino determinado para el ordenador, dándole un algoritmo claro paso a paso para lograr el resultado deseado.
Tanto si usted es nuevo en la programación como si solo le interesa la organización del código, la programación procedimental le ofrecerá un punto de entrada sencillo e intuitivo en un tema.
Propiedades básicas de la programación procedimental
- Funciones:
El núcleo de la programación procedimental son las funciones. Son conjuntos de instrucciones agrupadas para realizar una tarea específica. Las funciones encapsulan la funcionalidad, lo cual permite la modularidad y la reutilización del código. - Diseño descendente:
En la programación procedimental, con frecuencia se utiliza un enfoque descendente para el diseño. Los desarrolladores dividen el problema en subtareas más pequeñas y manejables. Cada subtarea se resuelve individualmente, contribuyendo a la solución global. - Estilo imperativo:
La naturaleza imperativa, o de órdenes, de la programación procedimental, implica declaraciones explícitas que modifican el estado del programa. Los desarrolladores especifican cómo debe resolver un programa un problema usando una serie de órdenes procedimentales. - Variables y datos:
Los procedimientos o funciones de la programación procedimental manipulan variables y datos. Estas variables pueden almacenar valores que cambien durante la ejecución de un programa. Los cambios de estado suponen un aspecto fundamental de la programación procedimental. - Rendimiento constante:
La ejecución del programa tiene lugar de forma secuencial. Las sentencias se ejecutan una tras otra, y las estructuras de control como los ciclos y expresiones condicionales guían el flujo del programa. - Modularidad:
La programación procedimental fomenta la modularidad, organizando para ello el código en procedimientos o funciones. Cada módulo se ocupa de un aspecto concreto de la funcionalidad del programa, lo que mejora la organización del código y su mantenimiento. - Posibilidad de reutilización:
La capacidad de reutilización del código es una ventaja clave de la programación procedimental. Una vez escrita y probada una función, podremos utilizarla siempre que necesitemos esa funcionalidad concreta en el programa, lo cual reduce la redundancia y aumenta la eficacia. - Legibilidad:
El código procedimental suele ser más legible, sobre todo para quienes están acostumbrados al enfoque paso a paso. El flujo lineal de ejecución facilita el seguimiento de la lógica del programa.
Programación funcional
La programación funcional se apoya en el concepto de las funciones como ciudadanos de primera clase y sobre la inmutabilidad. El paradigma es similar a la programación procedimental, excepto por el principio básico de procesamiento de datos y ejecución de tareas.
A diferencia de la programación procedimental, en la que los datos pueden cambiar de aspecto y función durante la ejecución de un programa, la programación funcional prefiere un entorno más estable. Una vez creados los datos, estos permanecen inalterados. El compromiso con la inmutabilidad ofrece un cierto nivel de previsibilidad y ayuda a evitar efectos secundarios imprevistos en el código.
Propiedades básicas de la programación funcional
- Inmutabilidad:
Es el principio básico de la programación funcional. Una vez creados los datos, estos permanecen inalterados. En lugar de modificar los datos existentes, se crean nuevos datos con los cambios necesarios. Esto posibilita una mayor previsibilidad y ayuda a evitar efectos secundarios imprevistos. - Funciones como ciudadanos de primera clase:
Las funciones se tratan como elementos de primera clase, lo cual significa que pueden asignarse a variables, transmitirse como argumentos a otras funciones y retornarse como resultados de otras funciones. Esta flexibilidad permite crear funciones de orden superior y fomenta un estilo de codificación más modular y expresivo. - Estilo declarativo:
La programación funcional favorece un estilo de programación declarativo, centrado en lo que debe conseguir el programa y no en cómo conseguirlo. Esto redunda en un código más conciso y legible. - Prevención de estados volátiles:
La condición a cambiar se minimiza o elimina. Los datos se consideran inmutables y las funciones evitan los cambios de estado externos. Esta característica simplifica el razonamiento sobre el comportamiento de las funciones. - Recursión y funciones de orden superior:
La recursión y las funciones de orden superior se usan habitualmente en la programación funcional, cuando las funciones toman otras funciones como argumentos o las devuelven como resultados. Así se logra un código más modular y reutilizable.
Enfoque procedimental para el desarrollo de un asesor basado en la dinámica de precios (Price Action)
Ahora que nos hemos familiarizado con la esencia de los paradigmas de la programación procedimental y funcional, podremos en práctica la teoría: vamos a crear un asesor basado en la dinámica de precios. En primer lugar, hablaremos de la estrategia comercial que vamos a automatizar. Más adelante, recorreremos los distintos componentes del código, descubriendo su funcionalidad y cómo trabajan juntos a la perfección.
Estrategia de dinámica de precios con el indicador EMA
Nuestra estrategia comercial se basa en un único indicador conocido como Media Móvil Exponencial (EMA). El indicador se usa ampliamente en el análisis técnico y ayuda a determinar la dirección del mercado según la configuración comercial seleccionada. La media móvil es un indicador estándar en MQL5, lo cual simplifica su inclusión en nuestro código.
Compra:
Abrimos una posición de Compra cuando la última vela cerrada sea alcista y sus precios mínimo y máximo estén por encima de la media móvil exponencial (EMA).
Venta:
Abrimos una posición de Venta cuando la última vela cerrada sea bajista y sus precios mínimo y máximo estén por debajo de la media móvil exponencial (EMA).
Salida:
Cerramos automáticamente todas las posiciones abiertas y obtenemos los beneficios o pérdidas correspondientes cuando se alcance el porcentaje de beneficios o pérdidas especificado por el usuario para la cuenta, o utilicemos órdenes tradicionales de stop loss o take profit.
Ajustes | Condiciones generales |
---|---|
Compra | La última vela cerrada es alcista y sus precios mínimo y máximo están por encima de la media móvil exponencial (EMA). |
Venta | La última vela cerrada es bajista y sus precios mínimo y máximo están por debajo de la media móvil exponencial (EMA). |
Salida | cerramos todas las posiciones abiertas y obtenemos los beneficios o pérdidas correspondientes cuando se alcance el porcentaje de beneficios/pérdidas especificado por el usuario para la cuenta o se active un stop loss/take profit. |
Aplicación de una estrategia comercial en el código
Ahora que hemos establecido nuestras reglas y nuestro plan de trading, vamos a poner en práctica nuestra estrategia comercial escribiendo un código MQL5 en el entorno del MetaEditor. Siga los pasos que indicamos a continuación para empezar con una plantilla de asesor limpia que contenga únicamente las características obligatorias requeridas:
Paso 1: Abra el MetaEditor e inicie el Wizard pulsando el botón "Crear".
Segundo paso: Seleccione "Asesor (plantilla)" y haga clic en "Siguiente".
Paso 3: En "Parámetros generales", introduzca el nombre del asesor y clique en "Siguiente".
Paso 4: Asegúrese de que no hay ninguna opción seleccionada en el apartado "Controladores de eventos". Desactive las casillas de verificación, si las hay, y clique en Siguiente.
Paso 5: Asegúrese de que no hay ninguna opción seleccionada en el apartado "Controladores de eventos de prueba". Desmarque todas las opciones si están seleccionadas, y luego haga clic en "Listo" para crear una plantilla de asesor MQL5.
Ahora tenemos una plantilla de asesor MQL5 limpia con las funciones obligatorias (OnInit, OnDeinit y OnTick). No olvide guardar el nuevo archivo antes de continuar.
Este es el aspecto de nuestro código de asesor recién generado:
//+------------------------------------------------------------------+ //| PriceActionEMA.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
En primer lugar, escribimos una breve descripción del asesor y declaramos e inicializamos las variables de usuario y, a continuación, declaramos todas las variables globales. Luego colocamos este código justo antes de la función OnInit().
#property description "A price action EA to demonstrate how to " #property description "implement the procedural programming paradigm." //--User input variables input long magicNumber = 101;//Magic Number (Set 0 [Zero] to disable input group "" input ENUM_TIMEFRAMES tradingTimeframe = PERIOD_H1;//Trading Timeframe input int emaPeriod = 20;//Moving Average Period input int emaShift = 0;//Moving Average Shift input group "" input bool enableTrading = true;//Enable Trading input bool enableAlerts = false;//Enable Alerts input group "" input double accountPercentageProfitTarget = 10.0;//Account Percentage (%) Profit Target input double accountPercentageLossTarget = 10.0;//Account Percentage (%) Loss Target input group "" input int maxPositions = 3;//Max Positions (Max open positions in one direction) input int tp = 5000;//TP (Take Profit Points/Pips [Zero (0) to diasable]) input int sl = 10000;//SL (Stop Loss Points/Pips [Zero (0) to diasable])
Como ya sabemos, "en el corazón de la programación procedimental se encuentran las funciones. Son conjuntos de instrucciones agrupadas para realizar una tarea específica. Las funciones encapsulan la funcionalidad, lo cual posibilita la modularidad y la reutilización del código". Vamos a crear funciones personalizadas.
Función GetInit:
Esta función es responsable de inicializar todas las variables globales y realizar cualquier otra tarea al cargar o inicializar el asesor.
int GetInit() //Function to initialize the robot and all the variables { int returnVal = 1; //create the iMA indicator emaHandle = iMA(Symbol(), tradingTimeframe, emaPeriod, emaShift, MODE_EMA, PRICE_CLOSE); if(emaHandle < 0) { Print("Error creating emaHandle = ", INVALID_HANDLE); Print("Handle creation: Runtime error = ", GetLastError()); //force program termination if the handle is not properly set return(-1); } ArraySetAsSeries(movingAverage, true); //reset the count for positions totalOpenBuyPositions = 0; totalOpenSellPositions = 0; buyPositionsProfit = 0.0; sellPositionsProfit = 0.0; buyPositionsVol = 0.0; sellPositionsVol = 0.0; closedCandleTime = iTime(_Symbol, tradingTimeframe, 1); startingCapital = AccountInfoDouble(ACCOUNT_EQUITY);//used to calculate the account percentage profit if(enableAlerts) { Alert(MQLInfoString(MQL_PROGRAM_NAME), " has just been LOADED in the ", Symbol(), " ", EnumToString(Period()), " period chart."); } //structure our comment string commentString = "\n\n" + "Account No: " + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + "\nAccount Type: " + EnumToString((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)) + "\nAccount Leverage: " + IntegerToString(AccountInfoInteger(ACCOUNT_LEVERAGE)) + "\n-----------------------------------------------------------------------------------------------------"; return(returnVal); }
Función GetDeinit:
Esta función se encarga de liberar toda la memoria usada, borrar los comentarios del gráfico y realizar otras tareas de desinicialización antes de borrar el asesor.
void GetDeinit() //De-initialize the robot on shutdown and clean everything up { IndicatorRelease(emaHandle); //delete the moving average handle and deallocate the memory spaces occupied ArrayFree(movingAverage); //free the dynamic arrays containing the moving average buffer data if(enableAlerts) { Alert(MQLInfoString(MQL_PROGRAM_NAME), " has just been REMOVED from the ", Symbol(), " ", EnumToString(Period()), " period chart."); } //delete and clear all chart displayed messages Comment(""); }
Función GetEma:
Esta función extrae el valor de la media móvil exponencial y determina la dirección del mercado comparando el valor de la EMA con los precios de apertura, cierre, máximo y mínimo de una vela cerrada recientemente. Luego almacena el valor de la dirección del mercado en variables globales para su posterior procesamiento por otras funciones.
void GetEma() { //Get moving average direction if(CopyBuffer(emaHandle, 0, 0, 100, movingAverage) <= 0) { return; } movingAverageTrend = "FLAT"; buyOk = false; sellOk = false; if(movingAverage[1] > iHigh(_Symbol, tradingTimeframe, 1) && movingAverage[1] > iLow(_Symbol, tradingTimeframe, 1)) { movingAverageTrend = "SELL/SHORT"; if(iClose(_Symbol, tradingTimeframe, 1) < iOpen(_Symbol, tradingTimeframe, 1)) { sellOk = true; buyOk = false; } } if(movingAverage[1] < iHigh(_Symbol, tradingTimeframe, 1) && movingAverage[1] < iLow(_Symbol, tradingTimeframe, 1)) { movingAverageTrend = "BUY/LONG"; if(iClose(_Symbol, tradingTimeframe, 1) > iOpen(_Symbol, tradingTimeframe, 1)) { buyOk = true; sellOk = false; } } }
Función GetPositionsData:
Esta función explora todas las posiciones abiertas, guardando sus propiedades, como el importe de los beneficios, el número total de posiciones abiertas, el número total de posiciones abiertas de compra y venta, y el volumen/lote total de cada tipo de posición. Asimismo, excluye los datos de las posiciones no abiertas por nuestro asesor o aquellas que no coinciden con el número mágico del asesor.
void GetPositionsData() { //get the total number of all open positions and their status if(PositionsTotal() > 0) { //variables for storing position properties values ulong positionTicket; long positionMagic, positionType; string positionSymbol; int totalPositions = PositionsTotal(); //reset the count totalOpenBuyPositions = 0; totalOpenSellPositions = 0; buyPositionsProfit = 0.0; sellPositionsProfit = 0.0; buyPositionsVol = 0.0; sellPositionsVol = 0.0; //scan all the open positions for(int x = totalPositions - 1; x >= 0; x--) { positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket positionMagic = PositionGetInteger(POSITION_MAGIC); positionSymbol = PositionGetString(POSITION_SYMBOL); positionType = PositionGetInteger(POSITION_TYPE); if(positionMagic == magicNumber && positionSymbol == _Symbol) { if(positionType == POSITION_TYPE_BUY) { ++totalOpenBuyPositions; buyPositionsProfit += PositionGetDouble(POSITION_PROFIT); buyPositionsVol += PositionGetDouble(POSITION_VOLUME); } if(positionType == POSITION_TYPE_SELL) { ++totalOpenSellPositions; sellPositionsProfit += PositionGetDouble(POSITION_PROFIT); sellPositionsVol += PositionGetDouble(POSITION_VOLUME); } } } //Get and save the account percentage profit accountPercentageProfit = ((buyPositionsProfit + sellPositionsProfit) * 100) / startingCapital; } else //if no positions are open then the account percentage profit should be zero { startingCapital = AccountInfoDouble(ACCOUNT_EQUITY); accountPercentageProfit = 0.0; //reset position counters too totalOpenBuyPositions = 0; totalOpenSellPositions = 0; } }
Función TradingIsAllowed:
Esta función comprueba si el asesor tiene permiso para comerciar por parte del usuario, el terminal y el bróker.
bool TradingIsAllowed() { //check if trading is enabled if(enableTrading && MQLInfoInteger(MQL_TRADE_ALLOWED) && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_EXPERT) ) { tradingStatus = "\n-----------------------------------------------------------------------------------------" + "\nTRADING IS FULLY ENABLED! *** SCANNING FOR ENTRY ***"; return(true); } else //trading is disabled { tradingStatus = "\n-----------------------------------------------------------------------------------------" + "\nTRADING IS NOT FULLY ENABLED! *** GIVE EA PERMISSION TO TRADE ***"; return(false); } }
Función TradeNow:
Esta función es la responsable de abrir nuevas posiciones cuando todas las comprobaciones y señales necesarias indiquen que puede iniciarse una nueva operación.
void TradeNow() { //Detect new candle formation and open a new position if(closedCandleTime != iTime(_Symbol, tradingTimeframe, 1)) //-- New candle found { //use the candle time as the position comment to prevent opening dublicate trades on one candle string positionComment = IntegerToString(iTime(_Symbol, tradingTimeframe, 1)); //open a buy position if(buyOk && totalOpenBuyPositions < maxPositions) { //Use the positionComment string to check if we had already have a position open on this candle if(!PositionFound(_Symbol, POSITION_TYPE_BUY, positionComment)) //no position has been openend on this candle, open a buy position now { BuySellPosition(POSITION_TYPE_BUY, positionComment); } } //open a sell position if(sellOk && totalOpenSellPositions < maxPositions) { //Use the positionComment string to check if we had already have a position open on this candle if(!PositionFound(_Symbol, POSITION_TYPE_SELL, positionComment)) //no position has been openend on this candle, open a sell position now { BuySellPosition(POSITION_TYPE_SELL, positionComment); } } //reset closedCandleTime value to prevent new entry orders from opening before a new candle is formed closedCandleTime = iTime(_Symbol, tradingTimeframe, 1); } }
Función ManageProfitAndLoss:
Esta función comprueba si se han alcanzado los umbrales de pérdidas y ganancias establecidos por el usuario. Si se cumplen las condiciones, liquidará todos los beneficios o pérdidas cerrando todas las posiciones abiertas.
void ManageProfitAndLoss() { //if the account percentage profit or loss target is hit, delete all positions double lossLevel = -accountPercentageLossTarget; if( (accountPercentageProfit >= accountPercentageProfitTarget || accountPercentageProfit <= lossLevel) || ((totalOpenBuyPositions >= maxPositions || totalOpenSellPositions >= maxPositions) && accountPercentageProfit > 0) ) { //delete all open positions if(PositionsTotal() > 0) { //variables for storing position properties values ulong positionTicket; long positionMagic, positionType; string positionSymbol; int totalPositions = PositionsTotal(); //scan all the open positions for(int x = totalPositions - 1; x >= 0; x--) { positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket positionMagic = PositionGetInteger(POSITION_MAGIC); positionSymbol = PositionGetString(POSITION_SYMBOL); positionType = PositionGetInteger(POSITION_TYPE); int positionDigits= (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS); double positionVolume = PositionGetDouble(POSITION_VOLUME); ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); if(positionMagic == magicNumber && positionSymbol == _Symbol) { //print the position details Print("*********************************************************************"); PrintFormat( "#%I64u %s %s %.2f %s [%I64d]", positionTicket, positionSymbol, EnumToString(positionType), positionVolume, DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), positionDigits), positionMagic ); //reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult); //set the operation parameters tradeRequest.action = TRADE_ACTION_DEAL;//type of trade operation tradeRequest.position = positionTicket;//ticket of the position tradeRequest.symbol = positionSymbol;//symbol tradeRequest.volume = positionVolume;//volume of the position tradeRequest.deviation = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);//allowed deviation from the price tradeRequest.magic = magicNumber;//MagicNumber of the position //set the price and order type depending on the position type if(positionType == POSITION_TYPE_BUY) { tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID); tradeRequest.type = ORDER_TYPE_SELL; } else { tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK); tradeRequest.type = ORDER_TYPE_BUY; } //print the position close details PrintFormat("Close #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); //send the tradeRequest if(OrderSend(tradeRequest, tradeResult)) //trade tradeRequest success, position has been closed { if(enableAlerts) { Alert( _Symbol + " PROFIT LIQUIDATION: Just successfully closed POSITION (#" + IntegerToString(positionTicket) + "). Check the EA journal for more details." ); } PrintFormat("Just successfully closed position: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); PrintFormat("retcode=%u deal=%I64u order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order); } else //trade tradeRequest failed { //print the information about the operation if(enableAlerts) { Alert( _Symbol + " ERROR ** PROFIT LIQUIDATION: closing POSITION (#" + IntegerToString(positionTicket) + "). Check the EA journal for more details." ); } PrintFormat("Position clossing failed: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); PrintFormat("OrderSend error %d", GetLastError());//print the error code } } } } } }
Función PrintOnChart:
Esta función formatea y muestra el estado del asesor en el gráfico, ofreciendo al usuario una representación visual textual del estado de la cuenta y del asesor.
void PrintOnChart() { //update account status strings and display them on the chart accountStatus = "\nAccount Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + accountCurrency + "\nAccount Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + accountCurrency + "\nAccount Profit: " + DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT), 2) + accountCurrency + "\nAccount Percentage Profit: " + DoubleToString(accountPercentageProfit, 2) + "%" + "\n-----------------------------------------------------------------------------------------" + "\nTotal Buy Positions Open: " + IntegerToString(totalOpenBuyPositions) + " Total Vol/Lots: " + DoubleToString(buyPositionsVol, 2) + " Profit: " + DoubleToString(buyPositionsProfit, 2) + accountCurrency + "\nTotal Sell Positions Open: " + IntegerToString(totalOpenSellPositions) + " Total Vol/Lots: " + DoubleToString(sellPositionsVol, 2) + " Profit: " + DoubleToString(sellPositionsProfit, 2) + accountCurrency + "\nPositionsTotal(): " + IntegerToString(PositionsTotal()) + "\n-----------------------------------------------------------------------------------------" + "\nJust Closed Candle: Open: " + DoubleToString(iOpen(_Symbol, tradingTimeframe, 1), _Digits) + " Close: " + DoubleToString(iClose(_Symbol, tradingTimeframe, 1), _Digits) + " High: " + DoubleToString(iHigh(_Symbol, tradingTimeframe, 1), _Digits) + " Low: " + DoubleToString(iLow(_Symbol, tradingTimeframe, 1), _Digits) + "\n-----------------------------------------------------------------------------------------" + "\nMovingAverage (EMA): " + DoubleToString(movingAverage[1], _Digits) + " movingAverageTrend = " + movingAverageTrend + "\nsellOk: " + IntegerToString(sellOk) + "\nbuyOk: " + IntegerToString(buyOk); //show comments on the chart Comment(commentString + accountStatus + tradingStatus); }
Función BuySellPosition:
Esta función abre nuevas posiciones de compra y venta.
bool BuySellPosition(int positionType, string positionComment) { //reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult); //initialize the parameters to open a position tradeRequest.action = TRADE_ACTION_DEAL; tradeRequest.symbol = Symbol(); tradeRequest.deviation = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD); tradeRequest.magic = magicNumber; tradeRequest.comment = positionComment; double volumeLot = NormalizeDouble(((SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) * AccountInfoDouble(ACCOUNT_EQUITY)) / 10000), 2); if(positionType == POSITION_TYPE_BUY) { if(sellPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200) { volumeLot = NormalizeDouble((sellPositionsVol + volumeLot), 2); } if(volumeLot < 0.01) { volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); } if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)) { volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); } tradeRequest.volume = NormalizeDouble(volumeLot, 2); tradeRequest.type = ORDER_TYPE_BUY; tradeRequest.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if(tp > 0) { tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits); } if(sl > 0) { tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits); } if(OrderSend(tradeRequest, tradeResult)) //successfully openend the position { if(enableAlerts) { Alert(_Symbol, " Successfully openend BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price); } PrintFormat("retcode=%u deal=%I64u order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order); return(true); } else { if(enableAlerts) { Alert(_Symbol, " ERROR opening a BUY POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK)); } PrintFormat("ERROR: Opening a BUY POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code return(false); } } if(positionType == POSITION_TYPE_SELL) { if(buyPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200) { volumeLot = NormalizeDouble((buyPositionsVol + volumeLot), 2); } if(volumeLot < 0.01) { volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); } if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)) { volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); } tradeRequest.volume = NormalizeDouble(volumeLot, 2); tradeRequest.type = ORDER_TYPE_SELL; tradeRequest.price = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(tp > 0) { tradeRequest.tp = NormalizeDouble(tradeRequest.price - (tp * _Point), _Digits); } if(sl > 0) { tradeRequest.sl = NormalizeDouble(tradeRequest.price + (sl * _Point), _Digits); } if(OrderSend(tradeRequest, tradeResult)) //successfully openend the position { if(enableAlerts) { Alert(_Symbol, " Successfully openend SELL POSITION #", tradeResult.order, ", Price: ", tradeResult.price); } PrintFormat("retcode=%u deal=%I64u order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order); return(true); } else { if(enableAlerts) { Alert(_Symbol, " ERROR opening a SELL POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK)); } PrintFormat("ERROR: Opening a SELL POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code return(false); } } return(false); }
Función PositionFound:
Esta función comprueba si la posición especificada existe, para que el asesor no abra múltiples posiciones repetidas en la misma vela.
bool PositionFound(string symbol, int positionType, string positionComment) { if(PositionsTotal() > 0) { ulong positionTicket; int totalPositions = PositionsTotal(); //scan all the open positions for(int x = totalPositions - 1; x >= 0; x--) { positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket if( PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_TYPE) == positionType && PositionGetString(POSITION_COMMENT) == positionComment ) { return(true);//a similar position exists, don't open another position on this candle break; } } } return(false); }
Ahora que hemos definido nuestras funciones personalizadas, vamos a llamarlas para que ejecuten las tareas previstas.
- Primero colocaremos y llamaremos a la función GetInit en la función OnInit.
int OnInit() { //--- if(GetInit() <= 0) { return(INIT_FAILED); } //--- return(INIT_SUCCEEDED); }
- Luego colocaremos y llamaremos a la función GetDeinit en la función OnDeinit.
void OnDeinit(const int reason) { //--- GetDeinit(); }
- Y colocaremos y llamaremos a las siguientes funciones en la función OnTick en el orden apropiado. Como algunas funciones modifican variables globales de las que dependen otras funciones para tomar decisiones clave, nos aseguraremos de que sean llamadas en primer lugar, garantizando así que los datos se procesen y actualicen antes de que otras funciones puedan acceder a ellos.
void OnTick() { //--- GetEma(); GetPositionsData(); if(TradingIsAllowed()) { TradeNow(); ManageProfitAndLoss(); } PrintOnChart(); }
Cuando el código del asesor esté listo, lo guardaremos y compilaremos. Esto nos permitirá acceder a nuestro asesor directamente desde nuestro terminal comercial. El código completo se adjunta a continuación.
Probando nuestro asesor en el simulador de estrategias
Debemos asegurarnos de que el asesor funciona según nuestro plan. Podemos hacerlo, o bien cargándolo en el gráfico del símbolo activo y comerciando con él en una cuenta demo, o bien utilizando el simulador de estrategias para realizar una evaluación exhaustiva. Aunque podemos probarlo en una cuenta demo, aquí utilizaremos el simulador de estrategias para evaluar su eficacia.
Estos son los ajustes que aplicaremos en el simulador de estrategias:
-
Bróker: MetaQuotes-Demo (se crea automáticamente al instalar MetaTrader 5)
-
Símbolo: EURUSD
-
Periodo de prueba (intervalo): Último año (de noviembre de 2022 a noviembre de 2023)
-
Modelado: Cada tick basado en ticks reales
-
Depósito inicial: 10.000 USD
-
Apalancamiento: 1:100
Analizando los resultados de nuestras pruebas en la historia, vemos que nuestro asesor no solo ha obtenido beneficios, sino que también ha mantenido una reducción sorprendentemente baja. Esta estrategia es prometedora y puede seguir modificándose y optimizándose para lograr resultados aún mejores, sobre todo al aplicarse a varios símbolos simultáneamente.
Conclusión
Incluso los programadores novatos de MQL5 pueden entender fácilmente el código procedimental del asesor que acabamos de crear. Esta simplicidad surge de la naturaleza clara y directa de la programación procedimental, sobre todo al utilizar funciones para organizar el código según las tareas específicas y las variables globales para transmitir los datos modificados a distintas funciones.
Quizá haya notado que una desventaja del código procedimental es su tendencia a ampliarse significativamente a medida que el asesor se hace más complejo, lo cual lo hace adecuado sobre todo para proyectos menos complejos. Cuando el proyecto es muy complejo, será mejor optar por un enfoque orientado a objetos.
En nuestro próximo artículo, aprenderemos más sobre la programación orientada a objetos y convertiremos en un código orientado a objetos nuestro código recién creado de asesor procedimental basado en el impulso de precio. Esto permitirá comparar claramente estos paradigmas y comprender mejor las diferencias.
¡Gracias por su atención! Le deseo éxito en el desarrollo de MQL5 y el trading.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13771





- 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