自定义品种(符号):实践基础(基础篇)
📘

自定义品种(符号):实践基础(基础篇)

第 1/3 篇

自定义品种能玩出哪些衍生图表

MT5 从 2020 年 12 月就已支持创建自定义品种(符号),可用 MQL API 在终端和程序层直接访问。这类品种能挂标准图表、加载指标、画对象,甚至拿去跑 EA,不局限于经纪商给的原始报价。 数据源可以是券商真实品种,也可以是外部喂价。实操里常见的几种转换玩法包括:等量和等范围图表、即时报价图、报价时移附带 K 线形态变换、以及 Renko。这些衍生图给老手多了一层过滤噪音的视角,但外汇和贵金属本身高杠杆高风险,衍生图只是分析辅助,不替你兜底。 文中约定用黑底图表示源品种、白底图表示自定义品种,方便对照形态差异。后续我们还会搭一套自适应 EA 机制,让它交易真实品种,但信号来自图表上的衍生自定义品种。

◍ 用自定义品种在 MT5 拼出等量柱

等量图表不按固定时间帧切柱,而是等相邻柱线的交易量(或价格变动点数)累到预设值才收一根。MT4/5 都不自带这种图,MT5 里得靠自定义品种加一段 EA 把真实 tick 或 M1 数据重排成等量/等范围柱。 原 EqualVolumeBars.mq4 移植到 MT5 时,extern 改成 input,删掉 CustomPeriod,用 StartDate 替掉 StartYear/StartMonth。MT5 不需要再写 hst 文件,直接走自定义品种 API 读真实报价历史。若经纪商对外汇给的是真实成交量(交易所品种常见,外汇通常没有),WorkMode 设 EqualRealVolumes 就能按实际交易量切柱,而不数 tick 次数。 FromM1 参数决定吃 M1 柱(true)还是裸 tick 历史(false)。跑 tick 模式别把起点拉太远,EURUSD 每根柱装 1000 个 tick 的实测图里,除最后一根在长,其余 tick 数严丝合缝相等;等范围模式设 100 点波动收一根也正常出图。自定义品种图表时间帧必须锁 M1,剧烈行情下柱时间可能领先真实钟,但这类图本就弱化绝对时间。 下方片段是品种克隆与写柱的核心:SymbolSelect 失败就 CloneProperties 从当前图拷贝属性;WriteToFile 用 CustomRatesUpdate 推一根 MqlRates;RefreshWindow 用 CustomTicksAdd 造 tick 触发 OnTick。Tick 模式靠 TicksBuffer 类封 CopyTicks/CopyTicksRange,一次拉 10000 条缓着读。

