MQL5仓位历史属性函数库实战
📚

MQL5仓位历史属性函数库实战

用交易历史获取首仓价与持仓时长

MQL5 编程 难度 · 进阶 2026-05-30 6 分钟阅读
#MQL5#仓位管理#交易历史#函数库

为什么需要交易历史函数库

在MetaTrader 5中,若交易系统在整个持仓周期保持固定手数,标准仓位标识符已足够。但当你计划实现资金管理系统,在策略运行中途动态加仓或减仓时,仅靠POSITION_PRICE_OPEN等实时标识无法追溯仓位的建仓原点。此时必须访问账户交易历史,提取第一笔成交价、初始手数、最后一笔加仓价等信息。本文函数库正是为这类需求而写。

举例来说,一个EA在EURUSD上首仓0.1手,行情顺着趋势后再加0.1手,此时POSITION_VOLUME返回0.2,但策略可能需要知道首仓成本是1.1000还是1.1020,以计算均价距离。这些信息只能从历史成交记录中按仓位开启时间过滤获得。

✦ 适用读者
若你刚接触MQL5,建议先阅读本系列前文:仓位属性、策略测试器分析等文章,再回到本篇动手修改EA。

核心数据结构设计

原文用枚举ENUM_POSITION_PROPERTIES统一描述仓位属性,并新增P_PRICE_FIRST_DEAL(6)、P_PRICE_LAST_DEAL(9)、P_INITIAL_VOLUME(12)、P_DURATION(16)等标识。同时用struct position_properties替代零散全局变量,使代码更清晰,编辑器可自动提示字段注释。

类似地,symbol_properties结构保存点差、止损位限制等品种属性。创建结构变量pos与symb后,访问方式形如pos.exists、pos.first_deal_price,与类对象用法一致。将旧全局变量如pos_open用查找替换(Ctrl+H)批量改为pos.exists,可大幅提升重构效率。

enum ENUM_POSITION_PROPERTIES
  {
   P_TOTAL_DEALS     = 0,
   P_SYMBOL          = 1,
   P_MAGIC           = 2,
   P_COMMENT         = 3,
   P_SWAP            = 4,
   P_COMMISSION      = 5,
   P_PRICE_FIRST_DEAL= 6,
   P_PRICE_OPEN      = 7,
   P_PRICE_CURRENT   = 8,
   P_PRICE_LAST_DEAL = 9,
   P_PROFIT          = 10,
   P_VOLUME          = 11,
   P_INITIAL_VOLUME  = 12,
   P_SL              = 13,
   P_TP              = 14,
   P_TIME            = 15,
   P_DURATION        = 16,
   P_ID              = 17,
   P_TYPE            = 18,
   P_ALL             = 19
  };
struct position_properties
  {
   uint              total_deals;
   bool              exists;
   string            symbol;
   long              magic;
   string            comment;
   double            swap;
   double            commission;
   double            first_deal_price;
   double            price;
   double            current_price;
   double            last_deal_price;
   double            profit;
   double            volume;
   double            initial_volume;
   double            sl;
   double            tp;
   datetime          time;
   ulong             duration;
   long              id;
   ENUM_POSITION_TYPE type;
  };

外部参数与下单辅助函数

EA新增VolumeIncrease等input参数,并用sinput修饰MagicNumber与ShowInfoPanel,使它们在策略测试器中灰显不可优化。OpenPosition()函数封装CTrade的PositionOpen,提前用trade.SetExpertMagicNumber与SetDeviationInPoints配置幻数和滑点。

在TradingBlock()中,若同方向信号且VolumeIncrease>0,则计算加仓手数并调用OpenPosition增量下单;若反向且Reverse=true,则平掉原仓并反手。这样开启、反转、加仓仅需一行调用。

sinput   long        MagicNumber=777;
input    double      VolumeIncrease=0.1;
sinput   bool        ShowInfoPanel=true;
void OpenPosition(double lot, ENUM_ORDER_TYPE order_type, double price, double sl, double tp, string comment)
  {
   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
   if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment))
     { Print("开启仓位错误: ",GetLastError()); }
  }

从历史提取仓位交易数量

CurrentPositionTotalDeals()以pos.time为起点调用HistorySelect(pos.time,TimeCurrent()),再用HistoryDealsTotal()遍历,统计与_Symbol同品种成交数。注意反转仓位时仓位ID不变,但开启时间不变,因此用时间选历史比用HistorySelectByPosition更直观。

uint CurrentPositionTotalDeals()
  {
   int total=0, count=0;
   string deal_symbol="";
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      total=HistoryDealsTotal();
      for(int i=0; i<total; i++)
        {
         deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL);
         if(deal_symbol==_Symbol) count++;
        }
     }
   return(count);
  }

