English Русский Español Deutsch 日本語 Português
preview
开发先进的 ICT 交易系统:在订单块指标中实现信号

开发先进的 ICT 交易系统:在订单块指标中实现信号

MetaTrader 5示例 |
465 1
Niquel Mendoza
Niquel Mendoza
  1. 概述



概述

欢迎阅读我们关于 MQL5 的文章!在本期中,我们将重点介绍在指标中添加缓冲区和入场信号,完成自动交易策略中使用所需的基本功能。

如果您是本系列的新手,我们建议您查看第一部分,我们在其中解释了如何从头开始创建指标并涵盖了基本概念。


基于市场深度的订单块检测

我们使用市场深度识别订单块的逻辑结构如下:

算法            

  1. Array Creation(数组创建):我们将创建两个数组来存储市场深度中每根烛形的交易量。这使我们能够有效地组织和分析交易量数据。
  2. Market Depth Data Collection(市场深度数据收集) —— 在事件中。                                
void  OnBookEvent( )       

在 OnBookEvent() 函数中,我们将捕捉市场深度的每一个变化,记录新的交易量数据,以确保实时更新。

      3.Rules to validate Order Blocks(验证订单块的规则):将交易量存储在数组中之后,我们将应用价格行为规则以及该数据来验证订单块。

识别具有市场深度的订单块的规则

最初,在构建我们的指标时,我们在特定的烛形范围内搜索订单块。然而,在市场深度的情况下,我们不会使用较宽的 “x 范围”。我们将特别关注第三根烛形(其中烛形 0 是当前烛形)。

规则 看涨订单块 看跌订单块 
烛形 3 上的成交量峰值: 烛形 3 的买入量必须按照一定比例超过烛形 2 和 4 的总买入量和卖出量。 烛形 3 的卖出量必须按照一定比例超过烛形 2 和 4 的买入量和卖出量的总和。
3 根连续的烛形: 必须是三个连续的看涨烛形
(烛形 1、2 和 3)
必须是三个连续的看跌烛形
(烛形 1、2 和 3)
烛形 3 的主体: 烛形 2 的最低价必须大于烛形 3 主体的一半。 烛形 2 的最高点必须低于烛形 3 主体的一半。
烛形 3 的高点或低点: 烛形 3 的最高价必须低于烛形 2 的收盘价。 烛形 3 的最低价必须高于烛形 2 的收盘价。

通过这些规则,我们确保以下几点:

  • 买入/卖出不平衡:我们验证了特定烛形内买入或卖出的严重不平衡,其中买入或卖出订单超过前一个和下一个烛形的订单,且比例达到设定值。
  • 不平衡状态下的烛形主体控制:我们确认,由供需过剩引起的未执行订单不会被后续的烛形吸收,从而验证了订单区块的持续性。
  • 强劲看涨或看跌走势:我们确认,这一模式反映了决定性的上涨或下跌趋势,突显了价格行为失衡的强度。

考虑到所有这些,我们现在可以将逻辑转换为代码。


初始化和完成“订单簿”事件并创建数组

创建数组

在使用订单簿之前,您需要创建用于存储交易量数据的动态数组。这些数组的类型为:

long

它们将分别用于存储买入交易量和卖出交易量。

  1. 转到程序的全局部分并声明动态数组:

long buy_volume[];
long sell_volume[];

      2.在 OnInit 事件中,调整数组大小,使其初始大小为 1。然后,将值 0 分配给每个数组的索引 0:

  ArrayResize(buy_volume,1);
  ArrayResize(sell_volume,1);
  buy_volume[0] = 0.0;
  sell_volume[0] = 0.0;

市场深度事件的初始化和完成

在开始市场深度之前,我们将创建一个全局变量来指示此函数是否可用。这将使我们能够避免使用:

INIT_FAILED
并非每个经纪商的所有交易品种都提供市场深度的已交易量。通过这种方法,指标将不再完全依赖于提供此功能的经纪商。

  • 要检查您想要交易的交易品种是否支持具有交易量的市场深度,请按照以下步骤操作:

1.单击图表左上角相应的框: 

2.验证该交易品种是否具有可用的市场深度交易量。如果支持,您将看到类似于下图的确认。

具有市场深度交易量的交易品种:

市场深度 2

无市场深度交易量的交易品种:

ETHUSD 市场深度

如上所述,并非所有工具都具备市场交易量深度,这取决于您的经纪商。

让我们继续初始化和完成市场深度。

1.全局控制变量

我们定义了一个全局布尔变量来标记市场深度的可用性:

bool use_market_book = true; //true by default

该变量开始时为 true,但如果市场深度初始化失败,则将会改变。

2.市场深度初始化

为了初始化市场深度,我们使用以下函数: 

MarketBookAdd()

这将打开指定交易品种的市场深度。该函数需要当前交易品种:

_Symbol

在 OnInit 事件中,我们检查初始化是否成功:

 if(!MarketBookAdd(_Symbol)) //Verify initialization of the order book for the current symbol
     {
      Print("Error Open Market Book: ", _Symbol, " LastError: ", _LastError); //Print error in case of failure
      use_market_book = false; //Mark use_market_book as false if initialization fails
     }

3.市场深度终止

在 OnDeinit 事件中,我们使用以下方式释放市场深度:

 MarketBookRelease()

然后我们检查关闭并相应地打印一条消息:

//---
   if(MarketBookRelease(_Symbol)) //Verify if closure was successful
     Print("Order book successfully closed for: " , _Symbol); //Print success message if so
   else
     Print("Order book closed with errors for: " , _Symbol , "   Last error: " , GetLastError()); //Print error message with code if not


收集市场深度数据以进行数组中的交易量检测

初始化市场深度后,我们就可以开始收集相关数据。为此,我们将创建 OnBookEvent 事件,每次市场深度发生变化时都会触发该事件。

  1. 创建 OnBookEvent:

void OnBookEvent(const string& symbol)
     2. 检查交易品种和市场深度的可用性:
 if(symbol !=_Symbol || use_market_book == false)
      return; 
// Exit the event if conditions are not met

通过此检查,完整的 OnBookEvent 函数可以构建如下:   

