使用优化算法即时配置 EA 参数·进阶篇
⚙️

使用优化算法即时配置 EA 参数·进阶篇

(2/3)· 还在手动重跑优化器?这篇把自我优化EA的骨架与虚拟化拆给你看

含代码示例实战向 第 2/3 篇
很多交易者把优化当成一次性离线任务,跑完就以为参数永久有效。市场波动结构一变,昨天的黄金参数今天可能直接放大回撤。把优化嵌进EA运行循环,才是应对非平稳行情的现实做法。

◍ EMA与信号线的缓冲区算法拆解

MACD 自定义类里,快、慢 EMA 与信号线都先走 ArrayInitialize 把缓冲区清零,避免历史残留值污染首根 K 线的计算。ExtFastMaBuffer、ExtSlowMaBuffer、ExtSignalBuffer 三个数组在 OnCalculate 开头被置为 0.0,这是 MT5 指标重载时容易漏掉的一步。 ExponentialMAOnBuffer 的核心平滑系数是 2.0/(1.0+period)。当 prev_calculated==0 时,前 begin 根强制填 0.0,从 begin 到 period+begin 之间用递推式 buffer[i]=price[i]*smooth_factor+buffer[i-1]*(1.0-smooth_factor) 预热,start_position 定为 period+begin,意味着首根有效 EMA 出现在第 period+begin 根。 信号线走的是 SimpleMAOnBuffer,把 MACD 主线 ExtMacdBuffer 再套一层简单移动平均,周期由 inpSignalSMA 控制。主循环里 i 从 prev_calculated-1 起步,仅重算新增及上一根,这是 MT5 增量计算的标准写法,能省掉重复遍历。 开 MT5 把这段贴进自定义指标类,改 inpFastEMA / inpSlowEMA / inpSignalSMA 三个输入,能在 EURUSD 15 分钟图上直接比对与系统 iMACD 的偏移量;外汇与贵金属杠杆品种波动剧烈,指标仅描述概率倾向,实盘须自担高风险。

