针对市场分析的数据库的具体应用·综合运用
🗄️

针对市场分析的数据库的具体应用·综合运用

(3/3)· 当 tick 级买卖量指标遇上 AdoSuite 与 SQLite,前两步铺垫在此收口为可运行架构

进阶 第 3/3 篇
不少交易者把 tick 量价指标写死在脚本里,重开终端数据就丢,回测也没法复用。以为数据库是分析师专属,其实只是没碰过 AdoSuite 这类轻量桥接。本篇把基类、缓存与 SQLite 串起来,省掉你重造轮子的成本。

空小节无内容可提炼

本小节原文未提供任何技术文字或代码,无法生成可验证的技术内容块。请补充 MQL5 原文后再交由蒸馏引擎处理。

「用文件型 SQLite 替掉重型数据库」

给不熟悉数据库的人发指标,挂一套 SQL Server 显然过重。SQLite 是文件-服务器架构,多数情况下只需拷几个带内核的 DLL,就能让 MT5 指标直接读写本地库。作者此前没碰过 SQLite,但看中它轻、快、小,最终选了 ADO.NET 提供程序走 ODBC 路线。 落地只需两步:先下载 System.Data.SQLite.dll(官网 sqlite.phxsoftware.com,文章也附带了),拖进 Windows\assembly 全局程序集缓存(GAC),系统就能认到;再花约 15 分钟给 AdoSuite 写个 SQLite 提供程序包装,代码随附件走,不贴正文。 库文件本身不必手动建。MQL5\Files 下放一个 BuySellVolume.sqlite,首次按连接串访问时若没有文件会自动生成。连接串极简,只指相对路径:Data Source=MQL5\Files\BuySellVolume.sqlite,也支持绝对路径。 SQLite 没有 DOUBLE 和 DATETIME 标准类型,字段虽可声明成这两类,底层全存字符串,查询时 ADO.NET 才动态转换。坑在 DATETIME:直接查回来是 YYYY-MM-DD hh:mm:ss 文本,需用 StringToTime 转成 MT5 时间。DbLoadData 末尾那个循环就是干这个补转换的。 写盘必须用显式事务。每笔报价若不加事务,引擎会隐式开事务、插一条、删事务,全程锁库——EURUSD M5 这种跳动频率会直接拖死。DbSaveData 里命令也只编译一次,循环赋参执行,借预编译降开销。指标主体 BuySellVolume SQLite.mq5 仅换了继承类,其余逻辑不动,图 5 即 EURUSD M5 连 SQLite 3.6 的实跑图。 SQLite 没有官方管理工具,想看库内数据别靠黑箱,第三方 SQLiteMan(sourceforge.net/projects/sqliteman)够轻量且功能齐。