void OnBookEvent(const string& symbol)
  {
   if(symbol !=_Symbol || use_market_book == false)
      return;
// Define array to store Market Book data
   MqlBookInfo book_info[];

// Retrieve Market Book data
   bool book_count = MarketBookGet(_Symbol,book_info);

// Verify if data was successfully obtained
   if(book_count == true)
     {
      // Iterate through Market Book data
      for(int i = 0; i < ArraySize(book_info); i++)
        {
         // Check if the record is a buy order (BID)
         if(book_info[i].type == BOOK_TYPE_BUY  || book_info[i].type ==  BOOK_TYPE_BUY_MARKET)
           {
           
            buy_volume[0] += book_info[i].volume;
           }
         // Check if the record is a sell order (ASK)
         if(book_info[i].type == BOOK_TYPE_SELL || book_info[i].type == BOOK_TYPE_SELL_MARKET)
           {
            sell_volume[0] += book_info[i].volume;
           }
        }
     }
   else
     {
      Print("No Market Book data retrieved.");
     }
  }

代码解释:

  • 交易量检索:每次市场深度发生变化时,OnBookEvent 都会收集最新注册订单的交易量。
  • 数组更新:它将买入交易量和卖出交易量分别添加到 buy_volume 和 sell_volume 数组的索引 0 中。

为了确保数组累积每个新烛形的市场深度交易量并保留滚动历史记录(例如 30 个元素),需要进行以下调整。

1.新的烛形验证和计数器验证(超过 1 个)

为了避免程序启动时出现误报,并确保仅在新烛形开启时(以及至少一次先前开启之后)更新数组,我们实施了将计数器变量与 new_vela 相结合的检查。这确保了只有当有真正新的信息可用时才会发生数组更新。

声明静态变量
我们将计数器声明为静态变量,以便它在对 OnCalculate 的调用之间持续存在。new_vela 变量应指示是否已打开新的烛形。
static int counter = 0;

新的烛形和计数器验证条件
我们验证计数器是否大于 1、new_vela 是否为 true 以及市场深度是否可用。只有满足所有这些条件,我们才会调整数组大小并移动其元素。这可以防止过早调整大小,并确保仅在有效数据可用且市场订单簿提供当前交易品种的交易量时才进行更新。

if(counter > 1 && new_vela == true && use_market_book == true)

计数器更新
每次检测到新的烛形时,计数器就会增加 1。

counter++;

2.数组大小控制

我们检查数组的最大大小不超过 30 个元素。如果要超过 30,我们将其大小调整回 30,并丢弃最旧的元素:

if(ArraySize(buy_volume) >= 30)
{
   ArrayResize(buy_volume, 30); // Keep buy_volume size at 30
   ArrayResize(sell_volume, 30); // Keep sell_volume size at 30
}

3.调整大小以适应新值

我们在数组中添加一个额外的位置,以将新交易量存储在初始位置:

ArrayResize(buy_volume, ArraySize(buy_volume) + 1);
ArrayResize(sell_volume, ArraySize(sell_volume) + 1);

4.移动元素

我们将所有数组元素向前移动一个位置。这确保了最新数据始终存储在索引 0 处,而较旧的值则移至更高的索引。

for(int i = ArraySize(buy_volume) - 1; i > 0; i--)
{
   buy_volume[i] = buy_volume[i - 1];
   sell_volume[i] = sell_volume[i - 1];
}

5.交易量验证

我们在数组的位置 1 处打印买交易入和卖出交易量,以验证最后一个烛形记录的成交量:

Print("Buy volume of the last candle: ", buy_volume[1]);
Print("Sell volume of the last candle: ", sell_volume[1]);

6.交易量重置

我们将两个数组的索引 0 的值重置为 0,以便它们开始累积新烛形交易量:

buy_volume[0] = 0;
sell_volume[0] = 0;

7.防止因市场订单簿数据不一致而导致错误的条件

如果最近位置(索引 3、2 和 1)的 buy_volume 和 sell_volume 值都为零,则引入额外的保护措施以自动禁用 use_market_book。这种调整是必要的,因为即使某个交易品种在实时交易中似乎有市场订单簿数据,在策略测试器中运行时它也可能看起来是活动的,但由于缺乏市场深度更新,数组可能无法正确填充。这可能会导致存储了 0 值,从而导致指标处理不正确的信息。

此验证可确保指标不会处理无意义的数据,并且仅当市场订单簿包含有效值时才应用 use_market_book。

if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4)
        {
         if(buy_volume[3] == 0 && sell_volume[3] == 0 &&  buy_volume[2] == 0 && sell_volume[2] == 0 &&  buy_volume[1] == 0 && sell_volume[1] == 0)  use_market_book = false;       
        }     

集成的代码片段

if(counter > 1 && new_vela == true && use_market_book == true)
     {
      if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4)
        {
         if(buy_volume[3] == 0 && sell_volume[3] == 0 &&  buy_volume[2] == 0 && sell_volume[2] == 0 &&  buy_volume[1] == 0 && sell_volume[1] == 0)  use_market_book = false;       
        }
      
       // If array size is greater than or equal to 30, resize to maintain a fixed length
     if(ArraySize(buy_volume) >= 30)
      {
      ArrayResize(buy_volume, 30); // Ensure buy_volume does not exceed 30 elements
      ArrayResize(sell_volume, 30); // Ensure sell_volume does not exceed 30 elements
      }   
  
      ArrayResize(buy_volume,ArraySize(buy_volume)+1);
      ArrayResize(sell_volume,ArraySize(sell_volume)+1);

      for(int i = ArraySize(buy_volume) - 1; i > 0; i--)
        {
         buy_volume[i] = buy_volume[i - 1];
         sell_volume[i] = sell_volume[i - 1];
        }

      // Reset volumes at index 0 to begin accumulating for the new candlestick 
      buy_volume[0] = 0;
      sell_volume[0] = 0;
     }


利用市场深度定位订单块的策略

该策略将遵循与以前相同的逻辑,但有一个主要区别:我们不是通过循环迭代,而是直接在烛形 3 上执行检查。总体逻辑保持不变:我们验证特定条件,识别最接近的相关烛形(取决于订单块的类型),为结构分配相应的值,然后将订单块添加到数组中。在这里,我们采用相同的过程,但方式简化了。

