根据指定的分布法则为自定义品种的时间序列建模·进阶篇
📊

根据指定的分布法则为自定义品种的时间序列建模·进阶篇

(2/3)· 不会 MQL5 也能造出带趋势和形态的测试行情,这篇把脚本拆给你看

案例拆解 第 2/3 篇
很多人以为自定义品种只能手动填几根 K 线,结果回测和实盘偏差大到没法看。直接用现成货币对复制属性的偷懒做法,根本测不出你想验证的形态逻辑。本文接着上篇,把自动建品种和按分布法则灌数据的脚本拆开讲。

「自定义品种的手工参数注入」

在 MT5 里用代码造一个自定义品种时,成交与保证金相关的字段必须逐条写死,否则品种建出来也跑不了单。下面这段把最大成交量压到 500.00、最小步长 0.01,并清掉单向持仓限制(SYMBOL_VOLUME_LIMIT 设 0),适合做小合约模拟。 对冲保证金 SYMBOL_MARGIN_HEDGED 给的是 100000,意味着一手反向仓占约 10 万单位保证金;多空掉期分别为 -0.7 与 -1,隔夜持空比持多贵约 0.3 点,贵金属或外汇跨期价差需自己重算。 若基于已有基准品种克隆,则基础货币、计价货币、保证金币种都从 BaseName 继承,描述与 ISIN 等也照抄,只改名字和少数字段即可。 开 MT5 按 F4 把下面代码塞进脚本,跑完看 Experts 标签有没有 'created successfully';外汇与贵金属杠杆高,自建品种滑点和掉期可能偏离实盘,验证后勿直接上真金。

MQL5 / C++
(SetProperty(SName,SYMBOL_VOLUME_MAX,class="num">500.00,"")) &&              class=class="str">"cmt">// 成交执行的最大交易量
(SetProperty(SName,SYMBOL_VOLUME_STEP,class="num">0.01,"")) &&                  class=class="str">"cmt">// 成交执行的最小交易量变动间距
(SetProperty(SName,SYMBOL_VOLUME_LIMIT,class="num">0,"")) &&                    class=class="str">"cmt">// 此品种单向(买入或卖出)持仓和挂单的最大允许总交易量
(SetProperty(SName,SYMBOL_MARGIN_INITIAL,class="num">0,"")) &&                  class=class="str">"cmt">// 初始保证金
(SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,class="num">0,"")) &&              class=class="str">"cmt">// 维持保证金
(SetProperty(SName,SYMBOL_MARGIN_HEDGED,class="num">100000,"")) &&              class=class="str">"cmt">// 一个品种的一手反向持仓的合约或保证金大小
(SetProperty(SName,SYMBOL_SWAP_LONG,-class="num">0.7,"")) &&                    class=class="str">"cmt">// 多头的掉期利率
(SetProperty(SName,SYMBOL_SWAP_SHORT,-class="num">1,"")))                       class=class="str">"cmt">// 空头的掉期利率
      Print("Symbol ",SName," created successfully");
   else
      Print("Error setting symbol properties. Error code: ",GetLastError());
   }
   elseclass=class="str">"cmt">// 依据基准品种创建
   {
    if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) &&
       (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"",BaseName)) &&
       (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"",BaseName)) &&
       (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyFullName,"")) &&
       (SetProperty(SName,SYMBOL_BASIS,"",BaseName)) &&
       (SetProperty(SName,SYMBOL_FORMULA,"",BaseName)) &&
       (SetProperty(SName,SYMBOL_ISIN,"",BaseName)) &&
       (SetProperty(SName,SYMBOL_PAGE,"",BaseName)) &&
       (SetProperty(SName,SYMBOL_CHART_MODE,class="num">0,BaseName)) &&

自定义品种的属性清零初始化

