作为MT4软件自带的EA之一,这个EA真的很经典,原因如下:
一,作为新手教学EA,代码很简单,包括仓位计算,开仓,平仓几个基础部分,修改很容易;
二,包含复利开仓模块,可以直接复制套用到其它EA;
三,包含亏损减仓模块,这其实是最宝贝的部分,只可惜年少不知风控好。
我直接把EA源码进行了备注,对新手很友好,以后我会以这个EA为基础,逐步展开EA的编程与实战的内容分享,欢迎关注。
先上我进行了标注的源码,方便大家进行学习,然后我会把这个EA的使用方法和局限性展开讨论,对源码比较熟悉的可以直接跳过。
--------源码分界线--------
//+------------------------------------------------------------------+
//| Moving Average.mq4 |
//| Copyright 2000-2026, MetaQuotes Ltd. |
//| http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "J的量化实验室" //标注EA的作者或者版权,加载EA时可以在窗口看到
#property link "VX:jiayeeslab" //标注EA的作者或者版权,加载EA时可以在窗口看到
#property description "Moving Average sample expert advisor" // 移动平均线示例智能交易系统
#define MAGICMA 20131111 // 魔术码,识别EA订单的主要方法,每个EA对应一个魔术码
//--- 输入参数
input double Lots =0.1; // 固定交易手数
input double MaximumRisk =0.02; // 最大风险比例(按账户净值的百分比计算手数)
input double DecreaseFactor=3; // 减仓因子(连续亏损后减少手数)
input int MovingPeriod =12; // 移动平均线周期
input int MovingShift =6; // 移动平均线平移
//+------------------------------------------------------------------+
//| 计算当前持仓订单数量 |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol) // 计算指定交易品种当前持仓订单数量
{
int buys=0,sells=0; // 初始化多单和空单计数
//---
for(int i=0;i<OrdersTotal();i++) // 遍历所有订单
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break; // 选择订单,如果失败则跳出循环
if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA) // 判断是否为本EA的本品种订单
{
if(OrderType()==OP_BUY) buys++; // 如果是多单,多单计数加1
if(OrderType()==OP_SELL) sells++; // 如果是空单,空单计数加1
}
}
//--- 返回订单数量(正数表示多单,负数表示空单,0表示无持仓)
if(buys>0) return(buys);
else return(-sells);
}
//+------------------------------------------------------------------+
//| 计算最优手数 |
//+------------------------------------------------------------------+
double LotsOptimized() // 计算优化后的交易手数
{
double lot=Lots; // 初始化为固定手数
int orders=HistoryTotal(); // 获取历史订单总数
int losses=0; // 连续亏损订单计数
//--- 基于风险计算手数
lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1); // 按账户可用保证金的百分比计算手数
//--- 计算连续亏损次数
if(DecreaseFactor>0) // 如果减仓因子大于0
{
for(int i=orders-1;i>=0;i--) // 从最近的订单开始向前遍历历史订单
{
if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false) // 选择历史订单
{
Print("Error in history!"); // 如果选择失败,打印错误
break;
}
if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL) // 跳过非本品种订单或挂单
continue;
//---
if(OrderProfit()>0) break; // 如果遇到盈利订单,停止计数(连续亏损被打断)
if(OrderProfit()<0) losses++; // 如果是亏损订单,亏损计数加1
}
if(losses>1) // 如果有连续亏损
lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1); // 按亏损次数和减仓因子减少手数
}
//--- 返回最终手数(确保不低于0.1手)
if(lot<0.1) lot=0.1;
return(lot);
}
//+------------------------------------------------------------------+
//| 检查开仓条件 |
//+------------------------------------------------------------------+
void CheckForOpen() // 检查是否满足开仓条件
{
double ma; // 移动平均线值
int res; // 订单发送结果
//--- 只在新K线的第一个跳动点交易
if(Volume[0]>1) return; // 如果当前K线的成交量大于1,说明不是新K线,返回
//--- 获取移动平均线值
ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0); // 计算当前K线的移动平均线值
//--- 卖出条件:前一根K线开盘在均线上方,收盘在均线下方(形成向下穿越)
if(Open[1]>ma && Close[1]<ma)
{
res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red); // 发送卖出订单
return;
}
//--- 买入条件:前一根K线开盘在均线下方,收盘在均线上方(形成向上穿越)
if(Open[1]<ma && Close[1]>ma)
{
res=OrderSend(Symbol(),OP_BUY,LotsOptimized(),Ask,3,0,0,"",MAGICMA,0,Blue); // 发送买入订单
return;
}
//---
}
//+------------------------------------------------------------------+
//| 检查平仓条件 |
//+------------------------------------------------------------------+
void CheckForClose() // 检查是否满足平仓条件
{
double ma; // 移动平均线值
//--- 只在新K线的第一个跳动点交易
if(Volume[0]>1) return; // 如果当前K线的成交量大于1,说明不是新K线,返回
//--- 获取移动平均线值
ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0); // 计算当前K线的移动平均线值
//---
for(int i=0;i<OrdersTotal();i++) // 遍历所有订单
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break; // 选择订单
if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue; // 跳过非本EA的本品种订单
//--- 检查订单类型
if(OrderType()==OP_BUY) // 如果是多单
{
if(Open[1]>ma && Close[1]<ma) // 如果价格向下穿越均线
{
if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,White)) // 平掉多单
Print("OrderClose error ",GetLastError()); // 如果平仓失败,打印错误
}
break; // 找到本EA的订单后就跳出循环(此EA假设只持有一个方向的单子)
}
if(OrderType()==OP_SELL) // 如果是空单
{
if(Open[1]<ma && Close[1]>ma) // 如果价格向上穿越均线
{
if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White)) // 平掉空单
Print("OrderClose error ",GetLastError()); // 如果平仓失败,打印错误
}
break; // 找到本EA的订单后就跳出循环
}
}
//---
}
//+------------------------------------------------------------------+
//| 每次价格跳动时执行的主函数 |
//+------------------------------------------------------------------+
void OnTick() // MT4的每个跳动点都会调用此函数
{
//--- 检查历史数据和交易是否允许
if(Bars<100 || IsTradeAllowed()==false) // 如果K线数量不足或交易被禁止
return; // 直接返回
//--- 计算当前品种的持仓订单数量
if(CalculateCurrentOrders(Symbol())==0) CheckForOpen(); // 如果没有持仓,检查开仓条件
else CheckForClose(); // 如果已有持仓,检查平仓条件
//---
}
//+------------------------------------------------------------------+
------源码分界线-------
这个源码我只进行了备注,没有对代码进行过修改。
接下来我把EA源码进行拆解,大家有问题的可以评论或者私信。
------EA参数说明------
先从EA的外设参数开始说。
Lots,就是初始仓位,这个EA年代久远,那个时候很多平台最小手数都只能下0.1手,所以源码设计的最小仓位是0.1手,这里输入0.01手也不起作用,EA最小仓位只会是0.1手。如何修改,我们稍后再说。
MaximumRisk,单笔交易最大风险,按照源码的意思,是通过账户可用保证金(AccountFreeMargin)来进行计算,这里以后可以修改为按照账户余额或者账户净值进行计算。
DecreaseFactor,减仓因子,按照源码的公式,可以实现连续亏损次数越多,开仓手数越少的效果。配合上面的复利模块加上这个减仓模块,就能实现我们常说的“赢冲输缩”的逻辑!!!当然,这里面还有可以优化的空间。
MovingPeriod,均线周期的参数,10就代表10日均线,20就代表20日均线。源码里面缺少对均线模式的选择,默认SMA(简单移动均线),后期我们可以加上均线模式的选择,来实现不同的效果。
MovingShift,均线的平移,6就代表均线向右边平移6个K线的位置,可以过滤掉一些假信号。我个人对均线平移默认0,如果以后有必要再研究看看。
------EA逻辑说明------
这个EA逻辑很简单,就是一个价格突破均线的交易策略。
开仓逻辑:
当前一根K线的开盘价OPEN位于均线下方,收盘价CLOSE位于均线上方,就开多单;空单相反。
平仓逻辑:
当前一根K线的开盘价OPEN位于均线上方,收盘价CLOSE位于均线下方,就平掉多单;空单相反。
------EA的优势与缺陷------
1.这个EA的最大好处就是,一旦趋势确立,在不人为干预的情况下,能拿到很大一波趋势,理论上图表周期越大(比如日线),均线周期越大(比如100日均线),能拿多少利润,取决于趋势、图表周期,均线周期这三个变量。比如下图,使用H4图表,顺利抓到了比较大的波段,而且一个接近20年前的EA还能盈利!
2.带有复利和亏损减仓模块,可以实现“赢冲输缩”!
3.根据图表,均线等变量可以实现基础的差异化交易。
------EA的弊端------
1.最小只能下0.1手,且因为魔术号不能修改,每次只能做一个品种和一个图表;
2.每次只能持有一个方向的仓位,会错过一些交易信号;
3.没有初始止损,止盈以及移动止损等,风险管理和盈利能力有限;
4.如果价格在均线上下震荡,会频繁亏损;
5.还有没有其它?欢迎留言讨论
今天的分享先到这里,下次我们将讨论如何解决上面的问题,让这个EA可以实盘交易。
欢迎大家留言或私信讨论,我会根据大家的需求来调整分享内容。 |