让我们从创建存储订单块信息的结构开始:

OrderBlocks newVela_Order_block_Book_bajista;
OrderBlocks newVela_Order_block_Book;

1.初始条件

首先,我们验证 buy_volume 和 sell_volume 数组至少包含 5 个元素。这确保了有足够的历史数据可供分析。我们还确认 use_market_book 处于活动状态,以便处理市场深度。

if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)

2.控制变量定义

我们定义变量 case_book 来表示是否满足特定的成交量条件。该比率设置为 1.4,作为检测买入交易量显著增加的比较因素。

bool case_book = false;
double ratio = 1.4;

3.买入交易量条件(case_book)

在这里,我们检查索引 3 的买入交易量是否明显大于索引 2 和 4 的买入量,无论买方还是卖方,我们都使用该比率作为乘数。如果满足此条件,则激活 case_book。

看涨情况:

if(buy_volume[3] > buy_volume[4] * ratio && buy_volume[3] > buy_volume[2] * ratio &&
   buy_volume[3] > sell_volume[4] * ratio && buy_volume[3] > sell_volume[2] * ratio)
{
    case_book = true;
}
看跌情况:
if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio &&
sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio)
{
case_book = true;
}

4.烛形主体计算

我们通过从收盘价中减去开盘价来计算索引 3 处烛形 (body_tree) 的主体大小。

double body_tree = closeArray[3] - openArray[3]; 
double body_tree = openArray[3] - closeArray[3];

5.看涨价格条件验证的设置

我们评估前面提到的条件(见上表)。

看涨情况:

if(lowArray[2] > ((body_tree * 0.5) + openArray[3]) && highArray[3] < closeArray[2] &&
   closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])

看跌情况:

if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] &&
            closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])

6.识别先前的看涨烛形

我们将该函数称为 FindFurthestAlcista,该函数在距离索引 3 的 20 个烛形范围内搜索最远的看涨烛形。这有助于识别强劲看涨设置的参考烛形。如果发现看涨烛形,其索引大于 0,则允许该过程继续。

看涨情况:

int furthestAlcista = FindFurthestAlcista(Time[3], 20);
if(furthestAlcista > 0)

7.为订单块分配值

如果所有条件都满足,我们将使用已识别烛形的值定义订单块(newVela_Order_block_Book 或 newVela_Order_block_Book_bearish)。

看涨情况:

Print("Case Book Found");
datetime time1 = Time[furthestAlcista]; 
double price2 = openArray[furthestAlcista];
double price1 = lowArray[furthestAlcista]; 

//Assign the above variables to the structure
newVela_Order_block_Book.price1 = price1;
newVela_Order_block_Book.time1 = time1;
newVela_Order_block_Book.price2 = price2;
newVela_Order_block_Book.mitigated = false;
newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1);
AddIndexToArray_alcistas(newVela_Order_block_Book);

看跌情况:

Print("Case Book Found");
datetime time1 = Time[furthestBajista];
double price1 = closeArray[furthestBajista];
double price2 = lowArray[furthestBajista];

//Assign the above variables to the structure
newVela_Order_block_Book_bajista.price1 = price1;
newVela_Order_block_Book_bajista.time1 = time1;
newVela_Order_block_Book_bajista.price2 = price2;
newVela_Order_block_Book_bajista.mitigated = false;
newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1);
AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);

完整代码:

if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
  {

   bool case_book = false;
   double ratio = 1.4;

   if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio &&
      sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio)
     {
      case_book = true;
     }
   double body_tree =   openArray[3] - closeArray[3];

   if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] &&
      closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])
     {
      int furthestBajista = FindFurthestBajista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlesticks before "one candle"
      if(furthestBajista  > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle".
        {
         Print("Case Book Found");
         datetime time1 = Time[furthestBajista];
         double price1 = closeArray[furthestBajista];
         double price2 = lowArray[furthestBajista];

         //Assign the above variables to the structure
         newVela_Order_block_Book_bajista.price1 = price1;
         newVela_Order_block_Book_bajista.time1 = time1;
         newVela_Order_block_Book_bajista.price2 = price2;
         newVela_Order_block_Book_bajista.mitigated = false;
         newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1);

         AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);

        }
     }
  }
//--------------------    Bullish   -------------------- 

if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
  {

   bool case_book = false;
   double ratio = 1.4;


   if(buy_volume[3] > buy_volume[4]*ratio && buy_volume[3] > buy_volume[2]*ratio &&
      buy_volume[3] > sell_volume[4]*ratio && buy_volume[3] > sell_volume[2]*ratio)
     {
      case_book = true;
     }
   double body_tree =  closeArray[3] - openArray[3];

   if(lowArray[2] > ((body_tree * 0.5)+openArray[3]) && highArray[3] < closeArray[2] &&
      closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])
     {
      int furthestAlcista = FindFurthestAlcista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlessticks before "one candle"
      if(furthestAlcista > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle".
        {
         Print("Case Book Found");
         datetime time1 = Time[furthestAlcista]; //let's assign the index time of furthestAlcista to the variable time1
         double price2 = openArray[furthestAlcista]; //let's assign the open of furthestAlcista as price 2 (remember that we draw it on a bearish candlestick most of the time)
         double price1 = lowArray[furthestAlcista]; //let's assign the low of furthestAlcista as price 1

         //Assign the above variables to the structure
         newVela_Order_block_Book.price1 = price1;
         newVela_Order_block_Book.time1 = time1;
         newVela_Order_block_Book.price2 = price2;
         newVela_Order_block_Book.mitigated = false;
         newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1);

         AddIndexToArray_alcistas(newVela_Order_block_Book);

        }
     }
  }


创建指标缓冲区

为了在 MQ L5中为我们的订单区块指标创建和配置缓冲区,我们首先在全局范围内定义两个缓冲区和两个图,以存储和显示看涨和看跌订单块的价格水平。

1.缓冲区和绘图的声明

我们在程序的全局部分声明两个缓冲区来存储订单块的价格数据。此外,我们定义了两个绘图来可视化图表上的订单块。

