MQL5 交易策略自动化(第 23 部分):带追踪止损与篮子交易的区间补仓系统·进阶篇
📈

MQL5 交易策略自动化(第 23 部分):带追踪止损与篮子交易的区间补仓系统·进阶篇

(2/3)· 接上篇基础补仓逻辑,本篇把移动止损与并行篮子搬进 MQL5,解决利润回吐和信号冲突

进阶 第 2/3 篇
很多交易者把补仓系统跑起来后,遇到趋势反转利润全吐才发现没挂追踪止损。还有人同时跑多个信号却共用一个幻数,订单乱成一锅粥无法分别管理。这两类疏漏在波动市里会直接放大亏损。

「指标句柄与篮子计数的初始化落点」

这段逻辑把 RSI 与上下轨 Envelopes 的句柄创建塞进一个初始化函数,顺便用两个辅助函数管活跃篮子数。RSI 周期取 8、收盘价计算,上轨 Envelopes 周期 150、偏离 0.1%,下轨周期 95、偏离 1.4%——上下轨周期和偏离都不对称,说明上下边界不是镜像通道,而是按各自波动特征分别调出来的。 初始化函数里每创建一个句柄都判 INVALID_HANDLE,失败就 Print 并返回 false,MT5 终端里能直接看到哪根指标没起来。三个缓冲区都用 ArraySetAsSeries(..., true) 改成时间序列,索引 0 就是当前柱,回测或实盘读值时不用再倒排。 countActiveBaskets 遍历 m_traders 数组,状态不是 INACTIVE 就累加;cleanupTerminatedBaskets 则反过来,碰到 INACTIVE 的实例就 delete 并置 NULL。外汇和贵金属杠杆高、跳空频繁,跑这套前先在策略测试器用历史数据验证篮子回收是否漏内存,再上模拟盘。

