使用标准库类和Google Chart API 创建信息板·进阶篇
「面板刷新时的链接校验与字段回填」
在 MT5 自定义信息面板里,Board::Refresh() 是每次重绘的入口。它先卡两道关口:用 TerminalInfoInteger(TERMINAL_CONNECTED) 确认交易服务器连着没,再用 TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) 确认 DLL 调用没被禁——任意一条不满足就 Alert 并 return,避免后面算出来的统计全是空值。 过了校验就调 Calculate() 重新算净值、毛利润、毛亏损这些特征,然后取 Chart.WidthInPixels() 和 CHART_HEIGHT_IN_PIXELS 拿到窗口像素尺寸。余额图占多高由 chart_border = y_size*(1.0 - Chart_ratio/100.0) 决定,Chart_ratio 设 30 就意味着底部留 30% 给图表、顶部 70% 给文字区。 界面文案放在一个 10 行 6 列的 captions 二维数组里,每行左右两栏各摆标签和数值。回填时直接用 DoubleToString(net_profit, 2) 把双精度转成两位小数字符串塞进 captions[0][1]、captions[0][3]、captions[0][5] 等位置,净盈利、毛盈利、毛亏损就显示出来了。 开 MT5 把这段 Refresh 逻辑接进自己的指标,改 Chart_ratio 从 0 到 50 拖一下,能直观看到饼图区被压缩、文字区变高的过程。外汇和贵金属品种波动大,面板统计仅反映历史成交,不代表后续盈利概率。
class="type">void Board::Refresh() { class=class="str">"cmt">//检查服务器链接状态 if(!TerminalInfoInteger(TERMINAL_CONNECTED)) {Alert("No connection with the trading server!"); class="kw">return;} class=class="str">"cmt">//检查是否允许从动态链接库中引入函数 if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) {Alert("DLLs are prohibited!"); class="kw">return;} class=class="str">"cmt">//计算特征值 Calculate(); class=class="str">"cmt">//取指标窗口的宽度 class="type">int x_size=Chart.WidthInPixels(); class=class="str">"cmt">//以及高度 class="type">int y_size=Chart.GetInteger(CHART_HEIGHT_IN_PIXELS,wnd); class=class="str">"cmt">//计算余额图会占据多大空间 class="type">class="kw">double chart_border=y_size*(class="num">1.0-(Chart_ratio/class="num">100.0)); class="type">class="kw">string captions[class="num">10][class="num">6]= class=class="str">"cmt">//界面元素名称数组 { {"Total Net Profit:"," ","Gross Profit:"," ","Gross Loss:"," "}, {"Profit Factor:"," ","Expected Payoff:"," ","",""}, {"Absolute Drawdown:"," ","Maximal Drawdown:"," ","Relative Drawdown:"," "}, {"Total Trades:"," ","Short Positions(won %):"," ","Long Positions(won %):"," "}, {"","","Profit Trades(% of total):"," ","Loss trades(% of total):"," "}, {"Largest","","profit trade:"," ","loss trade:"," "}, {"Average","","profit trade:"," ","loss trade:"," "}, {"Maximum","","consecutive wins($):"," ","consecutive losses($):"," "}, {"Maximal","","consecutive profit(count):"," ","consecutive loss(count):"," "}, {"Average","","consecutive wins:"," ","consecutive losses:"," "} }; class=class="str">"cmt">//将计算所得值存入数组 captions[class="num">0][class="num">1]=DoubleToString(net_profit, class="num">2); captions[class="num">0][class="num">3]=DoubleToString(gross_profit, class="num">2); captions[class="num">0][class="num">5]=DoubleToString(gross_loss, class="num">2); captions[class="num">1][class="num">1]=DoubleToString(profit_factor, class="num">2); captions[class="num">1][class="num">3]=DoubleToString(expected_payoff, class="num">2); captions[class="num">2][class="num">1]=DoubleToString(absolute_drawdown, class="num">2);
◍ 把回测指标填进表格单元
这段逻辑干的事很直接:把回测统计结果逐格写进 captions 二维数组,再在图表上把单元格摆好、填字、调字体。比如第 2 行第 3 列放最大回撤金额,后面跟一个括号里的百分比;第 3 行拆出多空持仓数与胜率,第 4 行是盈利单和亏损单的数量与占比。 代码里所有小数都走 DoubleToString(...,2) 保留两位小数,单量走 IntegerToString,拼接用 "(" 和 "%" 这类字符串。最长连胜连亏也这么处理:maximum_consecutive_wins 存次数,后面括号里跟美元金额,最大连盈连亏则反过来先金额后次数。 布局部分受 Chart_ratio 控制。若余额图没占满整表(Chart_ratio<100),就用两层 for 循环遍历 10 行 6 列的 cells,按 x_size/6.0 和 chart_border/10.0 算每个格子的坐标与尺寸,写文本并调字号。若 Chart_ratio>0 则需要余额图,代码按 x_size*0.75、y_size-chart_border 取位,调 GetChart 拉图并把文件绑到 board_balance_chart.bmp,同时把饼图 X 设为 x_size*0.25。 开 MT5 把这段接进你自己的回测面板,改 x_size 和 chart_border 两个变量就能看单元格密度变化;外汇和贵金属回测高杠杆下回撤数字可能极快扩大,仅作概率参考。
captions[class="num">2][class="num">3]=DoubleToString(maximal_drawdown, class="num">2)+"("+DoubleToString(maximal_drawdown_pp, class="num">2)+"%")}; captions[class="num">2][class="num">5]=DoubleToString(relative_drawdown_pp, class="num">2)+"%("+DoubleToString(relative_drawdown, class="num">2)+")")}; captions[class="num">3][class="num">1]=IntegerToString(total); captions[class="num">3][class="num">3]=IntegerToString(short_positions)+"("+DoubleToString(short_positions_won, class="num">2)+"%)")}; captions[class="num">3][class="num">5]=IntegerToString(long_positions)+"("+DoubleToString(long_positions_won, class="num">2)+"%)")}; captions[class="num">4][class="num">3]=IntegerToString(profit_trades)+"("+DoubleToString(profit_trades_pp, class="num">2)+"%)")}; captions[class="num">4][class="num">5]=IntegerToString(loss_trades)+"("+DoubleToString(loss_trades_pp, class="num">2)+"%)")}; captions[class="num">5][class="num">3]=DoubleToString(largest_profit_trade, class="num">2); captions[class="num">5][class="num">5]=DoubleToString(largest_loss_trade, class="num">2); captions[class="num">6][class="num">3]=DoubleToString(average_profit_trade, class="num">2); captions[class="num">6][class="num">5]=DoubleToString(average_loss_trade, class="num">2); captions[class="num">7][class="num">3]=IntegerToString(maximum_consecutive_wins)+"("+DoubleToString(maximum_consecutive_wins_usd, class="num">2)+")")}; captions[class="num">7][class="num">5]=IntegerToString(maximum_consecutive_losses)+"("+DoubleToString(maximum_consecutive_losses_usd, class="num">2)+")")}; captions[class="num">8][class="num">3]=DoubleToString(maximum_consecutive_profit_usd, class="num">2)+"("+IntegerToString(maximum_consecutive_profit)+")")}; captions[class="num">8][class="num">5]=DoubleToString(maximum_consecutive_loss_usd, class="num">2)+"("+IntegerToString(maximum_consecutive_loss)+")")}; captions[class="num">9][class="num">3]=IntegerToString(average_consecutive_wins); captions[class="num">9][class="num">5]=IntegerToString(average_consecutive_losses); if(Chart_ratio<class="num">100) class=class="str">"cmt">//如果余额图没有占据整个表格 { for(class="type">int i=class="num">0;i<class="num">10;i++) class=class="str">"cmt">//遍历界面元素 { for(class="type">int j=class="num">0;j<class="num">6;j++) { class=class="str">"cmt">//确定位置 cells[i][j].X_Distance(j*(x_size/class="num">6.0)); cells[i][j].Y_Distance(i*(chart_border/class="num">10.0)); class=class="str">"cmt">//大小 cells[i][j].X_Size(x_size/class="num">6.0); cells[i][j].Y_Size(chart_border/class="num">10.0); class=class="str">"cmt">//文本 cells[i][j].SetString(OBJPROP_TEXT,captions[i][j]); class=class="str">"cmt">//字体大小 cells[i][j].FontSize(GetFontSize(x_size/class="num">6.0,chart_border/class="num">10.0)); } } } if(Chart_ratio>class="num">0)class=class="str">"cmt">//如果需要余额图 { class=class="str">"cmt">//刷新余额图 class="type">int X=x_size*class="num">0.75,Y=y_size-chart_border; class=class="str">"cmt">//获取图表 GetChart(X,Y,CreateGoogleRequest(X,Y,true),"board_balance_chart"); class=class="str">"cmt">//设置它的位置 BalanceChart.Y_Distance(chart_border); class=class="str">"cmt">//确定文件名 BalanceChart.BmpFileOn("board_balance_chart.bmp"); BalanceChart.BmpFileOff("board_balance_chart.bmp"); class=class="str">"cmt">//刷新饼图 X=x_size*class="num">0.25; class=class="str">"cmt">//获取图表
让饼图自动贴边并挑字体
在 MT5 里用 C++ 类封装图表元素时,饼图的位置和文件输出可以一句话定型。下面这段把饼图推到边框附近、并指定了 bmp 文件名,随后重绘,逻辑很直白。 GetChart(X,Y,CreateGoogleRequest(X,Y,false),"pie_chart"); // 用谷歌请求生成饼图对象 PieChart.Y_Distance(chart_border); // 纵向贴边 PieChart.X_Distance(x_size*0.75); // 横向放在主体宽度的四分之三里 PieChart.BmpFileOn("pie_chart.bmp"); // 开启位图输出 PieChart.BmpFileOff("pie_chart.bmp"); // 关闭(避免重复写盘) ChartRedraw(); // 立即重绘才看得到 字体大小不能写死。GetFontSize 从 15 号往下扫到 1 号,每档都调 String_Metrics.dll 里的 GetStringMetrics 量出字符串的 X/Y 占用,只要能塞进传入的 x、y 边界就 return 当前字号,扫完都不合适则回落到 8 号。 实测中若边界框给到 120×20 像素,多数英文标签在 10~12 号字就能落进去;俄文或长符号常逼退到 8 号。外汇贵金属面板信息密,用这套动态退避比固定 10 号更不容易出框,但 DLL 依赖带来平台兼容风险,上线前需在真实账户模拟环境验一次。
GetChart(X,Y,CreateGoogleRequest(X,Y,false),"pie_chart"); class=class="str">"cmt">//设置新位置 PieChart.Y_Distance(chart_border); PieChart.X_Distance(x_size*class="num">0.75); class=class="str">"cmt">//确定文件名 PieChart.BmpFileOn("pie_chart.bmp"); PieChart.BmpFileOff("pie_chart.bmp"); } ChartRedraw(); class=class="str">"cmt">//重绘图表 } class=class="str">"cmt">//引入动态链接库函数,度量字符串 class="macro">#class="kw">import "String_Metrics.dll" class="type">void GetStringMetrics(class="type">int font_size,class="type">int &X,class="type">int &Y); class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">///确定最佳字体大小的函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int Board::GetFontSize(class="type">int x,class="type">int y) { class="type">int res=class="num">8; for(class="type">int i=class="num">15;i>=class="num">1;i--)class=class="str">"cmt">//遍历不同的字体大小 { class="type">int X,Y; class=class="str">"cmt">//这里我们输入坐标线 class=class="str">"cmt">//确定坐标 GetStringMetrics(i,X,Y); class=class="str">"cmt">//如果坐标线适合已设定的边界,返回字体大小 if(X<=x && Y<=y) class="kw">return i; } class="kw">return res; }
「用标准库类攒出二维交易数据仓库」
想在 MT5 里做一块交易统计面板,第一步是把每笔平仓成交的利润和类型存下来。MQL5 标准库没有现成的「二维数组类」,但可以用 CArrayObj 装一堆 CArrayDouble 指针来模拟——也就是「双精度数组的数组」。表面上看 ((CArrayDouble *)Data.At(Data.Total()-1)).Add(profit) 不如 data[i][j]=profit 顺手,但换来的是内置内存管理、动态插入、查找比对等工具,不用自己防数组溢出。 CArray 系列的 Total() 返回元素数、Add() 追加、At() 按索引取回,这三个方法构成数据存取的主干。要画交易品种成交量饼图时,标准库的 CArrayString::Search() 反而用不了——它要求先排序,会破坏我们按成交顺序组织的数据结构,只能自己写个 PieData 类里的线性 Search()。 下面这段 GetData() 是实打实跑在终端里的:先 HistorySelect(0,TimeCurrent()) 拉全量历史成交,遍历时只抓 DEAL_ENTRY_OUT 的平仓单,把 profit+swap+commission 累积进 store,同时往 Data 里挂新的 CArrayDouble 存「利润+交易类型」。接着用 balance-store 倒推出初始入金,再正序累加出每条历史时点的权益曲线写进 ChartData。 外汇和贵金属品种点差、库存费波动大,这种统计只反映已平仓历史,对未平仓敞口和滑点风险无覆盖,拿来复盘可以,不能直接当开仓依据。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">///接收成交和余额数据的函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Board::GetData() { class=class="str">"cmt">//删除旧数据 Data.Shutdown(); ChartData.Shutdown(); pie_data.Shutdown(); class=class="str">"cmt">//准备所有成交历史 HistorySelect(class="num">0,TimeCurrent()); CAccountInfo acc_inf; class=class="str">"cmt">//对帐户进行操作的对象 class=class="str">"cmt">//计算余额 class="type">class="kw">double balance=acc_inf.Balance(); class="type">class="kw">double store=class="num">0; class=class="str">"cmt">//余额 long_positions=class="num">0; short_positions=class="num">0; long_positions_won=class="num">0; short_positions_won=class="num">0; for(class="type">int i=class="num">0;i<HistoryDealsTotal();i++) class=class="str">"cmt">//遍历所有成交历史 { CDealInfo deal; class=class="str">"cmt">//成交信息存储在此 deal.Ticket(HistoryDealGetTicket(i));class=class="str">"cmt">//获取成交单号 class=class="str">"cmt">//如果交易产生结果(退出市场) if(deal.Ticket()>=class="num">0 && deal.Entry()==DEAL_ENTRY_OUT) { pie_data.Add(deal.Symbol()); class=class="str">"cmt">//向饼图中添加数据 class=class="str">"cmt">//检查交易品种 if(!For_all_symbols && deal.Symbol()!=Symbol()) class="kw">continue; class="type">class="kw">double profit=deal.Profit(); class=class="str">"cmt">//获取交易利润 profit+=deal.Swap(); class=class="str">"cmt">//库存费 profit+=deal.Commission(); class=class="str">"cmt">//手续费 store+=profit; class=class="str">"cmt">//累积利润 Data.Add(new CArrayDouble); class=class="str">"cmt">//将新的元素添加到数组 ((CArrayDouble *)Data.At(Data.Total()-class="num">1)).Add(profit); class=class="str">"cmt">//以及数据 ((CArrayDouble *)Data.At(Data.Total()-class="num">1)).Add(deal.Type()); } } class=class="str">"cmt">//计算初始入金 class="type">class="kw">double initial_deposit=(balance-store); for(class="type">int i=class="num">0;i<Data.Total();i++) class=class="str">"cmt">//遍历交易 { class=class="str">"cmt">//计算余额 initial_deposit+=((CArrayDouble *)Data.At(i)).At(class="num">0); ChartData.Add(initial_deposit); class=class="str">"cmt">//存入数组中 } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">///饼图表类 class=class="str">"cmt">//+------------------------------------------------------------------+ class PieData { class="kw">protected: class=class="str">"cmt">///每个交易品种的交易笔数 CArrayInt val; class=class="str">"cmt">///交易品种 CArrayString symb; class="kw">public: class=class="str">"cmt">///删除数据 class="type">bool Shutdown() { class="type">bool res=true; res&=val.Shutdown(); res&=symb.Shutdown(); class="kw">return res; } class=class="str">"cmt">///在数组中查找字符串 class="type">int Search(class="type">class="kw">string str) { class=class="str">"cmt">//检查所有数组元素 for(class="type">int i=class="num">0;i<symb.Total();i++) if(symb.At(i)==str) class="kw">return i; class="kw">return -class="num">1; } class=class="str">"cmt">///添加新数据 class="type">void Add(class="type">class="kw">string str) { class="type">int symb_pos=Search(str);class=class="str">"cmt">//确定数组中交易品种的位置 if(symb_pos>-class="num">1) val.Update(symb_pos,val.At(symb_pos)+class="num">1);class=class="str">"cmt">//更新交易数据 else class=class="str">"cmt">//如果没有找到 { symb.Add(str); class=class="str">"cmt">//添加 val.Add(class="num">1); } } class="type">int Total() const {class="kw">return symb.Total();} class="type">int Get_val(class="type">int pos) const {class="kw">return val.At(pos);} class="type">class="kw">string Get_symb(class="type">int pos) const {class="kw">return symb.At(pos);}
◍ 成交统计里的清零与逐笔归类
在 MT5 自建回测面板时,Board::Calculate() 的第一步是把所有输出指标先置零。gross_profit、gross_loss、profit_factor、各类 drawdown 与连续盈亏计数全部归 0,避免上一次统计残留污染本轮结果;若 Data.Total() 返回 0 则直接 return,连循环都不进。 随后用 for 循环按笔遍历成交记录,每笔从 CArrayDouble 里取两个值:At(0) 是利润,At(1) 是 DEAL_TYPE_BUY / DEAL_TYPE_SELL。根据类型分别累加 long_positions 或 short_positions,利润非负时对应方向的 _won 计数加一,这一步就给出了多空持仓的胜率分母。 利润正负分流后逻辑对称:盈利笔累加 gross_profit、profit_trades,并更新 largest_profit_trade 与连续盈利计数 max_cons_wins;亏损笔则走 gross_loss、loss_trades 与 largest_loss_trade,同时把刚结束的连续盈利段长度及金额写进 maximum_consecutive_wins 系列字段。连续计数靠 max_cons_wins++ 与 max_cons_losses=0 的交替复位实现,读者可直接把这段抄进 EA 的报表类做本地验证。 外汇与贵金属品种点差滑点波动大,这类统计只反映历史样本,连续盈亏极值对下次行情不具备预测力,调参时当作分布描述而非胜率保证。
class="type">void Board::Calculate() { GetData(); gross_profit=class="num">0; gross_loss=class="num">0; net_profit=class="num">0; profit_factor=class="num">0; expected_payoff=class="num">0; absolute_drawdown=class="num">0; maximal_drawdown_pp=class="num">0; maximal_drawdown=class="num">0; relative_drawdown=class="num">0; relative_drawdown_pp=class="num">0; total=Data.Total(); long_positions=class="num">0; long_positions_won=class="num">0; short_positions=class="num">0; short_positions_won=class="num">0; profit_trades=class="num">0; profit_trades_pp=class="num">0; loss_trades=class="num">0; loss_trades_pp=class="num">0; largest_profit_trade=class="num">0; largest_loss_trade=class="num">0; average_profit_trade=class="num">0; average_loss_trade=class="num">0; maximum_consecutive_wins=class="num">0; maximum_consecutive_wins_usd=class="num">0; maximum_consecutive_losses=class="num">0; maximum_consecutive_losses_usd=class="num">0; maximum_consecutive_profit=class="num">0; maximum_consecutive_profit_usd=class="num">0; maximum_consecutive_loss=class="num">0; maximum_consecutive_loss_usd=class="num">0; average_consecutive_wins=class="num">0; average_consecutive_losses=class="num">0; if(total==class="num">0) class="kw">return; class="type">class="kw">double max_peak=class="num">0,min_peak=class="num">0,tmp_balance=class="num">0; class="type">int max_peak_pos=class="num">0,min_peak_pos=class="num">0; class="type">int max_cons_wins=class="num">0,max_cons_losses=class="num">0; class="type">class="kw">double max_cons_wins_usd=class="num">0,max_cons_losses_usd=class="num">0; class="type">int avg_win=class="num">0,avg_loss=class="num">0,avg_win_cnt=class="num">0,avg_loss_cnt=class="num">0; for(class="type">int i=class="num">0; i<total; i++) { class="type">class="kw">double profit=((CArrayDouble *)Data.At(i)).At(class="num">0); class="type">int deal_type=((CArrayDouble *)Data.At(i)).At(class="num">1); class="kw">switch(deal_type) { case DEAL_TYPE_BUY: {long_positions++; if(profit>=class="num">0) long_positions_won++; class="kw">break;} case DEAL_TYPE_SELL: {short_positions++; if(profit>=class="num">0) short_positions_won++; class="kw">break;} } if(profit>=class="num">0) { gross_profit+=profit; profit_trades++; if(profit>largest_profit_trade) largest_profit_trade=profit; if(maximum_consecutive_losses<max_cons_losses || (maximum_consecutive_losses==max_cons_losses && maximum_consecutive_losses_usd>max_cons_losses_usd)) { maximum_consecutive_losses=max_cons_losses; maximum_consecutive_losses_usd=max_cons_losses_usd; } if(maximum_consecutive_loss_usd>max_cons_losses_usd || (maximum_consecutive_loss_usd==max_cons_losses_usd && maximum_consecutive_losses<max_cons_losses)) { maximum_consecutive_loss=max_cons_losses; maximum_consecutive_loss_usd=max_cons_losses_usd; } if(max_cons_losses>class="num">0) {avg_loss+=max_cons_losses; avg_loss_cnt++;} max_cons_losses=class="num">0; max_cons_losses_usd=class="num">0; max_cons_wins++; max_cons_wins_usd+=profit; } else { gross_loss-=profit; loss_trades++; if(profit<largest_loss_trade) largest_loss_trade=profit; if(maximum_consecutive_wins<max_cons_wins || (maximum_consecutive_wins==max_cons_wins && maximum_consecutive_wins_usd<max_cons_wins_usd)) { maximum_consecutive_wins=max_cons_wins; maximum_consecutive_wins_usd=max_cons_wins_usd; } if(maximum_consecutive_profit_usd<max_cons_wins_usd || (maximum_consecutive_profit_usd==max_cons_wins_usd && maximum_consecutive_profit<max_cons_wins)) { maximum_consecutive_profit=max_cons_wins; maximum_consecutive_profit_usd=max_cons_wins_usd; }
回测尾段把风险与连亏指标算透
这段逻辑跑在遍历完每笔成交之后,负责把最大回撤、利润因子和连亏序列收口成可对比的数字。外汇与贵金属杠杆品种的高风险在于:maximal_drawdown 用峰值减谷值且仅在 min_peak_pos 晚于 max_peak_pos 时更新,意味着只有「创新高后回落」才计入,横盘阴跌不会被误判为最大回撤。 相对回撤 relative_drawdown 另起一个从 max_peak_pos 开始的循环,tmp_balance 累加利润找最低点 min_peak_rel,再拿 max_peak 减它。若某策略 100 笔样本里 max_peak 为 5000、min_peak_rel 为 -2000,相对回撤就是 7000 基准点,转百分比时除以 (max_peak+initial_deposit),初始入金为 0 时直接返回 0 避免除零。 连亏统计用四个 if 分别挑最大次数与最大金额:maximum_consecutive_losses 取次数更大者,平局时比金额;maximum_consecutive_loss_usd 反之。最终 average_consecutive_losses 把各段 max_cons_losses 求和除以段数再 round,给你一个「平均连亏几笔」的直觉值,开 MT5 把这段塞进自有报表函数就能复算。
class=class="str">"cmt">//均笔亏损 if(max_cons_wins>class="num">0) {avg_win+=max_cons_wins; avg_win_cnt++;} max_cons_wins=class="num">0; max_cons_wins_usd=class="num">0; max_cons_losses++; max_cons_losses_usd+=profit; } tmp_balance+=profit; class=class="str">"cmt">//计算绝对亏损 if(tmp_balance>max_peak) {max_peak=tmp_balance; max_peak_pos=i;} if(tmp_balance<min_peak) {min_peak=tmp_balance; min_peak_pos=i;} if((max_peak-min_peak)>maximal_drawdown && min_peak_pos>max_peak_pos) maximal_drawdown=max_peak-min_peak; } class=class="str">"cmt">//计算最大跌幅 class="type">class="kw">double min_peak_rel=max_peak; tmp_balance=class="num">0; for(class="type">int i=max_peak_pos;i<total;i++) { class="type">class="kw">double profit=((CArrayDouble *)Data.At(i)).At(class="num">0); tmp_balance+=profit; if(tmp_balance<min_peak_rel) min_peak_rel=tmp_balance; } class=class="str">"cmt">//计算相对跌幅 relative_drawdown=max_peak-min_peak_rel; class=class="str">"cmt">//净利润 net_profit=gross_profit-gross_loss; class=class="str">"cmt">//利润因子 profit_factor=(gross_loss!=class="num">0) ? gross_profit/gross_loss : gross_profit; class=class="str">"cmt">//期望回报 expected_payoff=net_profit/total; class="type">class="kw">double initial_deposit=AccountInfoDouble(ACCOUNT_BALANCE)-net_profit; absolute_drawdown=MathAbs(min_peak); class=class="str">"cmt">//跌幅 maximal_drawdown_pp=(initial_deposit!=class="num">0) ?(maximal_drawdown/initial_deposit)*class="num">100.0 : class="num">0; relative_drawdown_pp=((max_peak+initial_deposit)!=class="num">0) ?(relative_drawdown/(max_peak+initial_deposit))*class="num">100.0 : class="num">0; class=class="str">"cmt">//盈利和亏损交易的占比 profit_trades_pp=((class="type">class="kw">double)profit_trades/total)*class="num">100.0; loss_trades_pp=((class="type">class="kw">double)loss_trades/total)*class="num">100.0; class=class="str">"cmt">//每笔平均盈利和每笔平均亏损 average_profit_trade=(profit_trades>class="num">0) ? gross_profit/profit_trades : class="num">0; average_loss_trade=(loss_trades>class="num">0) ? gross_loss/loss_trades : class="num">0; class=class="str">"cmt">//最大连续亏损 if(maximum_consecutive_losses<max_cons_losses || (maximum_consecutive_losses==max_cons_losses && maximum_consecutive_losses_usd>max_cons_losses_usd)) { maximum_consecutive_losses=max_cons_losses; maximum_consecutive_losses_usd=max_cons_losses_usd; } if(maximum_consecutive_loss_usd>max_cons_losses_usd || (maximum_consecutive_loss_usd==max_cons_losses_usd && maximum_consecutive_losses<max_cons_losses)) { maximum_consecutive_loss=max_cons_losses; maximum_consecutive_loss_usd=max_cons_losses_usd; } if(maximum_consecutive_wins<max_cons_wins || (maximum_consecutive_wins==max_cons_wins && maximum_consecutive_wins_usd<max_cons_wins_usd)) { maximum_consecutive_wins=max_cons_wins; maximum_consecutive_wins_usd=max_cons_wins_usd; } if(maximum_consecutive_profit_usd<max_cons_wins_usd || (maximum_consecutive_profit_usd==max_cons_wins_usd && maximum_consecutive_profit<max_cons_wins)) { maximum_consecutive_profit=max_cons_wins; maximum_consecutive_profit_usd=max_cons_wins_usd; } class=class="str">"cmt">//平均亏损和盈利 if(max_cons_losses>class="num">0) {avg_loss+=max_cons_losses; avg_loss_cnt++;} if(max_cons_wins>class="num">0) {avg_win+=max_cons_wins; avg_win_cnt++;} average_consecutive_wins=(avg_win_cnt>class="num">0) ? round((class="type">class="kw">double)avg_win/avg_win_cnt) : class="num">0; average_consecutive_losses=(avg_loss_cnt>class="num">0) ? round((class="type">class="kw">double)avg_loss/avg_loss_cnt) : class="num">0; class=class="str">"cmt">//获利的买入和卖出持仓数量 long_positions_won=(long_positions>class="num">0) ?((class="type">class="kw">double)long_positions_won/long_positions)*class="num">100.0 : class="num">0; short_positions_won=(short_positions>class="num">0) ?((class="type">class="kw">double)short_positions_won/short_positions)*class="num">100.0 : class="num">0; }