#property  indicator_buffers 2
#property  indicator_plots 2
#property indicator_label1 "Bullish Order Block"
#property indicator_label2 "Bearish Order Block"

2.为缓冲区创建动态数组

我们声明两个动态数组 buyOrderBlockBuffer 和 sellOrderBlockBuffer,用于存储看涨和看跌订单块对应的价格。这些数组链接到指标缓冲区,从而允许订单块数据在图表上可视化。

//--- Define the buffers
double buyOrderBlockBuffer[];   // Buffer for bullish order blocks
double sellOrderBlockBuffer[];  // Buffer for bearish order blocks

描述:

  • buyOrderBlockBuffer:存储看涨订单块的价格水平并表示价格可能获得支撑的点。
  • sellOrderBlockBuffer:存储看跌订单块的价格水平并表示价格可能遇到阻力的点。


修改 OnInit 函数来配置缓冲区

在本节中,我们调整 OnInit 函数来配置指标缓冲区,将看涨和看跌订单块数组分配给指标缓冲区。这可确保指标正确存储并在图表上显示数据。

步骤:

1.使用 SetIndexBuffer 分配数据缓冲区

在 OnInit 中,我们使用 SetIndexBuffer 将数组 buyOrderBlockBuffer 和 sellOrderBlockBuffer 分配给指标缓冲区。这确保数组可以存储并在图表上显示数据。

//--- Assign data buffers to the indicator
   SetIndexBuffer(0, buyOrderBlockBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, sellOrderBlockBuffer, INDICATOR_DATA)

2.将缓冲区配置为序列并填充空值

为了按时间倒序显示数据(如时间序列),我们将数组设置为序列。我们还使用 EMPTY_VALUE 初始化两个缓冲区,以防止在计算出真实值之前显示不正确的数据。

  ArraySetAsSeries(buyOrderBlockBuffer, true);
  ArraySetAsSeries(sellOrderBlockBuffer, true);
  ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
  ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE


在指标中实现缓冲区 (2)

在本节中,我们将看涨和看跌订单块的价格分配给指标缓冲区。这些缓冲区使数据在每个订单块的时间(time1)的相应索引处可用。

1.为看涨订单块指定价格

在评估 ob_alcistas 中每个看涨块的循环内,我们将 price2 分配给 buyOrderBlockBuffer。我们使用 iBarShift 来获取图表上 time1 与订单块时间匹配的精确索引。

buyOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_alcistas[i].time1)] = ob_alcistas[i].price2;

这里,看涨块的 price2 被分配给 buyOrderBlockBuffer 中的相应索引,因此缓冲区反映了图表上块的价格水平。

2.为看跌订单块指定价格

类似地,我们通过遍历 ob_bajistas 数组并将价格设置在相应的索引处,将每个看跌块的 price2 分配给 sellOrderBlockBuffer。

sellOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_bajistas[i].time1)] = ob_bajistas[i].price2;

总结:
  • iBarShift 定位区块时间与图表位置相匹配的精确索引。
  • buyOrderBlockBuffersellOrderBlockBuffer 接收 price2 值,允许在正确的时间记录价格以供图表和进一步的指标计算使用。


更新输入参数(Inputs)

在本节中,我们配置输入参数以允许用户自定义止盈(TP)和止损(SL)计算样式。我们创建一个枚举来在两个选项之间进行选择:ATR (平均真实范围)或 POINT (固定点)。

ENUM_TP_SL_STYLE

该枚举允许用户在两种 TP 和 SL 计算模式之间进行选择。

  • АТR :根据平均价格变动范围设置 TP 和 SL,并根据当前市场波动自动调整。
  • POINT:在用户定义的固定点设置 TP 和 SL。

enum ENUM_TP_SL_STYLE
  {
   ATR,
   POINT
  };

解释:

  • АТR :用户设置乘数来确定相对于 ATR 的 TP 和 SL 距离。更高的乘数会根据市场波动增加 TP 和 SL 距离。

  • POINT:用户手动定义固定点的 TP 和 SL,允许静态水平而不管波动性如何。

现在,继续使用指标参数,我们使用 sinput 构建指标输入参数,并将设置分为几部分。这在界面中提供了更直观、更有组织的参数显示,使用户更容易配置。

1.策略部分 

我们创建一个包含 TP 和 SL 计算样式的策略组:

sinput group "-- Strategy --"
input ENUM_TP_SL_STYLE tp_sl_style = POINT; // TP and SL style: ATR or fixed points

这里,tp_sl_style 允许用户选择是否基于 ATR(平均真实范围)或固定点计算 TP 和 SL。

2.按选定方法配置 TP 和 SL

为了解释每种方法的具体设置,我们添加了两个额外的组:一个用于 ATR 方法,一个用于固定点。

ATR 组:这里我们包括两个 double 型输入变量,允许用户指定 ATR 乘数,从而根据波动性调整 TP 和 SL 范围。

sinput group " ATR "
input double Atr_Multiplier_1 = 1.5; // Multiplier for TP
input double Atr_Multiplier_2 = 2.0; // Multiplier for SL

POINT 组:在此组中,我们添加了两个 int 类型的输入变量来定义固定点 TP 和 SL,这使我们能够手动精确地控制这些距离。

sinput group " POINT "
input int TP_POINT = 500; // Fixed points for TP
input int SL_POINT = 275; // Fixed points for SL

这种结构使参数保持整齐的组织和分类,使其更易于使用,并在设置指标时提高了清晰度。用户将能够直观地设置 TP 和 SL 样式,在基于 ATR 的自动配置或以点为单位的手动设置之间进行选择。

参数完整代码:

sinput group "--- Order Block Indicator settings ---"
sinput group "-- Order Block --"
input          int  Rango_universal_busqueda = 500;
input          int  Witdth_order_block = 1;

input          bool Back_order_block = true;
input          bool Fill_order_block = true;

input          color Color_Order_Block_Bajista = clrRed;
input          color Color_Order_Block_Alcista = clrGreen;

sinput group "-- Strategy --"
input          ENUM_TP_SL_STYLE tp_sl_style = POINT;

