English Русский Deutsch 日本語
preview
MQL5交易策略自动化(第十六部分):基于结构突破(BoS)价格行为的午夜区间突破策略

MQL5交易策略自动化(第十六部分):基于结构突破(BoS)价格行为的午夜区间突破策略

MetaTrader 5交易 |
21 7
Allan Munene Mutiiria
Allan Munene Mutiiria

概述

前一篇文章(第15部分)中,我们通过密码(Cypher)谐波形态交易策略,实现了对市场反转行情的捕捉。而在本篇第16部分中,我们将聚焦于在MetaQuotes Language 5(MQL5)中自动化实现午夜区间突破结合BoS策略,开发一款能够识别午夜至凌晨6点价格区间、检测BoS并执行交易的智能交易系统(EA)。我们将涵盖以下主题:

  1. 理解午夜区间突破结合BOS策略
  2. 在MQL5中的实现
  3. 回测
  4. 结论

到本文结尾时,您将拥有一款功能完备的MQL5程序——该程序可视化关键价格水平、确认突破信号,并依据预设风险参数执行交易。让我们开始吧!


理解午夜区间突破结合BOS策略

午夜区间突破结合BOS策略的核心逻辑是:利用午夜至凌晨6点形成的低波动区间(以最高价和最低价作为突破边界),同时通过BOS确认交易信号的有效性。BOS通过识别价格突破关键摆动高点(看涨信号)或摆动低点(看跌信号)来捕捉趋势反转,从而过滤虚假突破,确保交易方向与市场契机一致。该策略尤其适用于市场时段转换期(如伦敦开盘时段),或您关注的任何其他时段。不过,使用时需注意时区对齐,并在重大新闻事件期间谨慎操作,以避免价格剧烈波动导致的止损触发。

我们将实现一款MQL5的EA,其功能包括:计算午夜至凌晨6点的价格区间;在设定时间窗口内监测突破;在指定时间框架(通常为5、10或15分钟,用户可动态选择输入参数)上通过BOS确认突破。系统将根据区间范围设置止损和止盈水平,在图表上可视化关键水平,提升操作透明度,并且严格实施风险管理,确保策略在不同市场环境下保持稳健的表现。简言之,这就是我们的核心思路。

策略精要



在MQL5中的实现

要在MQL5中创建该程序,请打开MetaEditor,进入导航器,找到“指标”文件夹,点击“新建”选项卡,并按照提示创建文件。文件创建完成后,在编码环境中,我们需要声明一些将在整个程序中使用到的全局变量

//+------------------------------------------------------------------+
//|                   Midnight Range Break of Structure Breakout.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Trade/Trade.mqh> //--- Include the Trade library for handling trade operations
CTrade obj_Trade;          //--- Create an instance of the CTrade class for trade execution

double maximum_price = -DBL_MAX;      //--- Initialize the maximum price variable to negative infinity
double minimum_price = DBL_MAX;       //--- Initialize the minimum price variable to positive infinity
datetime maximum_time, minimum_time;  //--- Declare variables to store the times of maximum and minimum prices
bool isHaveDailyRange_Prices = false; //--- Initialize flag to indicate if daily range prices are calculated
bool isHaveRangeBreak = false;        //--- Initialize flag to indicate if a range breakout has occurred
bool isTakenTrade = false;            //--- Initialize flag to indicate if a trade is taken for the current day

#define RECTANGLE_PREFIX "RANGE RECTANGLE " //--- Define a prefix for rectangle object names
#define UPPER_LINE_PREFIX "UPPER LINE "     //--- Define a prefix for upper line object names
#define LOWER_LINE_PREFIX "LOWER LINE "     //--- Define a prefix for lower line object names

// bos
input ENUM_TIMEFRAMES timeframe_bos = PERIOD_M5; // Input the timeframe for Break of Structure (BoS) analysis

这里,我们开始通过搭建程序的基础组件来实现该策略。我们引入<Trade/Trade.mqh>库以支持交易操作,并实例化"CTrade"类为"obj_Trade"对象,该对象将负责执行交易指令(例如:以指定参数开立多头或空头头寸)。

我们定义了多个全局变量,用于跟踪策略所需的关键数据。其中,"maximum_price"和"minimum_price"变量分别初始化为-DBL_MAXDBL_MAX,用于存储午夜至凌晨6点区间内的最高价和最低价,从而确定该区间的边界。"maximum_time"和"minimum_time"变量为日期时间型,用于记录上述极值价格出现的具体时间,这样对于在图表上可视化区间范围至关重要。此外,我们还使用布尔标识变量:"isHaveDailyRange_Prices"指示是否已计算出当日区间价格;"isHaveRangeBreak"跟踪是否已发生突破;"isTakenTrade"确保每日仅执行一笔交易,避免过度交易。

为便于图表可视化,我们定义用于对象命名的常量:将"RECTANGLE_PREFIX"设为"RANGE RECTANGLE"(区间矩形);将"UPPER_LINE_PREFIX"设为 "UPPER LINE"(上边界线);将"LOWER_LINE_PREFIX"设为 "LOWER LINE"(下边界线)。这些前缀确保图表中的矩形和线条对象具有唯一名称,从而清晰地标记区间范围及突破水平,使策略操作一目了然。此外,我们引入了一个用户输入参数 "timeframe_bos",默认值为PERIOD_M5(5分钟周期),允许交易者指定用于BOS分析的时间框架(如5分钟图),以检测摆动高点和低点。至此,前期准备已就绪。接下来,我们需要定义两个函数,以实现在新交易日和新K线出现时控制交易实例。

//+------------------------------------------------------------------+
//| Function to check for a new bar                                  |
//+------------------------------------------------------------------+
bool isNewBar(){ //--- Define a function to detect a new bar on the current timeframe
   static int prevBars = 0;                //--- Store the previous number of bars
   int currBars = iBars(_Symbol,_Period);  //--- Get the current number of bars
   if (prevBars==currBars) return (false); //--- Return false if no new bar has formed
   prevBars = currBars;                    //--- Update the previous bar count
   return (true);                          //--- Return true if a new bar has formed
}

