保持敬畏之心
交易是一场持久战

MQL4(43):实现“每K线开盘时执行”的逻辑

如何让你的炒外汇的EA不那么“激动”,不是一有风吹草动(每个微小的价格变动)就去分析和做决定,而是更有“耐心”,只在每根新的K线蜡烛图(比如每小时一根、每4小时一根或每天一根)刚开始的时候才去思考和行动一次。

想象一下:

  • 默认情况(每个tick执行): 你的机器人就像一个高度紧张的哨兵,市场上价格一有任何微小的波动(一个tick),它就要立刻打起精神,重新分析一遍所有情况,看看要不要买入或卖出。这样反应很快,但可能因为一点点“假信号”或“市场噪音”就做出错误的判断,或者过于频繁地交易。

  • 控制后(每根K线开盘时执行): 你的机器人变成了一个更有经验的交易员。它会说:“别急,等这根蜡烛走完(比如这个小时结束),我看看这一个小时完整的价格走势是怎样的,再决定下一步怎么做。”

在某些交易策略中,我们可能更希望仅在每根新的K线形成时检查一次交易条件。这种做法的主要考虑是:

  • 信号更靠谱,过滤“噪音”: 等一根K线走完再做判断,能更好地看清楚这根K线到底是涨了还是跌了,力度如何。这样可以避免被K线中间那些短暂的、没有意义的波动(市场噪音)迷惑,从而抓住更可靠的交易信号。就像开车看红绿灯,你会等灯稳定了再走,而不是一闪就动。
  • 历史测试(回测)结果更准: 你在用历史数据测试你的机器人策略好不好的时候,如果用“每个tick”模式,有时候因为测试用的tick数据不完美(很多是模拟出来的),结果可能不太准,跟你真实交易时的表现差很多。但如果你的策略本来就是设计成“每根K线开始时做决定”的,那你用“仅开盘价”模式去回测,结果往往更稳定,也更接近真实情况。
  • 代价是什么?——可能反应慢一点: 如果市场在一根K线内部(比如一个小时内)突然发生大行情,你的机器人因为要等这根K线走完、下一根K线开始才行动,可能会错过最佳的入场或出场点。这就是在“信号的准确性”和“反应的及时性”之间做个取舍。

实现“每K线开盘时执行”的逻辑:

核心思想是记录上一根已处理K线的时间戳,然后在每个tick到达时,将当前K线的时间戳与记录的时间戳进行比较。如果两者不同,则表明一根新的K线已经开始,此时才执行交易条件的检查。

  1. 时间戳跟踪:

    • 需要一个全局变量(例如 datetime g_CurrentTimeStamp;)来存储最近一次执行交易逻辑时所在K线的开盘时间。
    • OnInit() 函数中,将 g_CurrentTimeStamp 初始化为EA启动时当前K线的开盘时间 (Time[0])。这样做是为了确保EA在加载后的第一个tick不会立即执行交易逻辑,而是会等到下一根新K线形成时才开始。
  2. 数据引用的调整:

    • 当我们决定在新K线开盘时(即基于前一根K线的收盘信息)做决策,那么所有指标计算、价格数据读取(如 High[], Low[], Open[], Close[])都应该引用前一根K线(索引为1)的数据,而不是当前正在形成的K线(索引为0)的数据。
    • 为此,可以引入一个整数变量 barShift。当启用“每K线执行一次”模式时,barShift 设为 1;否则(每个tick执行模式),barShift 设为 0。然后,在所有获取历史数据的地方,使用这个 barShift 变量作为位移参数。
  3. 新K线标志 (isNewBarSignal):

    • 引入一个布尔变量 isNewBarSignal。只有当检测到新K线形成时,才将其设为 true,并在该tick中执行交易逻辑。

可能有人看不明白上面这段,我用通俗点的白话来解释下:

  1. 记住上一根K线的时间: 交易机器人需要一个“小本本”(一个全局变量),记下它上次做分析和交易时,那根K线的开始时间。
  2. 比较当前K线的时间: 每当市场有新价格(新tick)来的时候,机器人就看看当前这根K线的开始时间,跟它“小本本”上记的时间是不是一样。
  3. 发现新K线就行动:
    • 如果时间不一样了,说明新的一根K线开始了!这时候,机器人就把“小本本”上的时间更新成当前这根新K线的开始时间,并且设置一个“行动标志”(比如一个叫 isNewBarSignal 的变量设为 true)。
    • 如果时间还是一样,说明还在同一根K线内,那就不更新时间,“行动标志”就是 false,机器人这次就“按兵不动”。
  4. 数据看哪根K线?——看上一根! 当机器人决定在新K线开始时行动,它做决策所依据的数据(比如最高价、最低价、收盘价、指标计算等)应该是刚刚走完的那一根K线(也就是索引为1的K线),而不是当前这根刚刚开始、还没走完的K线(索引为0的K线)。用一个叫 barShift 的变量来控制,如果按K线执行,barShift 就是1。
  5. 交易逻辑看“行动标志”: 所有真正要下单、平仓的交易代码,都要先检查一下那个“行动标志”(isNewBarSignal)是不是 true。只有它是 true 的时候,才执行这些核心的交易操作。