sinput group " ATR "
input          double Atr_Multiplier_1 = 1.5;
input          double Atr_Multiplier_2 = 2.0;
sinput group " POINT "
input          int TP_POINT = 500;
input          int SL_POINT = 275;


指标信号生成逻辑

为了生成买入或卖出信号,使用了两个静态变量:

变量 描述
time_ 和 time_b 存储订单块缓和的时间,并添加 5 根烛形的边界(以秒为单位)。
buscar_oba 和 buscar_obb 控制对新缓和的订单块的搜索。根据条件激活或停用。

信号生成过程

检测缓和订单块:
  • 当订单块被缓和时,time_a 被设置为当前时间加上 5 个烛形的边界。
  • searcher 变量设置为 false,以在验证信号条件时暂停进一步搜索。
买入和卖出信号条件:
  • 信号评估基于 30 周期指数移动平均线 (EMA) 和缓和时间 (time_b)。 
下表总结了具体情况:
信号类型   EMA 条件
时间条件
 买入  30 周期 EMA低于烛形 1 的收盘价   time_a 必须大于当前时间
 卖出  30 周期 EMA高于烛形 1 的收盘价   time_b 必须大于当前时间

请注意:这些条件确保在订单块缓和后在 5 个烛形范围内生成信号。

条件满足或不满足时的操作:

状态   操作
 已满足 填充 TP 和 SL 缓冲区以执行相应的交易。
 未满足 将 searcher 重置为 true,将 time_ 和 time_b 重置为 0,如果最大时间已过,则允许恢复对新订单块的搜索。

框图:

买入

建立买入仓位的逻辑

卖出

建立卖出仓位的逻辑


实现交易策略

在开始之前,我们将创建一个指数移动平均线句柄。

我们创建全局变量(数组和句柄):

int hanlde_ma;
double ma[]; 

在 OnInit 中,我们初始化句柄并检查它是否已分配值。

hanlde_ma = iMA(_Symbol,_Period,30,0,MODE_EMA,PRICE_CLOSE);

if(hanlde_ma == INVALID_HANDLE)
{
Print("The EMA indicator is not available. Failure: ", _LastError); 
return INIT_FAILED;
}
我们声明静态变量来控制搜索状态和 OB(订单块) 激活时间,区分买入和卖出场景。
//Variables for buy
static bool buscar_oba = true;
static datetime time_ = 0;

//Variables for sell
static bool buscar_obb = true;
static datetime time_b = 0;

然后我们循环遍历软订单块(类似于我们在上一篇文章中对警报所做的操作)。

我们首先添加条件:

//Bullish case
 if(buscar_oba == true)
//Bearish case
 if(buscar_obb == true)

接下来要判断 OB 是否已经缓和,也就是价格是否与其发生互动。如果发现缓和的 OB,则记录其时间并暂停搜索。这对于看涨和看跌情景均适用。

// Bearish case
for(int i = 0; i < ArraySize(ob_bajistas); i++) {
    if(ob_bajistas[i].mitigated == true &&
       !Es_Eliminado_PriceTwo(ob_bajistas[i].name, pricetwo_eliminados_obb) &&
       ObjectFind(ChartID(), ob_bajistas[i].name) >= 0) {
        
        Alert("The bearishorder block is being mitigated: ", TimeToString(ob_bajistas[i].time1));
        buscar_obb = false;  // Pause search
        time_b = iTime(_Symbol,_Period,1);  //  Record the mitigation time
        Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name);
        break;
    }
}

// Bullish case
for(int i = 0; i < ArraySize(ob_alcistas); i++) {
    if(ob_alcistas[i].mitigated == true &&
       !Es_Eliminado_PriceTwo(ob_alcistas[i].name, pricetwo_eliminados_oba) &&
       ObjectFind(ChartID(), ob_alcistas[i].name) >= 0) {
        
        Alert("The bullish order block is mitigated: ", TimeToString(ob_alcistas[i].time1));
        time_ = iTime(_Symbol,_Period,0);
        Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name);
        buscar_oba = false;  // Pause search
        break;
    }
}

本节确保系统在检测到缓和后停止搜索,避免重复信号。

执行交易的初始条件

一旦 OB 得到缓和并且未超过最大等待时间,该策略就会使用特定条件来触发寻找买入或卖出信号。

// Buy
if(buscar_oba == false && time_ > 0 && new_vela) { /* Code for Buy */ }

// Sell
if(buscar_obb == false && time_b > 0 && new_vela) { /* Code for Sell */ }

在这些条件下:

  1. buscar_oba 或 buscar_obb 必须为 false(确认先前的缓解措施)。
  2. time_a 或 time_b 必须大于0,表示已经记录了时间。
  3. new_vela 确保逻辑仅应用于新烛形,有助于防止重复操作。

买入或卖出条件的验证

为了建立必要的条件,首先我们需要一个变量来存储最大等待时间。然后,必须知道烛形 1 的收盘价及其 EMA(指数移动平均线)。为了获得收盘价,我们使用 iClose 函数,并将 EMA 值存储在包含移动平均线完整历史序列的数组中。

// Buy
double close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits);
datetime max_time_espera = time_ + (PeriodSeconds() * 5);

if(close_ > ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) {
    // Code for Buy...
}

// Sell
close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits);
max_time_espera = time_b + (PeriodSeconds() * 5);

if(close_ < ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) {
    // Code for Sell...
}

重置订单块搜索

最后,如果超出最大等待时间且条件不满足,代码将重置搜索以允许检测新的 OB:

// Reset for Buy
if(iTime(_Symbol,_Period,0) > max_time_espera) {
    time_ = 0;
    buscar_oba = true;
}

// Reset for Sell
if(iTime(_Symbol,_Period,0) > max_time_espera) {
    time_b = 0;
    buscar_obb = true;
}

现在我们缺少一个绘制 tp 和 sl 以及将它们添加到缓冲区的函数。我们通过以下代码实现这个功能:

让我们继续新的部分。


设置止盈(TP)和止损(SL)水平

在本节中,我们将开发函数 GetTP_SL,该函数使用两种方法计算 TP 和 SL 水平:使用 ATR(平均真实范围)或固定点,如之前在输入配置中提到的。

1:函数定义

