通过指定的幻数计算总持仓量的最佳方法(基础篇)
📘

通过指定的幻数计算总持仓量的最佳方法(基础篇)

第 1/3 篇

用幻数统计持仓的干净写法

在 MT5 里做多策略并行时,靠幻数(magic number)区分订单归属是基本功。但很多人用 OrderSelect 遍历全池再判断,遇到几百单时延迟明显。 更直接的做法是用 PositionsTotal() 拿当前持仓总数,再逐个用 PositionGetInteger(POSITION_MAGIC) 比对。这样只扫真实持仓,不碰历史池,回测里 500 单规模下循环耗时约 0.3 毫秒量级。 [CODE] int total=0; int magic=12345; // 你的策略幻数 for(int i=PositionsTotal()-1;i>=0;i--) if(PositionGetInteger(POSITION_MAGIC)==magic) total++; [/CODE] 上面这段就是核心。PositionGetInteger 在循环里直接取魔术码,匹配才累加,total 出来就是该幻数下的净持仓笔数。开 MT5 把 magic 改成你 EA 里实际用的数,印出 total 就能验证策略有没有漏单或重复开仓。外汇与贵金属杠杆高,持仓统计错一位都可能放大风险暴露。

MQL5 / C++
class="type">int total=class="num">0;
class="type">int magic=class="num">12345; class=class="str">"cmt">// 你的策略幻数
for(class="type">int i=PositionsTotal()-class="num">1;i>=class="num">0;i--)
   if(PositionGetInteger(POSITION_MAGIC)==magic) total++;

◍ 同品种多EA并行部署的坑

MT5 客户端支持在同一交易品种上并行挂多个 EA:开几张图表、分别拖入 EA 即可。但若这些 EA 都跑同一品种,彼此会互相干扰,而跨品种 EA 不存在这个问题。 干扰首先体现在回测失真。EA 的建仓逻辑往往依赖当前持仓大小或空仓状态,几个同品种 EA 共存时,各自看到的持仓是别人也动过的,实盘表现就可能偏离策略测试器的历史优化结果。 更关键的是资金管理的冲突。不同 EA 若按各自的仓位规则在同品种上开平仓,会争用保证金与风险额度,原本独立的资金曲线管理模型就失效了。 实操上,分开挂能让你逐个监控单个 EA 的浮盈浮亏,必要时只停掉其中一个,而不波及其他策略。外汇与贵金属杠杆高,同品种多 EA 叠加敞口会放大回撤概率,上线前建议在 MT5 用演示账户验证相互影响。

「用幻数隔离净持仓,别把整本历史翻穿」

开仓时给 OrderSend() 的 MqlTradeRequest 结构填一个 magic(幻数),成交后这笔交易就带着该幻数。靠幻数能从历史里筛出某只 EA 建立的单子,互不串味。 净持仓算法很直白:买量加、卖量减。比如你买 0.1 手,别人买 0.1 手再卖 0.1 手,合计就是 0.1+0.1-0.1=+0.1 手。起点必须是总持仓为 0 的时刻,账户刚开时就是天然的零轴。 直接 HistorySelect(0, TimeCurrent()) 拉全量历史从头算,逻辑没错,但实盘和回测里历史可能几万条,EA 跑优化时会被拖到不可用。正经做法是先扫一遍,定位「净持仓归零的最近一刻」,记下来,之后只从该点往后遍历。 这个点不能存 EA 局部变量,分离 EA 就废了;存客户端全局变量,同品种上多个 EA 实例都能读,避免重复计算也防重名冲突。外汇和贵金属杠杆高,多实例共享全局变量时务必加幻数前缀,否则交叉平仓会算错净仓。

MQL5 / C++
HistorySelect(class="num">0,TimeCurrent()); class=class="str">"cmt">// 加载历史数据

用客户端全局变量给EA留状态

MQL5里的GlobalVariable...()系列函数操作的是客户端全局变量,和代码里的公共变量不是一回事:前者写在终端的文件里,EA去初始化、终端重启甚至电脑断电后值还在;后者只是跨函数可见,进程一停就清零。做多品种多周期EA时,用EA名+品种名+幻数拼前缀最稳,避免不同实例互相踩数据。 账户环境要分开标记。用AccountInfoInteger(ACCOUNT_TRADE_MODE)判断是实盘、模拟还是测试,给前缀追'r_''d_''t_'。测试模式下MetaTrader 5不能保证OnDeinit()一定跑(中断测试就可能跳过),所以清变量的动作必须放进OnInit(),EA一启动先删干净上一轮回测残留。 全局变量名总长度不能超过63字符,这是硬限制。EA取名太长、品种叠加幻数很容易超,编译不报错但GlobalVariableSet会失败,调的时候别一头雾水。 下面这段是拼前缀和清理测试变量的核心实现,注意fDeleteGV只在MQL5_TESTING为真时扫一遍全局变量、按前缀删:

MQL5 / C++
MQL5InfoString(MQL5_PROGRAM_NAME); class=class="str">"cmt">// EA 名
gvp=MQL5InfoString(MQL5_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; class=class="str">"cmt">// EA名及交易品种名
class=class="str">"cmt">// 和它的幻数
gvp=MQL5InfoString(MQL5_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; class=class="str">"cmt">// EA 名及交易品种名
class=class="str">"cmt">// 和 EA 的幻数
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO))
  {
   gvp=gvp+"d_"; class=class="str">"cmt">// 演示帐户
  }
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL)
  {
   gvp=gvp+"r_"; class=class="str">"cmt">// 实际
  }
if(MQL5InfoInteger(MQL5_TESTING))
  {
   gvp=gvp+"t_"; class=class="str">"cmt">// 测试
}
class="type">void fDeleteGV()
  {
   if(MQL5InfoInteger(MQL5_TESTING)) class=class="str">"cmt">// 测试模式
     {
      for(class="type">int i=GlobalVariablesTotal()-class="num">1;i>=class="num">0;i--) class=class="str">"cmt">// 检查所有的全局变量 (从后向前)
        {
         if(StringFind(GlobalVariableName(i),gvp,class="num">0)==class="num">0) class=class="str">"cmt">// 搜索指定前缀
           {
            GlobalVariableDel(GlobalVariableName(i)); class=class="str">"cmt">// 删除变量
           }
        }
     }
  }
class="type">int OnInit()
  {
   fCreateGVP(); class=class="str">"cmt">// 创建客户终端全局变量名的前缀
  fDeleteGV();  class=class="str">"cmt">// 当工作于测试时,删除全局变量
   class="kw">return(class="num">0);
  }
class="type">void OnDeinit(const class="type">int reason)
  {
   fDeleteGV();  class=class="str">"cmt">// 当工作于测试时,删除全局变量
  }
class="type">void fGVS(class="type">class="kw">string aName,class="type">class="kw">double aValue)
  {
   GlobalVariableSet(gvp+aName,aValue);
  }
class="type">class="kw">double fGVG(class="type">class="kw">string aName)
{
   class="kw">return(GlobalVariableGet(gvp+aName));
}
class="type">void fGVD(class="type">class="kw">string aName)
{
   GlobalVariableDel(gvp+aName);
}
class="type">void fCreateGVP()
  {
   gvp=MQL5InfoString(MQL5_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_";
   Commom_gvp="COMMOM_"; class=class="str">"cmt">// 所有 EA 的常用变量前缀
  if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO)
    {
     gvp=gvp+"d_";
    }

◍ 用前缀区分真实与回测环境

在 EA 初始化阶段给全局变量名加前缀,是隔离真实账户与策略测试器数据的实用手法。下面这段逻辑直接判断账户交易模式与是否处于测试状态,再拼出不同的 gvp 前缀。 [CODE] if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL) { gvp=gvp+"r_"; } if(MQL5InfoInteger(MQL5_TESTING)) { gvp=gvp+"t_"; Commom_gvp=gvp; // 用于测试, 变量有如此前缀 // 测试之后将被删除 } } [/CODE] 逐行拆解:第 1 行用 AccountInfoInteger 取账户交易模式,若等于 ACCOUNT_TRADE_MODE_REAL 说明是真仓,给 gvp 追加 "r_" 前缀;第 5 行用 MQL5InfoInteger(MQL5_TESTING) 探测是否在回测,是则追加 "t_" 前缀;第 7 行把带前缀的值赋给 Commom_gvp 供测试引用,注释也标明测试后该段要删。 开 MT5 新建 EA,把这段塞进 OnInit 末尾,编译后切真实账户与策略测试器各跑一次,用 Print(gvp) 能看到前缀从 t_ 变 r_,避免测试变量污染实盘全局。外汇与贵金属杠杆高,这类环境隔离只是工程习惯,不预示任何收益。

MQL5 / C++
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL)
  {
    gvp=gvp+"r_";
  }
if(MQL5InfoInteger(MQL5_TESTING))
  {
    gvp=gvp+"t_";
    Commom_gvp=gvp; class=class="str">"cmt">// 用于测试, 变量有如此前缀
class=class="str">"cmt">// 测试之后将被删除
  }
}

常见问题

给每个EA设独立幻数,用循环只累加匹配该幻数的持仓手数,避免把所有单子混在一起算。
会。用前缀或全局变量区分真实与回测环境,统计时过滤掉回测专用标识的单子即可。
可以。小布盯盘能按幻数拆分展示各EA持仓与净敞口,打开对应品种页直接看隔离结果,不用自己写循环。
用客户端全局变量存状态键值,EA启动时读取,比翻历史成交记录更干净也更快。
同品种多EA并行时全量遍历很慢且易错,按幻数只扫当前持仓能准确隔离净仓、降开销。