//+------------------------------------------------------------------+
//| Function to check for a new day                                  |
//+------------------------------------------------------------------+
bool isNewDay(){ //--- Define a function to detect a new trading day
   bool newDay = false;  //--- Initialize the new day flag

   MqlDateTime Str_DateTime;                 //--- Declare a structure to hold date and time information
   TimeToStruct(TimeCurrent(),Str_DateTime); //--- Convert the current time to the structure

   static int prevDay = 0;         //--- Store the previous day's date
   int currDay = Str_DateTime.day; //--- Get the current day's date

   if (prevDay == currDay){ //--- Check if the current day is the same as the previous day
      newDay = false;       //--- Set the flag to false (no new day)
   }
   else if (prevDay != currDay){ //--- Check if a new day has started
      Print("WE HAVE A NEW DAY WITH DATE ",currDay); //--- Log the new day
      prevDay = currDay;                             //--- Update the previous day
      newDay = true;                                 //--- Set the flag to true (new day)
   }

   return (newDay); //--- Return the new day status
}

这里,我们实现"isNewBar"和"isNewDay"函数,使MQL5中的午夜区间突破结合BOS策略与市场时间同步。在"isNewBar"函数中,我们通过静态变量"prevBars"结合iBars(_Symbol,_Period)函数记录K线数量,当新K线形成时返回true,触发价格更新逻辑。在"isNewDay"函数中,我们利用MqlDateTime结构体、 TimeToStruct (TimeCurrent)函数及静态变量"prevDay"检测新交易日。如果当前日期"currDay"发生变化,重置区间计算并通过Print函数记录日志。借助以上函数,我们可直接在OnTick事件处理器中定义策略逻辑。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   //---
   static datetime midnight = iTime(_Symbol,PERIOD_D1,0);            //--- Store the current day's midnight time
   static datetime sixAM = midnight + 6 * 3600;                      //--- Calculate 6 AM time by adding 6 hours to midnight
   static datetime scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Set the time of the next bar after 6 AM for scanning
   static double midnight_price = iClose(_Symbol,PERIOD_D1,0);       //--- Store the closing price at midnight

   static datetime validBreakTime_start = scanBarTime;               //--- Set the start time for valid breakout detection
   static datetime validBreakTime_end = midnight + (6+5) * 3600;     //--- Set the end time for valid breakouts to 11 AM

   if (isNewDay()){ //--- Check if a new trading day has started
      midnight = iTime(_Symbol,PERIOD_D1,0);                          //--- Update midnight time for the new day
      sixAM = midnight + 6 * 3600;                                    //--- Recalculate 6 AM time for the new day
      scanBarTime = sixAM + 1 * PeriodSeconds(_Period);               //--- Update the scan bar time to the next bar after 6 AM
      midnight_price = iClose(_Symbol,PERIOD_D1,0);                   //--- Update the midnight closing price
      Print("Midnight price = ",midnight_price,", Time = ",midnight); //--- Log the midnight price and time

      validBreakTime_start = scanBarTime;           //--- Reset the start time for valid breakouts
      validBreakTime_end = midnight + (6+5) * 3600; //--- Reset the end time for valid breakouts to 11 AM

      maximum_price = -DBL_MAX; //--- Reset the maximum price to negative infinity
      minimum_price = DBL_MAX;  //--- Reset the minimum price to positive infinity

      isHaveDailyRange_Prices = false; //--- Reset the flag indicating daily range calculation
      isHaveRangeBreak = false;        //--- Reset the flag indicating a range breakout
      isTakenTrade = false;            //--- Reset the flag indicating a trade is taken
   }
}

这里,我们在OnTick函数中开发策略的核心逻辑——该函数是程序的主事件处理器,会在每个价格tick到来时执行。我们初始化静态变量以跟踪关键时间点:使用 iTime(_Symbol, PERIOD_D1, 0) 将“midnight”作为当日零点时间;在"midnight"的基础上增加6小时(21,600秒)计算“sixAM”;通过PeriodSeconds (_Period)确定6点后的下一根K线时间"scanBarTime";通过iClose函数获取零点收盘价并保存为“midnight_price”。我们还定义:将"validBreakTime_start"设置为"scanBarTime",将"validBreakTime_end"设置为上午11点(零点加11小时),确保突破信号仅在该时段内有效。

当"isNewDay"函数检测到新交易日开始时,我们会更新这些时间变量以反映当日最新数据,确保区间计算始终基于当前交易日。通过iTimeiClose函数重置"midnight"、"sixAM"、"scanBarTime"和"midnight_price",使用Print函数记录零点时间细节,以便调试。另外,我们重置"validBreakTime_start"和"validBreakTime_end",定义新突破有效时间窗口,将"maximum_price"重置为-DBL_MAX、"minimum_price"重置为DBL_MAX,并且将布尔标识"isHaveDailyRange_Prices"、"isHaveRangeBreak"和"isTakenTrade"设为false,确保EA能重新计算午夜至6点的价格区间,持续监测新突破信号。至此,我们已经完成时间范围的计算逻辑验证。

if (isNewBar()){ //--- Check if a new bar has formed on the current timeframe
   datetime currentBarTime = iTime(_Symbol,_Period,0); //--- Get the time of the current bar

   if (currentBarTime == scanBarTime && !isHaveDailyRange_Prices){              //--- Check if it's time to scan for daily range and range is not yet calculated
      Print("WE HAVE ENOUGH BARS DATA FOR DOCUMENTATION. MAKE THE EXTRACTION"); //--- Log that the scan for daily range is starting
      int total_bars = int((sixAM - midnight)/PeriodSeconds(_Period))+1;        //--- Calculate the number of bars from midnight to 6 AM
      Print("Total Bars for scan = ",total_bars);                               //--- Log the total number of bars to scan
      int highest_price_bar_index = -1;                                         //--- Initialize the index of the bar with the highest price
      int lowest_price_bar_index = -1;                                          //--- Initialize the index of the bar with the lowest price

      for (int i=1; i<=total_bars ; i++){ //--- Loop through each bar from midnight to 6 AM
         double open_i = open(i);   //--- Get the open price of the i-th bar
         double close_i = close(i); //--- Get the close price of the i-th bar

         double highest_price_i = (open_i > close_i) ? open_i : close_i; //--- Determine the highest price (open or close) of the bar
         double lowest_price_i = (open_i < close_i) ? open_i : close_i;  //--- Determine the lowest price (open or close) of the bar

         if (highest_price_i > maximum_price){ //--- Check if the bar's highest price exceeds the current maximum
            maximum_price = highest_price_i;   //--- Update the maximum price
            highest_price_bar_index = i;       //--- Store the bar index of the maximum price
            maximum_time = time(i);            //--- Store the time of the maximum price
         }
         if (lowest_price_i < minimum_price){ //--- Check if the bar's lowest price is below the current minimum
            minimum_price = lowest_price_i; //--- Update the minimum price
            lowest_price_bar_index = i;     //--- Store the bar index of the minimum price
            minimum_time = time(i);         //--- Store the time of the minimum price
         }
      }
      Print("Maximum Price = ",maximum_price,", Bar index = ",highest_price_bar_index,", Time = ",maximum_time); //--- Log the maximum price, its bar index, and time
      Print("Minimum Price = ",minimum_price,", Bar index = ",lowest_price_bar_index,", Time = ",minimum_time);  //--- Log the minimum price, its bar index, and time

      isHaveDailyRange_Prices = true; //--- Set the flag to indicate that the daily range is calculated
   }
}