获取首笔与末笔成交价

CurrentPositionFirstDealPrice()正向遍历,当交易时间等于pos.time且品种匹配时退出,返回该笔DEAL_PRICE。CurrentPositionLastDealPrice()反向遍历,通常首轮即命中,但多品种同跑时需继续至品种匹配。两者均依赖HistorySelect覆盖的时间窗。

double CurrentPositionFirstDealPrice()
  {
   int total=0; string deal_symbol=""; double deal_price=0.0; datetime deal_time=NULL;
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      total=HistoryDealsTotal();
      for(int i=0; i<total; i++)
        {
         deal_price=HistoryDealGetDouble(HistoryDealGetTicket(i),DEAL_PRICE);
         deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL);
         deal_time=(datetime)HistoryDealGetInteger(HistoryDealGetTicket(i),DEAL_TIME);
         if(deal_time==pos.time && deal_symbol==_Symbol) break;
        }
     }
   return(deal_price);
  }

计算初始仓位交易量

CurrentPositionInitialVolume()最复杂:反向遍历历史,累加入场后的加仓量sum_volume,若DEAL_ENTRY为INOUT(反转)则用pos.volume减sum_volume得首仓量。需处理无加仓、减仓等边界,代码注释详尽,实测无误。

⚠ 常见坑
部分券商历史返回可能触发4755错误(未找到交易),需检查中介数据权限;HistoryDealGetDouble失败时应查日志订单数、票号。
double CurrentPositionInitialVolume()
  {
   int total=0; ulong ticket=0; ENUM_DEAL_ENTRY deal_entry=WRONG_VALUE; bool inout=false;
   double sum_volume=0.0, deal_volume=0.0; string deal_symbol=""; datetime deal_time=NULL;
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      total=HistoryDealsTotal();
      for(int i=total-1; i>=0; i--)
        {
         if((ticket=HistoryDealGetTicket(i))>0)
           {
            deal_volume=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY);
            deal_time=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);
            deal_symbol=HistoryDealGetString(ticket,DEAL_SYMBOL);
            if(deal_time<=pos.time) break;
            if(deal_symbol==_Symbol) sum_volume+=deal_volume;
           }
        }
     }
   if(deal_entry==DEAL_ENTRY_INOUT)
     {
      if(fabs(sum_volume)>0) { double result=pos.volume-sum_volume; deal_volume=result>0?result:pos.volume; }
      if(sum_volume==0) deal_volume=pos.volume;
     }
   return(NormalizeDouble(deal_volume,2));
  }

持仓时间函数与格式化

枚举ENUM_POSITION_DURATION提供天/时/分/秒模式。CurrentPositionDuration()用TimeCurrent()-pos.time算秒,再除相应系数。CurrentPositionDurationToString()将秒拆为DD:HH:MM:SS字符串,便于信息面板展示。

ulong CurrentPositionDuration(ENUM_POSITION_DURATION mode)
  {
   ulong seconds=TimeCurrent()-pos.time;
   switch(mode)
     {
      case DAYS: return seconds/(60*60*24);
      case HOURS: return seconds/(60*60);
      case MINUTES: return seconds/60;
      case SECONDS: return seconds;
      default: return 0;
     }
  }

回测优化与多品种验证

原文在策略测试器对参数优化,按最大采收率排序后取4.07组,在EURUSD、AUDUSD、NZDUSD均正面。说明该函数库不影响EA逻辑,仅丰富属性输出,可安全用于实盘资金管理。

优化时sinput参数灰显排除,避免无意义维度。使用者应自测经纪商历史质量,再决定是否用于生产。

✦ 结语
任何交易系统都应先充分测试再弃用。本文函数库可扩展至几乎任意自定义系统,后续可结合GUI面板增强。

常见问题

用HistorySelect(仓位开启时间,当前时间)选定历史,遍历HistoryDealsTotal,对比DEAL_TIME与pos.time且DEAL_SYMBOL==_Symbol,返回该笔DEAL_PRICE,详见CurrentPositionFirstDealPrice函数。
用HistorySelect后反向遍历成交,累加非首笔同品种交易量,若DEAL_ENTRY为DEAL_ENTRY_INOUT则用POSITION_VOLUME减累加值,边界处理见CurrentPositionInitialVolume。
错误4755代表未找到交易,常因券商历史数据权限或时间窗无记录。检查HistorySelect是否成功、打印订单票号与总数排查,或联系经纪商。
用sinput修饰符声明外部变量,如sinput long MagicNumber=777;,测试器中该参数灰显且不可勾选优化。