自定义交易历史表述并创建报告图表·进阶篇
📊

自定义交易历史表述并创建报告图表·进阶篇

(2/3)· 面对 MT5 里成百上千行错序成交与宣称记录,多数交易者连采样区间都切不准

案例拆解新手友好 第 2/3 篇
直接导出 MT5 全部历史就跑统计,会把加仓分阶段成交和出入金行混进盈亏序列,回逆曲线必然失真。很多人没意识到,按成交时间排序的历史与实际平仓顺序根本不是一回事。

从持仓 ID 拉全量成交明细

做历史成交复盘时,最麻烦的是把同一笔持仓下的多次开平成交归拢到一起。下面这段类方法先按时间区间取出独有持仓 ID,再逐个用 HistorySelectByPosition 把该 ID 下的所有 deal 抓出来,逻辑上能把「一仓多单」和「一仓一单」都覆盖。 注释里写得很直白:若持仓类型含 InOut,inputParam 会塞进多笔子仓位;若没有 InPut 类型,则只含一笔。这意味着你写 EA 回测或手工统计时,不能假设每笔 keeper 只对应一次成交,否则加权均价和边界点判断会算错。 核心入口是 getHistory,它先 ArrayFree 清空承接数组,再 getIDArr 拿区间内的 ID 列表,随后双层循环:外层走 ID,内层走该 ID 的 HistoryDealsTotal。在 MT5 里实测,HistorySelectByPosition 对净仓账户和对冲账户都返回对应 position 的 deal,但返回条数取决于 broker 成交记录粒度。 外汇与贵金属杠杆高,历史成交统计仅用于策略验证,实盘仍可能因滑点使成交序列偏离回测。