当有新K线形成时(通过"isNewBar"函数检测),我们计算午夜至凌晨6点的价格区,使用iTime(_Symbol,_Period, 0)获取当前K线时间,存储于"currentBarTime"中。如果"currentBarTime"等于"scanBarTime"且"isHaveDailyRange_Prices"为false,则通过Print记录区间扫描启动;使用PeriodSeconds (_Period) 计算"total_bars" ,循环遍历K线,使用"open"和"close"函数找出最高/最低价格,并更新"maximum_price"、"minimum_price"、"maximum_time"、"minimum_time"及其索引。记录结果后将"isHaveDailyRange_Prices"设为true,即可启用突破监控。

为简化代码,此处直接调用预定义函数获取价格数据,具体如下:

//+------------------------------------------------------------------+
//| Helper functions for price and time data                         |
//+------------------------------------------------------------------+
double open(int index){return (iOpen(_Symbol,_Period,index));}   //--- Return the open price of the specified bar index on the current timeframe
double high(int index){return (iHigh(_Symbol,_Period,index));}   //--- Return the high price of the specified bar index on the current timeframe
double low(int index){return (iLow(_Symbol,_Period,index));}     //--- Return the low price of the specified bar index on the current timeframe
double close(int index){return (iClose(_Symbol,_Period,index));} //--- Return the close price of the specified bar index on the current timeframe
datetime time(int index){return (iTime(_Symbol,_Period,index));} //--- Return the time of the specified bar index on the current timeframe

double high(int index,ENUM_TIMEFRAMES tf_bos){return (iHigh(_Symbol,tf_bos,index));}   //--- Return the high price of the specified bar index on the BoS timeframe
double low(int index,ENUM_TIMEFRAMES tf_bos){return (iLow(_Symbol,tf_bos,index));}     //--- Return the low price of the specified bar index on the BoS timeframe
datetime time(int index,ENUM_TIMEFRAMES tf_bos){return (iTime(_Symbol,tf_bos,index));} //--- Return the time of the specified bar index on the BoS timeframe

我们通过定义辅助函数来高效获取价格和时间数据,分别定义"open"、"high"、"low"、"close"、"time"函数,各接收一个"index"参数,分别调用"iOpen"、iHigh、"iLow"、"iClose"、iTime,传入"_Symbol"与 _Period,返回指定K线索引对应的开盘价、最高价、最低价、收盘价或K线时间。

此外,我们还重载"high"、"low"和"time"函数,使其接受ENUM_TIMEFRAMES参数"tf_bos",以便通过"iHigh"、"iLow"和"iTime",配合_Symbol与"tf_bos",获取用于BOS时间框架的最高价、最低价或K线时间。定义好区间后,让我们在图表上将其可视化。为此,我们还需定义一些附加的辅助函数。

//+------------------------------------------------------------------+
//| Function to create a rectangle object                            |
//+------------------------------------------------------------------+
void create_Rectangle(string objName,datetime time1,double price1,
               datetime time2,double price2,color clr){ //--- Define a function to draw a rectangle on the chart
   if (ObjectFind(0,objName) < 0){                                       //--- Check if the rectangle object does not already exist
      ObjectCreate(0,objName,OBJ_RECTANGLE,0,time1,price1,time2,price2); //--- Create a rectangle object with specified coordinates

      ObjectSetInteger(0,objName,OBJPROP_TIME,0,time1);  //--- Set the first time coordinate of the rectangle
      ObjectSetDouble(0,objName,OBJPROP_PRICE,0,price1); //--- Set the first price coordinate of the rectangle
      ObjectSetInteger(0,objName,OBJPROP_TIME,1,time2);  //--- Set the second time coordinate of the rectangle
      ObjectSetDouble(0,objName,OBJPROP_PRICE,1,price2); //--- Set the second price coordinate of the rectangle
      ObjectSetInteger(0,objName,OBJPROP_FILL,true);     //--- Enable filling the rectangle with color
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);     //--- Set the color of the rectangle
      ObjectSetInteger(0,objName,OBJPROP_BACK,false);    //--- Ensure the rectangle is drawn in the foreground

      ChartRedraw(0); //--- Redraw the chart to display the rectangle
   }
}

