Skip to content

一目均衡云

一目均衡云指标解析

指标定义与特点

一目均衡云(Ichimoku Kinko Hyo)是综合性技术分析指标,旨在让交易者“一眼看清市场”,涵盖趋势方向、支撑阻力、进出场信号等多维度信息,无需依赖其他指标即可构建完整交易系统。

指标构成(五条线)

  • 转折线(Tenkan-sen,快线):过去9根K线最高价与最低价的平均值,反映短期趋势,敏感性高。
  • 基线(Kijun-sen,慢线):过去26根K线最高价与最低价的平均值,代表中期潜在支撑/阻力。
  • 先行线A(Span A,云上线):快线与慢线的平均值,向前推移26根K线,构成云的上沿,代表潜在支撑/阻力。
  • 先行线B(Span B,云下线):过去52根K线最高价与最低价的平均值,向前推移26根K线,构成云的下沿。
  • 滞后线(Chikou Span):当前收盘价向后推移26根K线,用于辅助判断趋势(本次策略未使用)。

可视化特征

  • 云上线(黄色)与云下线(白色)构成“云带”,反映中期支撑/阻力区间。
  • 快线(红色)与慢线(蓝色)类似“快慢均线”,交叉信号用于判断短期趋势变化。

交易系统设计(以多单为例,空单条件反向)

核心逻辑

  • 进场信号:快线向上穿过慢线(黄金交叉),且满足趋势与噪声滤网条件。
  • 出场信号:快线向下穿过慢线(死亡交叉)。

滤网条件

  • 趋势滤网:产生交叉的K线收盘价位于云上线之上(确认价格处于云上强势区域)。
  • 市场噪声滤网:使用效率比指标,要求噪声值 >0.55(低噪声环境,适合趋势策略)。

止损与止盈

  • 止损:设于云下线(Span B)下方,取云上线与云下线中的较远值。
  • 止盈:不设固定止盈,仅通过死亡交叉出场或触发止损。

特殊说明

  • 快线与慢线交叉可能在多根K线中出现“黏连”(数值相同),需通过代码向前遍历确认有效交叉信号。

策略图示

代码实现要点

cpp
#property strict
#include <Indicators/Trend.mqh>
#include <Trade/Trade.mqh>

CTrade trade;
CPositionInfo positionInfo;
COrderInfo orderInfo;
string tradeComment = __FILE__;
// 不可以做参数优化的参数
input group "固定和输入通用部分";
sinput int MagicNum = 12345; // 魔术编号
sinput int SlType = 1; // 止损方式(1:固定金额{100}美金 2:账户{1}% 3:固定{0.1}手
                       // 4:子账户总账户{1}%)
sinput int SlParam =
    100; // 止损依据(根据上方【止损方式】的不同,所代表的含义也不同)

input group "参数优化";
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // 时间周期
input int ICOKuaiXian = 9;                                          // 快线
input int ICOManXian = 26;                                          // 慢线

input int efficiencyRatio = 10; // 效率滤网

int barsTotal;

int handle;
double kuaixianBuffer[];
double manxianBuffer[];

int initBalance;
double point;
MqlTick currentTick;

int efficiencyRatioHandle;
double efficiencyRatioBuffer[];

