Skip to content
cpp
#property strict
#include <Trade/Trade.mqh>

CTrade trade;
CPositionInfo posInfo;
COrderInfo orderInfo;
string tradeComment = __FILE__; // 将文件名作为注释

// sinput 和 input的区别  sinput是不可以做参数优化的参数
input group "设置参数";
sinput int MagicNum = 12345; // EA的唯一编号
sinput int SlType = 1; // 止损方式(1:固定金额{100}美金 2:账户{1}% 3:固定{0.1}手
                       // 4:按照子账户{10000}1%止损)
sinput int SlParam =100; // 止损依据(根据上方【止损方式】的不同,所代表的含义也不同)

input group "参数优化";
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // 时间周期
input int RSIPeriod = 14;                    // RSI 周期
input int RSILevel = 70;                     // RSI 上沿值

input group "止盈止损";
input double StopLoss = 2000;   // 止盈点数(0没有止损)
input double TakeProfit = 2000; // 止损点数(0没有止盈)
input bool CloseSignal = false; // 出现反向信号是否平仓, 如果设置为false
                                // 就不平仓 直接开反向仓 多空并存

input group "均线";
input bool UseMAFilter = false;                // 是否使用均线过滤器
input int MAPeriod = 50;                       // 均线周期
input ENUM_TIMEFRAMES MATimeFrame = PERIOD_H1; // 均线时间周期
int barsTotal;

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

int initBalance;
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("StopLoss<0");
    return INIT_PARAMETERS_INCORRECT;
  }
  if (TakeProfit < 0) {
    Alert("TakeProfit<0");
    return INIT_PARAMETERS_INCORRECT;
  }
  // 通常是看大做小
  if (UseMAFilter && MATimeFrame < TimeFrame) {
    Alert("UseMAFilter 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_EMA, PRICE_CLOSE);
    if (MAHandle == INVALID_HANDLE) {
      Alert((string)MagicNum + " ", _Symbol, " MAHandle 创建失败");
      return INIT_FAILED;
    }
  }
  Print(MagicNum, " ", _Symbol, "实例化成功");
  // 当用户没有打开参数对应的图表周期,自动帮他打开对应周期
  if (!MQLInfoInteger(MQL_TESTER)) {
    ChartSetSymbolPeriod(0, _Symbol, TimeFrame);
  }
  initBalance = SlParam;
  return INIT_SUCCEEDED;
}
// 节省内存后 买VPS就可以买小一点
void OnDeinit(const int reason) {
  if (RsiHandle != INVALID_HANDLE) {
    IndicatorRelease(RsiHandle);
  }
  if (MAHandle != INVALID_HANDLE) {
    IndicatorRelease(MAHandle);
  }
}
void OnTick() {

  if (!isNewBar(_Symbol, TimeFrame, barsTotal))
    return;

  if (!SymbolInfoTick(_Symbol, currentTick)) {
    Print("tick数据获取异常");
  }
  CopyBuffer(RsiHandle, 0, 0, 3, RsiBuffer);
  if (UseMAFilter) {
    CopyBuffer(MAHandle, 0, 0, 2, MABuffer);
  }
  int cntBuy, cntSell;
  if (!countOpenPositions(_Symbol, cntBuy, cntSell))
    return;

  if (cntBuy > 0 || cntSell > 0)
    return;
  // 买入价
  double ask = currentTick.ask;
  // 卖出价
  double bid = currentTick.bid;
  
  // RSILevel 表示上轨  (100 - RSILevel) 表示下轨
  bool bullSignal = cntBuy == 0 && RsiBuffer[2] >= (100 - RSILevel) &&
                    RsiBuffer[1] < (100 - RSILevel);
  if (UseMAFilter) {
    bullSignal = bullSignal && ask > MABuffer[1];
  }


  if (bullSignal) {
    if (CloseSignal) {
      closePositions(MagicNum, POSITION_TYPE_SELL);
    }
    double sl = StopLoss == 0 ? 0 : ask - StopLoss * _Point; // 止损价
    double tp = TakeProfit == 0 ? 0 : ask + TakeProfit * _Point; // 止盈价

    if (!NormalizePrice(_Symbol, sl))
      return;
    if (!NormalizePrice(_Symbol, tp))
      return;
    double slp = SlParam;
    if (SlType == 4) {
      slp = calculateEachEaBalance(MagicNum, initBalance); // 子账户百分之一
    }
    double lotSize = calcLots(_Symbol, ask, sl, SlType, slp);
    trade.SetExpertMagicNumber(MagicNum);
    // 交易品种,订单类型,手数,当前价,止损价,止盈价,注释
    trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, lotSize, currentTick.ask, sl,
                       tp, __FILE__);
  }

  bool bearSignal =
      cntSell == 0 && RsiBuffer[2] <= (RSILevel) && RsiBuffer[1] > (RSILevel);
  if (UseMAFilter) {
    bearSignal = bearSignal && bid < MABuffer[1];
  }

  if (bearSignal) {
    if (CloseSignal) {
      closePositions(MagicNum, POSITION_TYPE_BUY);
    }
    double sl = StopLoss == 0 ? 0 : bid + StopLoss * _Point;
    double tp = TakeProfit == 0 ? 0 : bid - TakeProfit * _Point;
    if (!NormalizePrice(_Symbol, sl))
      return;
    if (!NormalizePrice(_Symbol, tp))
      return;
    double slp = SlParam;
    if (SlType == 4) {
      slp = calculateEachEaBalance(MagicNum, initBalance); // 子账户百分之一
    }
    double lotSize = calcLots(_Symbol, bid, sl, SlType, slp);
    trade.SetExpertMagicNumber(MagicNum);
    trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, lotSize, currentTick.bid, sl,
                       tp, __FILE__);
  }
}
// 平仓
bool closePositions(long magicNum, int posType) {
  int total = PositionsTotal();
  for (int i = total - 1; i >= 0; i--) {
    ulong ticket = PositionGetTicket(i);
    if (ticket < 0) {
      Print("获取ticket失败");
      return false;
    }
    if (!PositionSelectByTicket(ticket)) {
      Print("选中ticket失败");
      return false;
    }
    long magic;
    if (PositionGetInteger(POSITION_MAGIC, magic)) {
      Print("获取magic失败");
      return false;
    }
    if (magicNum == magic) {
      long type;
      if (!PositionGetInteger(POSITION_TYPE, type)) {
        Print("获取持仓类型失败");
        return false;
      }
      //如果要平的仓位是多单,但是当前选择的仓位类型是空单,就跳出本次循环,继续下一个仓位的处理
      if (posType == POSITION_TYPE_BUY && type == POSITION_TYPE_SELL)
        continue;
      //如果要平的仓位是空单,但是当前选择的仓位类型是多单,就跳出本次循环,继续下一个仓位的处理  
      if (posType == POSITION_TYPE_SELL && type == POSITION_TYPE_BUY)
        continue;
      trade.PositionClose(ticket);
      if (trade.ResultRetcode() != TRADE_RETCODE_DONE) {
        Print("平仓失败", ticket, " 异常:", (string)trade.ResultRetcode(), " :",
              trade.ResultRetcodeDescription());
      }
    }
  }
  return true;
}