//+------------------------------------------------------------------+
//| Function to create a line object with text                       |
//+------------------------------------------------------------------+
void create_Line(string objName,datetime time1,double price1,
               datetime time2,double price2,int width,color clr,string text){ //--- Define a function to draw a trend line with text
   if (ObjectFind(0,objName) < 0){                                   //--- Check if the line object does not already exist
      ObjectCreate(0,objName,OBJ_TREND,0,time1,price1,time2,price2); //--- Create a trend line object with specified coordinates

      ObjectSetInteger(0,objName,OBJPROP_TIME,0,time1);  //--- Set the first time coordinate of the line
      ObjectSetDouble(0,objName,OBJPROP_PRICE,0,price1); //--- Set the first price coordinate of the line
      ObjectSetInteger(0,objName,OBJPROP_TIME,1,time2);  //--- Set the second time coordinate of the line
      ObjectSetDouble(0,objName,OBJPROP_PRICE,1,price2); //--- Set the second price coordinate of the line
      ObjectSetInteger(0,objName,OBJPROP_WIDTH,width);   //--- Set the width of the line
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);     //--- Set the color of the line
      ObjectSetInteger(0,objName,OBJPROP_BACK,false);    //--- Ensure the line is drawn in the foreground

      long scale = 0; //--- Initialize a variable to store the chart scale
      if(!ChartGetInteger(0,CHART_SCALE,0,scale)){                                   //--- Attempt to get the chart scale
         Print("UNABLE TO GET THE CHART SCALE. DEFAULT OF ",scale," IS CONSIDERED"); //--- Log if the chart scale cannot be retrieved
      }

      int fontsize = 11; //--- Set the default font size for the text
      if (scale==0){fontsize=5;} //--- Adjust font size for minimized chart scale
      else if (scale==1){fontsize=6;}  //--- Adjust font size for scale 1
      else if (scale==2){fontsize=7;}  //--- Adjust font size for scale 2
      else if (scale==3){fontsize=9;}  //--- Adjust font size for scale 3
      else if (scale==4){fontsize=11;} //--- Adjust font size for scale 4
      else if (scale==5){fontsize=13;} //--- Adjust font size for maximized chart scale

      string txt = " Right Price";         //--- Define the text suffix for the price label
      string objNameDescr = objName + txt; //--- Create a unique name for the text object
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time2,price2);        //--- Create a text object at the line's end
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);          //--- Set the color of the text
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,fontsize);  //--- Set the font size of the text
      ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT); //--- Set the text anchor to the left
      ObjectSetString(0,objNameDescr,OBJPROP_TEXT," "+text);       //--- Set the text content (price value)
      ObjectSetString(0,objNameDescr,OBJPROP_FONT,"Calibri");      //--- Set the font type to Calibri

      ChartRedraw(0); //--- Redraw the chart to display the line and text
   }
}

为了可视化价格区间,我们定义两个函数。在"create_Rectangle"函数中,我们通过绘制填充矩形来表示零点至凌晨6点的价格区间,支持自定义参数s "objName"、"time1"、"price1"、"time2"、"price2"和"clr"。我们首先通过ObjectFind函数检测图表(ID为0)中是否已存在同名对象,以避免重复绘制。

如果对象不存在,我们使用ObjectCreate创建矩形,类型为OBJ_RECTANGLE,并通过ObjectSetInteger设置OBJPROP_TIME、ObjectSetDouble设置"OBJPROP_PRICE"来定义坐标。随后用"ObjectSetInteger"开启"OBJPROP_FILL"填充,设置矩形颜色,并将"OBJPROP_BACK"设置为false确保其显示在前端,最后调用ChartRedraw更新图表。

在"create_Line"函数中,我们绘制一条带描述文本的趋势线,用于标记区间边界,参数包括"objName"、"time1"、"price1"、"time2"、"price2"、"width"、"clr"和"text"。先用"ObjectFind"确认线条不存在,再用"ObjectCreate"创建 OBJ_TREND 对象,并通过"ObjectSetInteger"与"ObjectSetDouble"设置坐标、线宽和颜色。为确保文本可读,我们用"ChartGetInteger"获取图表比例(失败时用"Print"记录),并据此动态调整字体大小(5-13)。

我们使用"ObjectCreate"创建名为"objNameDescr"的文本对象,类型为"OBJ_TEXT",并通过"ObjectSetInteger"设置颜色、字体大小和左对齐锚点,再通过ObjectSetString设置字体为 "Calibri" 和价格文本,最后使用 "ChartRedraw"重绘图表。借助这些函数,我们即可在定义区间时调用。

create_Rectangle(RECTANGLE_PREFIX+TimeToString(maximum_time),maximum_time,maximum_price,minimum_time,minimum_price,clrBlue);                       //--- Draw a rectangle to mark the daily range
create_Line(UPPER_LINE_PREFIX+TimeToString(midnight),midnight,maximum_price,sixAM,maximum_price,3,clrBlack,DoubleToString(maximum_price,_Digits)); //--- Draw the upper line for the range
create_Line(LOWER_LINE_PREFIX+TimeToString(midnight),midnight,minimum_price,sixAM,minimum_price,3,clrRed,DoubleToString(minimum_price,_Digits));   //--- Draw the lower line for the range

我们通过调用"create_Rectangle"完成零点至凌晨6点价格区间的可视化:参数为"RECTANGLE_PREFIX+TimeToString(maximum_time)"、"maximum_time"、"maximum_price"、"minimum_time"、"minimum_price"和"clrBlue",绘制标记区间的矩形。随后,我们两次调用"create_Line":上轨线相关"UPPER_LINE_PREFIX+TimeToString(midnight)"、"midnight"、"maximum_price"、"sixAM"、线宽3、"clrBlack"、DoubleToString(maximum_price,_Digits);下轨线 相关"LOWER_LINE_PREFIX"、"minimum_price"、"clrRed",其余参数对应匹配。当前结果如下:

价格区间

由图可见,我们已经成功可视化价格区间。接下来需要实现的功能是:在预设的时间范围内监测价格突破,并同步在图表上可视化。为此,我们需要编写一个自定义函数,专门用于在图表上绘制突破标识。

//+------------------------------------------------------------------+
//| Function to draw a breakpoint marker                             |
//+------------------------------------------------------------------+
void drawBreakPoint(string objName,datetime time,double price,int arrCode,
   color clr,int direction){ //--- Define a function to draw a breakpoint marker
   if (ObjectFind(0,objName) < 0){ //--- Check if the breakpoint object does not already exist
      ObjectCreate(0,objName,OBJ_ARROW,0,time,price);        //--- Create an arrow object at the specified time and price
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrCode); //--- Set the arrow code for the marker
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);         //--- Set the color of the arrow
      ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,12);       //--- Set the font size for the arrow
      if (direction > 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);    //--- Set the anchor to top for upward breaks
      if (direction < 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM); //--- Set the anchor to bottom for downward breaks

      string txt = " Break"; //--- Define the text suffix for the breakpoint label
      string objNameDescr = objName + txt; //--- Create a unique name for the text object
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time,price);   //--- Create a text object at the breakpoint
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);   //--- Set the color of the text
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,12); //--- Set the font size of the text
      if (direction > 0) { //--- Check if the breakout is upward
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER); //--- Set the text anchor to left upper
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);           //--- Set the text content
      }
      if (direction < 0) { //--- Check if the breakout is downward
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); //--- Set the text anchor to left lower
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);           //--- Set the text content
      }
   }
   ChartRedraw(0); //--- Redraw the chart to display the breakpoint
}