在 MT5 里用代码创建自定义品种( synthetic symbol )时,第一步往往不是填真实参数,而是把整套交易与报价属性先归零,避免继承到脏默认值。上面这段连续 SetProperty 调用,就是把 33 个关键枚举属性一次性置 0。 注意 SYMBOL_SPREADSYMBOL_DIGITSSYMBOL_POINTSYMBOL_TRADE_CONTRACT_SIZE 这类字段若留空,后续计算保证金和 tick 价值会直接出错;外汇与贵金属自定义品种的高风险在于,点值和合约尺寸设错会让仓位风控完全失真。 BaseName 作为第三个参数传入,代表这些属性挂在哪个基础标的上;全部用 && 串接,意味着任意一行返回 false,整条初始化链就断。开 MT5 跑这段前,先确认 SNameBaseName 已正确声明,否则编译器会报未定义标识符。

MQL5 / C++
(SetProperty(SName,SYMBOL_SPREAD,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_SPREAD_FLOAT,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_DIGITS,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_BACKGROUND_COLOR,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TRADE_MODE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TRADE_EXEMODE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_ORDER_GTC_MODE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_FILLING_MODE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_EXPIRATION_MODE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_ORDER_MODE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TRADE_CALC_MODE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_SWAP_MODE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_OPTION_MODE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_OPTION_RIGHT,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_START_TIME,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_EXPIRATION_TIME,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_OPTION_STRIKE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_POINT,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,class="num">0,BaseName))

◍ 把基准品种的属性抄给自定义品种

上面那段连续 SetProperty 调用,是把 tick 价值、最小/最大/步进成交量、持仓限制、初始与维持保证金、对冲保证金、多空掉期全部先置 0,再尝试创建自定义品种。若整组写入成功,终端会打印 Symbol XXX created successfully;任意一项失败则走 else 分支报出 GetLastError 代码,方便你定位是哪一属性被经纪商拒绝。 SetProperty 有两个重载:字符串版先 ResetLastError,当 BaseSymName 为空时直接 CustomSymbolSetString 写死值;若传了基准名,则先用 SymbolInfoString 把基准品种的对应属性读出来,再原样抄给新符号。这样你不用手填 EURUSD 的点值描述,直接基于它克隆即可。 整数版函数头已给出(ENUM_SYMBOL_INFO_INTEGER + long 参数),实现逻辑与字符串版对称:无基准就写定值,有基准就 SymbolInfoInteger 读取后转发。外汇与贵金属自定义品种测试属高风险操作,属性填错可能在回测里给出失真的保证金占用,实盘前务必在策略测试器用极小量验证。 让小布替你跑这套:把上面代码塞进 MT5 脚本,BaseName 填 XAUUSD,SName 填 XAUUSD_TEST,跑完去市场报价窗口看是否出现克隆品种并选中。

MQL5 / C++
(SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_VOLUME_MIN,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_VOLUME_MAX,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_VOLUME_STEP,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_VOLUME_LIMIT,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_MARGIN_INITIAL,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_MARGIN_HEDGED,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_SWAP_LONG,class="num">0,BaseName)) &&
(SetProperty(SName,SYMBOL_SWAP_SHORT,class="num">0,BaseName)))
Print("Symbol ",SName," created successfully");
 else
Print("Error setting symbol properties. Error code: ",GetLastError());
}
if(SymbolSelect(SName,true))
 Print("Symbol ",SName," selected in Market Watch");