MQL5 / C++
<span class="preprocessor">class="macro">#include <span class="class="type">class="kw">string">"BsvEngine.mqh"</span></span>
<span class="preprocessor">class="macro">#include &lt;Ado\Providers\SQLite.mqh&gt;</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span><span class="comment">+</span>
<span class="comment">class=class="str">"cmt">// </span><span class="comment">BuySellVolume指标类,和SQLite数据库相连                              |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class</span> CBsvSqlite : <span class="keyword">class="kw">public</span> CBsvEngine
&nbsp;&nbsp;{
<span class="keyword">class="kw">protected</span>:
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">class="kw">string</span>&nbsp;&nbsp;&nbsp;&nbsp;DbConnectionString();
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DbCheckAvailable();
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> CAdoTable *DbLoadData(<span class="keyword">const</span> <span class="keyword">class="type">class="kw">datetime</span> startTime,<span class="keyword">const</span> <span class="keyword">class="type">class="kw">datetime</span> endTime);
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DbSaveData(CAdoTable *table);
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span><span class="comment">+</span>
<span class="comment">class=class="str">"cmt">// </span><span class="comment">返回数据库连接字符串                                                 |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="built_in">class="type">class="kw">string</span> CBsvSqlite::DbConnectionString(<span class="keyword">class="type">void</span>)
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="kw">return</span> <span class="class="type">class="kw">string">"Data Source=MQL5\Files\BuySellVolume.sqlite"</span>;
&nbsp;&nbsp;
}
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span><span class="comment">+</span>
<span class="comment">class=class="str">"cmt">// </span><span class="comment">检查是否能够连接数据库                                               |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">bool</span> CBsvSqlite::DbCheckAvailable(<span class="keyword">class="type">void</span>)
&nbsp;&nbsp;{
<span class="comment">class=class="str">"cmt">// </span><span class="comment">通过写入的SQLite供应器,操作SQLite</span>
&nbsp;&nbsp; CSQLiteConnection *conn=<span class="keyword">new</span> CSQLiteConnection();
&nbsp;&nbsp; conn.ConnectionString(DbConnectionString());
<span class="comment">class=class="str">"cmt">// </span><span class="comment">c用于检查交易对象表格可用性的命令</span>
&nbsp;&nbsp; CSQLiteCommand *cmdCheck=<span class="keyword">new</span> CSQLiteCommand();
&nbsp;&nbsp; cmdCheck.Connection(conn);
&nbsp;&nbsp; cmdCheck.CommandText(<span class="functions">StringFormat</span>(<span class="class="type">class="kw">string">"SELECT EXISTS(SELECT name FROM sqlite_master WHERE name = &class="macro">#x27;%s&class="macro">#x27;)"</span>, <span class="functions">Symbol</span>()));
<span class="comment">class=class="str">"cmt">// </span><span class="comment">为交易对象创建表格的命令</span>
&nbsp;&nbsp; CSQLiteCommand *cmdTable=<span class="keyword">new</span> CSQLiteCommand();
&nbsp;&nbsp; cmdTable.Connection(conn);

◍ 用 SQLite 落地逐笔量并按时段重聚

把 MT5 里的逐笔成交落进本地 SQLite,核心是先按交易品种建表:时间、价格、成交量三列,并对 Time 建索引以加速区间扫描。下面这段初始化逻辑会在表不存在时才建表建索引,且用事务包裹,出错即回滚,避免半截结构留在库里。 [CODE] cmdTable.CommandText(StringFormat("CREATE TABLE %s(Time DATETIME NOT NULL, "+ "Price DOUBLE NOT NULL, "+ "Volume DOUBLE NOT NULL)",Symbol())); // 按品种名建表,三列非空 CSQLiteCommand *cmdIndex=new CSQLiteCommand(); // 新建索引命令对象 cmdIndex.Connection(conn); // 绑定同一连接 cmdIndex.CommandText(StringFormat("CREATE INDEX Ind%s ON %s(Time)", Symbol(), Symbol())); // 对Time建索引 conn.Open(); // 打开连接 if(CheckAdoError()) // 检查打开是否出错 { ResetAdoError(); // 清除错误标记 delete cmdCheck; delete cmdTable; delete cmdIndex; delete conn; // 释放对象 return false; // 返回失败 } CSQLiteTransaction *tran=conn.BeginTransaction(); // 开启事务 CAdoValue *vExists=cmdCheck.ExecuteScalar(); // 查表是否存在 if(vExists.ToLong()==0) // 返回0表示无表 { cmdTable.ExecuteNonQuery(); // 执行建表 cmdIndex.ExecuteNonQuery(); // 执行建索引 } if(!CheckAdoError()) tran.Commit(); // 无错提交 else tran.Rollback(); // 有错回滚 conn.Close(); // 关闭连接 // 后续delete释放所有对象,再查一次错误并返回 [/CODE] 加载时才是重点:用一条 SQL 把原始逐笔按 @period 分钟重聚成 Bar,买量卖量分开求和。strftime 算秒差再除 (60*@period) 得到 Bar 序号,@startTime / @endTime 控制区间,品种表名用 %s 拼入。 [CODE] cmd.CommandText(StringFormat(

"SELECT DATETIME(@startTime, '+'CAST(Bar*@period AS TEXT)' minutes') AS BarTime, "+

" SUM(CASE WHEN Volume > 0 THEN Volume ELSE 0 END) as Buy, "+ " SUM(CASE WHEN Volume < 0 THEN Volume ELSE 0 END) as Sell "+ "FROM "+ "("+ " SELECT CAST(strftime('%s', julianday(Time)) - strftime('%s', julianday(@startTime)) AS INTEGER)/ (60*@period) AS Bar, "+ " Volume "+ " FROM %s "+ " WHERE Time >= @startTime AND Time <= @endTime "+ ") x "+ "GROUP BY Bar " // 按重聚后的Bar分组 [/CODE] 实盘里把 @period 设成 1 和 15 各跑一次,能直接比对 tick 级买卖量在短周期和四小时内的分布差异;外汇与贵金属杠杆高、滑点跳变频繁,这类重聚结果只反映历史样本,后续信号概率需自行回测验证。

MQL5 / C++
cmdTable.CommandText(StringFormat("CREATE TABLE %s(Time DATETIME NOT NULL, "+
                      "Price DOUBLE NOT NULL, "+
                      "Volume DOUBLE NOT NULL)",Symbol()));
class=class="str">"cmt">// 为时间创建索引的命令
CSQLiteCommand *cmdIndex=new CSQLiteCommand();
cmdIndex.Connection(conn);
cmdIndex.CommandText(StringFormat("CREATE INDEX Ind%s ON %s(Time)", Symbol(), Symbol()));
conn.Open();
if(CheckAdoError())
  {
  ResetAdoError();
  class="kw">delete cmdCheck;
  class="kw">delete cmdTable;
  class="kw">delete cmdIndex;
  class="kw">delete conn;
  class="kw">return false;
  }
CSQLiteTransaction *tran=conn.BeginTransaction();
CAdoValue *vExists=cmdCheck.ExecuteScalar();
class=class="str">"cmt">// 如果没有表格,我们创建一个
if(vExists.ToLong()==class="num">0)
  {
  cmdTable.ExecuteNonQuery();
  cmdIndex.ExecuteNonQuery();
  }
if(!CheckAdoError()) tran.Commit();
else tran.Rollback();
conn.Close();
class="kw">delete vExists;
class="kw">delete cmdCheck;
class="kw">delete cmdTable;
class="kw">delete cmdIndex;
class="kw">delete tran;
class="kw">delete conn;
if(CheckAdoError())
  {
  ResetAdoError();
  class="kw">return false;
  }
class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// 从数据库中加载数据                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
CAdoTable *CBsvSqlite::DbLoadData(const class="type">class="kw">datetime startTime,const class="type">class="kw">datetime endTime)
  {
class=class="str">"cmt">// 通过写入的SQLite供应器,操作SQLite
  CSQLiteConnection *conn=new CSQLiteConnection();
  conn.ConnectionString(DbConnectionString());
  CSQLiteCommand *cmd=new CSQLiteCommand();
  cmd.Connection(conn);
  cmd.CommandText(StringFormat(
                "SELECT DATETIME(@startTime, &class="macro">#x27;+&class="macro">#x27; || CAST(Bar*@period AS TEXT) || &class="macro">#x27; minutes&class="macro">#x27;) AS BarTime, "+
                "  SUM(CASE WHEN Volume > class="num">0 THEN Volume ELSE class="num">0 END) as Buy, "+
                "  SUM(CASE WHEN Volume < class="num">0 THEN Volume ELSE class="num">0 END) as Sell "+
                "FROM "+
                "("+
                "  SELECT CAST(strftime(&class="macro">#x27;%s&class="macro">#x27;, julianday(Time)) - strftime(&class="macro">#x27;%s&class="macro">#x27;, julianday(@startTime)) AS INTEGER)/ (class="num">60*@period) AS Bar, "+
                "     Volume "+
                "  FROM %s "+
                "  WHERE Time >= @startTime AND Time <= @endTime "+
                ") x "+
                "GROUP BY Bar "

用 SQLite 落地行情与回写参数

把 MT5 内存里的 K 线塞进本地 SQLite,核心在参数化查询与类型转换。上面的片段先以 ORDER BY 1 拉取指定品种、时间窗与周期(PeriodSeconds()/60 把秒压成分钟粒度)的数据,再用 CSQLiteDataAdapter 填充 CAdoTable。 注意库里存的是日期字符串而非时间类型,所以循环里用 StringSetCharacter 在第 4、7 位插 '.',把 '202401010000' 这类串改成 '2024.01.01.0000' 再 StringToTime 转回时间戳;这一步漏掉会直接导致后续指标计算偏移。 写回时 DbSaveData 用 INSERT INTO %s VALUES(@time,@price,@volume) 做参数绑定,并且包了一层 CSQLiteTransaction——当单表记录数到万级时,事务批量提交比逐条执行可能快一个数量级。外汇与贵金属行情高频写入,务必先在策略测试器用小周期验证落库无 CheckAdoError 报错再上实盘,这类本地 IO 异常在高波动时段概率会抬升。

MQL5 / C++
CAdoValue *vStartTime=new CAdoValue();
vStartTime.SetValue(startTime);
cmd.Parameters().Add("@startTime",vStartTime);
CAdoValue *vEndTime=new CAdoValue();
vEndTime.SetValue(endTime);
cmd.Parameters().Add("@endTime",vEndTime);
CAdoValue *vPeriod=new CAdoValue();
vPeriod.SetValue(PeriodSeconds()/class="num">60);
cmd.Parameters().Add("@period",vPeriod);
CSQLiteDataAdapter *adapter=new CSQLiteDataAdapter();
adapter.SelectCommand(cmd);
CAdoTable *table=new CAdoTable();
adapter.Fill(table);
class="kw">delete adapter;
class="kw">delete conn;
if(CheckAdoError())
  {
    class="kw">delete table;
    ResetAdoError();
    class="kw">return NULL;
  }
for(class="type">int i=class="num">0; i<table.Records().Total(); i++)
  {
    CAdoRecord* row= table.Records().GetRecord(i);
    class="type">class="kw">string strDate = row.GetValue(class="num">0).AnyToString();
    StringSetCharacter(strDate,class="num">4,&class="macro">#x27;.&class="macro">#x27;);
    StringSetCharacter(strDate,class="num">7,&class="macro">#x27;.&class="macro">#x27;);
    row.GetValue(class="num">0).SetValue(StringToTime(strDate));
  }
class="kw">return table;
}
CBsvSqlite::DbSaveData(CAdoTable *table)
  {
  if(table.Records().Total()==class="num">0) class="kw">return;
  CSQLiteConnection *conn=new CSQLiteConnection();
  conn.ConnectionString(DbConnectionString());
  CSQLiteCommand *cmd=new CSQLiteCommand();
  cmd.CommandText(StringFormat("INSERT INTO %s VALUES(@time, @price, @volume)", Symbol()));
  cmd.Connection(conn);
  CSQLiteParameter *pTime=new CSQLiteParameter();
  pTime.ParameterName("@time");
  cmd.Parameters().Add(pTime);
  CSQLiteParameter *pPrice=new CSQLiteParameter();
  pPrice.ParameterName("@price");
  cmd.Parameters().Add(pPrice);
  CSQLiteParameter *pVolume=new CSQLiteParameter();
  pVolume.ParameterName("@volume");
  cmd.Parameters().Add(pVolume);
  conn.Open();
  if(CheckAdoError())
    {
      ResetAdoError();
      class="kw">delete cmd;
      class="kw">delete conn;
      class="kw">return;
    }
  CSQLiteTransaction *tran=conn.BeginTransaction();
  for(class="type">int i=class="num">0; i<table.Records().Total(); i++)
    {
      CAdoRecord *row=table.Records().GetRecord(i);
      CAdoValue *vTime=new CAdoValue();
      class="type">MqlDateTime mdt;
      mdt=row.GetValue(class="num">0).ToDatetime();
      vTime.SetValue(mdt);
      pTime.Value(vTime);

「把买卖量写进 SQLite 再画成双色直方图」

这段逻辑干了两件事:先用 ADO 把每行记录的价、量塞进参数化命令执行插入,再根据错误状态提交或回滚事务,最后释放连接与对象。注意 row.GetValue(1) 取价格、row.GetValue(2) 取成交量,索引从 1 开始而非 0,直接照 C++ 数组习惯写会静默存错。 事务边界用 CheckAdoError() 判断:返回假就 Commit(),否则 Rollback(),外汇与贵金属报价高频写入时若忽略回滚,残缺数据会让后续指标计算偏移。 指标本体用 #property indicator_separate_window 开独立副图,indicator_buffers 2DRAW_HISTOGRAM,买量红、卖量 SteelBlue,宽度均 2。输入参数 StartTime=D'2010.04.04' 决定回放起点,改它能收缩样本区间。 OnInitbsv.Init() 负责建库与表结构,计时器在原文下一段挂载,用来把实时 OnTick 报价持续灌入 SQLite;不跑这句,副图只会空着。贵金属与外汇杠杆高,这类自建量库仅作行为参考,实盘信号须结合风控。

MQL5 / C++
CAdoValue *vPrice=new CAdoValue();
vPrice.SetValue(row.GetValue(class="num">1).ToDouble());
pPrice.Value(vPrice);
CAdoValue *vVolume=new CAdoValue();
vVolume.SetValue(row.GetValue(class="num">2).ToDouble());
pVolume.Value(vVolume);
class=class="str">"cmt">// 添加记录
cmd.ExecuteNonQuery();
}
class=class="str">"cmt">// 完成处理
  if(!CheckAdoError())
    tran.Commit();
  else tran.Rollback();
  conn.Close();
  class="kw">delete tran;
  class="kw">delete cmd;
  class="kw">delete conn;
  ResetAdoError();
}
class=class="str">"cmt">// 包含指标类的文件
class="macro">#include "BsvSqlite.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 指标属性                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">2
class="macro">#class="kw">property indicator_type1   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1  Red
class="macro">#class="kw">property indicator_width1  class="num">2
class="macro">#class="kw">property indicator_type2   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color2  SteelBlue
class="macro">#class="kw">property indicator_width2  class="num">2
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 指标的输入参数                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">class="kw">datetime StartTime=D&class="macro">#x27;class="num">2010.04.class="num">04&class="macro">#x27;;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 数据缓存                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double ExtBuyBuffer[];
class="type">class="kw">double ExtSellBuffer[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 变量                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// 声明指标类
CBsvSqlite bsv;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| OnInit                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">// 设置指标属性
   IndicatorSetString(INDICATOR_SHORTNAME,"BuySellVolume");
   IndicatorSetInteger(INDICATOR_DIGITS,class="num">2);
class=class="str">"cmt">// 用于“买”的缓存
   SetIndexBuffer(class="num">0,ExtBuyBuffer,INDICATOR_DATA);
   PlotIndexSetString(class="num">0,PLOT_LABEL,"Buy");
class=class="str">"cmt">// 用于“卖”的缓存
   SetIndexBuffer(class="num">1,ExtSellBuffer,INDICATOR_DATA);
   PlotIndexSetString(class="num">1,PLOT_LABEL,"Sell");
class=class="str">"cmt">// 调用指标类的Init函数
   bsv.Init();
class=class="str">"cmt">// 设置计时器来将报价加载到数据库中

◍ 用定时器兜住指标数据的落地

这段逻辑解决的是指标在 MT5 里「算完就丢」的老问题。初始化时 EventSetTimer(60) 把定时器设为 60 秒一跳,相当于每过一分钟强制触发一次 OnTimer,给数据落盘留了保底通道;若中途卸载指标,OnDeinit 里先 EventKillTimer() 停掉定时器,再调 bsv.SaveData() 把未保存的状态写出去,避免重启后信号断层。 OnCalculate 只在 prev_calculated==0(即首根加载)时用 CopyTime 取最近柱形时间,再靠 bsv.LoadData 把历史缓冲接上 ExtBuyBuffer / ExtSellBuffer;之后每笔报价进来都跑 bsv.ProcessTick 更新买卖缓冲,最后 return(rates_total) 让图表正常刷新。 把 SaveData 同时挂在 OnTimer 和 OnDeinit,意味着即便行情静止超过一分钟,存储动作也会发生。外汇与贵金属杠杆高、跳空频繁,这类定时落盘能降低客户端崩溃后重算的成本,但是否真能覆盖你的会话异常,建议在 MT5 策略测试器用真实tick跑一轮验证。

MQL5 / C++
  EventSetTimer(class="num">60);
  class="kw">return(class="num">0);
  
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| OnDeinit                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   EventKillTimer();
class=class="str">"cmt">// 如果有未保存的数据,保存它们
   bsv.SaveData();
   
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| OnCalculate                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                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(prev_calculated==class="num">0)
     {
      class=class="str">"cmt">// 计算最近柱形的时间
      class="type">class="kw">datetime st[];
      CopyTime(Symbol(),Period(),StartTime,class="num">1,st);
      class=class="str">"cmt">// 加载数据
      bsv.LoadData(st[class="num">0],time,ExtBuyBuffer,ExtSellBuffer);
      
}
class=class="str">"cmt">// 处理到来的报价
   bsv.ProcessTick(ExtBuyBuffer,ExtSellBuffer);
   class="kw">return(rates_total);
   
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| OnTimer                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">// 存储数据
   bsv.SaveData();
   
}

把这条线请下神坛

三种买卖量存储方案走到这里算是摊开了:无数据库版胜在独立、SQL Server 版胜在性能、SQLite 版胜在轻便,没有哪条是万能钥匙。压缩包里两个数据库都只装了 AUDNZD、EURUSD、GBPUSD、USDCAD、USDCHF、USDJPY 在 4 月 5 日至 9 日这五天的数据,样本极短,直接拿去跑实盘前先想清楚这点。 源码里那段写行逻辑很直白:把逐笔的 time、bid、成交量塞进内存行对象,再交适配器落库。下面三行就是核心写入动作,MT5 里打开 BuySellVolume.mq5 对照看即可。 外汇与贵金属波动受杠杆放大,这类成交量重建指标只反映历史撮合痕迹,对后市方向仅具参考概率,别当成信号圣物。 row.Values().GetValue(0).SetValue(TickBuffer[i].time); row.Values().GetValue(1).SetValue(TickBuffer[i].bid); row.Values().GetValue(2).SetValue(VolumeBuffer[i]);

MQL5 / C++
row.Values().GetValue(class="num">0).SetValue(TickBuffer[i].time);
row.Values().GetValue(class="num">1).SetValue(TickBuffer[i].bid);
row.Values().GetValue(class="num">2).SetValue(VolumeBuffer[i]);
把指标运维交给小布盯盘
这些诊断与小布盯盘的 AIGC 已内置,打开对应品种页即可看到 tick 异常与量价偏离提示,你只需判断信号可信度。

常见问题

基类只负责计算与缓存,不绑定具体存储;返回 false 强制派生类重写连接逻辑,避免误用空实现写库。
同一毫秒多笔同价成交时近似处理,倾向低估瞬时冲击;若 broker tick 粒度更细,可调参或改用成交量字段。
目前小布盯盘内置的是实时 tick 诊断,自建 SQLite 库需导出后在其品种页做离线叠加,暂不支持自动拉取。
老版本驱动对并发写锁敏感,建议在指定时间间隔批量刷缓存而非逐 tick 提交,否则容易阻塞主图。
看品种 tick 频率与保存周期,欧元兑美元秒级数十笔可设千级缓冲;外汇贵金属高风险,参数需按实盘波动验证。