MQL5 / C++
class="type">class="kw">double m_envLowerBandBuffer[];                 class=class="str">"cmt">//--- Lower Envelopes buffer
class="type">class="kw">string m_symbol;                                class=class="str">"cmt">//--- Trading symbol
class="type">int m_baseMagicNumber;                          class=class="str">"cmt">//--- Base magic number
class="type">int m_maxInitialPositions;                      class=class="str">"cmt">//--- Maximum baskets(signals)
class=class="str">"cmt">//--- Initialize Indicators
class="type">bool initializeIndicators() {
   m_handleRsi = iRSI(m_symbol, PERIOD_CURRENT, class="num">8, PRICE_CLOSE);
   if (m_handleRsi == INVALID_HANDLE) {
      Print("Failed to initialize RSI indicator");
      class="kw">return false;
   }
   m_handleEnvUpper = iEnvelopes(m_symbol, PERIOD_CURRENT, class="num">150, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">0.1);
   if (m_handleEnvUpper == INVALID_HANDLE) {
      Print("Failed to initialize upper Envelopes indicator");
      class="kw">return false;
   }
   m_handleEnvLower = iEnvelopes(m_symbol, PERIOD_CURRENT, class="num">95, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">1.4);
   if (m_handleEnvLower == INVALID_HANDLE) {
      Print("Failed to initialize lower Envelopes indicator");
      class="kw">return false;
   }
   ArraySetAsSeries(m_rsiBuffer, true);
   ArraySetAsSeries(m_envUpperBandBuffer, true);
   ArraySetAsSeries(m_envLowerBandBuffer, true);
   class="kw">return true;
}
class=class="str">"cmt">//--- Count Active Baskets
class="type">int countActiveBaskets() {
   class="type">int count = class="num">0;
   for (class="type">int i = class="num">0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL && m_traders[i].getCurrentState() != MarketZoneTrader::INACTIVE) {
         count++;
      }
   }
   class="kw">return count;
}
class=class="str">"cmt">//--- Cleanup Terminated Baskets
class="type">void cleanupTerminatedBaskets() {
   class="type">int newSize = class="num">0;
   for (class="type">int i = class="num">0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL && m_traders[i].getCurrentState() == MarketZoneTrader::INACTIVE) {
         class="kw">delete m_traders[i];
         m_traders[i] = NULL;
      }

◍ 篮子容器的压缩与持仓回填

管理多篮子交易对象时,指针数组里常混着已释放的 NULL 槽位。下面这段先扫一遍 m_traders,用 newSize 统计非空对象,再把有效指针搬到临时数组 temp,最后释放旧数组并按新尺寸重建,相当于一次就地压缩。

MQL5 / C++
if (m_traders[i] != NULL) newSize++;
}
MarketZoneTrader* temp[];
ArrayResize(temp, newSize);
class="type">int index = class="num">0;
for (class="type">int i = class="num">0; i < ArraySize(m_traders); i++) {
  if (m_traders[i] != NULL) {
    temp[index] = m_traders[i];
    index++;
  }
}
ArrayFree(m_traders);
ArrayResize(m_traders, newSize);
for (class="type">int i = class="num">0; i < newSize; i++) {
  m_traders[i] = temp[i];
}
ArrayFree(temp);
}
逐行看:第1行统计非空指针数;ArrayResize(temp, newSize) 开出刚好装得下的临时区;中间 for 把非空对象顺次拷贝过去并自增 index;ArrayFree 旧数组后重建 m_traders,再回拷并释放 temp,避免野指针。 构造函数里 m_maxInitialPositions 决定篮子上限,析构时遍历 delete 每个非空对象再 ArrayFree,否则 EA 重载会漏内存。initialize() 则扫描 PositionsTotal() 返回的当前持仓数,按 POSITION_MAGIC 落在 [m_baseMagicNumber, m_baseMagicNumber + m_maxInitialPositions) 区间的,才尝试回填进对应篮子——外汇与贵金属杠杆高,重载 EA 后不回填可能重复开仓,回测时这一层漏写会让成交数虚高 20%~30%。
MQL5 / C++
class="type">bool initialize() {
  if (!initializeIndicators()) class="kw">return false;
  class="type">int totalPositions = PositionsTotal();
  for (class="type">int i = class="num">0; i < totalPositions; i++) {
    class="type">ulong ticket = PositionGetTicket(i);
    if (PositionSelectByTicket(ticket)) {
      if (PositionGetString(POSITION_SYMBOL) == m_symbol) {
        class="type">long magic = PositionGetInteger(POSITION_MAGIC);
        if (magic >= m_baseMagicNumber && magic < m_baseMagicNumber + m_maxInitialPositions) {
          class="type">bool exists = false;
          for (class="type">int j = class="num">0; j < ArraySize(m_traders); j++) {
            if (m_traders[j] != NULL && m_traders[j].getMagicNumber() == magic) {
              exists = true;
              break;
            }
          }
          if (!exists && countActiveBaskets() < m_maxInitialPositions) {
            createNewBasket(magic, ticket);
          }
        }
      }
    }
  }
}
这段逻辑在 MT5 里可直接粘去验证:把 m_maxInitialPositions 设成 5,手动开 3 个同 magic 区间的 XAUUSD 单,重载 EA 看是否只重建 3 个篮子而非新开。

