EA开发系列---回测
对于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”。如下图所示:
![](https://pic2.zhimg.com/80/v2-053c45e0ab33daad0ef80a97c7bd3d61_720w.webp)
首先,我们进行一些基础的设置: 1、”专家“:下拉列表中选择FirstEA,如果完成了编译,这里就能找到 2、”交易品种“:这个下拉列表中你可以选择平台支持的交易品种,这里我们选择EURUSD(欧美)这个外汇品种。后面选择希望在哪个时间周期下进行回测,这里选择的是”H4“(4小时周期) 3、”日期“:指回测的时间周期,为了保证EA的有效性,可以选择尽可能长的回测周期,这里选择2018年1月1日到目前(大约3年的数据)进行回测 4、”入金“:这里设置的是进行测试的本金以及杠杆,这里设定的是:10000美金,1:500杠杆 以上是基础的设置,其他的不再赘述。这里建议你勾选”显示图表、指标和交易的可视化模式“,这样,当你回测的开始之前MT5会弹出一个窗口,让你能直观的看到EA回测的整个过程,非常刺激。
最后,我们设定一下EA的参数: 在这一份新的代码中我们增加了一个参数,我们需要在回测界面中点击”输入“标签。如下图所示:
![](https://pic2.zhimg.com/80/v2-81b5022ca688c113e7b6b540d0baa1bd_720w.webp)
在这里你可以修改EA的参数(代码中input关键字定义的变量),设置好以后点击”开始“按钮就开始执行一次回测。如下图所示:
![](https://pic3.zhimg.com/80/v2-c22769a36f8ae2daff2b441676d311c2_720w.webp)
这里开始了MT5的回测执行界面,这里包含了一些信息: 1、左侧包含:回测品种的报价, 2、大部分黑色区域为报价实况 3、下方区域暂时的是交易实况,这里会包含:订单、资金等内容EA开发系列---回测