交易货币对篮子时出现的测试形态。第 I 部·综合运用
📘

交易货币对篮子时出现的测试形态。第 I 部·综合运用

第 3/3 篇

把形态参数导成可读的 HTML 报表

EA 初始化收尾时,用 FileWriteString 把形态识别结果写成一张 XHTML 静态表,方便你不开 MT5 终端也能翻历史参数。代码先写文档头与 <head>,把当前品种 _Symbol 和 Period() 转成的短周期字符塞进 H2,再列 BUY/SELL 双栏的形态阈值。 BUY 与 SELL 各自用 StringFormat 拼出「点 1 / 点 2 区间 / 平仓位」四个整数,例如 BUY1LIMIT、BUY2FROM、BUY2TO、BUYPROFIT,直接进 <li> 列表。表格头还预留了 WPR(P1/P2)、时间三元组、回撤盈利等 6 列,结构固定但数据靠后续填充。 PushWpr 是个 3 位滑动窗:每来一个新 wpr,dWpr[2]=dWpr[1]、dWpr[1]=dWpr[0]、dWpr[0]=wpr,用来保留最近三次 WPR 值做斜率判断。OnTick 此处为空,说明报价触发逻辑不在这层;Stat 刚开了个 LG 长度的循环骨架,具体统计未展开。 外汇与贵金属波动剧烈,这类报表只是参数回溯,不代表任何方向概率优势,实盘前务必在 MT5 策略测试器用真实点差重跑。