这里,我们定义"drawBreakPoint"函数,用于在图表上直观地标记突破点。先通过ObjectFind检查对象是否存在,如果不存在,则使用参数"objName"、"time"、"price"、"arrCode"、"clr"和"direction",并调用ObjectCreate创建类型为OBJ_ARROW箭头对象,通过 "ObjectSetInteger"设置箭头样式、颜色和固定字体大小12,并且根据"direction"将锚点设置为"ANCHOR_TOP"或"ANCHOR_BOTTOM"。

我们再用"ObjectCreate"与"OBJ_TEXT"创建文本标签"Break",命名为"objNameDescr",通过"ObjectSetInteger"与ObjectSetString设置颜色、字体大小,并依据方向选择锚点ANCHOR_LEFT_UPPERANCHOR_LEFT_LOWER。最后,调用"ChartRedraw"显示这些标记,确保突破点清晰可见。现在即可用此函数在图表上标注突破位。

double barClose = close(1); //--- Get the closing price of the previous bar
datetime barTime = time(1); //--- Get the time of the previous bar

if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
    && barTime >= validBreakTime_start && barTime <= validBreakTime_end
){ //--- Check for a breakout above the maximum price within the valid time window
   Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout above the high range
   isHaveRangeBreak = true;                                                //--- Set the flag to indicate a range breakout has occurred
   drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a breakpoint marker for the high breakout
}
else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
         && barTime >= validBreakTime_start && barTime <= validBreakTime_end
){ //--- Check for a breakout below the minimum price within the valid time window
   Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout below the low range
   isHaveRangeBreak = true;                                              //--- Set the flag to indicate a range breakout has occurred
   drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a breakpoint marker for the low breakout
}

为检测并可视化有效突破,我们先通过"close(1)"获取前一根K线的收盘价到"barClose",并用"time(1)"获取其时间到"barTime"。如果同时满足"barClose"大于"maximum_price","isHaveDailyRange_Prices"为true,"isHaveRangeBreak"为false,并且"barTime"在"validBreakTime_start"与"validBreakTime_end"之间,我们通过"Print"记录高位突破信息,设置"isHaveRangeBreak"为true,并且调用"drawBreakPoint",参数为"TimeToString(barTime)"、"barClose", 箭头代码234、"clrBlack"和-1。

如果"barClose"小于"minimum_price"且其他条件相同,我们则记录低位突破,设置"isHaveRangeBreak",并调用"drawBreakPoint",箭头代码233、"clrBlue"和1。这样即可在图表上标注有效突破。我们使用MQL5预定义的箭头233和234,如下所示,您也可以换成自己喜欢的箭头样式。

箭头表

程序运行后,输出结果如下:

突破

由图可见,我们能够精准识别并可视化价格突破信号。接下来需要完成的任务是定义趋势结构转变和突破中断的判定逻辑。为此,我们需要编写一个函数,用于在图表上绘制已识别的关键拐点。

//+------------------------------------------------------------------+
//| Function to draw a swing point marker                            |
//+------------------------------------------------------------------+
void drawSwingPoint(string objName,datetime time,double price,int arrCode,
   color clr,int direction){ //--- Define a function to draw a swing point marker
   if (ObjectFind(0,objName) < 0){ //--- Check if the swing point object does not already exist
      ObjectCreate(0,objName,OBJ_ARROW,0,time,price);        //--- Create an arrow object at the specified time and price
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrCode); //--- Set the arrow code for the marker
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);         //--- Set the color of the arrow
      ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,10);       //--- Set the font size for the arrow

      if (direction > 0) {ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);}    //--- Set the anchor to top for swing lows
      if (direction < 0) {ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM);} //--- Set the anchor to bottom for swing highs

      string text = "BoS";                   //--- Define the text label for Break of Structure
      string objName_Descr = objName + text; //--- Create a unique name for the text object
      ObjectCreate(0,objName_Descr,OBJ_TEXT,0,time,price);   //--- Create a text object at the swing point
      ObjectSetInteger(0,objName_Descr,OBJPROP_COLOR,clr);   //--- Set the color of the text
      ObjectSetInteger(0,objName_Descr,OBJPROP_FONTSIZE,10); //--- Set the font size of the text

      if (direction > 0) { //--- Check if the swing is a low
         ObjectSetString(0,objName_Descr,OBJPROP_TEXT,"  "+text);            //--- Set the text content
         ObjectSetInteger(0,objName_Descr,OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER); //--- Set the text anchor to left upper
      }
      if (direction < 0) { //--- Check if the swing is a high
         ObjectSetString(0,objName_Descr,OBJPROP_TEXT,"  "+text);            //--- Set the text content
         ObjectSetInteger(0,objName_Descr,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); //--- Set the text anchor to left lower
      }
   }
   ChartRedraw(0); //--- Redraw the chart to display the swing point
}

我们通过实现"drawSwingPoint"函数来标记已识别的关键高低点。该函数使用这些参数:"objName"、"time"、"price"、"arrCode"、"clr"和"direction",我们先通过ObjectFind验证图表中是否已存在同名对象,确定不存在后,通过调用ObjectCreate并指定类型为 OBJ_ARROW绘制箭头,设置箭头样式、颜色及字体大小(如10),并根据"ANCHOR_TOP"(低点)和"ANCHOR_BOTTOM"(高点)锚定位置。我们还通过"ObjectCreate"创建"OBJ_TEXT"类型的文本标签"BoS",通过"ObjectSetInteger"与"ObjectSetString"设置颜色、字体大小和锚点(ANCHOR_LEFT_UPPERANCHOR_LEFT_LOWER)。我们调用ChartRedraw来显示这些标记,突出关键的拐点。通过此函数,我们将继续构建拐点识别逻辑。