MQL5 / C++
ArrayInitialize(ExtSignalBuffer, class="num">0.0);
ArrayInitialize(ExtFastMaBuffer, class="num">0.0);
ArrayInitialize(ExtSlowMaBuffer, class="num">0.0);
ExponentialMAOnBuffer(rates_total, prev_calculated, class="num">0, inpFastEMA, close, ExtFastMaBuffer);
ExponentialMAOnBuffer(rates_total, prev_calculated, class="num">0, inpSlowEMA, close, ExtSlowMaBuffer);
class="type">int start;
if (prev_calculated == class="num">0) start = class="num">0;
else start = prev_calculated - class="num">1;
class=class="str">"cmt">//--- calculate MACD
for (class="type">int i = start; i < rates_total && !IsStopped(); i++) ExtMacdBuffer [i] = ExtFastMaBuffer [i] - ExtSlowMaBuffer [i];
class=class="str">"cmt">//--- calculate Signal
SimpleMAOnBuffer(rates_total, prev_calculated, class="num">0, inpSignalSMA, ExtMacdBuffer, ExtSignalBuffer);
class="kw">return (rates_total);
}
class="type">int C_iMACD::ExponentialMAOnBuffer(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">int period, const class="type">class="kw">double& price [],class="type">class="kw">double& buffer [])
{
  if (period <= class="num">1 || period > (rates_total - begin)) class="kw">return (class="num">0);
  class="type">bool as_series_price   = ArrayGetAsSeries(price);
  class="type">bool as_series_buffer = ArrayGetAsSeries(buffer);
  ArraySetAsSeries(price, false);
  ArraySetAsSeries(buffer, false);
  class="type">int     start_position;
  class="type">class="kw">double smooth_factor = class="num">2.0 / (class="num">1.0 + period);
  if (prev_calculated == class="num">0)
  {
    for (class="type">int i = class="num">0; i < begin; i++) buffer [i] = class="num">0.0;
    start_position = period + begin;
    buffer [begin] = price [begin];
    for (class="type">int i = begin + class="num">1; i < start_position; i++) buffer [i] = price [i] * smooth_factor + buffer [i - class="num">1] * (class="num">1.0 - smooth_factor);
  }
  else start_position = prev_calculated - class="num">1;
  for (class="type">int i = start_position; i < rates_total; i++) buffer [i] = price [i] * smooth_factor + buffer [i - class="num">1] * (class="num">1.0 - smooth_factor);
  ArraySetAsSeries(price,  as_series_price);
  ArraySetAsSeries(buffer, as_series_buffer);
  class="kw">return (rates_total);
}
class="type">int C_iMACD::SimpleMAOnBuffer(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">int period, const class="type">class="kw">double& price [],class="type">class="kw">double& buffer [])
{
  class=class="str">"cmt">//--- check period

用递推算均线比循环累加更省

这段自定义指标的计算核心,走的是递推思路而不是每根 K 线都从头累加。首次加载时(prev_calculated==0),它把前 period+begin 根之前的缓冲区填 0,再对 begin 到 start_position 的收盘价做一次求和取平均,作为第一个有效值。 后续每根新柱只做一步:buffer[i] = buffer[i-1] + (price[i] - price[i-period]) / period。也就是说,只用一次加减除就更新了均线,时间复杂度从 O(n·period) 降到 O(n),在 period 设到 200 以上、品种刷新频繁时,CPU 占用差异可能肉眼可感。 数组方向是个暗坑。代码先 ArrayGetAsSeries 存下 price 和 buffer 原本的序列方向,统一设成 false(即下标 0 是最旧数据),算完再用 ArraySetAsSeries 还原。若你抄这段代码却漏掉还原,调用方按时间序列取数就会整体错位。

边界判断 if(period<=1period>(rates_total-begin)) return(0) 直接拦掉非法周期和样本不足,避免在少于 begin+period 根柱时写出越界。开 MT5 把这段塞进自定义指标 OnCalculate,设 period=50、begin=0,加载到 XAUUSD 的 M5 上即可验证递推值与内置 SMA 是否重合。外汇与贵金属杠杆高,指标仅作参考,实际信号失效概率不低。
MQL5 / C++
  if (period <= class="num">1 || period > (rates_total - begin)) class="kw">return (class="num">0);
  class=class="str">"cmt">//--- save as_series flags
  class="type">bool as_series_price = ArrayGetAsSeries(price);
  class="type">bool as_series_buffer = ArrayGetAsSeries(buffer);
  ArraySetAsSeries(price, false);
  ArraySetAsSeries(buffer, false);
  class=class="str">"cmt">//--- calculate start position
  class="type">int start_position;
  if (prev_calculated == class="num">0) class=class="str">"cmt">// first calculation or number of bars was changed
  {
    class=class="str">"cmt">//--- set empty value for first bars
    start_position = period + begin;
    for (class="type">int i = class="num">0; i < start_position - class="num">1; i++) buffer [i] = class="num">0.0;
    class=class="str">"cmt">//--- calculate first visible value
    class="type">class="kw">double first_value = class="num">0;
    for (class="type">int i = begin; i < start_position; i++) first_value += price [i];
    buffer [start_position - class="num">1] = first_value / period;
  }
  else start_position = prev_calculated - class="num">1;
  class=class="str">"cmt">//--- main loop
  for (class="type">int i = start_position; i < rates_total; i++) buffer [i] = buffer [i - class="num">1] + (price [i] - price [i - period]) / period;
  class=class="str">"cmt">//--- restore as_series flags
  ArraySetAsSeries(price, as_series_price);
  ArraySetAsSeries(buffer, as_series_buffer);
  class=class="str">"cmt">//---
  class="kw">return (rates_total);
}

「把随机EA改造成自优化虚拟策略」

在 EA 里做策略虚拟化,核心是把实盘逻辑复制一份到历史回测函数里,让优化算法能离线算适应度。读者提供的随机振荡 EA 示例里,关键优化参数是 InpKPeriod_P 和 InpUpperLevel_P,它们以字符串声明复合值:默认 18、起始 9、步长 3、终止 24(上限水平则是 9688298)。这种写法让 OnInit 时能把字符串拆进 Set、Range_Min、Range_Step、Range_Max 四个数组。

OnTick 中插入的 Optimize 函数充当调度器,流程和标准群体优化算法测试脚本一致:初始化算法、准备群体、取参数组、算适应度、更新最优、取最佳解。VirtualStrategy 则在历史数据上跑策略,载入 rates 数组后算 Stochastic,若指标失败返回 -DBL_MAX,无成交也返回 -DBL_MAX,否则返回最终余额作为适应度。 注意虚拟策略要和主 EA 执行节奏对齐:示例以柱线开盘价交易,对应主代码的开盘控制;若原逻辑跑在每 tick,就要下载 tick 历史并改写 VirtualStrategy。接 ESG 算法时,只需在 Optimize 里声明对象并配边界,搜索算法本身是裸逻辑、不含去重加速,迭代耗时会偏多。 外汇与贵金属自带高杠杆风险,自优化回测收益不代表实盘概率,参数过拟合可能让 live 表现明显衰减。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="preprocessor">class="macro">#class="kw">import </span><span class="class="type">class="kw">string">"\Market\AO Core.ex5"</span>
<span class="keyword">class="type">bool</span>&nbsp;&nbsp; Init(<span class="keyword">class="type">int</span> colonySize, <span class="keyword">class="type">class="kw">double</span> &amp;range_min [], <span class="keyword">class="type">class="kw">double</span> &amp;range_max [], <span class="keyword">class="type">class="kw">double</span> &amp;range_step []);
<span class="comment">class=class="str">"cmt">//------------------------------------------------------------------------------</span>
<span class="keyword">class="type">void</span>&nbsp;&nbsp; Preparation&nbsp;&nbsp;&nbsp;&nbsp;();
<span class="keyword">class="type">void</span>&nbsp;&nbsp; GetVariantCalc(<span class="keyword">class="type">class="kw">double</span> &amp;variant [], <span class="keyword">class="type">int</span> pos);
<span class="keyword">class="type">void</span>&nbsp;&nbsp; SetFitness&nbsp;&nbsp;&nbsp;&nbsp; (<span class="keyword">class="type">class="kw">double</span> value,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">int</span> pos);
<span class="keyword">class="type">void</span>&nbsp;&nbsp; Revision&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ();
<span class="comment">class=class="str">"cmt">//------------------------------------------------------------------------------</span>
<span class="keyword">class="type">void</span>&nbsp;&nbsp; GetVariant&nbsp;&nbsp;&nbsp;&nbsp; (<span class="keyword">class="type">class="kw">double</span> &amp;variant [], <span class="keyword">class="type">int</span> pos);
<span class="keyword">class="type">class="kw">double</span> GetFitness&nbsp;&nbsp;&nbsp;&nbsp; (<span class="keyword">class="type">int</span> pos);
<span class="preprocessor">class="macro">#class="kw">import
</span><span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="preprocessor">class="macro">#include </span>&lt;Trade\Trade.mqh&gt;
<span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"cStochastic.mqh"</span>
<span class="keyword">input</span> group&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="class="type">class="kw">string">"==== GENERAL ===="</span>;
<span class="keyword">sinput</span> <span class="keyword">class="type">long</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; InpMagicNumber&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">132516</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//Magic Number</span>
<span class="keyword">sinput</span> <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; InpLotSize&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">0.01</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//Lots</span>
<span class="keyword">input</span> group&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="class="type">class="kw">string">"==== Trading ===="</span>;
<span class="keyword">input</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; InpStopLoss&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">1450</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//Stoploss</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; InpTakeProfit&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">1200</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//Takeprofit</span>
<span class="keyword">input</span> group&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="class="type">class="kw">string">"==== Stochastic ==|value|start|step|end|=="</span>;
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InpKPeriod_P&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="class="type">class="kw">string">"<span style="background-class="type">class="kw">color:rgb(class="num">119, class="num">183, class="num">83);">class="num">18</span>|<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">235, class="num">85);">class="num">9</span>|<span style="background-class="type">class="kw">color:rgb(class="num">193, class="num">115, class="num">176);">class="num">3</span>|<span style="background-class="type">class="kw">color:rgb(class="num">164, class="num">192, class="num">228);">class="num">24</span>"</span>;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//STO K period&nbsp;&nbsp; : it is necessary to optimize</span>

◍ 把参数塞进数组留给自优化跑

这段初始化逻辑干的事很直接:把 Stochastic 的上轨阈值和 K 周期从输入字符串拆成四个数值(基准值、最小边界、步长、最大边界),分别填进 Set、Range_Min、Range_Step、Range_Max 这四个长度为 2 的 double 数组。索引 0 对应 K 周期,索引 1 对应上轨水平,拆不出来正好 4 段就直接 INIT_FAILED 退出。

注意 InpUpperLevel_P 的默认值 "9688298" 意味着上轨基准 96、搜索下限 88、步长 2、上限 98,这组区间在 EURUSD 这类品种上通常要重优化,原文也标注了 it is necessary to optimize。

自优化开关由 SelfOptimization 控制,开启后 EA 会用 18000 根历史 bar 做初始优化,每走过 1440 根 bar 触发一次重优化;种群规模 50、历史跑 10000 次、点差按 10 点固定计入。外汇与贵金属杠杆高,这类固定点差假设和重优化频率在实盘滑点下可能偏离回测。 OnInit 里先取了 SYMBOL_TRADE_TICK_SIZE 存进 TickSize,后面下单计算止损距离会用到;数组用 ArrayResize 显式开长度 2,避免越界。

MQL5 / C++
input class="type">class="kw">string        InpUpperLevel_P      = "class="num">96|class="num">88|class="num">2|class="num">98"; class=class="str">"cmt">//STO upper level: it is necessary to optimize
input group         "====Self-optimization====";
sinput class="type">bool         SelfOptimization      = true;
sinput class="type">int          InpBarsOptimize      = class="num">18000;          class=class="str">"cmt">//Number of bars in the history for optimization
sinput class="type">int          InpBarsReOptimize    = class="num">1440;            class=class="str">"cmt">//After how many bars, EA will reoptimize
sinput class="type">int          InpPopSize           = class="num">50;              class=class="str">"cmt">//Population size
sinput class="type">int          NumberFFlaunches     = class="num">10000;           class=class="str">"cmt">//Number of runs in the history during optimization
sinput class="type">int          Spread               = class="num">10;              class=class="str">"cmt">//Spread
class="type">MqlTick Tick;
CTrade  Trade;
C_iStochastic IStoch;
class="type">class="kw">double Set        [];
class="type">class="kw">double Range_Min  [];
class="type">class="kw">double Range_Step [];
class="type">class="kw">double Range_Max  [];
class="type">class="kw">double TickSize = class="num">0.0;
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">int OnInit()
{
   TickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   ArrayResize(Set,       class="num">2);
   ArrayResize(Range_Min, class="num">2);
   ArrayResize(Range_Step, class="num">2);
   ArrayResize(Range_Max, class="num">2);
   class="type">class="kw">string result [];
   if (StringSplit(InpKPeriod_P, StringGetCharacter("|", class="num">0), result) != class="num">4) class="kw">return INIT_FAILED;
   Set        [class="num">0] = (class="type">class="kw">double)StringToInteger(result [class="num">0]);
   Range_Min  [class="num">0] = (class="type">class="kw">double)StringToInteger(result [class="num">1]);
   Range_Step [class="num">0] = (class="type">class="kw">double)StringToInteger(result [class="num">2]);
   Range_Max  [class="num">0] = (class="type">class="kw">double)StringToInteger(result [class="num">3]);
   if (StringSplit(InpUpperLevel_P, StringGetCharacter("|", class="num">0), result) != class="num">4) class="kw">return INIT_FAILED;
   Set        [class="num">1] = (class="type">class="kw">double)StringToInteger(result [class="num">0]);
   Range_Min  [class="num">1] = (class="type">class="kw">double)StringToInteger(result [class="num">1]);
   Range_Step [class="num">1] = (class="type">class="kw">double)StringToInteger(result [class="num">2]);

把自优化塞进 OnTick 的节奏

这段逻辑把「自适应重优化」直接挂在新 K 线判定之后。IsNewBar() 为假就直接 return,保证一根bar只跑一次主逻辑,避免 tick 级重复计算拖慢 MT5 实盘。 SelfOptimization 开关打开时,用静态变量 LastOptimizeTime 记录上次优化时间,并与 iTime(_Symbol, PERIOD_CURRENT, InpBarsReOptimize) 对比。当 LastOptimizeTime 小于等于回看 N 根 bar 的时间戳,就触发 Optimize(),把旧参数组 Set 和边界数组 Range_Min/Step/Max 丢进去,顺带把点差按 SYMBOL_TRADE_TICK_SIZE 折算成绝对价格传入。 优化完成后会 ArrayPrint 新旧 Set,并重新 IStoch.Init((int)Set[0],1,3) 让随机指标用新周期参数。注意 Set[0] 同时是随机指标 K 周期,也是 CopyRates 索要的 bar 数基准:代码里取 Set[0]+1+3+1 根,确保算 Stochastic 不越界。 外汇与贵金属波动剧烈,这种自优化若在过小 InpBarsReOptimize 上跑,可能频繁重写参数导致过拟合,开 MT5 后建议先把重优化间隔调到 200 根以上观察稳定性。

MQL5 / C++
  Range_Max [class="num">1] = (class="type">class="kw">double)StringToInteger(result [class="num">3]);
  IStoch.Init((class="type">int)Set [class="num">0], class="num">1, class="num">3);
  class=class="str">"cmt">//  set magicnumber to trade object
  Trade.SetExpertMagicNumber(InpMagicNumber);
  class=class="str">"cmt">//---
  class="kw">return (INIT_SUCCEEDED);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void OnTick()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  if (!IsNewBar())
  {
    class="kw">return;
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  if (SelfOptimization)
  {
    class=class="str">"cmt">//--------------------------------------------------------------------------
    class="kw">static class="type">class="kw">datetime LastOptimizeTime = class="num">0;
    class="type">class="kw">datetime timeNow  = iTime(_Symbol, PERIOD_CURRENT, class="num">0);
    class="type">class="kw">datetime timeReop = iTime(_Symbol, PERIOD_CURRENT, InpBarsReOptimize);
    if (LastOptimizeTime <= timeReop)
    {
      LastOptimizeTime = timeNow;
      Print("-------------------Start of optimization----------------------");
      Print("Old set:");
      ArrayPrint(Set);
      Optimize(Set,
                Range_Min,
                Range_Step,
                Range_Max,
                InpBarsOptimize,
                InpPopSize,
                NumberFFlaunches,
                Spread * SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE));
      Print("New set:");
      ArrayPrint(Set);
      IStoch.Init((class="type">int)Set [class="num">0], class="num">1, class="num">3);
    }
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  if (!SymbolInfoTick(_Symbol, Tick))
  {
    Print("Failed to get current symbol tick"); class="kw">return;
  }
  class=class="str">"cmt">//data preparation------------------------------------------------------------
  class="type">MqlRates rates [];
  class="type">int dataCount = CopyRates(_Symbol, PERIOD_CURRENT, class="num">0, (class="type">int)Set [class="num">0] + class="num">1 + class="num">3 + class="num">1, rates);
  if (dataCount == -class="num">1)
  {
    Print("Data get error");
    class="kw">return;
  }
  class="type">class="kw">double hi [];
  class="type">class="kw">double lo [];
  class="type">class="kw">double cl [];
  ArrayResize(hi, dataCount);
  ArrayResize(lo, dataCount);
  ArrayResize(cl, dataCount);
  for (class="type">int i = class="num">0; i < dataCount; i++)
  {

「把随机指标拐点接进开仓逻辑」

这段把前面算好的 Stochastic 主缓冲接进了实盘判定:取倒数第二根与第三根柱的 ExtMainBuffer 值,分别存为 buff0、buff1,用来识别指标从超卖/超买区穿出的最新两根 K 线拐点。 若当前无多单(cntBuy==0),且 buff1 低于 100 减去 Set[1] 阈值、buff0 向上突破该阈值,则先平掉空单(ClosePositions(2)),以 Tick.bid 减止损点数、加止盈点数算出 sl/tp,再市价开多。反向同理:无空单且指标从超买区下穿 Set[1] 时平多开空,外汇与贵金属杠杆品种此处滑点与扩散成本可能显著侵蚀回测收益。 末尾的 Optimize 函数是遗传/火焰优化入口:用传入的 range_min、range_step、range_max 做参数边界,按 inpPopSize 与 numberFFlaunches 推算 epochCount,再调 Init 初始化种群。你在 MT5 策略测试器里把 inpPopSize 设成 50、numberFFlaunches 设成 500,相当于跑 10 代,可直观看 Set[1] 阈值寻优后的夏普变化。

MQL5 / C++
  hi [i] = rates [i].high;
  lo [i] = rates [i].low;
  cl [i] = rates [i].close;
}
class="type">int calc = IStoch.Calculate(dataCount, class="num">0, hi, lo, cl);
if (calc <= class="num">0) class="kw">return;
class="type">class="kw">double buff0 = IStoch.ExtMainBuffer [ArraySize(IStoch.ExtMainBuffer) - class="num">2];
class="type">class="kw">double buff1 = IStoch.ExtMainBuffer [ArraySize(IStoch.ExtMainBuffer) - class="num">3];
class=class="str">"cmt">//----------------------------------------------------------------------------
class=class="str">"cmt">// count open positions
class="type">int cntBuy, cntSell;
if (!CountOpenPositions(cntBuy, cntSell))
{
  Print("Failed to count open positions");
  class="kw">return;
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class=class="str">"cmt">// check for buy
if (cntBuy == class="num">0 && buff1 <= (class="num">100 - (class="type">int)Set [class="num">1]) && buff0 > (class="num">100 - (class="type">int)Set [class="num">1]))
{
  ClosePositions(class="num">2);
  class="type">class="kw">double sl = NP(Tick.bid - InpStopLoss   * TickSize);
  class="type">class="kw">double tp = NP(Tick.bid + InpTakeProfit * TickSize);
  Trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, InpLotSize, Tick.ask, sl, tp, "Stochastic EA");
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class=class="str">"cmt">// check for sell
if (cntSell == class="num">0 && buff1 >= (class="type">int)Set [class="num">1] && buff0 < (class="type">int)Set [class="num">1])
{
  ClosePositions(class="num">1);
  class="type">class="kw">double sl = NP(Tick.ask + InpStopLoss   * TickSize);
  class="type">class="kw">double tp = NP(Tick.ask - InpTakeProfit * TickSize);
  Trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, InpLotSize, Tick.bid, sl, tp, "Stochastic EA");
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void Optimize(class="type">class="kw">double       &set         [],
               class="type">class="kw">double       &range_min   [],
               class="type">class="kw">double       &range_step [],
               class="type">class="kw">double       &range_max   [],
               const class="type">int    inpBarsOptimize,
               const class="type">int    inpPopSize,
               const class="type">int    numberFFlaunches,
               const class="type">class="kw">double spread)
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">class="kw">double parametersSet [];
  ArrayResize(parametersSet, ArraySize(set));
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">int epochCount = numberFFlaunches / inpPopSize;
  Init(inpPopSize, range_min, range_max, range_step);
  class=class="str">"cmt">// Optimization-------------------------------------------------------------
把重跑优化器交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到参数漂移预警,把重复劳动交给小布,你专注决策。

常见问题

离线优化只在历史数据上跑一次,自我优化会在EA运行期内按设定节奏重算参数,更贴近当下市场状态,但需注意过拟合与计算开销。
通常把指标句柄与缓冲访问封装成可替换模块,EA在优化分支中切换不同周期或算法,而不必重写主逻辑,便于策略快速迭代。
概率上有可能,若缺乏约束条件。实战中应对虚拟化策略加风险边界与最小样本要求,避免低样本下激进切换。
可以,小布的品种页会标记参数重配事件与性能变化,帮你识别EA是否在异常波动中频繁自我改写,降低盲开仓风险。
外汇贵金属杠杆高、跳空频繁,自优化若基于短窗口数据,可能在重大数据发布后给出失真信号,务必加人工复核与高风险提示。