jjj321 发表于 2025-9-21 11:22:28

RSI&MA策略,黄金策略,顺势而为,根据MA和RSI开发出的黄金策略,适合中长线投资者

/*策略思路:
当价格在MA之上,RSI穿越下轨时做多
当价格在MA之下,RSI穿越上轨时做空
*/

//- - - - - - - - - - 加载库&声明库对象 - - - - - - - - - -
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\OrderInfo.mqh>

CTrade trade;
CPositionInfo positionInfo;
COrderInfo orderInfo;
//- - - - - - - - - - 加载库&声明库对象 - - - - - - - - - -

//- - - - - - - - - - EA相关设置 - - - - - - - - - -
input group "设置参数"
sinput long MagicNum = 8888;                      // EA 编号
sinput int SlType = 1;                         // 止损方式(1:固定金额美金 2:账户% 3:固定手, 4:真仓总账户%)
sinput double SlParam = 30;                     // 止损依据

input group "参数优化"
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1;         // 时间级别
input int            RSIPeriod = 14;            // RSI周期
input int            RSILevel = 70;             // RSI上沿值
input int            StopLoss = 50;             // 止损点数 (0=没有止损)
input int            TakeProfit = 50;         // 止盈点数 (0=没有止盈)
input bool         CloseSignal = true;          // 出现相反信号时是否平仓

input group "均线过滤"
input bool         UseMAFilter = true;          // 是否使用均线过滤?
input int            MAPeriod = 20;             // 均线周期
input ENUM_TIMEFRAMES MATimeFrame = PERIOD_H1;         // 均线时间级别
//- - - - - - - - - - EA相关设置 - - - - - - - - - -

//- - - - - - - - - - 全局变量 - - - - - - - - - -
int barsTotal;
double point;
double initBalance;

int RsiHandle;
double RsiBuffer[];
int MAHandle;
double MABuffer[];
MqlTick currentTick;

