English Русский Español Deutsch 日本語 Português
preview
理解编程范式(第 1 部分):开发价格行为智能系统的过程化方式

理解编程范式(第 1 部分):开发价格行为智能系统的过程化方式

MetaTrader 5示例 | 30 七月 2024, 10:04
637 0
Kelvin Muturi Muigua
Kelvin Muturi Muigua

概述

在软件开发领域,编程范式是编写和组织代码的指导性蓝图。就像从不同的路线中进行选择,以便到达目的地一样,利用 MQL5 完成任务也存在不同的编程方式或范式。

在两篇文章中,我们将探讨利用 MQL5 构建交易工具所需的基本编程范式。我的目标是分享有效和最佳实践的方法,即通过短洁高效的代码产生出色的结果。我将解释每种编程风格,并通过创建一个功能完备的智能系统来演示它。

编程范式的类型

每位 MQL5 开发人员或程序员都应该了解三种主要的编程范式:

  1. 过程化编程:本文将重点介绍该范式。
  2. 函数化编程:本文也将讨论这种范式,因为它与过程化编程非常相似。
  3. 面向对象编程(OOP):这种范式将在下一篇文章中讨论。
每种范式都有其独特的规则和性质,旨在解决特定问题,以及您如何利用 MQL5 有效地开发塑造各种交易工具。


理解过程化编程

过程化编程是一种系统的、循序渐进的代码编写方式。它涉及将任何问题分解为一系列精确的指令,就像遵循食谱一样。程序员为计算机创建一条清晰的路径,逐行引导它完成每个步骤,从而达到预期的结果。

无论您是编程萌新、亦或只是对代码组织感到好奇,过程化编程都提供了一个直接了当且直观的进入编码世界的切入点。

过程化编程的主要性质


以下是表征过程化编程的主要性质:

  1. 函数:
    过程化编程的核心是函数。这些是一组指令,它们组合在一起执行特定任务。函数封装功能,促进模块化和代码重用。
  2. 自上而下的设计:
    过程化编程通常采用自上而下的设计方式。开发人员将问题分解为更小、更易于管理的子任务。每个子任务都是单独解决的,为整体解决方案做出贡献。
  3. 命令式风格:
    过程化编程的命令式性质强调改变程序状态的显式语句。开发人员通过一系列过程化命令指定程序应如何完成任务。
  4. 变量和数据:
    过程化编程中的过程或函数操作变量和数据。这些变量在程序执行期间可以保存发生变化的述值。状态更改是过程化编程的一个基本面。
  5. 顺序执行:
    程序的执行遵循顺序流程。语句一条接一条地执行,循环和条件语句等控制结构指导程序的流程。
  6. 模块化:
    过程化编程通过将代码组织到过程、或函数中来促进模块化。每个模块都处理程序功能的特定方面,从而增强了代码的组织性和可维护性。
  7. 可重用性:
    代码可重用性是过程化编程的一个关键优势。一旦编并测试了一个函数,它就可以在程序中需要特定功能的任何地方使用,从而减少冗余,并提高效率。
  8. 可读性:
    过程代码往往更具可读性,特别是对于那些习惯于循序渐进方式的人来说。执行线性流程令遵循程序的逻辑变得容易。

理解函数化编程

函数化编程围绕着函数作为一等成员和不变性的概念。它类似于过程化编程,除了它如何处理数据并执行任务的主要原则之外。

与过程化编程不同,其中数据可以在程序执行过程中改变其外观和角色,而函数化编程偏爱更稳定的环境。一旦创建了数据,它就会保持原样。这种对不可变性的承诺确保了一定程度的可预测性,并有助于防止代码中出现意外的副作用。

函数化编程的主要性质


以下是函数化编程的一些关键定义特征:

  1. 不变性:
    在函数化编程中,不变性是一个核心原则。一旦创建了数据,它就会保持不变。宁可不修改现有数据,而是按期待的变化创建新数据。这确保了可预测性,并有助于避免意外的副作用。
  2. 函数作为一等成员:
    函数被视为一等成员,这意味着它们可以被分配给变量,作为参数传递给其它函数,以及作为其它函数的结果返回。这种灵活性允许创建高阶函数,并促进了更具模块化、及表现力的编码风格。
  3. 声明式风格:
    函数化编程偏爱声明式编程风格,其中重点是程序应该完成什么,而不是如何实现它。这有助于令代码更加简洁易读。
  4. 避免可变状态:
    在函数化编程中,可变状态被最小化或消除。数据被视为不可变的,函数避免修改外部状态。此特性简化了有关函数行为的推论。
  5. 递归和高阶函数:
    递归和高阶函数,其中函数将其它函数作为参数,或将它们作为结果返回,通常用于函数化编程。这导致了更多的模块化,及可重用的代码。