// bos logic
if (isHaveDailyRange_Prices){ //--- Proceed with BoS logic only if the daily range is calculated
   static bool isNewBar_bos = false;            //--- Initialize flag to indicate a new bar on the BoS timeframe
   int currBars = iBars(_Symbol,timeframe_bos); //--- Get the current number of bars on the BoS timeframe
   static int prevBars = currBars;              //--- Store the previous number of bars for comparison

   if (prevBars == currBars){isNewBar_bos = false;}                          //--- Set flag to false if no new bar has formed
   else if (prevBars != currBars){isNewBar_bos = true; prevBars = currBars;} //--- Set flag to true and update prevBars if a new bar has formed

   const int length = 4;                         //--- Define the number of bars to check for swing high/low (must be > 2)
   int right_index, left_index;                  //--- Declare variables to store indices for bars to the right and left
   int curr_bar = length;                        //--- Set the current bar index for swing analysis
   bool isSwingHigh = true, isSwingLow = true;   //--- Initialize flags to determine if the current bar is a swing high or low
   static double swing_H = -1.0, swing_L = -1.0; //--- Initialize variables to store the latest swing high and low prices

   if (isNewBar_bos){ //--- Check if a new bar has formed on the BoS timeframe
      for (int a=1; a<=length; a++){ //--- Loop through the specified number of bars to check for swings
         right_index = curr_bar - a; //--- Calculate the right-side bar index
         left_index = curr_bar + a;  //--- Calculate the left-side bar index
         if ( (high(curr_bar,timeframe_bos) <= high(right_index,timeframe_bos)) || (high(curr_bar,timeframe_bos) < high(left_index,timeframe_bos)) ){ //--- Check if the current bar's high is not the highest
            isSwingHigh = false; //--- Set flag to false if the bar is not a swing high
         }
         if ( (low(curr_bar,timeframe_bos) >= low(right_index,timeframe_bos)) || (low(curr_bar,timeframe_bos) > low(left_index,timeframe_bos)) ){ //--- Check if the current bar's low is not the lowest
            isSwingLow = false; //--- Set flag to false if the bar is not a swing low
         }
      }

      if (isSwingHigh){ //--- Check if the current bar is a swing high
         swing_H = high(curr_bar,timeframe_bos); //--- Store the swing high price
         Print("WE DO HAVE A SWING HIGH @ BAR INDEX ",curr_bar," H: ",high(curr_bar,timeframe_bos)); //--- Log the swing high details
         drawSwingPoint(TimeToString(time(curr_bar,timeframe_bos)),time(curr_bar,timeframe_bos),high(curr_bar,timeframe_bos),77,clrBlue,-1); //--- Draw a marker for the swing high
      }
      if (isSwingLow){ //--- Check if the current bar is a swing low
         swing_L = low(curr_bar,timeframe_bos); //--- Store the swing low price
         Print("WE DO HAVE A SWING LOW @ BAR INDEX ",curr_bar," L: ",low(curr_bar,timeframe_bos)); //--- Log the swing low details
         drawSwingPoint(TimeToString(time(curr_bar,timeframe_bos)),time(curr_bar,timeframe_bos),low(curr_bar,timeframe_bos),77,clrRed,+1); //--- Draw a marker for the swing low
      }
   }
}

一旦我们获取了日内价格(即已定义好日级价格区间),我们便用静态标识"isNewBar_bos"跟踪BOS时间框架的新K线,将当前iBars结合_Symbol和"timeframe_bos"获取的K线数与静态变量"prevBars"比较。如果出现新K线,则将"isNewBar_bos"设置为true并更新"prevBars"。

当"isNewBar_bos"为 true时,我们对索引为"curr_bar"(设置length = 4)的K线进行波段分析,用"right_index"与"left_index"分别检查其左右各"length"根K线,。通过"timeframe_bos"的"high"与"low"函数,比较当前K线的高/低是否分别为最高或最低,如果不符合则把"isSwingHigh"或"isSwingLow"设置为false。

如果"isSwingHigh"为true,则将价格存入"swing_H",用"Print"记录,并调用"drawSwingPoint",参数为"TimeToString"、K线时间、价格、箭头代码 77、"clrBlue"和-1;如果"isSwingLow"为true,则更新"swing_L"记录,并调用"drawSwingPoint",参数为"clrRed"和+1。编译后,呈现如下效果:

确认拐点

由图可见,我们已标注出关键拐点。接下来需实现的功能是跟踪这些拐点的突破情况——即当价格有效突破拐点时,视为BOS。为了清晰可视化效果,我们需要编写以下自定义函数。

//+------------------------------------------------------------------+
//| Function to draw a break level line                              |
//+------------------------------------------------------------------+
void drawBreakLevel(string objName,datetime time1,double price1,
   datetime time2,double price2,color clr,int direction){ //--- Define a function to draw a break level line
   if (ObjectFind(0,objName) < 0){ //--- Check if the break level object does not already exist
      ObjectCreate(0,objName,OBJ_ARROWED_LINE,0,time1,price1,time2,price2); //--- Create an arrowed line object
      ObjectSetInteger(0,objName,OBJPROP_TIME,0,time1);                     //--- Set the first time coordinate of the line
      ObjectSetDouble(0,objName,OBJPROP_PRICE,0,price1);                    //--- Set the first price coordinate of the line
      ObjectSetInteger(0,objName,OBJPROP_TIME,1,time2);                     //--- Set the second time coordinate of the line
      ObjectSetDouble(0,objName,OBJPROP_PRICE,1,price2);                    //--- Set the second price coordinate of the line

      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr); //--- Set the color of the line
      ObjectSetInteger(0,objName,OBJPROP_WIDTH,2);   //--- Set the width of the line

      string text = "Break";                 //--- Define the text label for the break
      string objName_Descr = objName + text; //--- Create a unique name for the text object
      ObjectCreate(0,objName_Descr,OBJ_TEXT,0,time2,price2); //--- Create a text object at the line's end
      ObjectSetInteger(0,objName_Descr,OBJPROP_COLOR,clr);   //--- Set the color of the text
      ObjectSetInteger(0,objName_Descr,OBJPROP_FONTSIZE,10); //--- Set the font size of the text

      if (direction > 0) { //--- Check if the break is upward
         ObjectSetString(0,objName_Descr,OBJPROP_TEXT,text+"  ");             //--- Set the text content
         ObjectSetInteger(0,objName_Descr,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER); //--- Set the text anchor to right upper
      }
      if (direction < 0) { //--- Check if the break is downward
         ObjectSetString(0,objName_Descr,OBJPROP_TEXT,text+"  ");             //--- Set the text content
         ObjectSetInteger(0,objName_Descr,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER); //--- Set the text anchor to right lower
      }
   }
   ChartRedraw(0); //--- Redraw the chart to display the break level
}