//- - - - - - - - - - 全局变量 - - - - - - - - - -

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit() {
//检查输入参数
   if(MagicNum<0) {
      Alert("MagicNum<0");
      return INIT_PARAMETERS_INCORRECT;
   }
   if(RSIPeriod<=1) {
      Alert("RSIPeriod <=1");
      return INIT_PARAMETERS_INCORRECT;
   }
   if(RSILevel>=100 || RSILevel<=50) {
      Alert("RSILevel>=100 || RSILevel<=50");
      return INIT_PARAMETERS_INCORRECT;
   }
   if(UseMAFilter && MAPeriod<=1) {
      Alert("UseMAFilter is True & MAPeriod <=1");
      return INIT_PARAMETERS_INCORRECT;
   }
   if(StopLoss<0) {
      Alert("Stop loss<0");
      return INIT_PARAMETERS_INCORRECT;
   }
   if(TakeProfit < 0) {
      Alert("Take profit <0");
      return INIT_PARAMETERS_INCORRECT;
   }
   if(UseMAFilter && MATimeFrame<TimeFrame) {
      Alert("UseMAFiilter is True & MATimeFrame <TimeFrame");
      return INIT_PARAMETERS_INCORRECT;
   }

//句柄买例化
   RsiHandle = iRSI(_Symbol,TimeFrame,RSIPeriod,PRICE_CLOSE);
   if(RsiHandle == INVALID_HANDLE) {
      Alert((string)MagicNum,"",_Symbol,"RsiHandle创建失败!");
      return INIT_FAILED;
   }
   ArraySetAsSeries (RsiBuffer, true);

   if(UseMAFilter) {
      MAHandle = iMA(_Symbol,MATimeFrame,MAPeriod,0,MODE_SMA,PRICE_CLOSE);
      if(MAHandle == INVALID_HANDLE) {
         Alert((string)MagicNum," ",_Symbol,"MAHandle创建失败!");
         return INIT_FAILED;
      }
      ArraySetAsSeries (MABuffer, true);
   }

   Print(MagicNum," ",_Symbol,"实例化成功");

   if(!MQLInfoInteger(MQL_TESTER)) ChartSetSymbolPeriod(0,_Symbol,TimeFrame);

   initBalance = SlParam;

   Comment("MagicNum:",MagicNum," SlType:",SlType,"SlParam:", SlParam);

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTick() {
   if(!isNewBar(_Symbol, TimeFrame, barsTotal)) return;
   point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

   if(!SymbolInfoTick (_Symbol,currentTick)) {
      Print("tick数据获取异常!");
      return;
   }

   CopyBuffer(RsiHandle,0,0,3,RsiBuffer);
   if(UseMAFilter) CopyBuffer(MAHandle,0,0,2,MABuffer);

// 统计开仓数量
   int cntBuy,cntSell;
   if(!countOpenPositions(MagicNum, cntBuy,cntSell)) return ;


// 多单开仓条件
   bool bullSignal = cntBuy==0 && RsiBuffer>=(100-RSILevel) && RsiBuffer<(100-RSILevel);
   if(UseMAFilter) bullSignal = bullSignal && currentTick.ask>MABuffer;

   if(bullSignal) {
      if(CloseSignal&&cntSell>0) closePositions(MagicNum,POSITION_TYPE_SELL);

      double sl   = StopLoss==0 ? 0 : currentTick.ask - StopLoss * point;
      double tp   = TakeProfit==0 ? 0 : currentTick.ask + TakeProfit * point;
      if(!NormalizePrice(_Symbol, sl)) return;
      if(!NormalizePrice(_Symbol, tp)) return;

      double slp = SlParam;
      if(SlType==4) slp = calculateEachEaBalace(MagicNum, initBalance);//子账户百分之1
      double lotSize = calculateLots(_Symbol,currentTick.ask,sl,SlType,slp);

      trade.SetExpertMagicNumber(MagicNum);
      trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lotSize,currentTick.ask,sl,tp,__FILE__);
   }

// 空单开仓条件
   bool bearSignal = cntSell==0 && RsiBuffer<=RSILevel && RsiBuffer>RSILevel;
   if(UseMAFilter) bearSignal = bearSignal && currentTick.bid<MABuffer;
   if(bearSignal) {
      if(CloseSignal&&cntBuy>0) closePositions(MagicNum,POSITION_TYPE_BUY);

      double sl   = StopLoss==0 ? 0 : currentTick.bid + StopLoss * point;
      double tp   = TakeProfit==0 ? 0 : currentTick.bid - TakeProfit * point;
      if(!NormalizePrice(_Symbol, sl)) return;
      if(!NormalizePrice(_Symbol, tp)) return;

      double slp = SlParam;
      if(SlType==4) slp = calculateEachEaBalace(MagicNum, initBalance);//子账户百分之1
      double lotSize = calculateLots(_Symbol,currentTick.bid,sl,SlType,slp);

      trade.SetExpertMagicNumber(MagicNum);
      trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lotSize,currentTick.bid,sl,tp,__FILE__);
   }
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if(RsiHandle!=INVALID_HANDLE) IndicatorRelease(RsiHandle);
   if(MAHandle!=INVALID_HANDLE) IndicatorRelease(MAHandle);
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool isNewBar(string symbol, ENUM_TIMEFRAMES timeframe, int& bTotal) {
   int bars = iBars(symbol,timeframe);
   if(bTotal == bars) return false;
   bTotal = bars;
   return true;
}
//-----------------------------------规范化价格-----------------------------------
// 交易品种symbol和一个引用类型的价格price
bool NormalizePrice(string symbol, double &price) {
   // 获取交易品种symbol的最小价格波动大小tickSize
   double tickSize=0;
   if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE,tickSize)) {
      Print("SYMBOL_TRADE_TICK_SIZE 获取异常!");
      return false;
   }
   // 获取交易品种symbol的小数点位数digits
   int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
   // 将价格price除以tickSize取整,然后再乘以tickSize得到规范化后的价格,并使用NormalizeDouble函数将其舍入到指定的小数点位数digits。
   price = NormalizeDouble(MathRound(price/tickSize)*tickSize,digits);
   return true;
}
//-----------------------------------平仓-----------------------------------
bool closePositions(long magicNum, ENUM_POSITION_TYPE positionType) {
// 获取当前已开仓的仓位数量
   int total = PositionsTotal();
// 循环遍历所有开仓仓位。从最后一个仓位开始循环,直到第一个仓位(i=0)。
   for(int i=total-1; i>=0; i--) {
      // 获取第i个仓位的订单号,并将其赋值给positionTicket。每个订单都有唯一的订单号,该函数的参数i表示第i个订单。
      ulong positionTicket = PositionGetTicket(i);
      if(positionTicket<=0) {
         Print("获取positionTicket失败");
         return false;
      }
      // 根据订单编号选择指定持仓
      if(!PositionSelectByTicket(positionTicket)) {
         Print("选中持仓失败");
         return false;
      }
      // 获取持仓的魔术号
      long magic;
      if(!PositionGetInteger(POSITION_MAGIC,magic)) {
         Print ("获取魔术号失败");
         return false;
      }
      // 判断持仓的魔术号是否与传入的魔术号相同
      if(magic==magicNum) {
         // 获取持仓的类型(买入/卖出)
         long type;
         if(!PositionGetInteger (POSITION_TYPE,type) ) {
            Print("持仓的类型获取失败");
            return false;
         }
         // 如果要关闭的仓位类型是多单,但是当前选择的仓位类型是空单,则跳过本次循环,继续下一个仓位的处理。
         if(positionType==POSITION_TYPE_BUY && type==POSITION_TYPE_SELL) continue;
         // 如果要关闭的仓位类型是空单,但是当前选择的仓位类型是多单,则跳过本次循环,继续下一个仓位的处理。
         if(positionType==POSITION_TYPE_SELL && type==POSITION_TYPE_BUY) continue;

         // 平仓 并且检查结果是否成功。如果不成功,则提示用户
         trade.PositionClose(positionTicket);
         if(trade.ResultRetcode()!=TRADE_RETCODE_DONE) {
            Print("平仓失败 positionTicket:",(string)positionTicket," 异常:", (string)trade.ResultRetcode(), " :", trade.ResultRetcodeDescription());
            return false;
         }
      }
   }
   return true;
}