CiIchimoku *IchimokuIndicator;
int OnInit() {
  ResetLastError();
  IchimokuIndicator = new CiIchimoku();
  if (!IchimokuIndicator.Create(_Symbol, TimeFrame, ICOKuaiXian, ICOManXian,
                                ICOManXian * 2)) {
    Alert((string)MagicNum + " ", _Symbol, " Ichimoku 创建指标 失败");
    return INIT_FAILED;
  }

  efficiencyRatioHandle = iCustom(_Symbol, TimeFrame, "Efficiency Ratio",
                                  efficiencyRatio, PRICE_CLOSE, 0.55);
  handle = IchimokuIndicator.Handle();
  if (handle == INVALID_HANDLE) {
    Alert((string)MagicNum + " ", _Symbol, " handle 创建失败");
    return INIT_FAILED;
  }

  ArraySetAsSeries(efficiencyRatioBuffer, true);

  Print(MagicNum, " ", _Symbol, "实例化成功");
  // 当用户没有打开参数对应的图表周期,自动帮他打开对应周期
  if (MQLInfoInteger(MQL_TESTER)) {
    ChartSetInteger(0, CHART_SHOW_GRID, false);
  } else {
    ChartSetSymbolPeriod(0, _Symbol, TimeFrame);
  }
  initBalance = SlParam;
  Comment("MagicNum:", MagicNum, " SlType: ", SlType, " SlParam: ", SlParam);
   ChartIndicatorAdd(ChartID(),0,handle);
   ChartIndicatorAdd(ChartID(),0,efficiencyRatioHandle);
  // trade.SetExpertMagicNumber(MagicNum);
  return INIT_SUCCEEDED;
}
// 节省内存后 买VPS就可以买小一点
void OnDeinit(const int reason) {
  if (handle != INVALID_HANDLE) {
    IndicatorRelease(handle);
  }
}
void OnTick() {

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

  if(isEnableTimezone()) return;  // 点差过大0 不交易

  point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
  int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);

  if (!SymbolInfoTick(_Symbol, currentTick)) {
    Print("tick数据获取异常");
  }
  CopyBuffer(efficiencyRatioHandle, 0, 0, 2, efficiencyRatioBuffer);
  IchimokuIndicator.Refresh(-1);

  double yunShang = IchimokuIndicator.SenkouSpanA(1);
  double yunXia = IchimokuIndicator.SenkouSpanB(1);
  double kuaiXian = IchimokuIndicator.TenkanSen(1);
  double manXian = IchimokuIndicator.KijunSen(1);

  int cntBuy, cntSell;
  if (!countOpenPositions(_Symbol, cntBuy, cntSell))
    return;

  double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
  double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
  double closePrice = iClose(_Symbol, TimeFrame, 1);
  double bullSignal = cntBuy == 0 && KuaixianManxianCrossBuy() &&
                      MathAbs(efficiencyRatioBuffer[1]) > 0.55 &&
                      closePrice >= MathMax(yunShang, yunXia);
  if (bullSignal) {

    double sl = MathMin(yunShang, yunXia);
    double tp = 0;
    double slPoint = NormalizeDouble(MathAbs(ask - sl), digits);

    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, "ico BUY");
  }

  bool bearSignal = cntSell == 0 && KuaixianManxianCrossSell() &&
                    MathAbs(efficiencyRatioBuffer[1]) > 0.55 &&
                    closePrice <= MathMin(yunShang, yunXia);

  if (bearSignal) {
    double sl = MathMax(yunShang, yunXia);
    double tp = 0;
    double slPoint = NormalizeDouble(MathAbs(bid - sl), digits);
    double slp = SlParam;
    if (SlType == 4) {
      slp = calculateEachEaBalance(MagicNum, initBalance); // 子账户百分之一
    }
    double lotSize = calcLots(_Symbol, bid, sl, SlType, slp);
    trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, lotSize, currentTick.bid, sl,
                       tp, "ico SELL");
  }
  // 出场
  if (!countOpenPositions(_Symbol, cntBuy, cntSell))
    return;
  if (cntBuy > 0 && KuaixianManxianCrossSell()) {
    closePositions(MagicNum, POSITION_TYPE_BUY);
  }
  if (cntSell > 0 && KuaixianManxianCrossBuy()) {
    closePositions(MagicNum, POSITION_TYPE_SELL);
  }
}

bool KuaixianManxianCrossBuy() {
  IchimokuIndicator.Refresh(-1);
  int currBar = 1;
  double tenkanSen = IchimokuIndicator.TenkanSen(currBar);
  double kijunSen = IchimokuIndicator.KijunSen(currBar);

  if (NormalizeDouble(tenkanSen, 6) <= NormalizeDouble(kijunSen, 6))
    return false;

  while (true) {
    currBar++;
    tenkanSen = IchimokuIndicator.TenkanSen(currBar);
    kijunSen = IchimokuIndicator.KijunSen(currBar);
    if (NormalizeDouble(tenkanSen, 6) > NormalizeDouble(kijunSen, 6))
      return false;
    if (NormalizeDouble(tenkanSen, 6) < NormalizeDouble(kijunSen, 6))
      return true;
  }
}

bool KuaixianManxianCrossSell() {
  IchimokuIndicator.Refresh(-1);
  int currBar = 1;
  double tenkanSen = IchimokuIndicator.TenkanSen(currBar);
  double kijunSen = IchimokuIndicator.KijunSen(currBar);
  if (NormalizeDouble(tenkanSen, 6) >= NormalizeDouble(kijunSen, 6))
    return false;
  while (true) {
    currBar++;
    tenkanSen = IchimokuIndicator.TenkanSen(currBar);
    kijunSen = IchimokuIndicator.KijunSen(currBar);
    if (NormalizeDouble(tenkanSen, 6) < NormalizeDouble(kijunSen, 6))
      return false;
    if (NormalizeDouble(tenkanSen, 6) > NormalizeDouble(kijunSen, 6))
      return true;
  }
}

bool closePositions(long magicNum, int positionType) {
  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 (positionType == POSITION_TYPE_BUY && type == POSITION_TYPE_SELL)
        continue;
      if (positionType == 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;
}
bool isEnableTimezone() {
  MqlDateTime timenow;
  TimeToStruct(TimeCurrent(), timenow);
  if (timenow.hour == 0)
    return false;
  return true;
}
// 计算子账户余额
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);
    if (magic == magicNum) {
      countProfit += profit;
      countCommission += commission;
      countSwap += swap;
      countMoney = countProfit + countCommission + countSwap;
    }
  }
  // 目前账户余额是多少
  double currentBalance = countMoney + initBa;
  return currentBalance / 100;
}

double calcLots(string symbol, double et, double sl, 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 slDistance = NormalizeDouble(MathAbs(et - sl), 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, "-", et, "-", sl, "-",
            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, "-", et, "-", sl, "-", slType, "-", slParam);
    Print("手数异常2:", point, "-", slDistance, "-", tickVal, "-", lot, "-",
          lotStep);
    lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
    return 0;
  }
  return lot;
}