找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
首页论坛EAHub 外汇论坛
查看: 0|回复: 0

【经典单均线策略】免费源码,零基础学EA编程,读懂经典移动平均线策略源码,评论区持续更新

[复制链接]

6608

主题

54

回帖

4871

积分

管理员

积分
4871
发表于 2026-8-16 15:44:59 | 显示全部楼层 |阅读模式
作为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可以实盘交易。

欢迎大家留言或私信讨论,我会根据大家的需求来调整分享内容。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|EAHub外汇论坛

GMT+8, 2026-8-25 07:37 , Processed in 0.107433 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表