EA开发系列---回测

174 0 2023-11-07

对于EA来说,比较重要的一点是验证其有效性。验证其有效性的最好方法就是回测。回测是指,使用一段比较长的历史时期内的行情数据来验证EA性能的方法。那么我们开发出来的EA是否能用就必须得经过回测的验证。这一章,我们来看看如何使用MT5进行EA的回测。一些准备前面我们开发了一个名叫FirstEA的EA,我们在这个基础上添加一些功能: 1、确保前一个订单没有完结,不再进行下一次交易 2、加入一些比较初级的盈亏比的概念,我们设定为1:1.5需要注意的是: 这一节我仍将提供完整的实例代码,但是,这个代码仍

对于EA来说,比较重要的一点是验证其有效性。验证其有效性的最好方法就是回测。回测是指,使用一段比较长的历史时期内的行情数据来验证EA性能的方法。那么我们开发出来的EA是否能用就必须得经过回测的验证。这一章,我们来看看如何使用MT5进行EA的回测。

一些准备

前面我们开发了一个名叫FirstEA的EA,我们在这个基础上添加一些功能: 1、确保前一个订单没有完结,不再进行下一次交易 2、加入一些比较初级的盈亏比的概念,我们设定为1:1.5

需要注意的是: 这一节我仍将提供完整的实例代码,但是,这个代码仍然是一个演示性的代码,不能作为真实交易使用。如果使用后果还请自行承担。

修改后的代码如下(以后以此为基础进行深入讲解):

//+------------------------------------------------------------------+//|                                                      ProjectName |//|                                      Copyright 2020, CompanyName |//|                                       http://www.companyname.net |//+------------------------------------------------------------------+#property copyright "Copyright 2021, MetaQuotes Ltd."#property link      "https://www.mql5.com"#property version   "1.00"//--- 输入参数定义部分input int      indicator_param = 50;       //MA周期设定input ulong    input_magic     = 20210427; //幻数(标识订单)int            m_ma_handle = -1;//+------------------------------------------------------------------+//| EA初始化函数,由系统调用,调用场景至少包括://| 1、EA首次加载到图表的时候执行//| 2、参数发生变化的时执行//| 3、EA重新编译并且EA未从图表中卸载的时候执行//| 4、图表周期发生变化的时候执行//+------------------------------------------------------------------+int OnInit() {//---    m_ma_handle = iMA(Symbol(), PERIOD_CURRENT, indicator_param, 0, MODE_SMA, PRICE_CLOSE);//初始化均线参数    //输出基本信息:当前Symbol,当前图表周期    PrintFormat("OnInit::symbol= %s, time_peroid= %d",  Symbol(), Period());//---    return(INIT_SUCCEEDED);}//+------------------------------------------------------------------+//| EA析构函数,//| 系统在环境发生变化或者EA被从所在图表中卸载的时候调用。//+------------------------------------------------------------------+void OnDeinit(const int reason) {//---    //输出基本信息:当前Symbol,当前图表周期以及卸载原因    PrintFormat("OnDeinit::symbol= %s, time_peroid= %d, reason= %d",  Symbol(), Period(), reason);
    IndicatorRelease(m_ma_handle);//释放指标}//+------------------------------------------------------------------+//| 每一次报价发生的时候调用,交易高峰期报价会很频繁,//| 因此,此函数会非常频繁的被系统调用。//| 实现的功能://| 1、获取15当前图表周期的简单均线(MA)值//| 2、如果当前一K线的开盘价和收盘价从上往下穿越的时候,当前周期现价开做多//| 3、反之开做空//+------------------------------------------------------------------+void OnTick() {//---    bool is_found = findMyOrder();
    if(is_found == true) {
        PrintFormat("OnTick::have order wait..........");
    } else {
        //获取上一周期的均线值        double ma_values[];
        CopyBuffer(m_ma_handle, 0, 0, 2, ma_values);
        double pre_ma_value = ma_values[0];

        //获取上一周期的开盘价和收盘价        double pre_close = iClose(Symbol(), PERIOD_CURRENT, 1);
        double pre_open  = iOpen(Symbol(), PERIOD_CURRENT, 1);
        double pre_high  = iHigh(Symbol(), PERIOD_CURRENT, 1);
        double pre_low   = iLow(Symbol(), PERIOD_CURRENT, 1);
        double current_open = iOpen(Symbol(), PERIOD_CURRENT, 0);

        PrintFormat("OnTick::pma= %.5f, pclose= %.5f, popen= %.5f, phigh= %.5f, plow= %.5f, cOpen= %.5f ==========", 
         pre_ma_value, pre_close, pre_open, pre_high, pre_low, current_open);
        //判断向上或者向下穿越        if(pre_open < pre_ma_value && pre_close > pre_ma_value) {
            double sl_diff = current_open - pre_low;//计算止损值            double tp_value = current_open + 1.5 * sl_diff;//1:1.5盈亏比            PrintFormat("OnTick::pma= %.5f, pclose= %.5f, popen= %.5f, sl_diff= %.5f, tp_value= %.5f ==========[Open buy ]", 
               pre_ma_value, pre_close, pre_open, sl_diff, tp_value);
            openBuyOrder(Symbol(), 0.1, pre_low, tp_value);
        } else if(pre_open > pre_ma_value && pre_close < pre_ma_value) {
            double sl_diff = pre_high - current_open;//计算止损值            double tp_value = current_open - 1.5 * sl_diff;//1:1.5盈亏比            PrintFormat("OnTick::pma= %.5f, pclose= %.5f, popen= %.5f, sl_diff= %.5f, tp_value= %.5f ==========[Open sell ]", 
               pre_ma_value, pre_close, pre_open, sl_diff, tp_value);
            openSellOrder(Symbol(), 0.1, pre_high, tp_value);
        } else {
            //啥都不做            PrintFormat("OnTick::pma= %.5f, pclose= %.5f, popen= %.5f ==========[Do nothing]", pre_ma_value, pre_close, pre_open);
        }
    }}//+------------------------------------------------------------------+//+------------------------------------------------------------------+//| 做多//+------------------------------------------------------------------+bool openBuyOrder(string input_symbol, double input_shares, double sl_price, double tp_price) {
    MqlTradeRequest request = {0};
    MqlTradeResult  result  = {0};

    request.action    = TRADE_ACTION_DEAL;
    request.type      = ORDER_TYPE_BUY;
    request.symbol    = input_symbol;
    request.volume    = input_shares;
    request.deviation = 5;//偏差设置,大致为滑点    request.price     = SymbolInfoDouble(input_symbol, SYMBOL_ASK);//ASK价格    request.magic     = input_magic;

    if(sl_price > 0) {
        request.sl = sl_price;
    }

    if(tp_price > 0) {
        request.tp = tp_price;
    }

    bool is_success = OrderSend(request, result);
    if(is_success == false) {
        PrintFormat("openBuyOrder::OrderSend error %d", GetLastError());
        PrintFormat("openBuyOrder::retcode=%u  deal=%I64u  order=%I64u", result.retcode, result.deal, result.order);
    }
    return is_success;}//+------------------------------------------------------------------+//| 做空//+------------------------------------------------------------------+bool openSellOrder(string input_symbol, double input_shares, double sl_price, double tp_price) {
    MqlTradeRequest request = {0};
    MqlTradeResult  result  = {0};

    request.action    = TRADE_ACTION_DEAL;
    request.type      = ORDER_TYPE_SELL;
    request.symbol    = input_symbol;
    request.volume    = input_shares;
    request.deviation = 5;//偏差设置,大致为滑点    request.price     = SymbolInfoDouble(input_symbol, SYMBOL_BID);//BID价格    request.magic     = input_magic;

    if(sl_price > 0) {
        request.sl = sl_price;
    }

    if(tp_price > 0) {
        request.tp = tp_price;
    }

    bool is_success = OrderSend(request, result);
    if(is_success == false) {
        PrintFormat("openSellOrder::OrderSend error %d", GetLastError());
        PrintFormat("openSellOrder::retcode=%u  deal=%I64u  order=%I64u", result.retcode, result.deal, result.order);
    }
    return is_success;}//+------------------------------------------------------------------+//+------------------------------------------------------------------+//|查找正在交易的订单                                               |//+------------------------------------------------------------------+bool findMyOrder() {
    int orders_p_total = PositionsTotal();//当前正在交易的订单总数    bool is_found = false;

    for(int p_order_index = 0; p_order_index < orders_p_total; p_order_index++) {//遍历订单信息        ulong p_order_tick = PositionGetTicket(p_order_index);//根据索引选定订单        bool select_result = PositionSelectByTicket(p_order_tick);//获取订单的幻数
        if(select_result == true) {
            ulong order_magic = PositionGetInteger(POSITION_MAGIC);
            if(order_magic == input_magic) {
                is_found = true;
                break;
            }
        }
    }
    return is_found;}//+------------------------------------------------------------------+