开发价格行为 EA 的过程化方式

现在我们已经深入研究了过程化和函数化编程范式的本质,我们来通过一个实践示例将理论付诸实践:创建一个基于价格动作的智能交易系统。首先,我将提供有关我们设置自动化交易策略的见解。稍后,我们将浏览代码的各个组件,解读它们的功能,以及它们如何无缝地协同工作。


依据 EMA 指标的价格动作策略


我们的交易策略依赖于一个称为指数移动平均线(EMA)的单一指标。该指标广泛用于技术分析,有助于基于您选择的交易设置判定市场方向。您可以在 MQL5 上轻松找到移动平均线作为标准指标,从而可以直接将其合并到我们的代码当中。


买入入场:
当最近收盘的蜡烛是根阳线,并且其最低价和最高价均高于指数移动平均线(EMA)时,开立多头持仓。

价格动作 EMA 策略买入信号


卖出入场:
当最近收盘的蜡烛是根阴线,并且其最低价和最高价都低于指数移动平均线(EMA)时,开立空头持仓。

价格动作 EMA 策略卖出信号


离场:
当达到用户为账户指定的盈亏百分比,或利用传统的止损或止盈订单时,所有持仓自动平仓,并实现相关的盈亏。


设置 条件
买入入场: 当最近收盘的蜡烛是根阳线(收盘价>开盘价),并且其最低价和最高价均高于指数移动平均线(EMA)。
卖出入场: 当最近收盘的蜡烛是根阴线(收盘<开盘),并且其最低价和最高价均低于指数移动平均线(EMA)。
离场:  当达到用户指定的百分比,或触发止损或止盈订单时,所有持仓平仓,并实现盈亏。


交易策略编码和实现

现在我们的交易规则和计划已建立,我们通过在 MetaEditor IDE 中编写我们的 MQL5 代码来将我们的交易策略具现。按照以下步骤操作,以确保我们从一个干净的智能交易系统模板开始,该模板仅包含所需的必要函数:


步骤1:打开 MetaEditor IDE,并使用 “新建” 菜单项按钮启动 “MQL 向导”。

MQL5 向导:新建智能系统


步骤 2:选择“智能系统(模板)”选项,然后单击“下一步”。

MQL5 向导:新建智能系统


步骤 3:在“常规属性”部分,填写智能系统名称,然后单击“下一步”继续。

MQL5 向导:新建智能系统


步骤 4:在“事件处理程序”部分中,确保未选择任何选项。取消选中的任何选项(如果已选择),然后单击“下一步”。

MQL5 向导:新建智能系统


步骤 5:在“测试器事件处理程序”部分中,确保未选择任何选项。如果已选中任何选项,请取消选中它们,然后单击“完成”,以生成我们的 MQL5 智能系统模板。

MQL5 向导:新建智能系统


我们现在有一个干净的 MQL5 智能系统模板,只有必需的函数(OnInit、OnDeinit 和 OnTick)。记住先保存新文件,然后再继续。

以下是我们新生成的智能系统代码的样子:

//+------------------------------------------------------------------+
//|                                               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()
  {
//---

  }
//+------------------------------------------------------------------+


首先我们编写一个 EA 的简要描述,声明并初始化用户输入变量,然后声明所有全局变量。将此代码放在 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])


正如我们之前所了解到的,“过程化编程的核心是函数。这些是一组指令,它们组合在一起执行特定任务。函数封装了功能,促进了模块化和代码重用。“我们将通过创建我们自己的自定义函数来实现这一点。

GetInit 函数:

该函数负责初始化所有全局变量,并在加载或初始化 EA 时执行任何其它任务。

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);
  }


GetDeinit 函数:

该函数负责在 EA 关闭之前释放任何已用内存、清除图表注释、以及处理其它逆初始化任务。

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("");
  }

GetEma 函数:

该函数提取指数移动平均线的数值,并通过将 EMA 值与最近收盘蜡烛的开盘价、收盘价、最高价和最低价进行比较来判定市场方向。它将市场方向值保存在全局变量之中,以便由其它函数进一步处理。

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;
        }
     }
  }


GetPositionsData 函数:

该函数扫描所有持仓,保存其属性,例如盈利金额、持仓总数、开立的多头和空头持仓总数、以及每种持仓类型的总交易量/手数。它不包括非由我们的 EA 开立的持仓,或与 EA 魔幻数字不匹配的持仓数据。

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;
     }
  }

TradingIsAllowed 函数:

该函数检查用户、终端和经纪商是否已授予 EA 交易权限。

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);
     }
  }

TradeNow 函数:

当所有必需的检查和信号都表明可以继续进行新的交易初始化时,负责开立新持仓。

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);
     }
  }

ManageProfitAndLoss 函数:

该函数检查是否达到用户输入的损益阈值。如果满足条件,它把所有持仓平仓,清算所有盈利或亏损。

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
                 }
              }
           }
        }
     }
  }

PrintOnChart 函数:

在图表上格式化和显示 EA 状态,为用户提供账户和 EA 状态的视觉文本表示。

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);
  }


BuySellPosition 函数:

该函数开立新的多头和空头持仓。

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);
  }

PositionFound 函数:

该函数检查是否存在指定持仓,从而 EA 不会在同一根蜡烛上开立多笔重复持仓。

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);
  }


现在我们已经定义了自定义函数,我们调用它们来执行其预期的任务。

  • 在 OnInit 函数中放置并调用 GetInit 函数。 

int OnInit()
  {
//---
   if(GetInit() <= 0)
     {
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }

  • 在 OnDeinit 函数中放置并调用 GetDeinit 函数。 

void OnDeinit(const int reason)
  {
//---
   GetDeinit();
  }

  • 在 OnTick 函数中按相应的顺序放置并调用以下函数。由于某些函数会修改其它函数所依赖的全局变量,并做出关键决策,因此我们将确保首先调用它们,确保数据在其它函数访问之前得到处理和更新。 

void OnTick()
  {
//---
   GetEma();
   GetPositionsData();
   if(TradingIsAllowed())
     {
      TradeNow();
      ManageProfitAndLoss();
     }
   PrintOnChart();
  }

完成 EA 代码后,保存并编译它。这令您可以直接从交易终端访问您的 EA。完整的代码附在文章底部。


在策略测试器中测试我们的 EA

确保我们的 EA 按照我们的计划运行至关重要。我们可以将其加载到活动品种图表上,并在模拟账户中执行交易来实现这一目标,或者利用策略测试器进行全面评估。虽然您可以在模拟账户上测试它,但现在,我们将使用策略测试器来评估其性能。

以下是我们将在策略测试器中应用的设置:

  • 经纪商:MT5 Metaquotes 模拟账户(在 MT5 安装时自动创建)

  • 品种: EURUSD

  • 测试区间(日期):1 年(2022 年 11 月至 2023 年 11 月)

  • 建模类型: 每次即刻报价都基于真实即刻报价

  • 本金: $10,000 USD

  • 杠杆: 1:100

PriceActionEA 策略测试器设置


PriceActionEA 策略测试器设置


回顾我们的回测结果,我们的 EA 不仅产生了盈利,而且还保持了非常低的回撤。该策略展现出前景,可以进一步修改和优化,从而产生更好的成果,尤其是在同时应用于多个品种时。

PriceActionEA 测试器结果

PriceActionEA 测试器结果

PriceActionEA 测试器结果


结束语

即使是 MQL5 初学者,也能轻松理解我们上面刚刚创建的 EA 的过程化代码。这种简单性源于过程化编程的清晰和直接的性质,特别是在利用函数来基于特定任务组织代码,并利用全局变量将修改后的数据传递给不同的函数之时。

不过,您可能会注意到,过程代码的一个缺点是,随着 EA 变得更加复杂,它往往会明显扩张,因而主要适用于不太复杂的项目。在项目高度复杂的情况下,选择面向对象的编程方法被证明比过程化编程更有优势。

在我们即将发表的文章中,我们将向您介绍面向对象的编程,并将我们最近创建的过程化价格动作 EA 代码转换为面向对象的代码。这将在这些范式之间提供独特的比较,从而更清楚地理解这些差异。

感谢您抽出宝贵时间阅读本文,我祝愿您在 MQL5 开发之旅和交易工作中一切顺利。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/13771

附加的文件 |
PriceActionEMA.mq5 (23.33 KB)
开发回放系统(第 39 部分):铺平道路(三) 开发回放系统(第 39 部分):铺平道路(三)
在进入开发的第二阶段之前,我们需要修正一些想法。您知道如何让 MQL5 满足您的需求吗?您是否尝试过超出文档所包含的范围?如果没有,那就做好准备吧。因为我们将做一些大多数人通常不会做的事情。
在 MQL5 中创建做市商算法 在 MQL5 中创建做市商算法
做市商是如何运作的?让我们探讨一下这个问题,创建一个初级的做市商算法。
开发回放系统(第 40 部分):启动第二阶段(一) 开发回放系统(第 40 部分):启动第二阶段(一)
今天我们将讨论回放/模拟器系统的新阶段。在这个阶段,谈话才会变得真正有趣,内容也相当丰富。我强烈建议您仔细阅读本文并使用其中提供的链接。这将帮助您更好地理解内容。
MQL5 中的定量分析:实现有前途的算法 MQL5 中的定量分析:实现有前途的算法
我们将分析什么是定量分析,以及主要参与者如何运用定量分析的问题。我们将用 MQL5 语言创建一种定量分析算法。