MQL5 / C++
class="macro">#include <Symbol.mqh>
if(!SymbolSelect(symbolName, true))
{
class="kw">const SYMBOL Symb(symbolName);
Symb.CloneProperties(_Symbol);
if(!SymbolSelect(symbolName, true))
{
Alert("Can&class="macro">#x27;t select symbol:", symbolName, " err:", GetLastError());
class="kw">return INIT_FAILED;
}
}
class="type">void WriteToFile(class="type">class="kw">datetime t, class="type">class="kw">double o, class="type">class="kw">double l, class="type">class="kw">double h, class="type">class="kw">double c, class="type">long v, class="type">long m = class="num">0)
{
class="type">MqlRates r[class="num">1];
r[class="num">0].time = t;
r[class="num">0].open = o;
r[class="num">0].low = l;
r[class="num">0].high = h;
r[class="num">0].close = c;
r[class="num">0].tick_volume = v;
r[class="num">0].spread = class="num">0;
r[class="num">0].real_volume = m;
class="type">int code = CustomRatesUpdate(symbolName, r);
if(code < class="num">1)
{
Print("CustomRatesUpdate failed: ", GetLastError());
}
}
class="type">void RefreshWindow(class="kw">const class="type">class="kw">datetime t)
{
class="type">MqlTick ta[class="num">1];
SymbolInfoTick(_Symbol, ta[class="num">0]);
ta[class="num">0].time = t;
ta[class="num">0].time_msc = ta[class="num">0].time * class="num">1000;
if(CustomTicksAdd(symbolName, ta) == -class="num">1)
{
Print("CustomTicksAdd failed:", GetLastError(), " ", (class="type">long) ta[class="num">0].time);
ArrayPrint(ta);
}
}
class="macro">#define TICKS_ARRAY class="num">10000
class TicksBuffer
{
class="kw">private:
class="type">MqlTick array[];
class="type">int tick;
class="kw">public:
class="type">bool fill(class="type">class="kw">ulong &cursor, class="kw">const class="type">bool history = class="kw">false)
{
class="type">int size = history ? CopyTicks(_Symbol, array, COPY_TICKS_ALL, cursor, TICKS_ARRAY) : CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, cursor);
if(size == -class="num">1)
{
Print("CopyTicks failed: ", GetLastError());

「用游标分页把历史 tick 啃完」

这段逻辑解决的是 MT5 里 CopyTicks 一次性拉不全的问题:用 cursor 做毫秒级游标,每次 fill 之后把 cursor 推进到 array[size-1].time_msc + 1,下一轮从这条 tick 之后接着取,避免重复和漏数据。 当 size==0 且 history 为真,说明历史区间已经到底,Print 打出 End of CopyTicks 的 datetime(cursor/1000 转成秒级时间),直接 return false 退出循环,不会空转烧 CPU。 read 方法靠 tick 下标自增遍历当前缓冲,ArraySize 边界内返回 true 并吐出 MqlTick,越界返回 false;外层 while 里 tb.fill 成功且未点停止,就 inner while 把这一批 tick 逐个读出来处理。 IsNewBar() 或 now_volume<1 时触发 WriteToFile,意味着新 K 线起点或首笔成交才落盘,盘中重复 tick 不写,文件不会爆。外汇与贵金属 tick 流在高波动时段可能瞬时过万笔,这种分页读法是实盘回放脚本的底线写法,断点重跑也只需存 cursor。

MQL5 / C++
class="kw">return class="kw">false;
}
else if(size == class="num">0)
{
if(history) Print("End of CopyTicks at ", (class="type">class="kw">datetime)(cursor / class="num">1000));
class="kw">return class="kw">false;
}
cursor = array[size - class="num">1].time_msc + class="num">1;
tick = class="num">0;
class="kw">return true;
}
class="type">bool read(class="type">MqlTick &t)
{
if(tick < ArraySize(array))
{
t = array[tick++];
class="kw">return true;
}
class="kw">return class="kw">false;
}
};
TicksBuffer tb;
class="kw">while(tb.fill(cursor, true) && !IsStopped())
{
class="type">MqlTick t;
class="kw">while(tb.read(t))
{
...
class=class="str">"cmt">// New or first bar
if(IsNewBar() || now_volume < class="num">1)
{
WriteToFile(...);
}
}
}

把逐笔报价变成能挂EA的M1柱

MT5 的「市场观察」里自带即时报价图表,但最多只留 2000 个 tick,不能全屏、不能加指标和 EA,基本只能肉眼瞅。想拿高频数据跑策略,得绕开这个残血面板。 办法是用自定义品种把 tick 重建成柱线:在 EURUSD 任意周期图上跑一个非交易 EA,它会生出 EURUSD_ticks 这个品种,每根 M1 其实就是一笔报价。核心靠 CopyTicks 和 CustomRatesUpdate 两个 API 搬数据,示例文件 Ticks2Bars.mq5 直接能用。 EA 几个输入要理清:Limit 默认 1000,设 0 就只联线不补历史;Reset 默认 true 会清掉旧 tick 历史;LoopBack 默认 true 时图表永远只留 Limit 根柱,超出的从尾部挤出;EmulateTicks 默认 true 用来模拟新 tick 到达事件,EA 逻辑才跑得动;RenderBars 选 OHLC 时报价带实体(高=ask、低=bid、收盘=last),选 HighLow 就画成针线(开收都在中间价)。 自定义品种的柱时间是编的:开 LoopBack 时最后一根对齐当前分钟,前一根倒推一分钟;关掉就自启动起每分钟累加,超 Limit 的部分全在虚拟未来。最右一根 M1 仍对应最新价,所以联线和测试器都能跑。真要下单得把代码里 EURUSD_ticks 换回 EURUSD,因为自定义品种券商服务器不认。 外汇和贵金属 tick 刷新极快,手动标图基本不可能,只能靠指标或脚本全自动。这套把 tick 当柱线的思路,能让你用普通指标测剥头皮策略,不用自己攒缓冲区算信号。

