MVC 设计范式及其可能的应用·进阶篇
📘

MVC 设计范式及其可能的应用·进阶篇

第 2/3 篇

「指标缓冲区的初始化与增量计算边界」

自定义指标在 OnCalculate 里最先要处理的是数据量不足的情况。代码用 if(rates_total <= 4) return 0; 直接拦截,说明该算法至少需要 5 根 K 线才能跑出第一个有效值,这是回测时前 4 根 bar 全为空值的根本原因。 增量刷新逻辑靠 limit = rates_total - prev_calculated 判断。limit 为 0 代表无新柱,1 代表只来一根新柱只算最新 shift,大于 1 才走全量重置:ArrayInitialize(lb, EMPTY_VALUE) 后从 rates_total-4 循环到 1 逐根重算,避免每次 tick 都全表扫。 视图类 CView 把缓冲区封装掉了。构造函数写死 _Width=2_Name="Primitive",Initialize 里 SetIndexBuffer(0,_lb,INDICATOR_DATA) 绑定一号缓冲并 ArraySetAsSeries(_lb,true) 把时间轴倒序,开 MT5 新建指标时改这两个值就能换线宽和面板名。 外汇与贵金属杠杆高、滑点跳空频繁,这类依赖 close 数组的轻量指标在重大数据行情中可能漏算瞬时跳空柱,实盘前务必用 EURUSD 的 M1 历史数据跑一遍验证边界。

