
理解编程范式(第 1 部分):开发价格行为智能系统的过程化方式
概述
在软件开发领域,编程范式是编写和组织代码的指导性蓝图。就像从不同的路线中进行选择,以便到达目的地一样,利用 MQL5 完成任务也存在不同的编程方式或范式。
在两篇文章中,我们将探讨利用 MQL5 构建交易工具所需的基本编程范式。我的目标是分享有效和最佳实践的方法,即通过短洁高效的代码产生出色的结果。我将解释每种编程风格,并通过创建一个功能完备的智能系统来演示它。编程范式的类型
每位 MQL5 开发人员或程序员都应该了解三种主要的编程范式:
- 过程化编程:本文将重点介绍该范式。
- 函数化编程:本文也将讨论这种范式,因为它与过程化编程非常相似。
- 面向对象编程(OOP):这种范式将在下一篇文章中讨论。
理解过程化编程
过程化编程是一种系统的、循序渐进的代码编写方式。它涉及将任何问题分解为一系列精确的指令,就像遵循食谱一样。程序员为计算机创建一条清晰的路径,逐行引导它完成每个步骤,从而达到预期的结果。
无论您是编程萌新、亦或只是对代码组织感到好奇,过程化编程都提供了一个直接了当且直观的进入编码世界的切入点。
过程化编程的主要性质
以下是表征过程化编程的主要性质:
- 函数:
过程化编程的核心是函数。这些是一组指令,它们组合在一起执行特定任务。函数封装功能,促进模块化和代码重用。 - 自上而下的设计:
过程化编程通常采用自上而下的设计方式。开发人员将问题分解为更小、更易于管理的子任务。每个子任务都是单独解决的,为整体解决方案做出贡献。 - 命令式风格:
过程化编程的命令式性质强调改变程序状态的显式语句。开发人员通过一系列过程化命令指定程序应如何完成任务。 - 变量和数据:
过程化编程中的过程或函数操作变量和数据。这些变量在程序执行期间可以保存发生变化的述值。状态更改是过程化编程的一个基本面。 - 顺序执行:
程序的执行遵循顺序流程。语句一条接一条地执行,循环和条件语句等控制结构指导程序的流程。 - 模块化:
过程化编程通过将代码组织到过程、或函数中来促进模块化。每个模块都处理程序功能的特定方面,从而增强了代码的组织性和可维护性。 - 可重用性:
代码可重用性是过程化编程的一个关键优势。一旦编并测试了一个函数,它就可以在程序中需要特定功能的任何地方使用,从而减少冗余,并提高效率。 - 可读性:
过程代码往往更具可读性,特别是对于那些习惯于循序渐进方式的人来说。执行线性流程令遵循程序的逻辑变得容易。
理解函数化编程
函数化编程围绕着函数作为一等成员和不变性的概念。它类似于过程化编程,除了它如何处理数据并执行任务的主要原则之外。
与过程化编程不同,其中数据可以在程序执行过程中改变其外观和角色,而函数化编程偏爱更稳定的环境。一旦创建了数据,它就会保持原样。这种对不可变性的承诺确保了一定程度的可预测性,并有助于防止代码中出现意外的副作用。
函数化编程的主要性质
以下是函数化编程的一些关键定义特征:
- 不变性:
在函数化编程中,不变性是一个核心原则。一旦创建了数据,它就会保持不变。宁可不修改现有数据,而是按期待的变化创建新数据。这确保了可预测性,并有助于避免意外的副作用。 - 函数作为一等成员:
函数被视为一等成员,这意味着它们可以被分配给变量,作为参数传递给其它函数,以及作为其它函数的结果返回。这种灵活性允许创建高阶函数,并促进了更具模块化、及表现力的编码风格。 - 声明式风格:
函数化编程偏爱声明式编程风格,其中重点是程序应该完成什么,而不是如何实现它。这有助于令代码更加简洁易读。 - 避免可变状态:
在函数化编程中,可变状态被最小化或消除。数据被视为不可变的,函数避免修改外部状态。此特性简化了有关函数行为的推论。 - 递归和高阶函数:
递归和高阶函数,其中函数将其它函数作为参数,或将它们作为结果返回,通常用于函数化编程。这导致了更多的模块化,及可重用的代码。
开发价格行为 EA 的过程化方式
现在我们已经深入研究了过程化和函数化编程范式的本质,我们来通过一个实践示例将理论付诸实践:创建一个基于价格动作的智能交易系统。首先,我将提供有关我们设置自动化交易策略的见解。稍后,我们将浏览代码的各个组件,解读它们的功能,以及它们如何无缝地协同工作。
依据 EMA 指标的价格动作策略
我们的交易策略依赖于一个称为指数移动平均线(EMA)的单一指标。该指标广泛用于技术分析,有助于基于您选择的交易设置判定市场方向。您可以在 MQL5 上轻松找到移动平均线作为标准指标,从而可以直接将其合并到我们的代码当中。
买入入场:
当最近收盘的蜡烛是根阳线,并且其最低价和最高价均高于指数移动平均线(EMA)时,开立多头持仓。
卖出入场:
当最近收盘的蜡烛是根阴线,并且其最低价和最高价都低于指数移动平均线(EMA)时,开立空头持仓。
离场:
当达到用户为账户指定的盈亏百分比,或利用传统的止损或止盈订单时,所有持仓自动平仓,并实现相关的盈亏。
设置 | 条件 |
---|---|
买入入场: | 当最近收盘的蜡烛是根阳线(收盘价>开盘价),并且其最低价和最高价均高于指数移动平均线(EMA)。 |
卖出入场: | 当最近收盘的蜡烛是根阴线(收盘<开盘),并且其最低价和最高价均低于指数移动平均线(EMA)。 |
离场: | 当达到用户指定的百分比,或触发止损或止盈订单时,所有持仓平仓,并实现盈亏。 |
交易策略编码和实现
现在我们的交易规则和计划已建立,我们通过在 MetaEditor IDE 中编写我们的 MQL5 代码来将我们的交易策略具现。按照以下步骤操作,以确保我们从一个干净的智能交易系统模板开始,该模板仅包含所需的必要函数:
步骤1:打开 MetaEditor IDE,并使用 “新建” 菜单项按钮启动 “MQL 向导”。
步骤 2:选择“智能系统(模板)”选项,然后单击“下一步”。
步骤 3:在“常规属性”部分,填写智能系统名称,然后单击“下一步”继续。
步骤 4:在“事件处理程序”部分中,确保未选择任何选项。取消选中的任何选项(如果已选择),然后单击“下一步”。
步骤 5:在“测试器事件处理程序”部分中,确保未选择任何选项。如果已选中任何选项,请取消选中它们,然后单击“完成”,以生成我们的 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
回顾我们的回测结果,我们的 EA 不仅产生了盈利,而且还保持了非常低的回撤。该策略展现出前景,可以进一步修改和优化,从而产生更好的成果,尤其是在同时应用于多个品种时。
结束语
即使是 MQL5 初学者,也能轻松理解我们上面刚刚创建的 EA 的过程化代码。这种简单性源于过程化编程的清晰和直接的性质,特别是在利用函数来基于特定任务组织代码,并利用全局变量将修改后的数据传递给不同的函数之时。
不过,您可能会注意到,过程代码的一个缺点是,随着 EA 变得更加复杂,它往往会明显扩张,因而主要适用于不太复杂的项目。在项目高度复杂的情况下,选择面向对象的编程方法被证明比过程化编程更有优势。
在我们即将发表的文章中,我们将向您介绍面向对象的编程,并将我们最近创建的过程化价格动作 EA 代码转换为面向对象的代码。这将在这些范式之间提供独特的比较,从而更清楚地理解这些差异。
感谢您抽出宝贵时间阅读本文,我祝愿您在 MQL5 开发之旅和交易工作中一切顺利。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/13771


信息量大,非常有趣
关于程序设计的精彩文章!
好文章。我期待一些程序化的价格行为编码,如 ABCD 波浪结构或有条件的之字形结构,其步骤如第一步找到一个波峰,第二步找到一个波谷等......如果不考虑交易功能,我不认为高于或低于 EMA 的低位高位蜡烛图是程序化的 "价格行为"。