// &cntBuy 是引用参数, 他们的作用是将函数内部的计算结果传递回函数外部
bool countOpenPositions(string symbol, int &cntBuy, int &cntSell) {
  cntBuy = 0;
  cntSell = 0;
  int total = PositionsTotal();
  for (int i = total - 1; i >= 0; i--) {
    ulong ticket = PositionGetTicket(i);
    if (ticket < 0) {
      Print("获取ticket失败");
      return false;
    }
    if (!PositionSelectByTicket(ticket)) {
      Print("选中ticket失败");
      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) {
        cntBuy++;
      } else if (type == POSITION_TYPE_SELL) {
        cntSell++;
      }
    }
  }
  return true;
}
/*
 将品种的价格规范化为最小变动单位的整数倍
*/
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 isNewBar(string symbol, ENUM_TIMEFRAMES tf, int &bTotal) {
  int bars = iBars(_Symbol, tf);
  if (bars == bTotal)
    return false;
  bTotal = bars;
  return true;
}

/*
计算子账户余额
magicNum 子账户魔术号, initBa 初始资金
*/ 
double calculateEachEaBalance(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;
    }
  }
  // 目前子账户余额的1%
  double currentBalance = countMoney + initBa;
  return currentBalance / 100;
}
/*
symbol 品种名称 ,entryPrice 入场价格, sl 止损价格, slType 止损类型, slParam 止损参数, tp 止盈价格, tpType 止盈类型, tpParam 止盈参数
*/
double calcLots(string symbol, double entryPrice, double slPrice, double slType,
                double slParam) {
  double slMoney = 0;
  if (slType == 1) // 1:固定金额{100}美金
    slMoney = slParam;
  else if (slType == 2) { // 2:账户{1}%
    slMoney = AccountInfoDouble(ACCOUNT_BALANCE) * slParam / 100;
  } else if (slType == 3) {
    return slParam; // 3:固定{0.1}手
  } else if (slType == 4) {
    slMoney = slParam; // 4:子账户总账户{1}%
  }
  // 当前品种小数位数
  int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
  // 当前品种的最小价格变动单位(即一个点的大小)
  double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
  // 计算止损距离
  double slDistance = NormalizeDouble(MathAbs(entryPrice - slPrice), digits) / point;
  if (slDistance <= 0)
    return 0;
  // IC平台获取 tickval有时会是0 获取点值 每次尝试间隔1秒
  // 如果尝试3次后仍然无法获取到交易点值,打印异常 ,返回0
  // 价格跳动一个点价值多少
  double tickVal = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
  if (tickVal == 0) {
    Sleep(1000);
    tickVal = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
    Print("SYMBOL_TRADE_TICK_VALUE 异常第一次尝试:" + (string)tickVal);
    if (tickVal == 0) {
      Sleep(1000);
      tickVal = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
      Print("SYMBOL_TRADE_TICK_VALUE 异常第二次尝试:" + (string)tickVal);
    }
    if (tickVal == 0) {
      Sleep(1000);
      tickVal = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
      Print("SYMBOL_TRADE_TICK_VALUE 异常第三次尝试:" + (string)tickVal);
    }
    if (tickVal == 0) {
      Print("SYMBOL_TRADE_TICK_VALUE 异常:" + symbol, "-", entryPrice, "-", slPrice, "-",
            slType, "-", slParam, "-", slMoney, "-", tickVal);
      Print("SYMBOL_TRADE_TICK_VALUE 异常2:" + (string)point, "-", slDistance,
            "-", tickVal);
      return 0;
    }
    return 0;
  }
  // 最小0.01手 没有0.001所以 用2够了
  double lot =
      NormalizeDouble(slMoney / slDistance / tickVal, 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, "-", tickVal, "-", lot, "-",
          lotStep);
    lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
    return 0;
  }
  return lot;
}