有了代码,我们下一步完成编译(参考:EA开发系列---EA的编译运行)后就可以开始执行回测了。

回测的主要设置

打开MT5回测界面

点击“显示或隐藏策略测试窗口,ctrl+R”。如下图所示:

首先,我们进行一些基础的设置: 1、”专家“:下拉列表中选择FirstEA,如果完成了编译,这里就能找到 2、”交易品种“:这个下拉列表中你可以选择平台支持的交易品种,这里我们选择EURUSD(欧美)这个外汇品种。后面选择希望在哪个时间周期下进行回测,这里选择的是”H4“(4小时周期) 3、”日期“:指回测的时间周期,为了保证EA的有效性,可以选择尽可能长的回测周期,这里选择2018年1月1日到目前(大约3年的数据)进行回测 4、”入金“:这里设置的是进行测试的本金以及杠杆,这里设定的是:10000美金,1:500杠杆 以上是基础的设置,其他的不再赘述。这里建议你勾选”显示图表、指标和交易的可视化模式“,这样,当你回测的开始之前MT5会弹出一个窗口,让你能直观的看到EA回测的整个过程,非常刺激。

最后,我们设定一下EA的参数: 在这一份新的代码中我们增加了一个参数,我们需要在回测界面中点击”输入“标签。如下图所示:

在这里你可以修改EA的参数(代码中input关键字定义的变量),设置好以后点击”开始“按钮就开始执行一次回测。如下图所示:

这里开始了MT5的回测执行界面,这里包含了一些信息: 1、左侧包含:回测品种的报价, 2、大部分黑色区域为报价实况 3、下方区域暂时的是交易实况,这里会包含:订单、资金等内容EA开发系列---回测


回测