GetTP_SL 函数接收以下参数:仓位的开仓价格、仓位类型(ENUM_POSITION_TYPE)以及 TP 和 SL 水平的参考(tp1、tp2、sl1 和 sl2),计算值将存储于其中。

void GetTP_SL(double price_open_position, ENUM_POSITION_TYPE type, double &tp1, double &tp2, double &sl1, double &sl2)

2:获取 ATR

为了计算基于 ATR 的水平,我们首先需要一个存储最新烛形的 ATR 值的数组。我们使用 CopyBuffer 用当前值填充 atr 数组。

double atr[];
ArraySetAsSeries(atr, true);
CopyBuffer(atr_i, 0, 0, 1, atr);

3:根据 ATR 计算 TP 和 SL

当 tp_sl_style 设置为 ATR 时,我们通过将 ATR 值乘以定义的乘数(Atr_Multiplier_1 和 Atr_Multiplier_2)来计算 TP 和 SL 水平。然后,根据头寸类型,将这些值与开盘价相加或相减。

if (type == POSITION_TYPE_BUY) {
    sl1 = price_open_position - (atr[0] * Atr_Multiplier_1);
    sl2 = price_open_position - (atr[0] * Atr_Multiplier_2);
    tp1 = price_open_position + (atr[0] * Atr_Multiplier_1);
    tp2 = price_open_position + (atr[0] * Atr_Multiplier_2);
}

if (type == POSITION_TYPE_SELL) {
    sl1 = price_open_position + (atr[0] * Atr_Multiplier_1);
    sl2 = price_open_position + (atr[0] * Atr_Multiplier_2);
    tp1 = price_open_position - (atr[0] * Atr_Multiplier_1);
    tp2 = price_open_position - (atr[0] * Atr_Multiplier_2);
}

4:根据点数计算 TP 和 SL

当 tp_sl_style 设置为 POINT 时,我们将指定点数(TP_POINT 和 SL_POINT)乘以当前交易品种的点值(_Point)与开盘价相加或相减。这为基于 ATR 的计算提供了一种更简单的替代方法。

if (type == POSITION_TYPE_BUY) {
    sl1 = price_open_position - (SL_POINT * _Point);
    sl2 = price_open_position - (SL_POINT * _Point * 2);
    tp1 = price_open_position + (TP_POINT * _Point);
    tp2 = price_open_position + (TP_POINT * _Point * 2);
}

if (type == POSITION_TYPE_SELL) {
    sl1 = price_open_position + (SL_POINT * _Point);
    sl2 = price_open_position + (SL_POINT * _Point * 2);
    tp1 = price_open_position - (TP_POINT * _Point);
    tp2 = price_open_position - (TP_POINT * _Point * 2);
}


在图表上可视化止盈和止损水平

我们将创建一个函数,使用线条和文本对象在图表上绘制 TP 和 SL 水平。

创建线条

bool TrendCreate(long            chart_ID,        // Chart ID
                 string          name,  // Line name
                 int             sub_window,      // Subwindow index
                 datetime              time1,           // Time of the first point
                 double                price1,          // Price of the first point
                 datetime              time2,           // Time of the second point
                 double                price2,          // Price of the second point
                 color           clr,        // Line color
                 ENUM_LINE_STYLE style, // Line style
                 int             width,           // Line width
                 bool            back,        // in the background
                 bool            selection    // Selectable form moving
                )
  {
   ResetLastError();
   if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2))
     {
      Print(__FUNCTION__,
            ": ¡Failed to create trend line! Error code = ",GetLastError());
      return(false);
     }

   ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
   ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);
   ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width);
   ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
   ChartRedraw(chart_ID);
   return(true);
  }

对于文本:

bool TextCreate(long              chart_ID,                // Chart ID
                string            name,              // Object name
                int               sub_window,             // Subwindow index
                datetime                time,                   // Anchor time
                double                  price,                  // Anchor price
                string            text,              // the text
                string            font,             // Font
                int               font_size,             // Font size
                color             clr,               // color
                double            angle,                // Text angle
                ENUM_ANCHOR_POINT anchor, // Anchor point
                bool              back=false,               // font
                bool              selection=false)          // Selectable for moving

  {

//--- reset error value
   ResetLastError();
//--- create "Text" object
   if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price))
     {
      Print(__FUNCTION__,
            ": ¡Failed to create object \"Text\"! Error code = ",GetLastError());
      return(false);
     }
   ObjectSetString(chart_ID,name,OBJPROP_TEXT,text);
   ObjectSetString(chart_ID,name,OBJPROP_FONT,font);
   ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size);
   ObjectSetDouble(chart_ID,name,OBJPROP_ANGLE,angle);
   ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,anchor);
   ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
   ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);

   ChartRedraw(chart_ID);
   return(true);
  }

现在让我们继续创建函数。

第一步,输入参数详细信息

该函数接收以下参数:

  • tp1 和 tp2 — 两个止盈水平的值。
  • sl1 和 sl2 — 两个止损水平的值。
void DrawTP_SL( double tp1, double tp2, double sl1, double sl2)

第二步:准备时间

首先,创建一个字符串 curr_time 来存储图表上烛形的当前日期和时间。然后,计算 extension_time,它从当前时间向前延伸 15 个周期,以将 TP 和 SL 线投射到右侧。text_time 用于将文本标签位置稍微调整到 extension_time 之后。

string curr_time = TimeToString(iTime(_Symbol, _Period, 0));
datetime extension_time = iTime(_Symbol, _Period, 0) + (PeriodSeconds(PERIOD_CURRENT) * 15);
datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);

第三步:绘制 TP 和 SL 线和标签

  1. 止盈 1 (tp1)
  • 使用 TrendCreate 在 tp1 处绘制一条绿色虚线 (STYLE_DOT)。
  • 使用 TextCreate 在 tp1 位置添加文本标签“TP1”。
TrendCreate(ChartID(), curr_time + " TP1", 0, iTime(_Symbol, _Period, 0), tp1, extension_time, tp1, clrGreen, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " TP1 - Text", 0, text_time, tp1, "TP1", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);

      2. 止盈 2 (tp2)
  • 在 tp2 处绘制另一条绿色虚线并添加“TP2”文本标签。