代码实现:

  • 声明变量:

    // --- 输入参数 ---
    extern bool ExecuteOncePerBar = true; // true: 每K线开盘执行一次; false: 每个tick执行
    
    // --- 全局变量 ---
    datetime g_LastBarOpenTime;        // 存储上一根已处理K线的开盘时间
    
  • OnInit() 函数:

    int OnInit()
     { // ... 其他初始化代码 ... 
    if (ExecuteOncePerBar) 
    { 
    g_LastBarOpenTime = Time[0]; // 初始化为当前K线时间,确保首次运行等到下一根K线
    }
    return(INIT_SUCCEEDED); 
    } 
  • OnTick()开头部分的逻辑:

    void OnTick()
    {
        bool isNewBarSignal = false; // 标记本轮tick是否因为新K线而触发逻辑
        int  barShift = 0;         // 指标和价格数据的默认位移 (当前K线)
    
        if (ExecuteOncePerBar) // 如果启用了“每K线执行一次”模式
        {
            barShift = 1; // 所有数据都应基于前一根K线
    
            // 检查当前K线的开盘时间是否与记录的上一根K线时间不同
            if (g_LastBarOpenTime != Time[0])
            {
                g_LastBarOpenTime = Time[0]; // 更新记录的时间为当前K线(即新K线)的开盘时间
                isNewBarSignal = true;      // 标记这是一个新K线的开始,可以执行交易逻辑
            }
            // else isNewBarSignal 保持 false,本轮tick不因新K线执行逻辑
        }
        else // 如果是“每个tick执行”模式
        {
            isNewBarSignal = true; // 每个tick都允许执行交易逻辑
            barShift = 0;        // 数据基于当前K线
        }
    
    
    • 逻辑解释:
      • 如果 ExecuteOncePerBartrue
        • barShift 设为 1,意味着所有指标和价格数据都将引用前一根K线。
        • 比较 g_LastBarOpenTimeTime[0](当前K线的开盘时间)。
          • 如果不相等,说明新K线开始了。更新 g_LastBarOpenTimeTime[0],并将 isNewBarSignal 设为 true
          • 如果相等,说明仍在同一根K线内,isNewBarSignal 保持或设为 false
      • 如果 ExecuteOncePerBarfalse
        • isNewBarSignal 总是设为 true,表示每个tick都允许检查交易条件。
        • barShift 设为 0,数据引用当前K线。
  • 在指标和价格数据调用中应用barShift: 当获取指标值时,将 barShift 作为最后一个参数(位移量)传入。

    // 获取MA值 (FastMA_Period, MA_Method, Applied_Price 已定义)
    double fastMA = iMA(NULL, 0, FastMA_Period, 0, MA_Method, Applied_Price, barShift);
    
    // 获取K线价格
    if (Close[barShift] > Open[barShift]) { /* ... */ }
    double lowOfBar = iLow(NULL, 0, barShift);
    

    如果需要引用比 barShift 所指向的K线更早的K线(例如,要获取“前一根K线的前一根K线”),则使用 barShift + N

    // 获取 barShift 指向的K线的前一根K线的快MA值
    double previousFastMA = iMA(NULL, 0, FastMA_Period, 0, MA_Method, Applied_Price, barShift + 1);
    
  • 控制交易执行: 在实际执行任何下单、平仓等交易操作的代码块之前,务必检查 isNewBarSignal 的值。

    // ... (前面的 isNewBarSignal 和 barShift 计算逻辑) ...
    
    // --- 仅当 isNewBarSignal 为 true 时才执行核心交易逻辑 ---
    // (假设 TradeAllowed 是另一个控制交易是否允许的全局开关)
    if (TradeAllowed == true && isNewBarSignal == true)
    {
        // 计算指标 (使用 barShift)
        double fastMA = iMA(NULL, 0, FastMA_Period, 0, MODE_SMA, PRICE_CLOSE, barShift);
        double slowMA = iMA(NULL, 0, SlowMA_Period, 0, MODE_SMA, PRICE_CLOSE, barShift);
    
        // 买入条件 (假设 g_BuyTicket, BuyOrderCount() 已定义)
        if (fastMA > slowMA && g_BuyTicket == 0 && BuyOrderCount(Symbol(), MagicNumber) == 0)
        {
            // ... 买单开仓代码 (为简洁省略) ...
        }
    
        // 卖出条件 (假设 g_SellTicket, SellOrderCount() 已定义)
        if (fastMA < slowMA && g_SellTicket == 0 && SellOrderCount(Symbol(), MagicNumber) == 0)
        {
            // ... 卖单开仓代码 (为简洁省略) ...
        }
    } // 结束交易逻辑块
    

虽然为EA添加“每K线开盘时执行”的逻辑会增加一些代码复杂度,但对于许多基于日内或更长周期K线信号的交易系统而言,这种方式可以显著提高回测结果的可靠性,并可能帮助过滤掉部分市场噪音。是否采用此模式,取决于您的交易策略特性以及对响应速度和回测准确性的具体需求。

赞(0)
未经允许不得转载:图道交易 » MQL4(43):实现“每K线开盘时执行”的逻辑
分享到