//-----------------------------------计算仓位-----------------------------------
// symbol(品种名称), entryPrice(入场价格), slPrice(止损价格), slType(止损类型), slParam(止损参数)
double calculateLots(string symbol, double entryPrice, double slPrice, int slType, double slParam) {

   double slMoney = 0;
   if(slType == 1) slMoney = slParam; //1 固定止损金额, eg:100U
   else if(slType == 2) slMoney = AccountInfoDouble(ACCOUNT_BALANCE) * slParam / 100; //2 账户余额百分比, eg:1%
   else if(slType == 3) return slParam; //3 固定手数, eg:0.1
   else if(slType == 4) slMoney = slParam; //4 子账户百分比, eg:1%
   //计算止损距离
   int digits = (int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);//小数位数
   double slDistance = NormalizeDouble(MathAbs(entryPrice - slPrice), digits) / point;
   if(slDistance <= 0) return 0; //zero divide

//获取点值 每次尝试间隔1秒。如果尝试三次后仍然无法获取到交易点值,则打印异常信息,并返回0。
   double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
   if(tickValue == 0) {
      Sleep(1000);
      tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
      Print("SYMBOL_TRADE_TICK_VALUE异常第一次尝试:"+(string)tickValue);
      if(tickValue == 0) {
         Sleep(1000);
         tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
         Print("SYMBOL_TRADE_TICK_VALUE异常第二次尝试:"+(string)tickValue);
      }
      if(tickValue == 0) {
         Sleep(1000);
         tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
         Print("SYMBOL_TRADE_TICK_VALUE异常第三次尝试:"+(string)tickValue);
      }
      if(tickValue == 0) {
         Print("获取SYMBOL_TRADE_TICK_VALUE异常:",symbol,"-",entryPrice,"-",slPrice,"-",slType,"-",slParam);
         Print("获取SYMBOL_TRADE_TICK_VALUE异常2:",point,"-",slDistance,"-",tickValue);
         return 0;
      }
   }

// 计算手数
   double lot = NormalizeDouble(slMoney / slDistance / tickValue, 2);//风控 / 止损 / 点值

// 规范手数 确保手数符合品种的步长要求
   double lotstep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   lot = MathRound(lot / lotstep) * lotstep;

   if(lot < SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN)) lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   else if(lot >= SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX)) {
      Print("手数异常:",symbol,"-",entryPrice,"-",slPrice,"-",slType,"-",slParam);
      Print("手数异常2:",point,"-",slDistance,"-",tickValue,"-",lot,"-",lotstep);
      return 0;
   }

   return lot;
}