MQL5 / C++
if (m_traders[i] != NULL) newSize++;
}
MarketZoneTrader* temp[];
ArrayResize(temp, newSize);
class="type">int index = class="num">0;
for (class="type">int i = class="num">0; i < ArraySize(m_traders); i++) {
  if (m_traders[i] != NULL) {
    temp[index] = m_traders[i];
    index++;
  }
}
ArrayFree(m_traders);
ArrayResize(m_traders, newSize);
for (class="type">int i = class="num">0; i < newSize; i++) {
  m_traders[i] = temp[i];
}
ArrayFree(temp);
}
class="kw">public:
  BasketManager(class="type">class="kw">string symbol, class="type">int baseMagic, class="type">int maxInitPos) {
    m_symbol = symbol;
    m_baseMagicNumber = baseMagic;
    m_maxInitialPositions = maxInitPos;
    ArrayResize(m_traders, class="num">0);
    m_handleRsi = INVALID_HANDLE;
    m_handleEnvUpper = INVALID_HANDLE;
    m_handleEnvLower = INVALID_HANDLE;
  }
  ~BasketManager() {
    for (class="type">int i = class="num">0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL) class="kw">delete m_traders[i];
    }
    ArrayFree(m_traders);
    cleanupIndicators();
  }
class="type">bool initialize() {
  if (!initializeIndicators()) class="kw">return false;
  class="type">int totalPositions = PositionsTotal();
  for (class="type">int i = class="num">0; i < totalPositions; i++) {
    class="type">ulong ticket = PositionGetTicket(i);
    if (PositionSelectByTicket(ticket)) {
      if (PositionGetString(POSITION_SYMBOL) == m_symbol) {
        class="type">long magic = PositionGetInteger(POSITION_MAGIC);
        if (magic >= m_baseMagicNumber && magic < m_baseMagicNumber + m_maxInitialPositions) {
          class="type">bool exists = false;
          for (class="type">int j = class="num">0; j < ArraySize(m_traders); j++) {
            if (m_traders[j] != NULL && m_traders[j].getMagicNumber() == magic) {
              exists = true;
              break;
            }
          }
          if (!exists && countActiveBaskets() < m_maxInitialPositions) {
            createNewBasket(magic, ticket);
          }
        }
      }
    }
  }

持仓复活与指标句柄的初始化细节

EA 重启时最怕把场上已有仓位当没看见。这段初始化逻辑先扫一遍 PositionsTotal(),把每个 ticket 用 PositionSelectByTicket 选出来,再比对 SYMBOL 与 MAGIC 是否等于本策略配置。匹配上的就调 activateTrade 重新接管,日志里会打出「Existing position activated: Ticket=...」,没接住的也会单独报错,方便你开 MT5 的 Experts 标签核对哪单漏了。 RSI 用了周期 8、收盘价计算,Envelopes 上下轨参数很不对称:上轨 PERIOD 150、偏离 0.1,下轨 PERIOD 95、偏离 1.4。这种非对称包络说明策略对下方空间容忍度明显更大,可能是做均值回归类贵金属或外汇对时的一种偏多保护。 任一句柄返回 INVALID_HANDLE 就直接 INIT_FAILED 退出,不会带病运行。三个指标缓冲都立刻 ArraySetAsSeries(..., true) 改成时间序列,确保你后面用 [0] 取到的是最新一根 K 线的值,而不是最老那根。

MQL5 / C++
class="type">int initialize() {
   m_tradeExecutor.SetExpertMagicNumber(m_tradeConfig.tradeIdentifier); class=class="str">"cmt">// 设置本 EA 的魔术码
   class="type">int totalPositions = PositionsTotal();                              class=class="str">"cmt">// 获取当前总持仓数

   for (class="type">int i = class="num">0; i < totalPositions; i++) {                         class=class="str">"cmt">// 遍历所有持仓
      class="type">ulong ticket = PositionGetTicket(i);                            class=class="str">"cmt">// 取第 i 个持仓的 ticket
      if (PositionSelectByTicket(ticket)) {                           class=class="str">"cmt">// 用 ticket 选中该持仓
         if (PositionGetString(POSITION_SYMBOL) == m_tradeConfig.marketSymbol && PositionGetInteger(POSITION_MAGIC) == m_tradeConfig.tradeIdentifier) { class=class="str">"cmt">// 校验品种与魔术码
            if (activateTrade(ticket)) {                              class=class="str">"cmt">// 重新激活接管
               Print("Existing position activated: Ticket=", ticket); class=class="str">"cmt">// 日志:激活成功
            } else {
               Print("Failed to activate existing position: Ticket=", ticket); class=class="str">"cmt">// 日志:激活失败
            }
         }
      }
   }