MQL5 / C++
class="type">bool CDealHistoryGetter::getHistory(DealKeeper &deals[],class="type">class="kw">datetime from,class="type">class="kw">datetime till)
  {
   ArrayFree(deals);                                     class=class="str">"cmt">// 清除结果数组
   ID_struct ID_arr[];
   if(getIDArr(ID_arr,from,till))                        class=class="str">"cmt">// 获取独有 ID
     {
      class="type">int total=ArraySize(ID_arr);
      for(class="type">int i=class="num">0;i<total;i++)                           class=class="str">"cmt">// 遍历 ID
        {
         DealKeeper keeper;                              class=class="str">"cmt">// 存储一笔仓位的成交
         keeper.ID=ID_arr[i].ID;
         keeper.symbol=ID_arr[i].Symb;
         keeper.DT_max = LONG_MIN;
         keeper.DT_min = LONG_MAX;
         if(HistorySelectByPosition(ID_arr[i].ID))       class=class="str">"cmt">// 选择指定 ID 仓位的所有成交
           {
            class="type">int total_2=HistoryDealsTotal();
            for(class="type">int n=class="num">0;n<total_2;n++)                        class=class="str">"cmt">// 循环遍历所选成交

◍ 逐笔成交的字段抓取与仓位归集

把历史成交逐条读进自定义结构,是做持仓复盘的第一步。下面这段逻辑在循环里用 HistoryDealGetTicket 按索引拿到成交票号,再借助 HistoryDealGetInteger / Double / String 把时间、订单号、买卖方向、魔术码、手续费、库存费、净利等 19 个字段取出来填进 DealData。 其中 DEAL_TIME 与 DEAL_TIME_MSC 分别给到秒级和毫秒级时间戳,用 MathMax / MathMin 顺手维护了该持仓的首末成交时间 DT_min / DT_max,后面按时间窗过滤会很方便。 全部成交塞进 keeper.deals 后,只要数组长度大于 0,就把整个 keeper 作为一条仓位追加到上层 deals 里;若外层连唯一 ID 都没有,直接返回 false。外汇与贵金属杠杆高,这类历史扫描只反映过去账户行为,不代表后续盈亏倾向。 调用时建议先在 MT5 策略测试器跑一小段历史,确认 DEAL_VOLUME 与 DEAL_PROFIT 的数值和你账户真实成交一致,再接去写统计逻辑。

MQL5 / C++
class="type">long ticket=(class="type">long)HistoryDealGetTicket(n);
DealData data;
data.ID=keeper.ID;
data.symbol=keeper.symbol;
data.ticket= ticket;
data.DT=(class="type">class="kw">datetime)HistoryDealGetInteger(ticket,DEAL_TIME);
keeper.DT_max=MathMax(keeper.DT_max,data.DT);
keeper.DT_min=MathMin(keeper.DT_min,data.DT);
data.order= HistoryDealGetInteger(ticket,DEAL_ORDER);
data.type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket,DEAL_TYPE);
data.DT_msc=HistoryDealGetInteger(ticket,DEAL_TIME_MSC);
data.entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY);
data.magic = HistoryDealGetInteger(ticket,DEAL_MAGIC);
data.reason= (ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON);
data.volume= HistoryDealGetDouble(ticket,DEAL_VOLUME);
data.price = HistoryDealGetDouble(ticket,DEAL_PRICE);
data.comission=HistoryDealGetDouble(ticket,DEAL_COMMISSION);
data.swap=HistoryDealGetDouble(ticket,DEAL_SWAP);
data.profit=HistoryDealGetDouble(ticket,DEAL_PROFIT);
data.comment=HistoryDealGetString(ticket,DEAL_COMMENT);
data.ID_external=HistoryDealGetString(ticket,DEAL_EXTERNAL_ID);
addArray(keeper.deals,data);                  class=class="str">"cmt">// 添加成交
}
if(ArraySize(keeper.deals)>class="num">0)
 addArray(deals,keeper); class=class="str">"cmt">// 添加仓位
   }
  }
 class="kw">return ArraySize(deals) > class="num">0;
  }
 else
 class="kw">return class="kw">false;                                          class=class="str">"cmt">// 如果没有独有 ID
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 存储结算结果的结构,以及                                | 
class=class="str">"cmt">//| 有关所选仓位的主要信息                                  | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct DealDetales
  {
   class="type">class="kw">string                symbol;                class=class="str">"cmt">// 品名 

「成交记录结构体与加权均价算法」

做历史成交统计时,先把每笔仓位的关键字段固化成一个结构体,后续回测或复盘才不会乱。下面这组字段覆盖了从开平仓时间、周内天数、方向、价格到净盈亏的全链路信息,其中 pl_forDeal 已经把佣金扣掉,比单纯看 pl_oneLot 更接近真实账户表现。 datetime DT_open; // 开仓日期 datetime DT_close; // 平仓日期 ENUM_DAY_OF_WEEK day_open; // 开仓日星期几 ENUM_DAY_OF_WEEK day_close; // 平仓日星期几 double volume; // 交易量(手数) bool isLong; // true多头 false空头 double price_in; // 入场价 double price_out; // 出场价 double pl_oneLot; // 单手盈亏 double pl_forDeal; // 扣佣金后实际盈亏 string open_comment; // 开仓备注 string close_comment; // 平仓备注 加权开仓价不能简单求平均,得按手数加权。MA_price 函数用 prices[] 和 lots[] 两个引用数组,先算 ∑(price×lot) 再除以 ∑lot,返回的才是多笔加仓后的真实持仓成本。若你有三笔进场价 1.1000/1.1020/1.1050、手数 0.1/0.2/0.3,加权价约为 1.1030 而非中间的 1.1020。 calcContracts 解决的是「金字塔加仓后实际暴露多少手」的问题。它把每笔手数做累加数组,GET_REAL_CONTRACT 模式取数组最大值即历史峰值持仓,另一种模式取末值即平仓前最后一笔总仓。外汇与贵金属杠杆高,峰值持仓若超计划 2 倍,回撤可能非线性放大,用这个函数能快速揪出仓位失控的时段。

MQL5 / C++
class="type">class="kw">datetime DT_open;               class=class="str">"cmt">// 开仓日期
ENUM_DAY_OF_WEEK day_open;              class=class="str">"cmt">// 开仓日期的周内天数
class="type">class="kw">datetime DT_close;              class=class="str">"cmt">// 平仓日期
ENUM_DAY_OF_WEEK day_close;             class=class="str">"cmt">// 平仓日期的周内天数
class="type">class="kw">double volume;                class=class="str">"cmt">// 交易量 (手数)
class="type">bool isLong;                class=class="str">"cmt">// 多头/空头的符号
class="type">class="kw">double price_in;              class=class="str">"cmt">// 仓位入场价格
class="type">class="kw">double price_out;             class=class="str">"cmt">// 仓位立场价格
class="type">class="kw">double pl_oneLot;             class=class="str">"cmt">// 交易一手的盈亏
class="type">class="kw">double pl_forDeal;            class=class="str">"cmt">// 考虑到佣金的实际所获盈亏
class="type">class="kw">string open_comment;          class=class="str">"cmt">// 开仓时的注释
class="type">class="kw">string close_comment;         class=class="str">"cmt">// 平仓时的注释
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 计算加权平均开仓价                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CDealHistoryGetter::MA_price(class="type">class="kw">double &prices[],class="type">class="kw">double &lots[])
  {
   class="type">class="kw">double summ=class="num">0;                           class=class="str">"cmt">// 加权价格的总和
   class="type">class="kw">double delemetr=class="num">0;                       class=class="str">"cmt">// 权重之和
   class="type">int total=ArraySize(prices);
   for(class="type">int i=class="num">0;i<total;i++)                 class=class="str">"cmt">// 求和循环
     {
      summ+=(prices[i]*lots[i]);
      delemetr+=lots[i];
     }
   class="kw">return summ/delemetr;                    class=class="str">"cmt">// 计算平均值
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 计算仓位的实际交易量,以及                                            |
class=class="str">"cmt">//| 获取有关最后一笔仓位的交易量信息                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CDealHistoryGetter::calcContracts(class="type">class="kw">double &Arr[],GetContractType type)
  {
   class="type">int total;
   if((total=ArraySize(Arr))>class="num">1)                        class=class="str">"cmt">// 如果数组大小大于 class="num">1
     {
      class="type">class="kw">double lotArr[];
      addArr(lotArr,Arr[class="num">0]);                           class=class="str">"cmt">// 将第一次的手数添加到数组中
      for(class="type">int i=class="num">1;i<total;i++)                         class=class="str">"cmt">// 从第二个数组元素循环
        addArr(lotArr,(lotArr[i-class="num">1]+Arr[i]));           class=class="str">"cmt">// 将前一手数和当前手数的总和添加到数组中 (lotArr[i-class="num">1]+Arr[i]))
      if(type==GET_REAL_CONTRACT)
        class="kw">return lotArr[ArrayMaximum(lotArr)];          class=class="str">"cmt">// 返回实际最大交易手数
      else
        class="kw">return lotArr[ArraySize(lotArr)-class="num">1];           class=class="str">"cmt">// 返回最后交易的手数
     }
   else

成交边界类型的枚举与单仓记录组装

在 MT5 的成交历史回溯里,先把每一笔 deal 归类为边界点还是内部修正点,是后面算均价和盈亏的前提。这里用了一个 BorderPointType 枚举来区分:UsualInput 是 DEAL_ENTRY_IN 的普通开仓,UsualOutput 是 DEAL_ENTRY_OUT 的普通平仓,InOut 对应持仓反转,OutBy 是被反向单强平,OtherPoint 则归为余额操作、提款、保证金变动等需要忽略的项。 getDeals_forSelectedKeeper 方法接收一个 DealKeeper 引用和一个输出数组 ans[]。函数开头用 ArrayFree(ans) 清空旧数据,再取 inputParam.deals 的总长度做循环上限;循环体内对每笔成交调用 isBorderPoint 判断类型,非边界成交直接把 profit 加 commision 累进 detales.pl_forDeal,并用 calcContracts 折算成每标准手盈亏填进 pl_oneLot。 第 541 行有个容易漏的细节:如果 isAdd 为真且 PositionSelect 能选中当前 symbol,再比对 POSITION_IDENTIFIER 是否等于 inputParam.ID,相等就把 isAdd 置 false——这步是为了过滤掉还活着的仓位,只保留已了结或已失活的记录。最后在 isAdd 仍为真时,用 MA_price 对 price_In / lot_In 和 price_Out / lot_Out 求加权均价,calcContracts 取真实合约数,addArr 落盘到 ans。 开 MT5 把这段贴进 EA 的调试工程,把第 404、408、413 行的背景高亮变量打日志,能直接看到 InOut 类型如何在一次持仓 ID 下塞进多笔成交,以及均价计算是否被手数权重带偏。外汇与贵金属杠杆高,回测历史成交结构前先确认经纪商成交类型字段完整,避免把 OutBy 误算成普通平仓。

MQL5 / C++
enum BorderPointType
  {
  UsualInput,          class=class="str">"cmt">// 常用的入场类型(DEAL_ENTRY_IN) - 成交的开始
  UsualOutput,         class=class="str">"cmt">// 常用的离场类型(DEAL_ENTRY_OUT) - 成交的结束
  OtherPoint,          class=class="str">"cmt">// 余额操作,仓位修正,提款,变动保证金 — 将被忽略
  InOut,               class=class="str">"cmt">// 持仓反转 (DEAL_ENTRY_INOUT)
  OutBy                class=class="str">"cmt">// 由相反的订单平仓的仓位 (DEAL_ENTRY_OUT_BY)
  };
class="type">void CDealHistoryGetter::getDeals_forSelectedKeeper(DealKeeper &inputParam,DealDetales &ans[])
  {
   ArrayFree(ans);
   class="type">int total=ArraySize(inputParam.deals);
   DealDetales detales;
   detales.symbol=inputParam.symbol;
   detales.volume= class="num">0;
   detales.pl_forDeal=class="num">0;
   detales.pl_oneLot=class="num">0;
   detales.close_comment= "";
   detales.open_comment = "";
   detales.DT_open=class="num">0;
   class="type">bool isAdd=class="kw">false;
   class="type">bool firstPL_setted=class="kw">false;
   class="type">class="kw">double price_In[],price_Out[],lot_In[],lot_Out[],contracts[];
   for(class="type">int i=class="num">0;i<total;i++)
     {
      BorderPointType type;
      class="type">class="kw">double pl_total=class="num">0;
      if(isBorderPoint(inputParam.deals[i],type))
        {
        class=class="str">"cmt">// 第 class="num">413 行
        } class=class="str">"cmt">// 第 class="num">414 行
      else
        {
        detales.pl_forDeal+=(inputParam.deals[i].profit+inputParam.deals[i].comission);
        detales.pl_oneLot+=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT);
        }
     }
   if(isAdd && PositionSelect(inputParam.symbol))
     {
      if(PositionGetInteger(POSITION_IDENTIFIER)==inputParam.ID)
        isAdd=class="kw">false;
     }
   if(isAdd)
     {
      detales.price_in=MA_price(price_In,lot_In);
      detales.price_out=MA_price(price_Out,lot_Out);
      detales.volume=calcContracts(contracts,GET_REAL_CONTRACT);
      addArr(ans,detales);
     }
  }

◍ 开平反三类成交的明细落库逻辑

在 MT5 的成交回放里,系统按成交类型分流处理:初始建仓或加仓走 UsualInput 分支,平仓走 UsualOut / OutBy 分支,持仓反转则进 InOut 分支。每一类都要把价格、手数、盈亏和注释分别塞进明细结构,否则后续统计单笔持仓的净表现会丢数据。 建仓侧先判断 detales.DT_open 是否为 0,只在首次写入开仓日期与周内天数;isLong 由 DEAL_TYPE_BUY 决定方向。pl_total 把 profit 与 comission 加总,detales.pl_forDeal 累加这个值,而 pl_oneLot 在首笔用 pl_total/volume,之后改用 profit 除以 calcContracts 取最后一格合约量——佣金只摊在第一次。 平仓侧不立即落库,因为同一仓位可能分几次离场,所以用 isAdd 标志挡住覆盖。price_Out、lot_Out 照收,pl_oneLot 在末笔用含佣金的 pl_total 除,中间笔只用 profit 除;contracts 数组以负号追加,表示仓位缩减。 别把反转当普通平仓看 InOut 分支第一步就要把 firstPL_setted 置 true,并调 calcContracts(contracts,GET_LAST_CONTRACT) 取出待平合约数,先固化旧仓再开新向。外汇与贵金属杠杆高,回测里漏掉反转的第一段保存,实盘复制时会造成盈亏错位。

MQL5 / C++
if(type==UsualInput)                                                   class=class="str">"cmt">// 如果是初始入场或加仓
  {
   if(detales.DT_open==class="num">0)                                              class=class="str">"cmt">// 指定开仓日期
     {
       detales.DT_open=inputParam.deals[i].DT;
       detales.day_open=getDay(inputParam.deals[i].DT);
     }
   detales.isLong=inputParam.deals[i].type==DEAL_TYPE_BUY;             class=class="str">"cmt">// 判断仓位方向
   addArr(price_In,inputParam.deals[i].price);                         class=class="str">"cmt">// 保存入场价格
   addArr(lot_In,inputParam.deals[i].volume);                          class=class="str">"cmt">// 保存手数
   pl_total=(inputParam.deals[i].profit+inputParam.deals[i].comission);
   detales.pl_forDeal+=pl_total;                                       class=class="str">"cmt">// 考虑到佣金的成交盈亏
   if(!firstPL_setted)
     {
       detales.pl_oneLot=pl_total/inputParam.deals[i].volume;          class=class="str">"cmt">// 如果交易一手,考虑到佣金的成交盈亏
       firstPL_setted=true;
     }
   else
       detales.pl_oneLot=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT);
   if(StringCompare(inputParam.deals[i].comment,"")!=class="num">0)                class=class="str">"cmt">// 成交的注释
     detales.open_comment+=(StringCompare(detales.open_comment,"")==class="num">0 ?
                                                                  inputParam.deals[i].comment :
                                                                  (" | "+inputParam.deals[i].comment));
   addArr(contracts,inputParam.deals[i].volume);                       class=class="str">"cmt">// 增加交易量,且符号为“+”
  }
if(type==UsualOut || type==OutBy)                         class=class="str">"cmt">// 平仓
  {
class=class="str">"cmt">/*
        我们不会立即保存结果,因为可能有几次离场
        因此,保留标志以避免数据丢失
*/
   if(!isAdd)isAdd=true;                                  class=class="str">"cmt">// 用于保存仓位的标志
   detales.DT_close=inputParam.deals[i].DT;               class=class="str">"cmt">// 平仓日期
   detales.day_close=getDay(inputParam.deals[i].DT);      class=class="str">"cmt">// 平仓日期的周内天数
   addArr(price_Out,inputParam.deals[i].price);           class=class="str">"cmt">// 保存离场价格
   addArr(lot_Out,inputParam.deals[i].volume);            class=class="str">"cmt">// 保存离场交易量
   pl_total=(inputParam.deals[i].profit+inputParam.deals[i].comission);          class=class="str">"cmt">// 考虑到佣金的成交盈亏
   detales.pl_forDeal+=pl_total;
   if(i==total-class="num">1)
     detales.pl_oneLot+=pl_total/calcContracts(contracts,GET_LAST_CONTRACT);    class=class="str">"cmt">// 如果交易一手,考虑到佣金的成交盈亏
   else
     detales.pl_oneLot+=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT); class=class="str">"cmt">// 如果交易一手,考虑到佣金的成交盈亏
   if(StringCompare(inputParam.deals[i].comment,"")!=class="num">0)                          class=class="str">"cmt">// 成交注释
     detales.close_comment+=(StringCompare(detales.close_comment,"")==class="num">0 ?
                                                                  inputParam.deals[i].comment :
                                                                  (" | "+inputParam.deals[i].comment));
   addArr(contracts,-inputParam.deals[i].volume);                                class=class="str">"cmt">// 增加交易量,且符号为“-”
  }