我们已定义了"drawBreakLevel"函数,用于可视化BOS的关键价位。该函数的可视化逻辑与之前开发的标注函数类似,因此无需重复赘述具体实现细节。我们将调用此函数,以可视化支撑/阻力位。

double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the current Ask price
double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the current Bid price

if (swing_H > 0 && Ask > swing_H){ //--- Check if the Ask price breaks above the swing high
   Print("$$$$$$$$$ BUY SIGNAL NOW. BREAK OF SWING HIGH"); //--- Log a buy signal due to swing high breakout
   int swing_H_index = 0;                //--- Initialize the index of the swing high bar
   for (int i=0; i<=length*2+1000; i++){ //--- Loop through bars to find the swing high
      double high_sel = high(i,timeframe_bos); //--- Get the high price of the i-th bar
      if (high_sel == swing_H){ //--- Check if the high matches the swing high
         swing_H_index = i;     //--- Store the bar index
         Print("BREAK HIGH FOUND @ BAR INDEX ",swing_H_index); //--- Log the swing high bar index
         break;                 //--- Exit the loop once found
      }
   }
   drawBreakLevel(TimeToString(time(0,timeframe_bos)),time(swing_H_index,timeframe_bos),high(swing_H_index,timeframe_bos),
   time(0,timeframe_bos),high(swing_H_index,timeframe_bos),clrBlue,-1); //--- Draw a line to mark the swing high breakout

   if (isTakenTrade == false){                                     //--- Check if no trade is taken yet
      obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,maximum_price); //--- Execute a buy trade with 0.01 lots, using minimum price as SL and maximum as TP
      isTakenTrade = true;                                         //--- Set the flag to indicate a trade is taken
   }

   swing_H = -1.0; //--- Reset the swing high price
   return;         //--- Exit the OnTick function to avoid further processing
}
if (swing_L > 0 && Bid < swing_L){ //--- Check if the Bid price breaks below the swing low
   Print("$$$$$$$$$ SELL SIGNAL NOW. BREAK OF SWING LOW"); //--- Log a sell signal due to swing low breakout
   int swing_L_index = 0; //--- Initialize the index of the swing low bar
   for (int i=0; i<=length*2+1000; i++){ //--- Loop through bars to find the swing low
      double low_sel = low(i,timeframe_bos); //--- Get the low price of the i-th bar
      if (low_sel == swing_L){ //--- Check if the low matches the swing low
         swing_L_index = i;    //--- Store the bar index
         Print("BREAK LOW FOUND @ BAR INDEX ",swing_L_index); //--- Log the swing low bar index
         break;                //--- Exit the loop once found
      }
   }
   drawBreakLevel(TimeToString(time(0,timeframe_bos)),time(swing_L_index,timeframe_bos),low(swing_L_index,timeframe_bos),
   time(0,timeframe_bos),low(swing_L_index,timeframe_bos),clrRed,+1); //--- Draw a line to mark the swing low breakout

   if (isTakenTrade == false){ //--- Check if no trade is taken yet
      obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,minimum_price); //--- Execute a sell trade with 0.01 lots, using maximum price as SL and minimum as TP
      isTakenTrade = true;                                          //--- Set the flag to indicate a trade is taken
   }

   swing_L = -1.0; //--- Reset the swing low price
   return;         //--- Exit the OnTick function to avoid further processing
}

这里,当出现有效突破信号时,我们实现自动执行交易订单的逻辑。我们通过SymbolInfoDoubleNormalizeDouble结合_Symbol_Digits,对交易品种的卖出价和买入价进行标准化处理,确保精度符合交易规则。

如果当前检测到的"swing_H"为正值,且当前卖出价超出高点价位,我们使用"Print"记录突破事件,通过"high"数组和"timeframe_bos"找出该波段高点的索引,调用"drawBreakLevel"结合TimeToString和"time",标记突破水平位,如果当前未持仓("isTakenTrade"为false),则通过"obj_Trade.Buy"以0.01手开仓买入,并设置止损价位"minimum_price"与止盈价位"maximum_price",将"isTakenTrade"标记为 true(防止重复开仓),并重置"swing_H"值(清除已突破的拐点)。

如果"swing_L"为正值且当前买入价跌破低点价位,我们则记录日志,通过"low"找到该波段低点索引,调用"drawBreakLevel"标记突破水平位,通过"obj_Trade.Sell"开仓卖出,并重置"swing_L"值(清除已突破的拐点)。我们在每次交易后均以"return"退出,确保精准的BOS交易。以下是输出结果:

交易设置

我们现已能够针对确认有效的交易形态自动开仓。然而,如果出现突破行情但未发生在既定区间内的情况,也就是价格从区间边界外发起突破。我们需要等待价格重新回至区间内,方可认定该突破为有效信号。为实现这一逻辑,需提取区间的最高价与最低价,并以此构建更严格的筛选条件,从而避免虚假突破信号的干扰。

