
如何使用 Controls 类创建交互式 MQL5 仪表盘/面板(第 2 部分):添加按钮响应。
引言
在先前的文章中,我们成功搭建了MQL5面板的核心组件。在那个阶段,我们的按钮和标签仍然是静态的,提供了一个基本的但尚不具备交互性的结构。现在,是时候改变这个纯视觉组件了。在接下来的这部分内容中,我们将专注于使面板真正具备交互性。我们将为组件注入活力,通过添加响应用户输入和点击的能力,将我们的面板转变为一个动态工具,使其准备好用于实时交易互动。
本质上,本文是第二部分,在这部分中,我们将探讨如何自动化在第一部分中创建的按钮的功能,确保它们在被点击和编辑时能够做出反应。我们将学习如何设置触发特定操作的事件,让用户能够以有意义的方式与面板进行交互。我们将涵盖以下关键主题:
- 自动化元素说明:对将获得相关功能的组件进行详细阐述。
- 在 MQL5 中自动化 GUI 交互:实现必要的代码,确保按钮能够有效地响应用户输入和点击。
- 总结:总结在创建交互式面板方面取得的进展。
让我们深入探讨这些主题,以增强我们的交易界面!
自动化元素的说明
我们将专注于自动化我们在 MQL5 面板第一部分中创建的按钮。每个按钮都有特定的功能,我们希望确保它们能够直观地响应用户的指令。这种反应是至关重要的,因为与在后台运行的程序不同,交易面板需要易于使用且易于访问。首先,我们关注面板右上角的按钮,它被设计用来关闭整个界面。因此,如果交易环境在MetaTrader 5图表上是开启的,那么应该能够像关闭应用程序一样关闭面板。
当交易按钮处于激活状态时,我们将放置一些执行特定交易操作的按钮。这些包括“市价买入”,“市价卖出”,“限价卖出”,“止损卖出”,“限价买入”和“止损买入”。这些按钮将允许快速下单,并帮助交易者对不断变化的市场做出即时反应。我们还将自动化平仓按钮,当平仓按钮处于激活状态时,这些按钮将实际管理交易。它们包括“平仓所有头寸”,“平仓所有盈利头寸”,还有许多其他按钮,其中几乎让我们手指发抖的按钮是“删除所有挂单”。当你点击一个按钮时,它会按照它所说的去做。
最后,我们将自动化信息按钮,当按下该按钮时,会展开一个包含按钮的界面,详细显示用户的账户信息和背景信息。我们希望这将有助于让交易者了解与他们的账户相关的关键细节,从而帮助他们做出更好的决策。所有这些的目标是创建一个响应式的交易面板,使交易者需要进行的操作变得简单,并且在某种程度上,比之前的面板更具吸引力。
为了便于理解这些自动化流程和组件,以下是在先前基础上对其进行的详细描述。
既然我们已经明确了要做的事情,那么让我们立刻开始吧。如果你还不了解交易面板相关的信息,请参考我们之前的文章,我们在其中创建了 GUI 元素的静态组件,这样你就可以跟上我们的步伐。让我们开始吧。
在MQL5中自动化GUI交互
我们将按照从简单到复杂的流程推进,从而使架构按时间先后顺序进行构建。因此,我们将在每个价格变化(tick)时更新账户信息。为了实现这一点,我们需要使用OnTick事件处理程序,这是 MQL5 中的一个内置函数,通常在价格报价发生变化时被调用。该函数是 void 数据类型,这意味着它直接处理执行,不需要返回任何输出。函数应该如下所示。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ ... } //+------------------------------------------------------------------+
这是事件处理程序,来处理价格变化,也是我们程序的核心。我们将向其中添加程序逻辑,如下所示:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Start of the OnTick function, called on every price tick //--- Check if the background color of the INFO button is yellow if (obj_Btn_INFO.ColorBackground() == clrYellow) { //--- Update the account equity display on the panel obj_Btn_ACC_EQUITY.Text(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2)); //--- Update the account balance display on the panel obj_Btn_ACC_BALANCE.Text(DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2)); //--- Update the server trade time display on the panel obj_Btn_TIME.Text(TimeToString(TimeTradeServer(), TIME_DATE | TIME_SECONDS)); } //--- End of the OnTick function } //+------------------------------------------------------------------+
在 OnTick 函数中,我们首先执行的操作是检查信息按钮("obj_Btn_INFO")的背景颜色。如果按钮的背景设置为黄色,这表明我们已经激活了信息显示模式。在满足这一条件的情况下,我们将更新面板上与账户相关的各种显示内容。具体来说,我们通过调用 AccountInfoDouble 函数并传入属性 "ACCOUNT_EQUITY" 作为输入参数来获取当前账户权益,并使用 DoubleToString 函数将其格式化为两位小数。然后将该值赋给 "obj_Btn_ACC_EQUITY" 按钮的文本内容,从而确保我们能够随时获取最新的权益信息。
接下来,我们以类似的方式更新账户余额显示:通过 "ACCOUNT_BALANCE" 参数属性获取账户余额,将其格式化为两位小数,并将其设置为 "obj_Btn_ACC_BALANCE" 按钮的文本内容。最后,我们通过调用 TimeTradeServer 函数获取当前服务器交易时间,将其格式化为包含日期和秒的时间格式,并用该值更新 "obj_Btn_TIME" 按钮的文本内容。这确保我们始终能够了解最新的交易时间,这对于交易场景中的及时决策至关重要。这些就是最终结果。
从图表中可以看到,时间字段已经相应地更新了,这说明成功了。现在,第一个自动化组件已经完成。这很简单,对吧?接下来,我们继续处理 GUI 面板的其他组件。其余元素的自动化将在 OnChartEvent 函数处理器中完成,因此我们来深入了解一下它的输入参数以及它的功能。
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... }
该函数的目的是处理由用户或 MQL5 程序对图表所做的更改。因此,用户进行的交互操作,例如移动鼠标、编辑按钮字段以及点击标签和按钮,都将被这个事件处理器捕获并处理。让我们进一步解读它的参数:
- id:此参数表示事件 ID,对应于 11 种预定义事件类型之一。这些事件类型包括按键事件、鼠标移动、对象创建、图表更改以及自定义事件。对于自定义事件,可以使用从 CHARTEVENT_CUSTOM 到 CHARTEVENT_CUSTOM_LAST 的 ID。11 种事件类型如下所示:
- lparam:长整型(long)事件参数。其值取决于正在处理的具体事件。例如,在按键事件中,它可能表示按键的代码。
- dparam:双精度型(double)事件参数。与 lparam 类似,其值根据事件类型而变化。例如,在鼠标移动事件中,它可能表示鼠标光标的位置。
- sparam:字符串型(string)事件参数。同样,其含义取决于事件类型。例如,在对象创建事件中,它可能包含新创建对象的名称。
为了更清晰地展示这一点,我们可以在函数中添加一个打印语句,将这四个参数的值输出到日志中。
// Print the 4 function parameters Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam);
该函数将打印图表事件 ID、其长整型事件值、双精度型事件值以及字符串型值。让我们查看以下 GIF,以便更直观地理解。
从提供的 GIF 中,一切应该都清楚了。接下来,我们将专注于捕获 GUI 面板元素上的图表点击事件。因此,我们的 ID 将是 CHARTEVENT_OBJECT_CLICK。
//Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam); if (id==CHARTEVENT_OBJECT_CLICK){ //--- }
我们首先将之前的代码行注释掉,因为我们不希望日志中充斥着无关信息。使用的两个斜杠(//)称为单行注释,它们从注释的起始位置开始,并持续到该行的末尾,因此得名“单行”注释。在执行过程中,计算机会忽略注释。我们使用 if 语句来检查是否发生了对象点击事件。这是通过将图表事件 ID 等于对象点击枚举值来实现的。如果确实点击了对象,让我们打印参数并查看结果。以下是代码。
if (id==CHARTEVENT_OBJECT_CLICK){ Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam); ... }
在打印函数中,我们将 "LPARAM" 改为 "LP","DPARAM" 改为 "DP",以便我们只关注图表事件 ID 和被点击对象的名称。从那里,我们可以获取对象的 ID,并在必要时采取行动。以下是逻辑的示意图:
我们自动化的第一个按钮是交易按钮。
if (sparam==obj_Btn_TRADE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_TRADE.Name()); // Reset the pressed states of all buttons to ensure only one button appears pressed at a time obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Change the background color of the Trade button to yellow to indicate it is active obj_Btn_TRADE.ColorBackground(clrYellow); // Set the background color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBackground(clrSilver); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to yellow to match its background obj_Btn_TRADE.ColorBorder(clrYellow); // Set the border color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBorder(clrSilver); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Close section if it exists destroySection_Close(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Trade section, bringing it to the forefront createSection_Trade(); }
在这里,我们通过检查字符串参数是否与“交易”按钮的名称匹配(使用“Name”函数)来管理“交易”按钮被点击时的交互。首先,我们使用Print函数记录被点击对象的名称,这有助于调试,因为它可以用于确认哪个按钮被点击。接下来,通过调用每个按钮的“Pressed”函数并传递一个 false 标志,来重置所有相关按钮(“obj_Btn_TRADE”、“obj_Btn_CLOSE”和“obj_Btn_INFO”)的按下状态。这确保了任何时候只有一个按钮在视觉上处于按下状态。在重置之后,我们通过使用“ColorBackground”函数将“交易”按钮的背景颜色改为黄色,同时将“平仓”和“信息”按钮的背景颜色设置为银色,从而在视觉上表明“交易”按钮处于激活状态,而其他按钮处于非激活状态。同样,我们使用“ColorBorder”函数更新边框颜色——“交易”按钮为黄色,其他按钮为银色。
然后,我们通过调用“destroySection_Close”和“destroySection_Information”函数来清理界面,移除“平仓”或“信息”部分显示的任何内容。最后,我们调用“createSection_Trade”函数来动态创建并显示交易部分,确保用户可以访问交易界面。我们对“平仓”按钮也做了类似的操作。
// Check if the clicked object is the Close button else if (sparam==obj_Btn_CLOSE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_CLOSE.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade button to silver, indicating it's inactive obj_Btn_TRADE.ColorBackground(clrSilver); // Change the background color of the Close button to yellow to indicate it is active obj_Btn_CLOSE.ColorBackground(clrYellow); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to silver obj_Btn_TRADE.ColorBorder(clrSilver); // Set the border color of the Close button to yellow obj_Btn_CLOSE.ColorBorder(clrYellow); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Close section, bringing it to the forefront createSection_Close(); }
在这里,我们几乎采用了与“交易”按钮完全相同的逻辑。我们通过检查字符串参数是否与“平仓”按钮的名称匹配(使用“Name”函数)来处理“平仓”按钮被点击的情况。首先,我们使用Print函数记录被点击按钮的名称,以便进行调试。这有助于我们确认点击了正确的按钮。接下来,我们通过调用“Pressed”函数重置“obj_Btn_TRADE”、“obj_Btn_CLOSE”和“obj_Btn_INFO”按钮的按下状态。这确保了点击后没有任何按钮为保持按下的状态。然后,我们通过使用“ColorBackground”函数将“平仓”按钮的背景颜色改为黄色,同时将“交易”和“信息”按钮的背景颜色设置为银色,从而更新界面,表明“平仓”按钮处于激活状态,而其他按钮处于非激活状态。
同样,我们使用“ColorBorder”函数调整按钮的边框颜色,将“平仓”按钮的边框颜色设置为黄色以匹配其背景,而“交易”和“信息”按钮的边框颜色设置为银色以表明它们处于非激活状态。为了清理界面,我们调用“destroySection_Trade”和“destroySection_Information”函数,移除交易或信息部分的任何现有内容。最后,我们调用“createSection_Close”函数,动态生成并显示与平仓头寸或订单相关的界面,确保用户可以在面板上与适当的平仓选项进行交互。信息按钮也采用了相同的逻辑。其代码片段如下:
// Check if the clicked object is the Information button else if (sparam==obj_Btn_INFO.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_INFO.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade and Close buttons to silver, indicating they are inactive obj_Btn_TRADE.ColorBackground(clrSilver); obj_Btn_CLOSE.ColorBackground(clrSilver); // Change the background color of the Info button to yellow to indicate it is active obj_Btn_INFO.ColorBackground(clrYellow); // Set the border color of the Trade and Close buttons to silver obj_Btn_TRADE.ColorBorder(clrSilver); obj_Btn_CLOSE.ColorBorder(clrSilver); // Set the border color of the Info button to yellow obj_Btn_INFO.ColorBorder(clrYellow); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Close section if it exists destroySection_Close(); // Create the Information section, bringing it to the forefront createSection_Information(); }
当我们编译并运行程序时,我们从同控制按钮的交互中得到下面的输出。
可见我们成功了。我们现在想要在点击窗口按钮时移除该面板。
// Check if the clicked object is the exit button (X button) else if (sparam==obj_Btn_X.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_X.Name()); // Call functions to destroy all sections, effectively closing the entire panel destroySection_Trade(); destroySection_Close(); destroySection_Information(); // Call a function to destroy the main panel itself destroySection_Main_Panel(); }
在这里,我们通过检查字符串参数是否与退出按钮的名称匹配(使用“Name”函数)来处理点击退出按钮的情况。首先,我们使用Print函数记录被点击对象的名称,以便进行调试。这有助于确认退出按钮被点击。
接下来,我们通过调用几个函数来关闭整个面板。首先,我们调用“destroySection_Trade”、“destroySection_Close”和“destroySection_Information”函数,移除当前显示的与交易、平仓头寸或显示账户信息相关的所有部分。这些函数确保面板的所有交互部分都被正确清除。最后,我们调用“destroySection_Main_Panel”函数,该函数负责销毁主面板本身。这将有效地从界面中移除面板的所有元素,从而完全关闭面板。图示如下:
现在,我们需要处理在点击相应按钮时的交易操作。为了方便实现这一点,我们需要引入一个类的实例来辅助这个过程。因此,我们在代码的开头使用#include引入一个交易实例。这使我们能够访问“CTrade”类,我们将使用该类来创建一个交易对象。这是至关重要的,因为我们需要它来执行交易操作。
#include <Trade/Trade.mqh>
CTrade obj_Trade;
预处理器会用文件Trade.mqh的内容替换#include <Trade/Trade.mqh>这一行。尖括号表示Trade.mqh文件将从标准目录(通常是terminal_installation_directory\MQL5\Include)中获取。当前目录不会包含在搜索路径中。这行代码可以放在程序的任何位置,但通常,为了代码结构更好和引用更方便,所有的包含指令都放在源代码的开头。声明CTrade类的obj_Trade对象将使我们能够轻松访问该类中包含的方法,这得益于MQL5开发者的设计。
在引入交易类库后,我们可以按下卖出按钮来开立一个空头头寸。这就是我们采用的逻辑代码。
else if (sparam==obj_Btn_SELL.Name()){ //--- Check if the Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELL.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Bid; //--- Set the entry price for selling to the current bid price double stopLoss = Ask+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Ask-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Sell(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the sell order }
在这里,一旦我们确认点击了卖出按钮,我们使用Print函数记录按钮点击事件,以便调试,该函数输出被点击按钮的名称。 然后,我们继续获取必要的交易要素。首先,我们使用SymbolInfoDouble函数获取当前交易品种的“卖价”(ask)和“买价”(bid),并使用NormalizeDouble函数根据_Digits定义的小数位数对这些价格进行格式化。
接下来,我们使用StringToDouble 函数,将“手数”(Lots)字段中的文本转换为数字来获取交易量。入场价格设置为当前的“买价”,因为卖出订单使用此价格进行执行。我们还根据用户输入计算止损和获利值。止损是通过将用户定义的值(来自“SL”输入字段)加到“卖价”上,并根据交易品种的最小价格变动单位_Point进行调整来计算的。同样,获利是通过从“卖价”中减去用户定义的值(来自“TP”输入字段)来计算的。
最后,我们使用 Print 函数记录订单详情,如交易量、入场价格、止损和获利。通过调用 “obj_Trade” 对象的 “Sell” 函数并传递必要的参数(交易量、交易品种、入场价格、止损和获利),执行卖出订单。对于买入头寸,使用类似的逻辑,如下所示。
else if (sparam==obj_Btn_BUY.Name()){ //--- Check if the Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUY.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Ask; //--- Set the entry price for buying to the current ask price double stopLoss = Bid-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Bid+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Buy(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the buy order }
当我们编译并测试程序后,我们得到如下输出。
开立限价和止损订单的逻辑类似。然而,由于这些订单并不直接使用当前市场价格,我们需要配置并引入一个额外的算法来进行安全检查。让我们从“sell-stop”按钮开始。
else if (sparam==obj_Btn_SELLSTOP.Name()){ //--- Check if the Sell Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid - stopslevel*_Point; //--- Calculate the valid price for placing a sell stop order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell stop order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell stop order } }
在这里,我们通过检查变量字符串参数是否与“sell-stop”按钮的名称匹配(使用“Name”函数)来处理“sell-stop”按钮被点击时的自动化逻辑。确认后,我们像往常一样使用Print函数记录事件,输出被点击按钮的名称,以便调试。我们首先使用SymbolInfoDouble函数获取市场价格,并通过NormalizeDouble函数根据交易品种的小数精度(由 "_Digits" 定义)对“卖价”(ask)和“买价”(bid)进行格式化。
接下来,我们从“Price”输入字段获取“用户定义的价格”,使用StringToDouble函数将其从文本转换为数值。此外,我们使用SymbolInfoInteger函数和“SYMBOL_TRADE_STOPS_LEVEL”参数获取交易品种的最小止损距离,这对于验证止损单是必要的。我们通过从“买价”中减去止损距离(转换为点数)来计算有效价格。
然后,我们通过将“用户定义价格”与计算出的有效价格进行比较,来检查其是否有效。如果用户价格超出有效范围,我们将记录一条错误消息,提示“无效的价格”。然而,如果用户定义的价格有效,我们将继续以下步骤。我们使用 StringToDouble 函数从输入字段获取手数,并将其从文本转换为数值。入场价格设置为用户定义的价格。然后,我们通过将用户定义的止损值(来自“SL”字段)加到入场价格上,并根据交易品种的价格变动单位_Point进行调整,来计算止损。同样,我们通过从入场价格中减去用户定义的获利值(来自“TP”字段)来计算获利。
最后,我们使用 Print 函数记录订单详情,包括交易量、“入场价格”、“止损”和“获利”。通过调用 “obj_Trade” 对象的 “Sell Stop” 函数并传递相关参数(如交易量、入场价格、交易品种、止损和获利),根据用户的指令下达卖出止损单。对于其他订单的执行,我们采用类似的方法。
else if (sparam==obj_Btn_BUYSTOP.Name()){ //--- Check if the Buy Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask + stopslevel*_Point; //--- Calculate the valid price for placing a buy stop order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy stop order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy stop order } } else if (sparam==obj_Btn_SELLLIMIT.Name()){ //--- Check if the Sell Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid + stopslevel*_Point; //--- Calculate the valid price for placing a sell limit order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell limit order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell limit order } } else if (sparam==obj_Btn_BUYLIMIT.Name()){ //--- Check if the Buy Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask - stopslevel*_Point; //--- Calculate the valid price for placing a buy limit order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy limit order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy limit order } }
当我们测试按钮时,我们得到如下结果。
成功了。现在我们来平仓头寸以及删除挂单。我们从平仓所有持仓头寸的代码逻辑开始。
else if (sparam==obj_Btn_CLOSE_ALL.Name()){ //--- Check if the Close All button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol obj_Trade.PositionClose(pos_ticket); //--- Close the position } } } } }
在这里,我们只是处理“平仓所有”按钮的自动化逻辑,该按钮在被点击时将平仓所有活跃的头寸。我们首先检查按钮是否被点击,并通过使用 Print 函数将被点击按钮的名称打印到日志中,以便调试。接下来,我们启动一个 for循环,通过使用PositionsTotal函数来遍历账户中的所有未平仓头寸,该函数返回未平仓头寸的总数。循环从列表中的最后一个头寸开始(由 “PositionsTotal() - 1” 表示),并逐步向前处理,每次迭代后递减 “i”,以确保我们处理所有头寸。
在每次迭代中,我们使用PositionGetTicket函数获取当前头寸的ticket编号。ticket编号唯一标识每个头寸。然后我们通过确保编号大于 0 来检查其是否有效。如果有效,我们使用PositionSelectByTicket函数选择与该编号相关联的头寸。
在选择头寸后,我们进一步通过使用PositionGetString函数和 “POSITION_SYMBOL” 参数来检查该头寸是否与我们正在管理交易的交易品种匹配,并将其与_Symbol(表示当前交易品种)进行比较。如果头寸与交易品种匹配,我们调用 “obj_Trade” 对象的 “PositionClose” 函数,并将ticlet编号作为参数传递以平仓该头寸。这个过程会持续进行,直到所有匹配交易品种的头寸都被平仓。为了平仓所有空头头寸,我们将使用相同的函数,但会增加额外的控制逻辑,如下所示。
else if (sparam==obj_Btn_CLOSE_ALL_SELL.Name()){ //--- Check if the Close All Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position obj_Trade.PositionClose(pos_ticket); //--- Close the sell position } } } } } }
这是一种与我们之前用来平仓所有头寸的方法类似的方法。因此,我们特别标注了新增的部分,以便专注于在点击“平仓所有卖单”按钮时仅平仓空头头寸所使用的额外逻辑。在遍历所有头寸后,我们使用带有常量POSITION_TYPE的PositionGetInteger函数获取每个头寸的类型。然后,我们将该值转换为枚举类型ENUM_POSITION_TYPE ,以便更容易理解,这一过程称为类型转换。一旦我们获得了头寸类型,我们通过将其与POSITION_TYPE_SELL属性进行比较,专门检查当前头寸是否为空头头寸。如果条件为真,即该头寸确实是一个空头头寸,我们将使用 “obj_Trade” 对象的 “PositionClose” 函数来平仓它。平仓多头头寸时,我们采用同样的方法。
else if (sparam==obj_Btn_CLOSE_ALL_BUY.Name()){ //--- Check if the Close All Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position obj_Trade.PositionClose(pos_ticket); //--- Close the buy position } } } } } }
在平仓所有处于亏损状态的空头头寸时,我们仍然需要扩展当前的方法,考虑所选头寸的盈亏情况,这样我们就可以进行比较并判断它们是盈利还是亏损。以下是其逻辑。
else if (sparam==obj_Btn_CLOSE_LOSS_SELL.Name()){ //--- Check if the Close Loss Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing sell position } } } } } } }
在这里,我们在遍历所有头寸并确认当前头寸是空头头寸之后,引入了一个对每个头寸的盈亏情况进行检查。PositionGetDouble函数通过使用常量POSITION_PROFIT来检索所选头寸的当前盈亏情况,从而判断该头寸是盈利还是亏损。然后,我们添加了一个条件语句来检查盈亏值是否小于零,这将表明该头寸处于亏损状态。如果满足此条件,我们将通过调用 “PositionClose” 函数来平仓亏损的空头头寸。我们用黄色标注的这部分额外逻辑,确保只有当前处于亏损状态的空头头寸才会被平仓。对于其他按钮,我们也采用了类似的方法。
else if (sparam==obj_Btn_CLOSE_LOSS_BUY.Name()){ //--- Check if the Close Loss Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_SELL.Name()){ //--- Check if the Close Profit Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable sell position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_BUY.Name()){ //--- Check if the Close Profit Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_LOSS.Name()){ //--- Check if the Close All Loss button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_LOSS.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_PROFIT.Name()){ //--- Check if the Close All Profit button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_PROFIT.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable position } } } } } }
在完成头寸的逻辑处理之后,我们现在可以转向挂单的逻辑处理。在这里,我们将删除所有挂单。这是通过以下逻辑实现的。
else if (sparam==obj_Btn_CLOSE_PENDING.Name()){ //--- Check if the Close Pending button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PENDING.Name()); //--- Log the button click event for (int i = OrdersTotal() -1; i >= 0; i--){ //--- Loop through all pending orders ulong order_ticket = OrderGetTicket(i); //--- Get the ticket of the order if (order_ticket > 0){ //--- Check if the order ticket is valid if (OrderSelect(order_ticket)){ //--- Select the order by ticket if (OrderGetString(ORDER_SYMBOL)==_Symbol){ //--- Check if the order matches the symbol obj_Trade.OrderDelete(order_ticket); //--- Delete the pending order } } } } }
在这里,我们专注于在点击“平仓挂单”按钮时删除挂单的功能。我们首先检查字符串参数是否与“平仓挂单”按钮的名称匹配,这表明该按钮已被激活。确认按钮点击后,我们使用Print函数记录该操作,以便调试,该函数输出被点击按钮的名称。接下来,我们进入一个循环,通过使用OrdersTotal函数获取订单总数,并以倒序递减索引,从而避免在删除过程中跳过任何订单,以此遍历所有挂单。对于每个订单,我们使用OrderGetTicket函数获取订单的编号。然后我们通过确保编号大于零来检查订单是否有效。如果有效,我们继续使用OrderSelect函数选择该订单。
一旦订单被选中,我们通过调用带有常量 "ORDER_SYMBOL" 的OrderGetString函数,验证它是否与_Symbol中指定的交易品种匹配。如果订单匹配,我们将调用OrderDelete函数来删除与检索到的编号相关联的挂单。整个过程允许在按钮被激活时高效地平仓所有与指定交易品种相关的挂单,确保用户能够有效地管理他们的订单。在完成所有这些操作后,我们只需使用以下代码调用ChartRedraw函数刷新图表,以确保图表上显示的更改生效。
ChartRedraw(0);
这就是我们需要自动化的面板的全部内容。当我们运行程序并测试界面时,我们得到了以下输出。
我们整合到系统中用于处理图表点击事件的OnChartEvent处理程序逻辑如下:
//+------------------------------------------------------------------+ //| Handling chart events | //+------------------------------------------------------------------+ void OnChartEvent( const int id, // Event ID indicating the type of event (e.g., mouse click, timer, etc.) const long& lparam, // Long type parameter associated with the event, usually containing data like mouse coordinates or object IDs const double& dparam, // Double type parameter associated with the event, used for floating-point values related to the event const string& sparam // String type parameter associated with the event, typically the name of the object that triggered the event ){ // Print the 4 function parameters //Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam); // Check if the event is a click on a chart object if (id == CHARTEVENT_OBJECT_CLICK){ //Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam); // Check if the clicked object is the Trade button if (sparam==obj_Btn_TRADE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_TRADE.Name()); // Reset the pressed states of all buttons to ensure only one button appears pressed at a time obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Change the background color of the Trade button to yellow to indicate it is active obj_Btn_TRADE.ColorBackground(clrYellow); // Set the background color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBackground(clrSilver); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to yellow to match its background obj_Btn_TRADE.ColorBorder(clrYellow); // Set the border color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBorder(clrSilver); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Close section if it exists destroySection_Close(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Trade section, bringing it to the forefront createSection_Trade(); } // Check if the clicked object is the Close button else if (sparam==obj_Btn_CLOSE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_CLOSE.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade button to silver, indicating it's inactive obj_Btn_TRADE.ColorBackground(clrSilver); // Change the background color of the Close button to yellow to indicate it is active obj_Btn_CLOSE.ColorBackground(clrYellow); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to silver obj_Btn_TRADE.ColorBorder(clrSilver); // Set the border color of the Close button to yellow obj_Btn_CLOSE.ColorBorder(clrYellow); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Close section, bringing it to the forefront createSection_Close(); } // Check if the clicked object is the Information button else if (sparam==obj_Btn_INFO.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_INFO.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade and Close buttons to silver, indicating they are inactive obj_Btn_TRADE.ColorBackground(clrSilver); obj_Btn_CLOSE.ColorBackground(clrSilver); // Change the background color of the Info button to yellow to indicate it is active obj_Btn_INFO.ColorBackground(clrYellow); // Set the border color of the Trade and Close buttons to silver obj_Btn_TRADE.ColorBorder(clrSilver); obj_Btn_CLOSE.ColorBorder(clrSilver); // Set the border color of the Info button to yellow obj_Btn_INFO.ColorBorder(clrYellow); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Close section if it exists destroySection_Close(); // Create the Information section, bringing it to the forefront createSection_Information(); } // Check if the clicked object is the exit button (X button) else if (sparam==obj_Btn_X.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_X.Name()); // Call functions to destroy all sections, effectively closing the entire panel destroySection_Trade(); destroySection_Close(); destroySection_Information(); // Call a function to destroy the main panel itself destroySection_Main_Panel(); } else if (sparam==obj_Btn_SELL.Name()){ //--- Check if the Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELL.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Bid; //--- Set the entry price for selling to the current bid price double stopLoss = Ask+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Ask-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Sell(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the sell order } else if (sparam==obj_Btn_BUY.Name()){ //--- Check if the Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUY.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Ask; //--- Set the entry price for buying to the current ask price double stopLoss = Bid-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Bid+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Buy(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the buy order } else if (sparam==obj_Btn_SELLSTOP.Name()){ //--- Check if the Sell Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid - stopslevel*_Point; //--- Calculate the valid price for placing a sell stop order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell stop order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell stop order } } else if (sparam==obj_Btn_BUYSTOP.Name()){ //--- Check if the Buy Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask + stopslevel*_Point; //--- Calculate the valid price for placing a buy stop order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy stop order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy stop order } } else if (sparam==obj_Btn_SELLLIMIT.Name()){ //--- Check if the Sell Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid + stopslevel*_Point; //--- Calculate the valid price for placing a sell limit order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell limit order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell limit order } } else if (sparam==obj_Btn_BUYLIMIT.Name()){ //--- Check if the Buy Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask - stopslevel*_Point; //--- Calculate the valid price for placing a buy limit order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy limit order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy limit order } } else if (sparam==obj_Btn_CLOSE_ALL.Name()){ //--- Check if the Close All button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol obj_Trade.PositionClose(pos_ticket); //--- Close the position } } } } } else if (sparam==obj_Btn_CLOSE_ALL_SELL.Name()){ //--- Check if the Close All Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position obj_Trade.PositionClose(pos_ticket); //--- Close the sell position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_BUY.Name()){ //--- Check if the Close All Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position obj_Trade.PositionClose(pos_ticket); //--- Close the buy position } } } } } } else if (sparam==obj_Btn_CLOSE_LOSS_SELL.Name()){ //--- Check if the Close Loss Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing sell position } } } } } } } else if (sparam==obj_Btn_CLOSE_LOSS_BUY.Name()){ //--- Check if the Close Loss Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_SELL.Name()){ //--- Check if the Close Profit Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable sell position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_BUY.Name()){ //--- Check if the Close Profit Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_LOSS.Name()){ //--- Check if the Close All Loss button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_LOSS.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_PROFIT.Name()){ //--- Check if the Close All Profit button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_PROFIT.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable position } } } } } } else if (sparam==obj_Btn_CLOSE_PENDING.Name()){ //--- Check if the Close Pending button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PENDING.Name()); //--- Log the button click event for (int i = OrdersTotal() -1; i >= 0; i--){ //--- Loop through all pending orders ulong order_ticket = OrderGetTicket(i); //--- Get the ticket of the order if (order_ticket > 0){ //--- Check if the order ticket is valid if (OrderSelect(order_ticket)){ //--- Select the order by ticket if (OrderGetString(ORDER_SYMBOL)==_Symbol){ //--- Check if the order matches the symbol obj_Trade.OrderDelete(order_ticket); //--- Delete the pending order } } } } } } ChartRedraw(0); }
简而言之,这就是我们所取得的成就。
这太棒了!我们成功地让面板“活”了起来,使其完全具备了交互性和响应性。它现在具备按钮点击、实时数据更新以及对激活状态的响应功能,从而提升了我们交易界面的整体用户体验和功能性。
结论
总结来说,我们在MQL5 GUI 面板中实现的增强功能显著提升了其交互性和功能性,创造了更具吸引力的用户体验。通过添加实时数据更新和响应式按钮点击,我们现在可以无缝且直观地与面板进行交互。这些功能不仅让执行买入和卖出订单变得更加容易,还确保用户能够即时获取实时交易账户信息,使他们能够在市场条件变化时迅速做出明智的交易决策。
此外,各种组件的自动化(如头寸管理和账户信息显示)为交易过程增加了便利性和效率。通过让用户只需点击一下即可平仓头寸和订单,并提供可定制选项,GUI 面板成为现代交易者的强大工具。这种转变营造了一个更干净、更有条理的工作空间,从而提高了交易专注度和效率。我们相信这篇文章为您提供了关于增强 MQL5 GUI 面板的宝贵见解,并希望您觉得这些解释清晰且富有信息量。祝您交易愉快!
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16146




你读过这篇文章吗?
面板既美观又实用。谢谢。
欢迎光临
有计划(最小化/卸载)面板吗?
小组真棒。很棒的文章!这篇文章不仅介绍了如何为交易者创建一个完美的面板,而且相关信息介绍得非常清楚,初学者可以将其作为指南使用。该指南不仅用于创建面板,还指导如何正确、熟练地编写 MQL5 代码。文章非常有价值,内容翔实。感谢作者 Allan Munene Mutiiria!!!
致敬、
B.V. Dolgikh