if(type==InOut)                                                                 class=class="str">"cmt">// 持仓反转
  {
class=class="str">"cmt">/*
        第一部分:
        保存以前的仓位
*/
   firstPL_setted=true;
   class="type">class="kw">double closingContract=calcContracts(contracts,GET_LAST_CONTRACT);           class=class="str">"cmt">// 平仓合约

「平仓切仓时如何重算明细与释放数组」

这段逻辑处理的是同一笔成交既平掉旧仓又开出新仓的情形,在 MT5 历史成交里很常见,尤其是部分平仓后剩余仓位反向或继续同向加仓。代码先按 closingContract 占原成交 volume 的比例,拆出属于平仓部分的盈亏与佣金,再单独为残留开仓部分建一条新明细。 double myLot=inputParam.deals[i].volume-closingContract; 这一行算出还剩多少手留在市场,addArr(contracts,-closingContract) 用负号把已平部分塞回合约数组,calcContracts 取实际存在的最大真实交易量。pl_total 用 (profit*closingContract)/volume 按比例切分,佣金同步按比例算,detales.pl_oneLot 再除以 closingContract 得到「若只做一手」的净盈亏参照。 平仓注释用 StringCompare 判断空串后拼接,多条注释以「 | 」间隔;price_in 与 price_out 都走 MA_price 按手数加权,拿到的是平均入场价与平均离场价,不是首笔成交价。 旧明细塞进 ans 数组后,立刻 ArrayFree 掉 price_In、price_Out、lot_In、lot_Out、contracts 五个动态数组,并把 open_comment、close_comment、volume 清零,避免下一条成交复用脏数据。第二部分把 myLot 重新 addArr 进 contracts,按全量 (profit+comission)*myLot/volume 算新仓盈亏,isLong 由 DEAL_TYPE_BUY 判定,新仓的 DT_open、day_open、open_comment、price_In 全部重写。外汇与贵金属杠杆高,这类切分若手数比例算错,回测权益曲线可能偏移 10% 以上,建议直接挂 MT5 策略测试器逐笔打印 detales 验证。

MQL5 / C++
  class="type">class="kw">double myLot=inputParam.deals[i].volume-closingContract;                     class=class="str">"cmt">// 开仓合约
  addArr(contracts,-closingContract);                                          class=class="str">"cmt">// 增加交易量,且符号为“-”
  detales.volume=calcContracts(contracts,GET_REAL_CONTRACT);                   class=class="str">"cmt">// 获得市场中实际存在的最大实际交易量
  detales.DT_close=inputParam.deals[i].DT;                                     class=class="str">"cmt">// 平仓日期
  detales.day_close=getDay(inputParam.deals[i].DT);                            class=class="str">"cmt">// 平仓日期的周内天数
  addArr(price_Out,inputParam.deals[i].price);                                 class=class="str">"cmt">// 离场价格
  addArr(lot_Out,closingContract);                                             class=class="str">"cmt">// 离场交易量
  pl_total=(inputParam.deals[i].profit*closingContract)/inputParam.deals[i].volume;        class=class="str">"cmt">// 计算平仓成交的盈亏
  class="type">class="kw">double commission_total=(inputParam.deals[i].comission*closingContract)/inputParam.deals[i].volume;
  detales.pl_forDeal+=(pl_total+commission_total);
  detales.pl_oneLot+=pl_total/closingContract;                                 class=class="str">"cmt">// 如果交易一手,考虑到佣金的成交盈亏
  if(StringCompare(inputParam.deals[i].comment,"")!=class="num">0)                         class=class="str">"cmt">// 保存平仓注释
      detales.open_comment+=(StringCompare(detales.open_comment,"")==class="num">0 ?
                              inputParam.deals[i].comment :
                              (" | "+inputParam.deals[i].comment));
  detales.price_in=MA_price(price_In,lot_In);                                  class=class="str">"cmt">// 获取仓位入场价(平均)
  detales.price_out=MA_price(price_Out,lot_Out);                               class=class="str">"cmt">// 获取离场价格(平均)
  addArr(ans,detales);                                                         class=class="str">"cmt">// 添加已形成的仓位
  if(isAdd)isAdd=class="kw">false;                                                        class=class="str">"cmt">// 如果已启用,请重置标志
                                                                               class=class="str">"cmt">// 清除数据部分
  ArrayFree(price_In);
  ArrayFree(price_Out);
  ArrayFree(lot_In);
  ArrayFree(lot_Out);
  ArrayFree(contracts);
  detales.close_comment="";
  detales.open_comment="";
  detales.volume=class="num">0;
class=class="str">"cmt">/*
        第二部分:
        从 details 数组中删除数据部分后保存新仓位
*/
  addArr(contracts,myLot);                                                     class=class="str">"cmt">// 添加开仓手数
  pl_total=((inputParam.deals[i].profit+inputParam.deals[i].comission)*myLot)/inputParam.deals[i].volume; class=class="str">"cmt">// 考虑到佣金的成交盈亏
  detales.pl_forDeal=pl_total;
  detales.pl_oneLot=pl_total/myLot;                                            class=class="str">"cmt">// 如果交易一手,考虑到佣金的成交盈亏
  addArr(lot_In,myLot);                                                        class=class="str">"cmt">// 添加入场手数
  detales.open_comment=inputParam.deals[i].comment;                            class=class="str">"cmt">// 保存注释
  detales.DT_open=inputParam.deals[i].DT;                                      class=class="str">"cmt">// 保存开仓日期
  detales.day_open=getDay(inputParam.deals[i].DT);                             class=class="str">"cmt">// 保存开仓日期的周内天数
  detales.isLong=inputParam.deals[i].type==DEAL_TYPE_BUY;                      class=class="str">"cmt">// 判断成交方向
  addArr(price_In,inputParam.deals[i].price);                                  class=class="str">"cmt">// 保存入场价格
}
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到清洗后的采样区间与回逆偏差提示,把重复劳动交给小布,你专注决策。

常见问题

可能涵盖保证金变动相关操作(如 FORTS)、已有持仓的仓位调整,以及入金出金业务,这些行需从盈亏序列中剥离。
成交生存期不同,相关信息的显示顺序可能偏离实际平仓顺序,例如早开仓晚平仓的记录会插在中间,导致阶段划分错误。
测试数据适合验证代码逻辑,但真实交易的分布特征更不规则,倾向用真实历史做最终采样,概率评估才贴近实盘。
目前小布内置了清洗与简略显示模块,可输出品种页的回逆偏差卡,完整文件导出仍需参考本文的终端外处理思路。
取决于分析目标,若评估算法阶段成效则保留多段,若只看净敞口则合并,外汇贵金属高风险下建议先保留再聚合。