if (swing_H > 0 && Ask > swing_H && swing_H <= maximum_price && swing_H >= minimum_price){ //--- Check if the Ask price breaks above the swing high within the range
   Print("$$$$$$$$$ BUY SIGNAL NOW. BREAK OF SWING HIGH WITHIN RANGE"); //--- Log a buy signal due to swing high breakout
   int swing_H_index = 0;                                               //--- Initialize the index of the swing high bar
   for (int i=0; i<=length*2+1000; i++){                                //--- Loop through bars to find the swing high
      double high_sel = high(i,timeframe_bos); //--- Get the high price of the i-th bar
      if (high_sel == swing_H){                //--- Check if the high matches the swing high
         swing_H_index = i; //--- Store the bar index
         Print("BREAK HIGH FOUND @ BAR INDEX ",swing_H_index); //--- Log the swing high bar index
         break;             //--- Exit the loop once found
      }
   }
   drawBreakLevel(TimeToString(time(0,timeframe_bos)),time(swing_H_index,timeframe_bos),high(swing_H_index,timeframe_bos),
   time(0,timeframe_bos),high(swing_H_index,timeframe_bos),clrBlue,-1); //--- Draw a line to mark the swing high breakout

   if (isTakenTrade == false){ //--- Check if no trade is taken yet
      obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,maximum_price); //--- Execute a buy trade with 0.01 lots, using minimum price as SL and maximum as TP
      isTakenTrade = true;                                         //--- Set the flag to indicate a trade is taken
   }

   swing_H = -1.0; //--- Reset the swing high price
   return;         //--- Exit the OnTick function to avoid further processing
}
if (swing_L > 0 && Bid < swing_L && swing_L <= maximum_price && swing_L >= minimum_price){ //--- Check if the Bid price breaks below the swing low within the range
   Print("$$$$$$$$$ SELL SIGNAL NOW. BREAK OF SWING LOW WITHIN RANGE"); //--- Log a sell signal due to swing low breakout
   int swing_L_index = 0;                //--- Initialize the index of the swing low bar
   for (int i=0; i<=length*2+1000; i++){ //--- Loop through bars to find the swing low
      double low_sel = low(i,timeframe_bos); //--- Get the low price of the i-th bar
      if (low_sel == swing_L){ //--- Check if the low matches the swing low
         swing_L_index = i;    //--- Store the bar index
         Print("BREAK LOW FOUND @ BAR INDEX ",swing_L_index); //--- Log the swing low bar index
         break;                //--- Exit the loop once found
      }
   }
   drawBreakLevel(TimeToString(time(0,timeframe_bos)),time(swing_L_index,timeframe_bos),low(swing_L_index,timeframe_bos),
   time(0,timeframe_bos),low(swing_L_index,timeframe_bos),clrRed,+1); //--- Draw a line to mark the swing low breakout

   if (isTakenTrade == false){ //--- Check if no trade is taken yet
      obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,minimum_price); //--- Execute a sell trade with 0.01 lots, using maximum price as SL and maximum as TP
      isTakenTrade = true;                                          //--- Set the flag to indicate a trade is taken
   }

   swing_L = -1.0; //--- Reset the swing low price
   return;         //--- Exit the OnTick function to avoid further processing
}

我们能够有效过滤虚假信号,并仅在确认有效的交易形态出现时开仓,从而成功实现了既定目标:识别、可视化并执行该策略。接下来需完成的工作是程序回测,相关内容将在下一章节详细阐述。


回测

经过全面回测后,我们得到以下结果:

回测图:

图表

回测报告:

报告


结论

综上所述,我们已成功开发了一款基于MQL5的EA,该系统实现了午夜区间突破结合BOS策略,通过当日关键拐点确认午夜区间内的突破信号,并自动执行交易。凭借精准的区间检测与可视化功能,您可以做进一步扩展,并定义更多策略,使其更加稳健,从而适应您的交易风格。

免责声明:本文仅用于教学目的。交易涉及重大财务风险,市场剧烈波动可能导致资金损失。在将此EA投入实盘交易前,务必进行全面回测并制定严谨的风险管理策略。

通过掌握这些技术,您可以提升算法交易能力,以更从容的姿态应对市场波动。祝您交易顺利!

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

最近评论 | 前往讨论 (7)
CapeCoddah
CapeCoddah | 3 5月 2025 在 19:47

你好,艾伦。


我准备下载你的系统并开始测试。 有趣的是,策略测试报告 既没有确定交易货币对,也没有确定时间框架。 由于你说明了 AUUSD M15,我想这就是你使用的,我将开始测试。 你对使用其他货币对或时间框架有什么感觉吗? 我怀疑这个 Ea 在亚洲交易货币对上可能会更好用,我说的对吗?

干杯,科达角


测试失败了,我尝试了澳元兑美元、澳元兑日元和美元兑日元,结果全部亏损,夏普比率为 -3.00 至 -5.00。除美元兑日元外,其他货币均立即转为负值,且再也没有恢复。 美元兑日元曾有过两次正收益,但最终转为负值,且再也没有恢复。


再见

Juan Guirao
Juan Guirao | 5 5月 2025 在 17:02
工作出色。谢谢 Allan!
Allan Munene Mutiiria
Allan Munene Mutiiria | 5 5月 2025 在 18:52
Juan Guirao #:
工作出色。谢谢你,艾伦

当然,欢迎感谢您的反馈。

sevkoff
sevkoff | 5 10月 2025 在 16:26
谢谢你,艾伦! 你是最棒的
Allan Munene Mutiiria
Allan Munene Mutiiria | 6 10月 2025 在 13:07
sevkoff #:
谢谢你,艾伦! 你是最棒的
感谢您的善意反馈。欢迎您的到来。
在交易图表上通过资源驱动的双三次插值图像缩放技术创建动态 MQL5 图形界面 在交易图表上通过资源驱动的双三次插值图像缩放技术创建动态 MQL5 图形界面
本文探讨了动态 MQL5 图形界面,利用双三次插值技术在交易图表上实现高质量的图像缩放。我们详细介绍了灵活的定位选项,支持通过自定义偏移量实现动态居中或位置定位。
MQL5 简介(第 14 部分):构建自定义指标的初学者指南(三) MQL5 简介(第 14 部分):构建自定义指标的初学者指南(三)
学习如何使用图表对象在 MQL5 中构建谐波形态指标。了解如何检测波动点、应用斐波那契回撤线以及自动识别形态。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
皇冠同花顺优化(RFO) 皇冠同花顺优化(RFO)
最初的皇冠同花顺优化算法提供了一种解决优化问题的新方法,受到扑克牌原则启发,以基于扇区的方式取代了传统的遗传二进制编码算法。RFO 展现出简化的基本原理如何带来高效、且实用的优化方法。文章呈现了一份详细的算法分析和测试结果。