MQL5 / C++
class="type">bool apply(class="kw">const class="type">class="kw">datetime cursor, class="kw">const class="type">MqlTick &t, class="type">MqlRates &r)
  {
    class="kw">static class="type">MqlTick p;
    
    class=class="str">"cmt">// eliminate strange things
    if(t.ask == class="num">0 || t.bid == class="num">0 || t.ask < t.bid) class="kw">return class="kw">false;
    
    r.high = t.ask;
    r.low = t.bid;
    
    if(t.last != class="num">0)
    {
      if(RenderBars == OHLC)
      {
        if(t.last > p.last)
        {
          r.open = r.low;
          r.close = r.high;
        }
        else
        {
          r.open = r.high;
          r.close = r.low;
        }
      }
      else
      {
        r.open = r.close = (r.high + r.low) / class="num">2;
      }
      
      if(t.last < t.bid) r.low = t.last;
      if(t.last > t.ask) r.high = t.last;
      r.close = t.last;
    }
    else
    {
      if(RenderBars == OHLC)
      {
        if((t.ask + t.bid) / class="num">2 > (p.ask + p.bid) / class="num">2)
        {
          r.open = r.low;
          r.close = r.high;
        }
        else
        {
          r.open = r.high;
          r.close = r.low;
        }
      }
      else
      {
        r.open = r.close = (r.high + r.low) / class="num">2;
      }
    }
    
    r.time = cursor;
    r.spread = (class="type">int)((t.ask - t.bid)/_Point);
    r.tick_volume = class="num">1;
    r.real_volume = (class="type">long)t.volume;
  
    p = t;
    class="kw">return true;
  }

◍ 把时间轴挪一挪,锤子线就变尖刺线

烛条形态本质是「价格序列 + 计算起点」的函数。把 H1 图表整体平移 15 分钟,原来成型的形态可能彻底消失,别处却长出新形态,但价格行为本身没变过。这说明形态外观很大程度是被经纪商时区和柱线开盘时间绑架的。 具体变形很直观:时间轴平移半根柱线,「锤子」能转成「尖刺」,「吊死鬼」能转成「乌云盖顶」;平移值和局部波动不同,还可能变成看涨或看跌的「吞噬」。切到更低周期,这些形态又可能变成「晨十字星」或「昏十字星」。 实战上,自定义品种能按指定秒数把报价偏移到过去或未来。例如把报价前移 5 分钟,M15 图表的柱线开盘收盘就从整点 0/15/30/45 分变成 10/25/40/55 分——价格没动,但指标和形态信号会提前冒出来。EURUSD H1 平移 30 分钟后,原图没出现的信号在自定义图上直接显现,常规烛条指标能给出双倍警报。外汇和贵金属波动剧烈,这类信号只是概率性参考,不等于走势反转确认。 TimeShift.mq5 这个 EA 用 Shift 参数(秒)控制偏移,从 Start 日期起重算历史,并默认开启 EmulateTicks 用 OnTick 实时处理。下面这段 add 函数就是偏移核心:先拉取新 tick,给每笔的时间戳加上 Shift,再写进自定义品种。这样你开 MT5 挂上 EA,改一个 Shift 值就能对比同源图表的不同形态分布。

MQL5 / C++
class="type">class="kw">ulong lastTick;
  
  class="type">void add()
  {
    class="type">MqlTick array[];
    class="type">int size = CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, lastTick + class="num">1, LONG_MAX);
    if(size > class="num">0)
    {
      lastTick = array[size - class="num">1].time_msc;
      for(class="type">int i = class="num">0; i < size; i++)
      {
        array[i].time += Shift;
        array[i].time_msc += Shift * class="num">1000;
      }
      if(CustomTicksAdd(symbolName, array) == -class="num">1)
      {
        Print("Tick error: ", GetLastError());
      }
    }
  }
  
  class="type">void OnTick(class="type">void)
  {
    ...
    if(EmulateTicks)
    {
      add();
    }
  }

常见问题

可以。新建一个自定义符号,用脚本把两个品种的历史报价按比例合成后写入,就能在图表上画出一条叠加曲线,用来观察背离。
建自定义符号后,用 EA 或脚本按累计成交量切分数据并生成柱体,每根柱代表固定手数而非固定分钟,等量柱就出来了。
小布可读取你建的自定义符号页面,自动标注异常波动并推送提醒,省去手动翻图表找信号的功夫。
写个脚本用游标分批请求,每次只取一万条 tick 处理完再拉下页,内存不爆也能把全历史啃完。
自定义品种允许改会话起点,重排 K 线边界后单根形态被拆开或合并,锤子线实体变小就成了尖刺,属正常视图变化。