//-----------------------------------计算子账户余额-----------------------------------
// magicNum(用于筛选交易单的魔术数字)和initBa(初始余额)
double calculateEachEaBalace(long magicNum, double initBa) {
   double countProfit = 0, countCommission = 0,countSwap = 0, countMoney = 0;
   // 选择历史交易记录
   HistorySelect(0,TimeCurrent());
   // 获取历史交易总数
   uint deals=HistoryDealsTotal();
   // 遍历历史交易记录
   for(uint i=0;i<deals;i++) {
      // 获取特定索引处交易的订单号
      ulong ticket = HistoryDealGetTicket(i);
      if(ticket<=0) continue;

      long magic = HistoryDealGetInteger(ticket,DEAL_MAGIC);
      string symbol = HistoryDealGetString(ticket,DEAL_SYMBOL);
      double profit = HistoryDealGetDouble(ticket,DEAL_PROFIT);
      double commission = HistoryDealGetDouble(ticket,DEAL_COMMISSION);
      double swap = HistoryDealGetDouble(ticket,DEAL_SWAP);

      // 累加全部的利润 手续费 库存费 得到该ea 的 绩效
      if(magic==magicNum) {
         countProfit+=profit;
         countCommission+=commission;
         countSwap+=swap;

         countMoney = countProfit + countCommission + countSwap;
      }
   }

   // 目前账户的余额是多少
   double currentBalance = countMoney + initBa;
   return currentBalance / 100;
}

//-----------------------------------统计该ea的多单和空单开仓数量-----------------------------------
// magicNum是要统计的特定魔术号
// countBuy和countSell是输出参数,分别用于存储多单和空单的开仓数量。在函数执行完毕后,它们会被更新。
// &countBuy和&countSell是引用参数。它们的作用是将函数内部的计算结果传递回函数外部。
// 值传递
bool countOpenPositions(long magicNum, int &countBuy,int &countSell) {
   // 初始化count为0,用于统计多/空单开仓数量。
   countBuy =0;
   countSell=0;
   // 获取当前已开仓的仓位数量
   int total =PositionsTotal();
   // 循环遍历所有开仓仓位。从最后一个仓位开始循环,直到第一个仓位(i=0)。
   for(int i=total-1; i>=0; i--) {
      // 获取第i个仓位的订单号,并将其赋值给positionTicket。每个订单都有唯一的订单号,该函数的参数i表示第i个订单。
      ulong positionTicket = PositionGetTicket(i);
      if(positionTicket<=0) {
         Print("获取positionTicket失败");
         return false;
      }
      // 根据订单编号选择指定持仓
      if(!PositionSelectByTicket(positionTicket)) {
         Print("选中持仓失败");
         return false;
      }
      // 获取持仓的魔术号
      long magic;
      if(!PositionGetInteger(POSITION_MAGIC,magic)) {
         Print ("获取魔术号失败");
         return false;
      }
      // 判断持仓的魔术号是否与传入的魔术号相同
      if(magic==magicNum) {
         // 获取持仓的类型(买入/卖出)
         long type;
         if(!PositionGetInteger (POSITION_TYPE,type)) {
            Print("持仓的类型获取失败");
            return false;
         }
         // 买入持仓
         if(type==POSITION_TYPE_BUY) countBuy++;
         // 卖出持仓
         if(type==POSITION_TYPE_SELL) countSell++;
      }
   }
   return true;
}
//+------------------------------------------------------------------+

jjj321 发表于 2025-9-21 11:23:30

有用的话,给博主打赏一下[调皮]

JH0924 发表于 2025-9-23 03:36:08

bucuo 支持一下

xinhua123 发表于 2025-10-2 16:38:41

很好源码谢谢

xiaoxie 发表于 2025-11-2 20:33:41

感谢分享,不错

yuwentao4761 发表于 2025-11-2 23:27:05

学习一下,希望有用!!

Lune 发表于 2025-11-3 21:20:26

学习学习感谢

pass91336559 发表于 2025-11-6 07:43:12

黄金策略,顺势而为,

yuwentao4761 发表于 2025-11-6 16:03:57

[偷笑] 怎么使用?加载到 H1的周期上吗?

btffff 发表于 2025-12-17 20:34:29

感谢分享,有很多收获

sunlei 发表于 2026-2-6 11:49:17

移动止盈有吗,楼主

zc071313 发表于 2026-2-12 18:09:02

4390876545678

abest860 发表于 2026-2-26 22:15:25

谢谢楼主的分享精神 ,收藏了

xqs1314 发表于 2026-3-1 23:34:19

感谢分享,有很多收获!

sanwich 发表于 2026-3-2 12:29:30

多好的策略,遇到单边就over

lovechenzhen 发表于 5 天前

谢谢分享精神 ,收藏了![微笑]
页: [1]
查看完整版本: RSI&MA策略,黄金策略,顺势而为,根据MA和RSI开发出的黄金策略,适合中长线投资者