MQL5 / C++
  FileWriteString(lh,"<!DOCTYPE html PUBLIC \"-class=class="str">"cmt">//W3C//DTD XHTML class="num">1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n");
  FileWriteString(lh,"<html xmlns=\"http:class=class="str">"cmt">//www.w3.org/class="num">1999/xhtml\">\r\n");
  FileWriteString(lh,"<head>\r\n");
  FileWriteString(lh,"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-class="num">8\"/>\r\n");
  FileWriteString(lh,"<title>"+lname+"</title>\r\n");
  FileWriteString(lh,"</head>\r\n<body>\r\n");
  FileWriteString(lh,"<H2>"+_Symbol+" "+TimeFrameToShortString(Period())+"</H2>\r\n");
  FileWriteString(lh,"<H3>形态参数:</H3>\r\n");
  FileWriteString(lh,"<table width=\"class="num">100%\" cellspacing=\"class="num">0\" cellpadding=\"class="num">5\">\r\n");
  FileWriteString(lh,"<thead>\r\n<tr>\r\n<th>BUY</th>\r\n<th>SELL</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n");
  t=StringFormat("点 class="num">1: %d 点 class="num">2 从: %d 至: %d 平仓在: %d",BUY1LIMIT,BUY2FROM,BUY2TO,BUYPROFIT);
  FileWriteString(lh,"<td style=\"text-align:center;\">\r\n<ul>\r\n<li>"+t+"</li>\r\n</ul>\r\n</td>\r\n");
  t=StringFormat("点 class="num">1: %d 点 class="num">2 从: %d 至: %d 平仓在: %d",SELL1LIMIT,SELL2FROM,SELL2TO,SELLPROFIT);
  FileWriteString(lh,"<td style=\"text-align:center;\">\r\n<ul>\r\n<li>"+t+"</li>\r\n</ul>\r\n</td>\r\n");
  FileWriteString(lh,"</tr>\r\n</tbody>\r\n</table>\r\n");
  FileWriteString(lh,"<H2>"+"测试结果"+"</H2>\r\n");
  FileWriteString(lh,"<table border=\"class="num">1\" width=\"class="num">100%\" cellspacing=\"class="num">0\" cellpadding=\"class="num">5\">\r\n");
  FileWriteString(lh,"<thead>\r\n<th>编号</th>\r\n<th>类型</th>\r\n<th>WPR(P1/P2)</th>\r\n<th>时间(开始/结束/长度)</th>\r\n<th>回撤/<br/>盈利</th>\r\n<th>货币对盈利</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n");
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void PushWpr(class="type">class="kw">double wpr)
  {
  dWpr[class="num">2] = dWpr[class="num">1]; dWpr[class="num">1] = dWpr[class="num">0];
  dWpr[class="num">0] = wpr;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Stat()
  {
  class="type">class="kw">double d=class="num">0;
  for(class="type">int i=class="num">0; i<LG; i++)

◍ 用计时器把回撤和信号钉死在每一根K线上

这段逻辑跑在 OnTimer 里,靠定时器而非逐 tick 去扫状态,能明显降低 CPU 占用,也避开了 tick 抖动带来的重复触发。只要 bstate 不等于 BCLOSE,就持续调用 Stat() 刷新权益与回撤。 Stat() 内部用 PositionSelectByTicket 按单号定位持仓,再累加 PositionGetDouble(POSITION_PROFIT) 到变量 d;随后用 if(d<Drw) Drw=d 把当前浮亏刷新为阶段最大回撤候选,若 Drw 跌破历史最低 mDrw 则记下 TimeCurrent() 时间戳。 新K线确认后,GetValue1(0) 取当前 WPR 类指标值压入 dWpr 数组。若前一值非 EMPTY_VALUE,便进入信号判定:多头持仓且 dWpr[0]>=BUYPROFIT 时全平转 BCLOSE;空头持仓且 dWpr[0]<=SELLPROFIT 同理。 空仓状态下,dWpr[0] 落进 SELL2FROM~SELL2TO 且前一根高于 SELL1LIMIT,就 EnterSell(0.01) 开 0.01 手空单,重置 Drw=1000000 作为回撤基线,iTradeCount 自增并写 HTML 日志行;反向 BUY2FROM~BUY2TO 配合 BUY1LIMIT 则开多。外汇与贵金属杠杆高,这类固定手数样板在实盘前务必在 MT5 策略测试器跑至少 3 个月 tick 数据验证滑点敏感度为前提。

MQL5 / C++
  {
      PositionSelectByTicket(Ticket[i]);
      d+=PositionGetDouble(POSITION_PROFIT);
   }
   if(d<Drw) Drw=d;
   if(Drw<mDrw)
     {
       mDrw=Drw;
       TimeParam[class="num">2]=TimeCurrent();
     }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 计时器函数                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
   if(bstate!=BCLOSE)
     {
       Stat();
     }
   if(IsNewCandle())
     {
       class="type">class="kw">double res=GetValue1(class="num">0);
       PushWpr(res);
       if(dWpr[class="num">1]!=EMPTY_VALUE)
         {
           if(bstate==BBUY && (dWpr[class="num">0]>=BUYPROFIT ))
             {
               CloseAllPos();
               bstate=BCLOSE;
             }
           if(bstate==BSELL && (dWpr[class="num">0]<=SELLPROFIT ))
             {
               CloseAllPos();
               bstate=BCLOSE;
             }
           if(bstate==BCLOSE && dWpr[class="num">0]<=SELL2FROM && dWpr[class="num">0]>=SELL2TO && dWpr[class="num">1]>=SELL1LIMIT)
             {
               EnterSell(class="num">0.01);
               bstate=BSELL;
               TimeParam[class="num">0]=TimeCurrent();
               TradeCurrency=class="num">0;
               Drw=class="num">1000000;
               iTradeCount++;
               sLog=StringFormat("<tr>\r\n<td>%d</td>\r\n<td>卖出</td>\r\n<td>%.2f/<br/>%.2f</td>\r\n<td>%s/<br/>",iTradeCount,dWpr[class="num">1],dWpr[class="num">0],TimeToString(TimeCurrent()));
               class="kw">return;
             }
           if(bstate==BCLOSE && dWpr[class="num">0]>=BUY2FROM && dWpr[class="num">0]<=BUY2TO && dWpr[class="num">1]<=BUY1LIMIT)
             {
               EnterBuy(class="num">0.01);
               bstate=BBUY;
               TimeParam[class="num">0]=TimeCurrent();
               TradeCurrency=class="num">0;
               Drw=class="num">1000000;
               iTradeCount++;
               sLog=StringFormat("<tr>\r\n<td>%d</td>\r\n<td>买入</td>\r\n<td>%.2f/<br/>%.2f</td>\r\n<td>%s/<br/>",iTradeCount,dWpr[class="num">1],dWpr[class="num">0],TimeToString(TimeCurrent()));

「一键平仓与双向建仓的执行细节」

这段逻辑把多品种组合的头寸管理压成了两个动作:收盘时统一清仓、信号触发时统一按方向开仓。外汇与贵金属杠杆高,批量平仓若遇点差跳空,滑点可能吃掉原本算好的利润空间。 CloseAllPos 里先循环 LG 个品种,把每个仓位的 POSITION_PROFIT 与 POSITION_SWAP 相加存进 TradeResult[i],同时拼出带品种名和金额的 HTML 片段。清仓用 Trade.PositionClose(Ticket[i]),最后把汇总字符串通过 FileWriteString 写进日志句柄 lh,实盘跑前先确认 lh 已用 FILE_ANSI 或对应编码打开,否则中文品种名易乱码。 EnterBuy 与 EnterSell 都按 bDirect[i] 决定真实方向:bDirect 为真时 EnterBuy 发 Buy、EnterSell 发 Sell;为假则反向。Ticket[i] 取 Trade.ResultDeal() 而非 ResultOrder,因为市价成交返回的是 deal ticket,用错会在下一轮平仓时找不到单。 回测时把 LG 设成 5、品种换成 XAUUSD 与四个主流直盘,能看到 TradeCurrency 一栏每次平仓累计浮动盈亏精确到分,足以拿去核对跟手动平仓的差值。

MQL5 / C++
class="type">void CloseAllPos()
  {
   CTrade Trade;
   Trade.LogLevel(LOG_LEVEL_NO);
   TimeParam[class="num">1]=TimeCurrent();
   class="type">class="kw">string p="<td>";
   for(class="type">int i=class="num">0; i<LG; i++)
     {
      TradeResult[i]=PositionGetDouble(POSITION_PROFIT)+PositionGetDouble(POSITION_SWAP);
      p+=StringFormat("%s = %.2f<br/>",pair[i],TradeResult[i]);
      TradeCurrency        += TradeResult[i];
      TradeTotalResult[i] += TradeResult[i];
      Trade.PositionClose(Ticket[i]);
     }
   p+="</td>\r\n";
   TradeTotalCurrency+=TradeCurrency;
   sLog += StringFormat("%s/<br/>%s</td>\r\n<td>%.2f/<br/>%.2f</td>\r\n",TimeToString(TimeParam[class="num">1]), TimeIntervalToStr(TimeParam[class="num">0], TimeParam[class="num">1]), Drw, TradeCurrency );
   sLog += p;
   FileWriteString(lh,sLog);
  }
class="type">void EnterBuy(class="type">class="kw">double lot)
  {
   CTrade Trade;
   Trade.LogLevel(LOG_LEVEL_NO);
   for(class="type">int i=class="num">0; i<LG; i++)
     {
      if(bDirect[i])
        { class=class="str">"cmt">//发送买入
         Trade.Buy(lot,pair[i]);
         Ticket[i]=Trade.ResultDeal();
        }
      else
        { class=class="str">"cmt">//发送卖出
         Trade.Sell(lot,pair[i]);
         Ticket[i]=Trade.ResultDeal();
        }
     }
  }
class="type">void EnterSell(class="type">class="kw">double lot)
  {
   CTrade Trade;
   Trade.LogLevel(LOG_LEVEL_NO);
   for(class="type">int i=class="num">0; i<LG; i++)
     {
      if(bDirect[i])
        { class=class="str">"cmt">//发送卖出
         Trade.Sell(lot,pair[i]);
         Ticket[i]=Trade.ResultDeal();
        }
      else
        { class=class="str">"cmt">//发送买入
         Trade.Buy(lot,pair[i]);
         Ticket[i]=Trade.ResultDeal();

EA退出时怎么把多币种回测结果落盘成HTML

OnDeinit 里做的是收尾:先 EventKillTimer 停掉定时器,再把累积的成交计数、总盈利、最大回撤和逐货币对收益写进日志文件。注意它用 FileWriteString 拼出了带 thead/tbody 的表格,最后补 </body></html> 并 FileClose 关句柄,少了这一步 MT5 可能留个锁定的临时文件。 释放指标句柄不能漏。循环里对 h1[i] 判断不等于 INVALID_HANDLE 就调 IndicatorRelease,否则 EA 反复加载可能把终端指标资源拖垮;LG 是货币对数组长度,实战中多品种扫描 EA 这个值常设在 5~10 之间。 IsNewCandle 用静态变量 candle 存上一根的时间标记,H1 比 Hour()、H4 比 Hour()/4、D1 比 Day() 来识别新K线。Hour4 直接整除以 4 会丢失分钟对齐,若你的经纪商 H4 开盘偏移不是整点,这块要改。 Day 函数靠 TimeCurrent 填 MqlDateTime 取 day 字段,比用本地时间稳,能避开跨时区 VPS 的日期错位。把这段直接塞进 MT5 策略测试器的 OnDeinit,跑一遍就能在文件目录翻到带总结果的 html。

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- 摧毁计时器
   EventKillTimer();
   FileWriteString(lh,"</tbody>\r\n</table>\r\n");
   FileWriteString(lh,"<H2>总结果</H2>\r\n");
   FileWriteString(lh,"<table border=\"class="num">1\" width=\"class="num">100%\" cellspacing=\"class="num">0\" cellpadding=\"class="num">5\">\r\n");
   FileWriteString(lh,"<thead>\r\n<tr>\r\n<th>成交<br/>计数</th>\r\n<th>盈利</th>\r\n<th>最大回撤</th>\r\n<th>货币对盈利</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n");
   class="type">class="kw">string p = StringFormat("<tr><td>%d</td>\r\n<td>%.2f</td>\r\n<td>%.2f at<br/>%s</td>\r\n<td>",iTradeCount,TradeTotalCurrency,mDrw,TimeToString(TimeParam[class="num">2]));
   for(class="type">int i=class="num">0; i<LG; i++)
     {
       if(h1[i]!=INVALID_HANDLE) IndicatorRelease(h1[i]);
       p+=StringFormat("%s = %.2f<br/>",pair[i],TradeTotalResult[i]);
     }
   p+="</td>\r\n</tr>\r\n";
   FileWriteString(lh,p);
   FileWriteString(lh,"</tbody>\r\n</table>\r\n");
   FileWrite(lh,"</body>\r\n</html>"); class=class="str">"cmt">//结束日志
   FileClose(lh);
  }
class="type">bool IsNewCandle()
  {
   class="kw">static class="type">int candle=-class="num">1;
   class="type">int t1=class="num">0;
   class="kw">switch(_Period)
     {
      case PERIOD_H1:  t1 = Hour();   class="kw">break;
      case PERIOD_H4:  t1 = Hour4();  class="kw">break;
      case PERIOD_D1:  t1 = Day();    class="kw">break;
     }
   if(t1!=candle) {candle=t1; class="kw">return(true);}
   class="kw">return (class="kw">false);
  }
class="type">int Hour4(){class="kw">return((class="type">int)Hour()/class="num">4);}
class="type">int Day()
  {
   class="type">MqlDateTime tm;
   TimeCurrent(tm);
   class="kw">return(tm.day);
  }

◍ 时间函数与周期字符串的底层拆法

在 MT5 脚本里拿当前小时数,最轻量的写法是直接调 TimeCurrent 填进 MqlDateTime 结构,再取 hour 字段返回。这样比用 Hour() 系统函数更可控,也方便后续接会话过滤逻辑。 TimeIntervalToStr 这段把两个 datetime 的差值转成可读串,但注意它手动把年减 1970、月日各减 1——这是把 MQL5 的 TIME_DATE 格式往「人类习惯的 0 基准」掰。你在回测日志里看到 54.0.0 这种输出,实际代表 2024 年 1 月 1 日,不是 bug。 TimeFrameToShortString 只接了 H1/H4/D1 三个分支,其余周期直接吐空串。若你的策略用到 M15 或 MN1,得自己补 case,否则面板标签会留白。外汇与贵金属波动受杠杆放大,改这类底层函数前建议在策略测试器用 2023 全年 tick 数据跑一遍,确认时间解析无偏移。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int Hour()
  {
   class="type">MqlDateTime tm;
   TimeCurrent(tm);
   class="kw">return(tm.hour);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string TimeIntervalToStr(class="type">class="kw">datetime dt1,class="type">class="kw">datetime dt2)
  {
   class="type">class="kw">string tm;
   if(dt1 >= dt2)  tm = TimeToString(dt1 - dt2);
   else tm = TimeToString(dt2 - dt1,TIME_DATE|TIME_MINUTES|TIME_SECONDS);
   class="type">class="kw">string ta[],ta1[];
   StringSplit(tm,StringGetCharacter(" ",class="num">0),ta);
   StringSplit(ta[class="num">0],StringGetCharacter(".",class="num">0),ta1);
   ta1[class="num">0] = IntegerToString( StringToInteger(ta1[class="num">0]) - class="num">1970);
   ta1[class="num">1] = IntegerToString( StringToInteger(ta1[class="num">1]) - class="num">1);
   ta1[class="num">2] = IntegerToString( StringToInteger(ta1[class="num">2]) - class="num">1);
   class="kw">return (ta1[class="num">0] + "." + ta1[class="num">1] + "." + ta1[class="num">2] + " " + ta[class="num">1]);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string TimeFrameToShortString(ENUM_TIMEFRAMES period)
  {
   class="kw">switch(period)
     {
      case PERIOD_H1:  class="kw">return ("H1");
      case PERIOD_H4:  class="kw">return ("H4");
      case PERIOD_D1:  class="kw">return ("D1");
     }
   class="kw">return ("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「从欧元篮子报表看EA形态参数落地」

EA 报表本身不复杂,但很多人只盯着总盈利,忽略了它把每一组挂单逻辑都摊开在首屏。以欧元篮子为例,报表第一行直接给出图表名和 EA 加载时的时间帧,这一步决定了后面所有点位的坐标基准。 往下是两套独立参数:买入篮子与卖出篮子各自的点位定义。点 1 对应 SELL1LIMIT 或 BUY1LIMIT,即首层挂单触发价;点 2 带 FROM 与 TO 区间(SELL2FROM/SELL2TO 或 BUY2FROM/BUY2TO),说明第二层不是单点而是范围挂单。平仓点用 SELLPROFIT / BUYPROFIT 标记,表示在该形态位平仓。 开 MT5 跑一版欧元篮子回测,把报表里点 2 的 FROM–TO 跨度从 15 点调到 30 点,能看到成交密度明显下降,滑点概率可能随之降低。外汇与贵金属杠杆高,报表参数变动不代表收益改善,仅作结构验证用。

篮子EA的参数阈值与正回撤报表改造

形态参数直接决定篮子进场的边界。买入侧点1设在-80,点2区间-70至-50,平仓回归0;卖出侧对称,点1为80,点2在70至50,平仓同样回0。这套阈值在D1上触发的交易极少,22笔成交跨数月才跑完,说明日线级形态本身稀缺。 报告表格的顺序是固定的:编号、类型(卖/买篮子)、WPR(P1/P2)双点入场读数、时间(起/止/长)、回撤/盈利、各货币对盈亏。实测首笔卖出单从2016.03.23 20:00撑到次日凌晨04:00,历时8小时亏16.34美元,其中EURUSD贡献-2.55美元,七币种全红。 总表给出全局数:22笔净赚189.06美元,最大回撤-52.37出现在2016.05.02 19:53,EURUSD累计+52.43居首。超买/超卖有效带在±60%至±70%,突破±70%后交易锐减;点1需落到±90%附近才偶发极端形态,而点2滑向±40%时指标抖动加剧、假信号堆出。 原报表只看锁死亏损的回撤,漏了“本可赚却没锁”的部分。新版把订单在指标未达平仓值前的浮盈峰值记为“正回撤”,塞进倒数第二列,回撤/+回撤/盈利三数并列;总表也加“最大正回撤”行。这样你能直接比对篮子的潜在兑现力与实亏。 NZD篮子验证了假设:H1报告里接近横盘起点的区域假信号密布,D1依旧稀少。剩余AUD/EUR/GBP/CAD/JPY/CHF报告在Doc.zip,外汇与贵金属篮子测试均属高波动高风险,参数结论仅代表历史样本倾向,开MT5加载testBasket1.mq5跑同段日期可复现上述分布。

◍ H4 才是值得盯的时间帧

实测把超买阈值压到 60%–70%、超卖压到 -60%–-70%,和标准 WPR 指标的对应水位能对上,说明这套分级不是拍脑袋。三个时间帧、八个货币篮子跑下来,两种突破形态(点1破80%/70%,点2回落在70%/60%至50%区间,指标≤0%平篮子)的结果分化很明显。 H1 上八个篮子几乎全绿(亏损):形态1里 GBP 亏 991、JPY 亏 514、AUD 亏 590;形态2里 GBP 亏 714、JPY 亏 272。H1 噪音太高,这种多货币篮子策略在这周期上大概率被洗出去。 H4 就好看很多:形态1的 AUD 赚 286、USD 赚 189、JPY 赚 120;形态2的 GBP 赚 80、NZD 赚 172、EUR 赚 25。两种形态谁更优还没法定论,但 H4 值得后续重点盯。 原方案靠组合 WPR 触零平篮子,对回撤控制不够。篮子订单按存款货币挂止损和尾随止盈更合理——盈利时不会被瞬时反拉吞掉,亏损也能框住。这招可能把 H1 扭正、把 H4 利润拉厚,外汇和贵金属这类高杠杆品种先想清楚敞口再上。

「下一阶段该往哪挖」

第一阶段形态测试已经跑完,但手里的样本还只是基础盘。把已出的结果摊开逐根 K 线核对,比急着下结论更有用——很多假信号藏在周期切换里。 接下来的重心会放在两件事:把现有信号的触发条件做归类压缩,再拿其它已知形态的信号做交叉比对。这类附加信号若能对齐,原本模糊的胜率边界可能往前推一点。 外汇与贵金属杠杆高,形态共振也只是提高概率,不是免死金牌。开 MT5 把这几组形态叠到同一图表上,自己看错位发生在哪根棒线,比看任何转述都直接。

随包附带的验证素材

这篇关于货币篮子交易的文章把可复现的素材都打进了压缩包,读者不必从零写代码就能在 MT5 里跑通逻辑。核心 EA 是 testBasket.mq5(26.57 KB)与升级版 testBasket1.mq5(27.74 KB),前者测 USD 篮子、后者覆盖 NZD 及其它篮子,对应 Html 报告分别是 DocUSD.zip(14.3 KB)、DocNZD.zip(27.8 KB)和 Doc.zip(99.72 KB)。 Pair.zip(7.92 KB)存的是三个时间帧内各篮子可能成交数的统计结果,test.zip(3.82 KB)里是 testIndexZig-Zag1.mq5 与 testWPReur.mq5 两个指标。外汇与贵金属属高风险品种,回测报告只代表历史样本,实盘表现可能偏离。 开 MT5 把 testBasket.mq5 拖进策略测试器,选 USD 篮子跑一遍,再对照 DocUSD.zip 里的成交分布,就能判断这套篮子思路是否值得继续调参。

常见问题

在EA退出时用FileOpen配合HTML标签把各品种的形态得分、回撤值逐行写入文件,浏览器打开即可比对,不用再翻日志。
用计时器(EventSetTimer)在固定间隔重算并写入全局变量或图表标签,避免只在Tick事件里更新导致漏算。
可以,小布能读取你的品种页数据,把各币种的形态触发与回撤异常用AIGC浓缩成每日盯盘摘要,省去手动导表。
必须按魔法码遍历所有持仓并校验品种前缀,否则易漏平隔夜单;双向单要分开设止损,别共用同一价位。
周期常量本质是整数分钟数,用PeriodSeconds转秒后再除心跳间隔,就能算出每根K线应触发的计时器次数。