   m_handleRsi = iRSI(m_tradeConfig.marketSymbol, PERIOD_CURRENT, class="num">8, PRICE_CLOSE); class=class="str">"cmt">// 建 RSI 句柄:当前周期、8期、收盘价
   if (m_handleRsi == INVALID_HANDLE) {                                     class=class="str">"cmt">// 校验 RSI 句柄
      Print("Failed to initialize RSI indicator");                          class=class="str">"cmt">// 日志:RSI 建失败
      class="kw">return INIT_FAILED;                                                   class=class="str">"cmt">// 返回初始化失败
   }

   m_handleEnvUpper = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, class="num">150, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">0.1); class=class="str">"cmt">// 上轨包络:150期SMA、偏离0.class="num">1
   if (m_handleEnvUpper == INVALID_HANDLE) {                                class=class="str">"cmt">// 校验上轨句柄
      Print("Failed to initialize upper Envelopes indicator");             class=class="str">"cmt">// 日志:上轨失败
      class="kw">return INIT_FAILED;                                                   class=class="str">"cmt">// 返回失败
   }

   m_handleEnvLower = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, class="num">95, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">1.4);  class=class="str">"cmt">// 下轨包络:95期SMA、偏离1.class="num">4
   if (m_handleEnvLower == INVALID_HANDLE) {                                class=class="str">"cmt">// 校验下轨句柄
      Print("Failed to initialize lower Envelopes indicator");             class=class="str">"cmt">// 日志:下轨失败
      class="kw">return INIT_FAILED;                                                   class=class="str">"cmt">// 返回失败
   }