MQL5 / C++
const class="type">class="kw">datetime &time[],
const class="type">class="kw">double &open[],
const class="type">class="kw">double &high[],
const class="type">class="kw">double &low[],
const class="type">class="kw">double &close[],
const class="type">long &tick_volume[],
const class="type">long &volume[],
const class="type">int &spread[]) {

  if(rates_total <= class="num">4) class="kw">return class="num">0;

  class="type">int limit = rates_total - prev_calculated;

  if (limit == class="num">0)      {
  } else if (limit == class="num">1) {

    lb[class="num">1] = Prepare(time, open, high, low, close, class="num">1);
    class="kw">return(rates_total);
  } else if (limit > class="num">1)  {

    ArrayInitialize(lb, EMPTY_VALUE);

    limit = rates_total - class="num">4;
    for(class="type">int i = limit; i >= class="num">1 && !IsStopped(); i--) {
      lb[i] = Prepare(time, open, high, low, close, i);
    }
    class="kw">return(rates_total);

  }
  lb[class="num">0] = Prepare(time, open, high, low, close, class="num">0);
  class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property indicator_buffers class="num">1
class="macro">#class="kw">property indicator_plots   class="num">1
class CView
{
   class="kw">public:
      class="type">void CView();
      class="type">void ResetBuffers();
      class="type">int  Initialize();
      class="type">void SetData(class="type">class="kw">double value, class="type">int shift = class="num">0);

   class="kw">private:
      class="type">class="kw">double _lb[];
      class="type">int    _Width;
      class="type">class="kw">string _Name;
      class="type">class="kw">string _Label;  

};class=class="str">"cmt">// class CView
class="type">void CView::CView()
{
      _Width = class="num">2;
      _Name  = "Primitive" ;
      _Label = "Label1";
}class=class="str">"cmt">// class="type">void CView::CView()
class="type">void CView::ResetBuffers()
{
  ArrayInitialize(_lb, EMPTY_VALUE);
}
class="type">int CView::Initialize()
{
      SetIndexBuffer(class="num">0,  _lb, INDICATOR_DATA);
      ArraySetAsSeries(_lb, true);

      IndicatorSetString(INDICATOR_SHORTNAME, _Name);

指标外观与缓冲写入的底层绑定

自定义指标在 MT5 里跑起来之前,得先把绘制属性钉死。上面这段把第 0 号绘图索引设成实线、深石板蓝、线宽走变量 _Width,标签用 _Label——改其中任意一个宏,终端里曲线的观感立刻变,但计算逻辑不受影响。 IndicatorSetInteger(INDICATOR_DIGITS, _Digits) 这行决定价格小数位跟随品种报价,黄金 XAUUSD 的 _Digits 通常是 2 或 3,EURUSD 多为 5,错设会让你在副图上看到错位的价格标注。 SetData 方法看着简单,_lb[shift] = value 直接把算好的值塞进线缓冲对应柱位移。shift=0 是当前柱,历史值靠正偏移回写;若外部调用时 shift 超出缓冲长度,MT5 会静默丢值,图表右侧出现断线,这种 bug 只能开 MT5 用专家日志抓。 IndicatorSetString(INDICATOR_SHORTNAME, _Name) 控制导航树里的短名,不设的话默认带参数尾巴,多品种加载时容易混淆。外汇与贵金属杠杆高,指标仅作概率参考,实盘前务必在策略测试器跑一遍不同点差环境。

MQL5 / C++
IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

PlotIndexSetString(class="num">0, PLOT_LABEL, _Label);
PlotIndexSetInteger(class="num">0, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(class="num">0, PLOT_LINE_COLOR, clrDarkSlateBlue);
PlotIndexSetInteger(class="num">0, PLOT_LINE_STYLE, STYLE_SOLID);
PlotIndexSetInteger(class="num">0, PLOT_LINE_WIDTH, _Width);

class="kw">return(INIT_SUCCEEDED);
}
class="type">void CView::SetData(class="type">class="kw">double value,class="type">int shift)
{
 _lb[shift] = value;
}
IndicatorSetString(INDICATOR_SHORTNAME, _Name);
IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

◍ 把伪EA拆成控制器、视图和模型

先搭一个极简伪EA:前一根蜡烛收阳就模拟多单,收阴就模拟空单,场内只允许一笔持仓,不真正下单。它的 OnTick 只判断新蜡烛和涨跌,然后切状态变量 iBuy / iSell。 在EA里,OnInit、OnDeinit、OnTick 这些生命周期函数天然属于控制器;视图不画图形,只负责把持仓状态呈现给用户,所以订单、虚拟止损止盈、加权均价都归视图;模型则装决策逻辑:怎么开仓、手数怎么算、止损止盈怎么定,资金管理也应在模型里实现。 把上面那个伪EA按职责拆开,函数挪进对应子文件夹后,OnTick 明显变短。EA 越复杂,MVC 这种分层就越能救命——第三方接手代码时,找问题不用通读全文。 为了验证范式在‘真实’代码上的效果,作者翻出 2013 年写的 $OrdersInTheMorning 旧草稿:周一指定时间挂一买一卖单,距市价一定距离,任一下单触发就删另一单,周五晚平仓,只跑指定货币对。原版是 MT4 的,重写成 MT5 后主函数又长又嵌套,WeekOrderParam 一个辅助函数既管入场又管开仓,明显越界。 重构时,先把货币对列表挪到输入参数,初始化垃圾清出 OnInit,建 EA_Init.mqh 用类装初始化(成员全公开只是为省访问器噪音,真实项目别学)。模型层建 Model.mqh 的 CModel 类,只留检查入场和离场两个方法加持仓标志;视图层建 View.mqh,先试了三个裸函数各 new 一个 CTrade 对象——不是好写法,随后收进 CView 类持有一个私密 CTrade 字段。控制器补 OnTrade 处理程序删未触发挂单,这是全场唯一有点绕的地方。 改完的主文件只剩调度,要加功能直接动对应组件,不影响另外两层。外汇与贵金属杠杆高,这类挂单策略在跳空时可能两边止损都被扫,实盘前务必在 MT5 策略测试器跑历史数据。

MQL5 / C++
<span class="keyword">class="type">class="kw">datetime</span> dtNow;
<span class="keyword">class="type">int</span> iBuy, iSell;
<span class="keyword">class="type">int</span> <span class="functions">OnInit</span>()
{
&nbsp;&nbsp; iBuy&nbsp;&nbsp;= iSell = <span class="number">class="num">0</span>;
&nbsp;&nbsp;
&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(<span class="macro">INIT_SUCCEEDED</span>);
}
<span class="keyword">class="type">void</span> <span class="functions">OnDeinit</span>(<span class="keyword">const</span> <span class="keyword">class="type">int</span> reason)
{
}
<span class="keyword">class="type">void</span> <span class="functions">OnTick</span>()
{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (IsNewCandle() )
{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> o = <span class="functions">iOpen</span>(<span class="macro">NULL</span>,<span class="macro">PERIOD_CURRENT</span>,<span class="number">class="num">1</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> c = <span class="functions">iClose</span>(<span class="macro">NULL</span>,<span class="macro">PERIOD_CURRENT</span>,<span class="number">class="num">1</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span> (c &lt; o)
{ <span class="comment">class=class="str">"cmt">// Enter Sell</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (GetSell() == <span class="number">class="num">1</span>) <span class="keyword">class="kw">return</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (GetBuy()&nbsp;&nbsp;== <span class="number">class="num">1</span>) CloseBuy();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EnterSell();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   }
<span class="keyword">else</span>
{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Enter Buy</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (GetBuy()&nbsp;&nbsp;== <span class="number">class="num">1</span>) <span class="keyword">class="kw">return</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (GetSell() == <span class="number">class="num">1</span>) CloseSell();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EnterBuy();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  }<span class="comment">class=class="str">"cmt">// if (IsNewCandle() )&nbsp;&nbsp; </span>
}<span class="comment">class=class="str">"cmt">// class="type">void OnTick()</span>
<span class="keyword">class="type">bool</span> IsNewCandle()
{
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">datetime</span> d = <span class="functions">iTime</span>(<span class="macro">NULL</span>, <span class="macro">PERIOD_CURRENT</span>, <span class="number">class="num">0</span>);
&nbsp;&nbsp; <span class="keyword">if</span> (dtNow == -<span class="number">class="num">1</span> || dtNow != d)
{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dtNow = d;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">true</span>;
&nbsp;&nbsp;   }&nbsp;&nbsp;
&nbsp;&nbsp; <span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>;
}<span class="comment">class=class="str">"cmt">// class="type">bool IsNewCandle()</span>
<span class="keyword">class="type">void</span> CloseBuy()&nbsp;&nbsp;{iBuy = <span class="number">class="num">0</span>;}
<span class="keyword">class="type">void</span> CloseSell() {iSell = <span class="number">class="num">0</span>;}
<span class="keyword">class="type">void</span> EnterBuy()&nbsp;&nbsp;{iBuy = <span class="number">class="num">1</span>;}

「多币种 H4 参数与开关的落地写法」

这段逻辑把六个日元交叉盘(AUDJPY、CADJPY、EURJPY、NZDJPY、GBPJPY、CHFJPY)统一塞进一个字符串数组,在 OnTick 里按 H4 周期逐个跑 WeekOrderParam。外汇与贵金属杠杆高,跨币种同时挂单会放大回撤,实盘前先在策略测试器里用 0.03 手验证。 input 参数里 delta=200 是点数阈值基准, volumes=0.03 固定手数,止损系数 sTopLossKoeff=1、止盈系数 tAkeProfitKoeff=2,也就是盈亏比倾向 2:1。iTradeCount=3 限制单币种交易次数,iSlippage=15 点容差,iTimeOut=2000 毫秒网络超时。 oNlyMondeyOrders=true 时只在周一 DayOfWeek()==1 出手;bHLprocess 控制高低点处理逻辑;sTimeToOpenOrders="05:05"、sTimeToCloseOrders="22:00" 框定日内交易窗口。iTimeIntervalForWork=0.5 若不在 0~24 区间会弹 Alert 拦截,避免乱设周期。 把这段代码直接贴进 MT5 EA 的全局区,改 smb[] 数组就能换品种池;想降频就把 iTradeCount 调到 1,日元夜盘跳空可能触发额外滑点。

MQL5 / C++
class="type">void EnterSell() {iSell = class="num">1;}
class="type">int GetBuy()    {class="kw">return iBuy;}
class="type">int GetSell()   {class="kw">return iSell;}
class="type">void OnTick()
{
      if (IsNewCandle() )
{
        class="type">class="kw">double o = iOpen(NULL,PERIOD_CURRENT,class="num">1);
        class="type">class="kw">double c = iClose(NULL,PERIOD_CURRENT,class="num">1);
        if (MaySell(o, c) ) EnterSell();
        if (MayBuy(o, c)  ) EnterBuy();
      }class=class="str">"cmt">// if (IsNewCandle() )  
}class=class="str">"cmt">// class="type">void OnTick()
class="macro">#class="kw">property copyright "Copyright class="num">2013, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "http:class=class="str">"cmt">//www.metaquotes.net"
class="macro">#include <Trade\Trade.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">class="kw">double delta = class="num">200;
input class="type">class="kw">double volumes = class="num">0.03;
input class="type">class="kw">double sTopLossKoeff = class="num">1;
input class="type">class="kw">double tAkeProfitKoeff = class="num">2;
input class="type">int iTHour = class="num">0;
input class="type">bool bHLprocess = true;
input class="type">bool oNlyMondeyOrders = class="kw">false;
input class="type">class="kw">string sTimeToCloseOrders = "class="num">22:class="num">00";
input class="type">class="kw">string sTimeToOpenOrders  = "class="num">05:class="num">05";
input class="type">class="kw">double iTimeIntervalForWork = class="num">0.5;
input class="type">int iSlippage = class="num">15;
input class="type">int iTradeCount = class="num">3;
input class="type">int iTimeOut = class="num">2000;
class="type">int dg;
class="type">bool bflag;
class="type">class="kw">string smb[] = {"AUDJPY","CADJPY","EURJPY","NZDJPY","GBPJPY","CHFJPY"};
class="type">int init()
{
   if ( (iTimeIntervalForWork < class="num">0) || (iTimeIntervalForWork > class="num">24) )
   {
      Alert("... ",iTimeIntervalForWork);
   }
   class="kw">return (class="num">0);
}
class="type">void OnTick()
{
   if ((oNlyMondeyOrders == true) && (DayOfWeek() != class="num">1) )
   {
   }
   else
   {
        class="type">int count=ArraySize(smb);
        class="type">bool br = true;
        for (class="type">int i=class="num">0; i<count;i++)
        {
          if (!WeekOrderParam(smb[i], PERIOD_H4, delta*SymbolInfoDouble(smb[i],SYMBOL_POINT) ) )
            br = class="kw">false;
        }
        if (!br)
          Alert("...");

按星期与小时锚定周内的突破价位

这段逻辑把「只在周一交易」和「按指定小时取 K 线」两套分支揉在一个函数里。当 oNlyMondeyOrders 为 true 时,向后翻 k=3 根 0 周期 K 线,用 TimeDayOfWeek 滤出周一、再用 TimeHour 匹配 iTHour,命中即把下标 j 记下并 break;否则就在最近 3 根里只按小时匹配。 若 iTHour 小于 0,直接令 j=0,相当于拿当前最新根做基准;若循环完 j 仍是 -1,Print("tf?") 并返回 false,说明时间条件在最近 3 根内没碰上,调用方应放弃挂单。 价位计算分两种:bHLprocess 为 true 时,买突破价 bsp = 第 j 根最高价 + dlt,卖突破价 ssp = 第 j 根最低价 - dlt;为 false 时改用开收二者的最大值/最小值加减 dlt。全部经 NormalizeDouble 按 dg 位数舍入,避免跨品种点值错位。 外汇与贵金属杠杆高,这套锚定只解决「取哪根、算什么价」,实盘触发仍可能滑点吞掉 dlt 缓冲,建议先在 MT5 策略测试器用 EURUSD 的 M15 跑一遍 j 的 Alert 输出确认星期过滤符合预期。

MQL5 / C++
  class="type">bool WeekOrderParam(class="type">class="kw">string symbol,class="type">int tf, class="type">class="kw">double dlt)
  {
   class="type">int j = -class="num">1;
   class="type">class="kw">datetime mtime = class="num">0;
   class="type">int k = class="num">3;
   Alert(symbol);
   if (iTHour >= class="num">0)
   {
      if (oNlyMondeyOrders == true)
      {
         for (class="type">int i = class="num">0; i < k; i++)
         {
            mtime = iTime(symbol,class="num">0,i);
            if (TimeDayOfWeek(mtime) == class="num">1)
            {
               if (TimeHour(mtime) == iTHour)
               {
                  j = i;
                  break;
               }
            }
         }
      }
      else
      {
         for (class="type">int i = class="num">0; i < k; i++)
         {
            mtime = iTime(symbol,class="num">0,i);
            if (TimeHour(mtime) == iTHour)
            {
               j = i;
               break;
            }
         }
      }
      if (j == -class="num">1)
      {
         Print("tf?");
         class="kw">return (class="kw">false);
      }
   }
   else
      j = class="num">0;
   Alert(j);
   class="type">class="kw">double bsp,ssp;
   if (bHLprocess)
   {
      bsp = NormalizeDouble(iHigh(symbol,class="num">0,j) + dlt, dg);
      ssp = NormalizeDouble(iLow(symbol,class="num">0,j) - dlt, dg);
   }
   else
   {
      bsp = NormalizeDouble(MathMax(iOpen(symbol,class="num">0,j),iClose(symbol,class="num">0,j)) + dlt, dg);
      ssp = NormalizeDouble(MathMin(iOpen(symbol,class="num">0,j),iClose(symbol,class="num">0,j)) - dlt, dg);
   }

◍ 挂单与时段开关的底层拼装

止损幅度先按系数折算:slsize 用 NormalizeDouble 把 (bsp - ssp) 乘 sTopLossKoeff 对齐到小数位 dg,买暂停损挂在 bsp、卖暂停损挂在 ssp。 止盈不是固定点值,而是 tAkeProfitKoeff 倍的 slsize:tpb = bsp + 系数*slsize,tps = ssp - 系数*slsize,这样盈亏比随通道宽度自适应,外汇与贵金属品种点值差异大时更不容易手抖填错。 下单走封装后的 mOrderSend,内部用 for 循环重试 iTradeCount 次,ticket<0 就打印错误码与超时值,成功即 break;原函数里真正的 OrderSend 被注释掉,实盘前必须换回官方发单调用。 CInit 负责把逗号分隔的品种对拆成 names[],再逐个填 points[i]=SymbolInfoDouble(name,SYMBOL_POINT),多品种遍历止损换算时不用反复查点值。 CModel 用 bFlag 锁住每周只开一次:TimeToOpen 限定 day_of_week==1 且 hour>=iHourOpen 才放行,TimeToClose 则卡 day_of_week==5 且 hour>=iHourClose,等于把仓位生命周期钉在周一开盘到周五某小时后,规避周末跳空这类高概率风险事件。 Enter() 里只看到 trade.SetExpertMagicNumber(Magic),具体 volume 与 symbol 选取在前述逻辑算完后再补,MT5 里把 Magic 与 iHourOpen / iHourClose 调成你经纪商冬夏令时对应的数值就能跑通这套框架。

MQL5 / C++
class="type">class="kw">double slsize = NormalizeDouble(sTopLossKoeff * (bsp - ssp), dg);
class="type">class="kw">double tpb = NormalizeDouble(bsp + tAkeProfitKoeff*slsize, dg);
class="type">class="kw">double tps = NormalizeDouble(ssp - tAkeProfitKoeff*slsize, dg);
class="type">class="kw">datetime expr = class="num">0;
class="kw">return (mOrderSend(symbol,ORDER_TYPE_BUY_STOP,volumes,bsp,iSlippage,ssp,tpb,NULL,class="num">0,expr,CLR_NONE) && mOrderSend(symbol,ORDER_TYPE_SELL_STOP,volumes,ssp,iSlippage,bsp,tps,NULL,class="num">0,expr,CLR_NONE) );
}

class="type">int mOrderSend( class="type">class="kw">string symbol, class="type">int cmd, class="type">class="kw">double volume, class="type">class="kw">double price, class="type">int slippage, class="type">class="kw">double stoploss, class="type">class="kw">double takeprofit, class="type">class="kw">string comment = "", class="type">int magic=class="num">0, class="type">class="kw">datetime expiration=class="num">0, class="type">color arrow_color=CLR_NONE)
{
   class="type">int ticket = -class="num">1;
        for (class="type">int i = class="num">0; i < iTradeCount; i++)
        {
class=class="str">"cmt">//         ticket=OrderSend(symbol,cmd,volume,price,slippage,stoploss,takeprofit,comment,magic,expiration,arrow_color);
            if(ticket<class="num">0)
                Print(symbol,": ",GetNameOP(cmd), GetLastError() ,iTimeOut);
            else
                break;
        }
   class="kw">return (ticket);
} 
class CInit {
class="kw">public:
   class="type">void CInit(){}
   class="type">void Initialize(class="type">class="kw">string pair);
   class="type">class="kw">string names[];
   class="type">class="kw">double points[];   
   class="type">int iCount;
};
class="type">void CInit::Initialize(class="type">class="kw">string pair) {

   iCount = StringSplit(pair, StringGetCharacter(",", class="num">0), names);
   ArrayResize(points, iCount);
   for (class="type">int i = class="num">0; i < iCount; i++) {
      points[i] = SymbolInfoDouble(names[i], SYMBOL_POINT);
   }
}
class CModel {
class="kw">public:
            class="type">void CModel(): bFlag(class="kw">false) {}
            class="type">bool TimeToOpen();
            class="type">bool TimeToClose();
class="kw">private:
   class="type">bool bFlag;  
};
class="type">bool CModel::TimeToOpen() {
   if (bFlag) class="kw">return class="kw">false;
   class="type">MqlDateTime tm;
   TimeCurrent(tm);
   if (tm.day_of_week != class="num">1) class="kw">return class="kw">false;
   if (tm.hour < iHourOpen) class="kw">return class="kw">false;

   bFlag = true;
   class="kw">return true;  
}
class="type">bool CModel::TimeToClose() {
   if (!bFlag) class="kw">return class="kw">false;

   class="type">MqlDateTime tm;
   TimeCurrent(tm);
   if (tm.day_of_week != class="num">5)  class="kw">return class="kw">false;
   if (tm.hour < iHourClose) class="kw">return class="kw">false;

   bFlag = class="kw">false;
   class="kw">return true;  
}
class="type">void Enter() {

   CTrade trade;

   trade.SetExpertMagicNumber(Magic);

常见问题

在 OnInit 里用 SetIndexBuffer 绑定缓冲区,并在 OnCalculate 用 static 变量记录上次计算的 prev_calculated,只从边界往后算新增柱,避免每 tick 重跑全历史。
外观属性(如线宽、色)和缓冲写入在同一结构体绑定,写外观时若越界覆盖缓冲索引会污染数据;分开配置 Plot 和 Buffer 字段即可。
可以。把代码贴给小布,它会按 MVC 范式标出哪段属于视图、哪段属于模型,并提示控制器里是否夹带了计算逻辑。
用输入参数做币种白名单和星期枚举,在 OnTick 开头用 TimeDayOfWeek 判断当前日,非周三直接 return 不下单。
时段开关要拼在挂单函数前:先算本周突破价,再用小时区间过滤,区间外不调用 OrderSend,避免跨时段误触。