TrendCreate(ChartID(), curr_time + " TP2", 0, iTime(_Symbol, _Period, 0), tp2, extension_time, tp2, clrGreen, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " TP2 - Text", 0, text_time, tp2, "TP2", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);

      3. 止损 1 (sl1)
  • 在 sl1 处绘制一条红色虚线和一个“SL1”文本标签。
TrendCreate(ChartID(), curr_time + " SL1", 0, iTime(_Symbol, _Period, 0), sl1, extension_time, sl1, clrRed, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " SL1 - Text", 0, text_time, sl1, "SL1", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);

       4. 止损 2 (sl2)
  • 同样,在 sl2 处画一条红线和一个“SL2”文本标签。
TrendCreate(ChartID(), curr_time + " SL2", 0, iTime(_Symbol, _Period, 0), sl2, extension_time, sl2, clrRed, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " SL2 - Text", 0, text_time, sl2, "SL2", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);

完整代码:

void DrawTP_SL(double tp1, double tp2, double sl1, double sl2)
  {


   string  curr_time = TimeToString(iTime(_Symbol,_Period,0));
   datetime extension_time = iTime(_Symbol,_Period,0) + (PeriodSeconds(PERIOD_CURRENT) * 15);
   datetime   text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);


   TrendCreate(ChartID(),curr_time+" TP1",0,iTime(_Symbol,_Period,0),tp1,extension_time,tp1,clrGreen,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" TP1 - Text",0,text_time,tp1,"TP1","Arial",8,clrGreen,0.0,ANCHOR_CENTER);

   TrendCreate(ChartID(),curr_time+" TP2",0,iTime(_Symbol,_Period,0),tp2,extension_time,tp2,clrGreen,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" TP2 - Text",0,text_time,tp2,"TP2","Arial",8,clrGreen,0.0,ANCHOR_CENTER);

   TrendCreate(ChartID(),curr_time+" SL1",0,iTime(_Symbol,_Period,0),sl1,extension_time,sl1,clrRed,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" SL1 - Text",0,text_time,sl1,"SL1","Arial",8,clrRed,0.0,ANCHOR_CENTER);

   TrendCreate(ChartID(),curr_time+" SL2",0,iTime(_Symbol,_Period,0),sl2,extension_time,sl2,clrRed,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" SL2 - Text",0,text_time,sl2,"SL2","Arial",8,clrRed,0.0,ANCHOR_CENTER);

  }


添加止盈和止损水平的缓冲区 (4)

正如我们对存储 price2 的两个缓冲区所做的那样,我们为 TP 和 SL 创建了额外的缓冲区:

#property indicator_label3 "Take Profit 1"
#property indicator_label4 "Take Profit 2"
#property indicator_label5 "Stop Loss 1"
#property indicator_label6 "Stop Loss 2"

我们将绘图和缓冲区的数量从 2 个增加到 6 个。

#property  indicator_buffers 6
#property  indicator_plots 6

创建缓冲区数组:

double tp1_buffer[];
double tp2_buffer[];
double sl1_buffer[];
double sl2_buffer[];

初始化数组并将它们设置为序列。

SetIndexBuffer(2, tp1_buffer, INDICATOR_DATA);
SetIndexBuffer(3, tp2_buffer, INDICATOR_DATA);

SetIndexBuffer(4, sl1_buffer, INDICATOR_DATA);
SetIndexBuffer(5, sl2_buffer, INDICATOR_DATA);


ArraySetAsSeries(buyOrderBlockBuffer, true);
ArraySetAsSeries(sellOrderBlockBuffer, true);
ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE

ArraySetAsSeries(tp1_buffer, true);
ArraySetAsSeries(tp2_buffer, true);
ArrayFill(tp1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
ArrayFill(tp2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE

ArraySetAsSeries(sl1_buffer, true);
ArraySetAsSeries(sl2_buffer, true);
ArrayFill(sl1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
ArrayFill(sl2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE

这可确保 TP 和 SL 值正确存储并显示在图表上。


完成主代码和清理

为了完成指标,实现清理和优化代码。这提高了回溯测试性能,并在不再需要时释放数组(例如存储 OrderBlocks 的数组)的内存资源。

1.清除数组

在 OnCalculate 中,监视新的每日烛形。使用全局变量来存储最后一个烛形时间。

datetime    tiempo_ultima_vela_1;
每次打开新的每日烛形时,从数组中释放内存以防止旧数据积累并优化性能。
 if(tiempo_ultima_vela_1 != iTime(_Symbol,PERIOD_D1,  0))
     {
      Eliminar_Objetos();

      ArrayFree(ob_bajistas);
      ArrayFree(ob_alcistas);
      ArrayFree(pricetwo_eliminados_oba);
      ArrayFree(pricetwo_eliminados_obb);

      tiempo_ultima_vela_1 = iTime(_Symbol,PERIOD_D1,  0);
     }

2.修改 OnDeinit

在 OnDeinit 中,释放 EMA 指标句柄并清除所有数组。这确保了当指标被移除时不会留下任何内存资源。

void OnDeinit(const int reason)
  {
   Eliminar_Objetos();

   ArrayFree(ob_bajistas);
   ArrayFree(ob_alcistas);
   ArrayFree(pricetwo_eliminados_oba);
   ArrayFree(pricetwo_eliminados_obb);

   if(atr_i  != INVALID_HANDLE)
      IndicatorRelease(atr_i);
   if(hanlde_ma != INVALID_HANDLE) //EMA
      IndicatorRelease(hanlde_ma);

   ResetLastError();

    if(MarketBookRelease(_Symbol)) //Verify if closure was successful
     Print("Order book successfully closed for: " , _Symbol); //Print success message if so
   else
     Print("Order book closed with errors for: " , _Symbol , "   Last error: " , GetLastError()); //Print error message with code if not
  }

3.对象删除函数

Eliminar_Objetos 函数已进行优化,可以删除 TP 和 SL 线以及订单块矩形,确保图表保持整洁。
void Eliminar_Objetos()
  {

   for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // iterate through the array of bullish order blocks
     {
      ObjectDelete(ChartID(),ob_alcistas[i].name); // delete the object using the order block's name
     }
   for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // iterate through the array of bearish order blocks
     {
      ObjectDelete(ChartID(),ob_bajistas[n].name);  // delete the object using the order block's name
     }
 //Delete all TP and SL lines
   ObjectsDeleteAll(0," TP",-1,-1);
   ObjectsDeleteAll(0," SL",-1,-1);
  }

4.OnInit 中的初始设置

配置指标简称和图表绘制标签,以确保在数据窗口中正确标记。

   string short_name = "Order Block Indicator";
   IndicatorSetString(INDICATOR_SHORTNAME,short_name);

// Set data precision for digits

// Assign labels for each plot
   PlotIndexSetString(0, PLOT_LABEL, "Bullish Order Block");
   PlotIndexSetString(1, PLOT_LABEL, "Bearish Order Block");
   PlotIndexSetString(2, PLOT_LABEL, "Take Profit 1");
   PlotIndexSetString(3, PLOT_LABEL, "Take Profit 2");
   PlotIndexSetString(4, PLOT_LABEL, "Stop Loss 1");
   PlotIndexSetString(5, PLOT_LABEL, "Stop Loss 2");

5.开仓时设置止盈和止损水平

最后,我们为买卖交易设置止盈和止损水平。对于买入交易,使用卖出价;对于卖出交易,使用买入价。然后在图表上绘制 TP 线和 SL 线进行监控。

//Buy
double ask= NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);

double tp1;
double tp2;
double sl1;
double sl2;
GetTP_SL(ask,POSITION_TYPE_BUY,tp1,tp2,sl1,sl2);

DrawTP_SL(tp1,tp2,sl1,sl2);

tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;

time_ = 0;
buscar_oba = true;

//Sell

double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
double tp1;
double tp2;
double sl1;
double sl2;
GetTP_SL(bid,POSITION_TYPE_SELL,tp1,tp2,sl1,sl2);

DrawTP_SL(tp1,tp2,sl1,sl2);

tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;

time_b = 0;
buscar_obb = true;
步骤   买入
卖出
 价格: 获取并规范化卖价。
获取并规范化买价。
变量: 初始化变量以存储止盈和止损值。

(tp1、tp2、sl1 和 sl2)。 
相同的变量用于存储止盈和止损水平。

(tp1、tp2、sl1 和 sl2)。 
计算: GetTP_SL 根据买入交易的卖价计算 TP 和 SL 水平。 GetTP_SL 根据卖出交易的买价计算 TP 和 SL 水平。 
绘画: DrawTP_SL 在图表上直观地显示买入交易的 TP 和 SL 水平。 DrawTP_SL 在图表上直观地显示卖出交易的 TP 和 SL 水平。
缓冲区: 使用 iBarShift 查找当前柱形索引并将 TP/SL 存储在缓冲区中。

 (tp1_buffer、tp2_buffer、sl1_buffer 和 sl2_buffer)。    
查找当前柱形索引并将 TP/SL 存储在相同的缓冲区中。

 (tp1_buffer、tp2_buffer、sl1_buffer 和 sl2_buffer)。   
静态变量:  重置静态变量以在下一次迭代中搜索新的看涨订单块。

(静态变量:“time_a” 和 “buscar_oba”)。
重置静态变量以在下一次迭代中搜索新的看跌订单块。

(静态变量:“time_b” 和 “search_obb”)。


结论

在本文中,我们探讨了如何基于市场深度量创建订单阻止指标,并通过在原始指标中添加额外的缓冲区来优化其功能。

我们的最终结果:

最终示例 GIF

通过本文,我们总结了订单块指标的开发。在接下来的文章中,我们将介绍从头开始创建风险管理类,并开发一个集成这种风险管理的交易机器人,使用我们指标中的信号缓冲区做出更精确和自动化的交易决策。

本文由MetaQuotes Ltd译自西班牙语
原文地址: https://www.mql5.com/es/articles/16268

最近评论 | 前往讨论 (1)
Vladislav Boyko
Vladislav Boyko | 4 10月 2025 在 19:24

https://www.mql5.com/zh/articles/16268

5.开仓时设置止盈和止损水平

最后,我们为买入和卖出交易设置止盈和止损水平。买入 交易 使用卖出价,卖出 交易 使用买入价。然后在图表上绘制止盈线和止损线,以便监控。

tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;

看起来可以简化一些。

分析交易所价格的二进制代码(第二部分):转换为 BIP39 并编写 GPT 模型 分析交易所价格的二进制代码(第二部分):转换为 BIP39 并编写 GPT 模型
继续尝试破译价格走势……我们将通过将二进制价格代码转换为 BIP39 来获得一个“市场词典”,那么,对这个词典进行语言学分析又如何呢?在本文中,我们将深入探讨一种创新的交易所数据分析方法,并研究如何将现代自然语言处理技术应用于市场语言。
交易中的神经网络:使用小波变换和多任务注意力的模型(终篇) 交易中的神经网络:使用小波变换和多任务注意力的模型(终篇)
在上一篇文章中,我们探索了理论基础,并开始实现多任务-Stockformer 框架的方式,其结合了小波变换和自注意力多任务模型。我们继续实现该框架的算法,并评估其在真实历史数据上的有效性。
在MQL5中构建带自定义画布图形的凯特纳通道(Keltner Channel)指标 在MQL5中构建带自定义画布图形的凯特纳通道(Keltner Channel)指标
本文将介绍如何在MQL5中构建一个带自定义画布图形的凯特纳通道(Keltner Channel)指标。我们将详细阐述移动平均线(MA)与平均真实波幅(ATR)计算的集成方法,以及如何增强型图表的可视化效果。此外,我们还将介绍如何通过回测评估该指标的实际交易表现,为实战交易提供有价值的参考依据。
MQL5中交易策略的自动化实现(第六部分):掌握智能资金交易中的订单块(Order Block)检测技巧 MQL5中交易策略的自动化实现(第六部分):掌握智能资金交易中的订单块(Order Block)检测技巧
在本文中,我们将运用纯粹的价格行为分析方法,在MQL5平台上实现订单块的自动化检测。我们将界定订单块的定义,实现其检测功能,并集成自动化交易执行系统。最后,我们通过回测来评估该策略的表现。