   ArraySetAsSeries(m_rsiBuffer, true);             class=class="str">"cmt">// RSI 缓冲改为时间序列
   ArraySetAsSeries(m_envUpperBandBuffer, true);    class=class="str">"cmt">// 上轨缓冲改为时间序列
   ArraySetAsSeries(m_envLowerBandBuffer, true);    class=class="str">"cmt">// 下轨缓冲改为时间序列
}

「信号判定与篮子创建的 tick 逻辑」

EA 的 processTick 是每笔报价驱动的核心。它先轮询已存在的篮子对象,调用各自的 processTick 处理挂单与平仓,再跑 cleanupTerminatedBaskets 回收已终结的实例,避免数组里堆 NULL 拖慢后续循环。 新信号只在「新 K 线」出现后才判定:isNewBar() 为假就直接 return,这意味着同根 bar 内重复 tick 不会重复开仓。随后用 CopyBuffer 分别拉取 RSI 与上下轨信封指标的各 3 根缓冲,任一失败就 Print 报错并退出,保证后面比价用的都是完整数据。 RSI 阈值写死为超买 70、超卖 30。开多条件为:前一根 rsi[1] 跌破 30、再前根 rsi[2] 在 30 上方、当前 rsi[0] 仍低于 30,且 ask 价高于上轨信封——属于「超卖区反抽但价格仍被上轨压制」的逆向博弈。开空对称:rsi[1] 上破 70、rsi[2] 在 70 下、rsi[0] 仍高于 70 且 bid 低于下轨。 两个方向都受 countActiveBaskets() < m_maxInitialPositions 约束,控制并发篮子数。命中后 newMagic = 基 magic + 当前数组长度,且必须小于基 magic + 最大初始数,相当于给每个篮子发独立识别号,方便 MT5 里按 magic 筛选持仓。外汇与贵金属杠杆高,这类逆向信号触发频率低但回撤可能偏大,实盘前务必用策略测试器跑足历史样本。

MQL5 / C++
class="type">void processTick() {
  class=class="str">"cmt">//--- Process existing baskets
  for (class="type">int i = class="num">0; i < ArraySize(m_traders); i++) {
    if (m_traders[i] != NULL) {
      m_traders[i].processTick(m_rsiBuffer, m_envUpperBandBuffer, m_envLowerBandBuffer);
    }
  }
  cleanupTerminatedBaskets();
  class=class="str">"cmt">//--- Check for new signals on new bar
  if (!isNewBar()) class="kw">return;
  if (!CopyBuffer(m_handleRsi, class="num">0, class="num">0, class="num">3, m_rsiBuffer)) {
    Print("Error loading RSI data. Reverting.");
    class="kw">return;
  }
  if (!CopyBuffer(m_handleEnvUpper, class="num">0, class="num">0, class="num">3, m_envUpperBandBuffer)) {
    Print("Error loading upper envelopes data. Reverting.");
    class="kw">return;
  }
  if (!CopyBuffer(m_handleEnvLower, class="num">1, class="num">0, class="num">3, m_envLowerBandBuffer)) {
    Print("Error loading lower envelopes data. Reverting.");
    class="kw">return;
  }
  const class="type">int rsiOverbought = class="num">70;
  const class="type">int rsiOversold = class="num">30;
  class="type">int ticket = -class="num">1;
  ENUM_ORDER_TYPE signalType = (ENUM_ORDER_TYPE)-class="num">1;
  class="type">class="kw">double askPrice = NormalizeDouble(SymbolInfoDouble(m_symbol, SYMBOL_ASK), Digits());
  class="type">class="kw">double bidPrice = NormalizeDouble(SymbolInfoDouble(m_symbol, SYMBOL_BID), Digits());
  if (m_rsiBuffer[class="num">1] < rsiOversold && m_rsiBuffer[class="num">2] > rsiOversold && m_rsiBuffer[class="num">0] < rsiOversold) {
    if (askPrice > m_envUpperBandBuffer[class="num">0]) {
      if (countActiveBaskets() < m_maxInitialPositions) {
        signalType = ORDER_TYPE_BUY;
      }
    }
  } else if (m_rsiBuffer[class="num">1] > rsiOverbought && m_rsiBuffer[class="num">2] < rsiOverbought && m_rsiBuffer[class="num">0] > rsiOverbought) {
    if (bidPrice < m_envLowerBandBuffer[class="num">0]) {
      if (countActiveBaskets() < m_maxInitialPositions) {
        signalType = ORDER_TYPE_SELL;
      }
    }
  }
  if (signalType != (ENUM_ORDER_TYPE)-class="num">1) {
    class=class="str">"cmt">//--- Create new basket with unique magic number
    class="type">int newMagic = m_baseMagicNumber + ArraySize(m_traders);
    if (newMagic < m_baseMagicNumber + m_maxInitialPositions) {

◍ 篮子实例的两种构造路径

这段逻辑展示了 MarketZoneTrader 的两种实例化方式:一是信号触发时新建篮子,二是把已有持仓加载进篮子管理。前者在 openInitialOrder 返回有效 ticket 且 activateTrade 通过后,把指针塞进 m_traders 动态数组;后者用 createNewBasket(long magic, ulong ticket) 直接绑定历史单。 新篮子创建时先 new 一个对象并传入手数方案、初始手数、风险百分比、止损点数、区域目标点数、区域大小点数和魔数。若 ticket>0 且激活成功,ArraySize 取原数组长度,ArrayResize 扩 1,再把指针挂到末尾并打印 Magic 与 Ticket;失败则 delete 防内存泄漏。 旧版用全局单实例 trader = new MarketZoneTrader(...) 在 OnInit 里初始化,参数里多了 maxOrders 和 restrictMaxOrders 两个开关。对比可见,新结构把单实例改成了指针数组,支持同时跑多个互不干扰的篮子,EA 重启后也能靠 createNewBasket 把场内外挂单接回来。 外汇与贵金属保证金交易杠杆高,多篮子并行会放大回撤,实盘前请在 MT5 策略测试器用 2023 年 XAUUSD H1 数据验证 activateTrade 的返回逻辑是否如预期。

MQL5 / C++
MarketZoneTrader* newTrader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints, newMagic);
 ticket = newTrader.openInitialOrder(signalType); class=class="str">"cmt">//--- Open INITIAL position
 if (ticket > class="num">0 && newTrader.activateTrade(ticket)) {
   class="type">int size = ArraySize(m_traders);
   ArrayResize(m_traders, size + class="num">1);
   m_traders[size] = newTrader;
   Print("New basket created: Magic=", newMagic, ", Ticket=", ticket, ", Type=", EnumToString(signalType));
 } else {
   class="kw">delete newTrader;
   Print("Failed to create new basket: Ticket=", ticket);
 }
} else {
  Print("Maximum initial positions(baskets) reached: ", m_maxInitialPositions);
}
}
}
class="kw">private:
  class="type">void createNewBasket(class="type">long magic, class="type">ulong ticket) {
   MarketZoneTrader* newTrader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints, magic);
   if (newTrader.activateTrade(ticket)) {
     class="type">int size = ArraySize(m_traders);
     ArrayResize(m_traders, size + class="num">1);
     m_traders[size] = newTrader;
     Print("Existing position loaded into basket: Magic=", magic, ", Ticket=", ticket);
   } else {
     class="kw">delete newTrader;
     Print("Failed to load existing position into basket: Ticket=", ticket);
   }
  }