else
 Print("Error selecting symbol in Market Watch. Error code: ",GetLastError());
 }
}
class=class="str">"cmt">// 用于设置品种属性的函数
class="type">bool SetProperty(class="type">class="kw">string SymName,ENUM_SYMBOL_INFO_STRING SProp,class="type">class="kw">string PropValue,class="type">class="kw">string BaseSymName)
 {
 ResetLastError();
 if(BaseSymName=="")
  {
   if(CustomSymbolSetString(SymName,SProp,PropValue))
    class="kw">return true;
   else
    Print("Error setting symbol class="kw">property: ",SProp,". Error code: ",GetLastError());
  }
 else
  {
   class="type">class="kw">string SValue=SymbolInfoString(BaseSymName,SProp);
   if(CustomSymbolSetString(SymName,SProp,SValue))
    class="kw">return true;
   else
    Print("Error setting symbol class="kw">property: ",SProp,". Error code: ",GetLastError());
  }
 class="kw">return false;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SetProperty(class="type">class="kw">string SymName,ENUM_SYMBOL_INFO_INTEGER IProp,class="type">long PropValue,class="type">class="kw">string BaseSymName)
 {

「自定义品种属性写入的两种路径」

给 MT5 自定义品种挂属性时,整数型和双精度型要分开走函数。上面两段分别封装了 SetProperty 的整数版与双精度版,逻辑一致:先 ResetLastError 清掉旧错误码,再根据是否传了基准品种名决定用直接赋值还是从基准品种抄值。 如果 BaseSymName 为空串,就按调用方给的 PropValue 写;否则用 SymbolInfoInteger / SymbolInfoDouble 先把基准品种的对应属性读出来,再原样塞给新品种。这样复制 EURUSD 去建 EURUSD.pro 时,点值、合约大小这类数值能自动对齐。 写失败不要静默吞掉。两段都在 else 分支里用 Print 把属性枚举和 GetLastError 代码打出来,开 MT5 跑脚本时能在专家日志里直接定位是哪一行枚举越界或品种没建好。外汇与贵金属自定义品种杠杆可调,价格跳动剧烈,建品种后务必先在策略测试器用极小手数验证。 真正建品种的地方靠 CustomSymbolCreate 落地。下面这段判断若返回 false,再查 SYMBOL_CUSTOM 确认是已存在还是真报错——已存在就别重复建,避免把旧属性覆盖乱。紧接着的字符串属性写入(基准货币、盈利货币)走的又是另一套重载,和前面的整数/双精度版不混用。

MQL5 / C++
ResetLastError();
if(BaseSymName=="")
  {
    if(CustomSymbolSetInteger(SymName,IProp,PropValue))
      class="kw">return true;
    else
      Print("Error setting symbol class="kw">property: ",IProp,". Error code: ",GetLastError());
  }
else
  {
    class="type">long IValue=SymbolInfoInteger(BaseSymName,IProp);
    if(CustomSymbolSetInteger(SymName,IProp,IValue))
      class="kw">return true;
    else
      Print("Error setting symbol class="kw">property: ",IProp,". Error code: ",GetLastError());
  }
class="kw">return false;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SetProperty(class="type">class="kw">string SymName,ENUM_SYMBOL_INFO_DOUBLE DProp,class="type">class="kw">double PropValue,class="type">class="kw">string BaseSymName)
  {
  ResetLastError();
  if(BaseSymName=="")
    {
      if(CustomSymbolSetDouble(SymName,DProp,PropValue))
        class="kw">return true;
      else
        Print("Error setting symbol class="kw">property: ",DProp,". Error code: ",GetLastError());
    }
  else
    {
      class="type">class="kw">double DValue=SymbolInfoDouble(BaseSymName,DProp);
      if(CustomSymbolSetDouble(SymName,DProp,DValue))
        class="kw">return true;
      else
        Print("Error setting symbol class="kw">property: ",DProp,". Error code: ",GetLastError());
    }
  class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
if(!CustomSymbolCreate(SName,"\\Forex"))
    {
      if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))
        Print("Symbol ",SName," already exists!");
      else
        Print("Error creating symbol. Error code: ",GetLastError());
    }
      class=class="str">"cmt">// 字符串类型属性
          if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && class=class="str">"cmt">// 基准货币
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&&               class=class="str">"cmt">// 盈利货币

自定义品种的清理与反选逻辑

在 MT5 里用脚本建过自定义货币对(比如名为 ExampleCurrency 的合成品种)之后,想把它从市场观察里彻底删掉,不能直接调一次 CustomSymbolDelete 就完事。如果该品种当前正处于选中状态,删除调用会失败,必须先反选再删。 下面这段脚本的输入参数写死了 SName="ExampleCurrency",在 OnStart 里先用 SymbolInfoInteger 配合 SYMBOL_CUSTOM 判断品种是否存在,存在才进清理分支。若首次 CustomSymbolDelete 返回 false,就检查 SYMBOL_SELECT 确认是否被市场观察选中,选中则先用 SymbolSelect(SName,false) 取消勾选,再尝试第二次删除。 实际跑下来有个细节:SymbolSelect 取消选中返回 true 并不代表品种立即从后台释放,少数情况下第二次 CustomSymbolDelete 仍可能报 4106(对象还在被引用)。遇到这种残留,手动在 MT5 市场观察右键刷新一次通常能清掉。外汇与贵金属自定义品种测试属高风险操作,误删真实报价品种可能影响盯盘,建议先在模拟环境验证。

MQL5 / C++
class="kw">input class="type">class="kw">string SName="ExampleCurrency";
class="type">void OnStart()
  {
  ResetLastError();
  if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))
    {
     if(!CustomSymbolDelete(SName))
       {
        if(SymbolInfoInteger(SName,SYMBOL_SELECT))
          {
           if(SymbolSelect(SName,false))
             {
              if(!CustomSymbolDelete(SName))
                Print("Error deleting symbol ",SName,". Error code: ",GetLastError());
              else
                Print("Symbol ",SName," deleted successfully");
             }

◍ 清理自定义品种的图表与注册信息

当某个自定义品种不再需要留在终端时,先把它在所有图表上打开的窗口关掉,否则直接删品种可能留下悬空图表句柄。下面这段逻辑就是遍历当前终端图表列表,把匹配品种名的图表 ID 收进数组再逐个关闭。 遍历从 ChartFirst() 拿第一个图表句柄,用 ChartNext() 顺着链往后走,直到返回 -1 表示没有更多图表;每碰到 ChartSymbol() 等于目标 SName 的,就 ArrayResize 扩一下 ChartIDArray 并把句柄存进去,i_id 计数。 存完之后用 for 循环调 ChartClose() 关图表,失败就 Print 错误码并 return,不继续删品种。最后 SymbolSelect(SName,false) 把品种从市场报价栏取消勾选,成功才调 CustomSymbolDelete() 真正删除,并打印删除结果——这套顺序能避免 MT5 里删了品种却还弹空图的情况。

MQL5 / C++
else
  {
   class=class="str">"cmt">// 尝试关闭该品种图表
   class="type">int i=class="num">0;
   class="type">long CurrChart=ChartFirst();
   class="type">int i_id=class="num">0;
   class="type">long ChartIDArray[];
   class="kw">while(CurrChart!=-class="num">1)
     {
      class=class="str">"cmt">// 循环遍历图表列表并存储标识符,来打开品种 SName 的图表
      if(ChartSymbol(CurrChart)==SName)
        {
         ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+class="num">1);
         ChartIDArray[i_id]=CurrChart;
         i_id++;
        }
      CurrChart=ChartNext(CurrChart);
     }
   class=class="str">"cmt">// 关闭品种 SName 的所有图表
   for(i=class="num">0;i<i_id;i++)
     {
      if(!ChartClose(ChartIDArray[i]))
        {
         Print("Error closing chart of symbol ",SName,". Error code: ",GetLastError());
         class="kw">return;
        }
     }
   class=class="str">"cmt">// 禁用并删除品种 
   if(SymbolSelect(SName,false))
     {
      if(!CustomSymbolDelete(SName))
        Print("Error deleting symbol ",SName,". Error code: ",GetLastError());
      else
        Print("Symbol ",SName," deleted successfully");
     }
   else
      Print("Error disabling symbol ",SName," in Market Watch. Error code: ",GetLastError());
把造数据的事交给小布
这些诊断和小布盯盘的 AIGC 已内置,打开对应品种页即可看到自定义品种的报价分布偏差提示,你把重复劳动交给小布,专注决策要不要上这个测试品种。

常见问题

在 CustomSymbolCreate 函数的第二个参数里写清子组名,比如 Custom/Forex 下的自建组,品种就会落在对应路径里。
写用 CustomSymbolSetString / Integer / Double,读用 SymbolInfoString / Integer / Double,由 SetProperty 分类型实例封装。
可以,小布盯盘对应品种页会对自定义品种的逐笔报价和柱线分布做偏差标注,省去你手动核对脚本输出的功夫。
能,附带脚本只需填名称、简称、全称和可选基准名,最小配置即可跑,不需要改 MQL5 代码。
是分开实现的,趋势偏重方向性漂移的分布参数,形态则按几何约束生成局部报价序列,后续小节会分别拆。