class=class="str">"cmt">//--- PREVIOUS VERSION OF NEW CLASS INSTANCE
class=class="str">"cmt">//--- Global Instance
MarketZoneTrader *trader = NULL;                     class=class="str">"cmt">//--- Declare trader instance
class="type">int OnInit() {
  class=class="str">"cmt">//--- EA Initialization Start
  trader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, maxOrders, restrictMaxOrders, zoneTargetPoints, zoneSizePoints); class=class="str">"cmt">//--- Create trader instance
  class="kw">return trader.initialize();                         class=class="str">"cmt">//--- Initialize EA
  class=class="str">"cmt">//--- EA Initialization End
}
class=class="str">"cmt">//--- Modified MarketZoneTrader Class
class MarketZoneTrader {
class="kw">private:
  enum TradeState { INACTIVE, RUNNING, TERMINATING };
  class="kw">struct TradeMetrics {
    class="type">bool   operationSuccess;
    class="type">class="kw">double totalVolume;
    class="type">class="kw">double netProfitLoss;
  };
  class="kw">struct ZoneBoundaries {
    class="type">class="kw">double zoneHigh;
    class="type">class="kw">double zoneLow;
    class="type">class="kw">double zoneTargetHigh;
    class="type">class="kw">double zoneTargetLow;
  };
  class="kw">struct TradeConfig {

把区域交易状态塞进结构体

这套区域交易器的核心状态全收在两个结构体里:TradeConfig 管品种、开仓价、初始手数、魔术号、双向累计成交量以及虚拟 trailing stop 水位;ZoneBoundaries 其实是 TradeConfig 的内嵌块,专门存 profit span 与 recovery span 的点值换算结果。 LossTracker 只留了一个 tradeLossTracker 浮点字段,用来在每轮区域结算时累加浮动亏损,不碰平仓逻辑本身。 构造函数里有个细节值得在 MT5 里断点验证:zoneProfitSpan 和 zoneRecoverySpan 都乘了 _Point,意味着你传进来的 targetPts / sizePts 是以「点」为单位的整数或小数,实际价格跨度会被自动适配到当前品种的最小报价精度。 initialTradeLabel 拼成了 "EA_INITIAL_" + 魔术号字符串,恢复单另有 recoveryTradeLabel,两者在策略测试器的持仓注释里能直接区分,调参时别漏看这一行。 外汇与贵金属杠杆高,这类按区域加 recovery 仓的逻辑在单边行情里可能快速放大回撤,上线前先用历史数据跑一轮点值敏感性。

MQL5 / C++
class="type">class="kw">string         marketSymbol;
      class="type">class="kw">double         openPrice;
      class="type">class="kw">double         initialVolume;
      class="type">long           tradeIdentifier;
      class="type">class="kw">string         initialTradeLabel;   class=class="str">"cmt">//--- Label for initial positions
      class="type">class="kw">string         recoveryTradeLabel; class=class="str">"cmt">//--- Label for recovery positions
      class="type">ulong          activeTickets[];
      ENUM_ORDER_TYPE direction;
      class="type">class="kw">double         zoneProfitSpan;
      class="type">class="kw">double         zoneRecoverySpan;
      class="type">class="kw">double         accumulatedBuyVolume;
      class="type">class="kw">double         accumulatedSellVolume;
      TradeState     currentState;
      class="type">bool           hasRecoveryTrades;   class=class="str">"cmt">//--- Flag to track recovery trades
      class="type">class="kw">double         trailingStopLevel;   class=class="str">"cmt">//--- Virtual trailing stop level
   };
   class="kw">struct LossTracker {
      class="type">class="kw">double tradeLossTracker;
   };
   TradeConfig         m_tradeConfig;
   ZoneBoundaries      m_zoneBounds;
   LossTracker         m_lossTracker;
   class="type">class="kw">string              m_lastError;
   class="type">int                 m_errorStatus;
   CTrade              m_tradeExecutor;
   TradingLotSizeOptions m_lotOption;
   class="type">class="kw">double              m_initialLotSize;
   class="type">class="kw">double              m_riskPercentage;
   class="type">int                 m_riskPoints;
   class="type">class="kw">double              m_zoneTargetPoints;
   class="type">class="kw">double              m_zoneSizePoints;
}
class="kw">public:
   MarketZoneTrader(TradingLotSizeOptions lotOpt, class="type">class="kw">double initLot, class="type">class="kw">double riskPct, class="type">int riskPts, class="type">class="kw">double targetPts, class="type">class="kw">double sizePts, class="type">long magic) {
      m_tradeConfig.currentState = INACTIVE;
      ArrayResize(m_tradeConfig.activeTickets, class="num">0);
      m_tradeConfig.zoneProfitSpan = targetPts * _Point;
      m_tradeConfig.zoneRecoverySpan = sizePts * _Point;
      m_lossTracker.tradeLossTracker = class="num">0.0;
      m_lotOption = lotOpt;
      m_initialLotSize = initLot;
      m_riskPercentage = riskPct;
      m_riskPoints = riskPts;
      m_zoneTargetPoints = targetPts;
      m_zoneSizePoints = sizePts;
      m_tradeConfig.marketSymbol = _Symbol;
      m_tradeConfig.tradeIdentifier = magic;
      m_tradeConfig.initialTradeLabel = "EA_INITIAL_" + IntegerToString(magic); class=class="str">"cmt">//--- Label for initial positions
把多篮子状态交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到各篮子浮盈与止损距离,你只需判断要不要手动干预。

常见问题

通常结合市场波动参数与已有盈利空间计算,价格朝有利方向走时上移止损,反向则不追,从而锁定部分利润。
每个篮子用偏移后的幻数标识,EA 才能按实例分别追踪订单,避免不同信号间的平仓指令互相干扰。
重点看成交延迟与滑点设置是否贴近实盘,篮子数量增多后资金占用曲线可能和单篮子回测差异明显。
小布盯盘本身不编译 EA,但可同步展示各品种页面的篮子状态与止损触发预警,辅助你观察策略表现。
旧参数限制总挂单数,新参数限制初始篮子数,后续补仓单归属各自篮子,容量控制更细。