//+------------------------------------------------------------------+
//| AccountStatsPro.mq4 |
//| 专业账户统计EA - 按日/月/年统计手数与盈亏 |
//| 双面板布局 + 分页表格 + 视图切换 |
//+------------------------------------------------------------------+
#property copyright "Account Statistics Pro"
#property version "2.00"
#property strict
#property description "专业账户统计面板 - 按日/月/年分组统计手数与盈亏"
#property description "双面板布局 | 分页表格 | 视图切换 | 胜率与利润因子"
//==================================================================
// 输入参数
//==================================================================
input string _g1 = "═══════ 面板位置 ═══════";
input int InpLeftPanelX = 20; // 左面板X坐标
input int InpLeftPanelY = 30; // 左面板Y坐标
input int InpLeftPanelW = 320; // 左面板宽度
input int InpRightPanelX = 350; // 右面板X坐标
input int InpRightPanelW = 560; // 右面板宽度
input int InpRowsPerPage = 18; // 每页行数
input string _g2 = "═══════ 样式 ═══════";
input int InpFontSize = 9; // 字体大小
input string InpFontName = "Consolas"; // 字体(建议等宽)
input color InpBgColor = C'22,25,35'; // 背景色
input color InpBorderColor = C'60,65,85'; // 边框色
input color InpTitleColor = C'255,215,0'; // 标题色
input color InpSectionColor = C'100,180,255'; // 分节色
input color InpLabelColor = C'170,175,190'; // 标签色
input color InpTextColor = C'230,230,240'; // 正文色
input color InpProfitColor = C'0,220,120'; // 盈利绿
input color InpLossColor = C'255,80,90'; // 亏损红
input color InpHeaderColor = C'140,200,255'; // 表头色
input color InpAltRowColor = C'32,36,48'; // 隔行色
input string _g3 = "═══════ 统计选项 ═══════";
input bool InpOnlyThisSymbol = false; // 仅统计当前品种
input int InpHistoryDays = 0; // 统计天数(0=全部)
input int InpRefreshSec = 3; // 自动刷新(秒)
input bool InpShowCommission = true; // 包含佣金和库存费
//==================================================================
// 常量
//==================================================================
#define OBJ_PREFIX "ASP2_"
#define MAX_PERIODS 500
//==================================================================
// 数据结构
//==================================================================
struct SPeriodStat
{
string label; // 周期标签: 2026-09-24 / 2026-09 / 2026
int trades; // 交易笔数
int wins; // 盈利笔数
double lots; // 总手数
double grossWin; // 总盈利
double grossLoss; // 总亏损
double netProfit; // 净利润
};
//==================================================================
// 全局变量
//==================================================================
SPeriodStat g_days[MAX_PERIODS];
SPeriodStat g_months[MAX_PERIODS];
SPeriodStat g_years[MAX_PERIODS];
int g_dayCount = 0;
int g_monthCount = 0;
int g_yearCount = 0;
int g_viewMode = 0; // 0=日 1=月 2=年
int g_page = 0; // 当前页
datetime g_lastRefresh = 0;
// 汇总变量
double g_totalNet = 0;
double g_totalLots = 0;
int g_totalTrades = 0;
int g_totalWins = 0;
double g_grossWin = 0;
double g_grossLoss = 0;
//==================================================================
// 辅助函数
//==================================================================
double GetPoint() { return MarketInfo(Symbol(), MODE_POINT); }
color ColorForProfit(double v)
{
if(v > 0.0001) return InpProfitColor;
if(v < -0.0001) return InpLossColor;
return InpTextColor;
}
string FmtMoney(double v, bool withSign = false)
{
string prefix = "";
if(withSign && v > 0.0001) prefix = "+";
return prefix + DoubleToString(v, 2);
}
string FmtLots(double v)
{
return DoubleToString(v, 2);
}
//--- 日期键
string GetDayKey(datetime t)
{
MqlDateTime dt;
TimeToStruct(t, dt);
return StringFormat("%04d-%02d-%02d", dt.year, dt.mon, dt.day);
}
string GetMonthKey(datetime t)
{
MqlDateTime dt;
TimeToStruct(t, dt);
return StringFormat("%04d-%02d", dt.year, dt.mon);
}
string GetYearKey(datetime t)
{
MqlDateTime dt;
TimeToStruct(t, dt);
return StringFormat("%04d", dt.year);
}
//==================================================================
// 聚合统计
//==================================================================
int FindOrCreate(SPeriodStat &arr[], int &count, string label)
{
for(int i = 0; i < count; i++)
if(arr[i].label == label) return i;
if(count >= MAX_PERIODS) return -1;
arr[count].label = label;
arr[count].trades = 0;
arr[count].wins = 0;
arr[count].lots = 0;
arr[count].grossWin = 0;
arr[count].grossLoss = 0;
arr[count].netProfit = 0;
count++;
return count - 1;
}
void AddTrade(SPeriodStat &arr[], int &count, string label,
double lots, double profit)
{
int idx = FindOrCreate(arr, count, label);
if(idx < 0) return;
arr[idx].trades++;
arr[idx].lots += lots;
arr[idx].netProfit += profit;
if(profit > 0)
{
arr[idx].wins++;
arr[idx].grossWin += profit;
}
else if(profit < 0)
{
arr[idx].grossLoss += profit; // 负值
}
}
//--- 按日期字符串降序排序(最新在前)
void SortDescending(SPeriodStat &arr[], int count)
{
for(int i = 0; i < count - 1; i++)
{
for(int j = 0; j < count - 1 - i; j++)
{
if(arr[j].label < arr[j + 1].label)
{
SPeriodStat tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
//--- 聚合所有历史订单
void AggregateStats()
{
g_dayCount = 0;
g_monthCount = 0;
g_yearCount = 0;
g_totalNet = 0;
g_totalLots = 0;
g_totalTrades = 0;
g_totalWins = 0;
g_grossWin = 0;
g_grossLoss = 0;
datetime cutoff = (InpHistoryDays > 0) ? TimeCurrent() - InpHistoryDays * 86400 : 0;
int total = OrdersHistoryTotal();
for(int i = 0; i < total; i++)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) continue;
if(OrderType() != OP_BUY && OrderType() != OP_SELL) continue;
if(InpOnlyThisSymbol && OrderSymbol() != Symbol()) continue;
datetime ct = OrderCloseTime();
if(ct == 0) continue;
if(cutoff > 0 && ct < cutoff) continue;
double profit = OrderProfit();
if(InpShowCommission)
profit += OrderSwap() + OrderCommission();
double lots = OrderLots();
//--- 累计
g_totalTrades++;
g_totalLots += lots;
g_totalNet += profit;
if(profit > 0) { g_totalWins++; g_grossWin += profit; }
else if(profit < 0) { g_grossLoss += profit; }
//--- 分组
AddTrade(g_days, g_dayCount, GetDayKey(ct), lots, profit);
AddTrade(g_months, g_monthCount, GetMonthKey(ct), lots, profit);
AddTrade(g_years, g_yearCount, GetYearKey(ct), lots, profit);
}
SortDescending(g_days, g_dayCount);
SortDescending(g_months, g_monthCount);
SortDescending(g_years, g_yearCount);
}
//==================================================================
// 对象绘制
//==================================================================
void DeleteObjectSafe(string name)
{
if(ObjectFind(0, name) >= 0) ObjectDelete(0, name);
}
void SetRect(string name, int x, int y, int w, int h, color bg, color border)
{
if(ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg);
ObjectSetInteger(0, name, OBJPROP_COLOR, border);
ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, name, OBJPROP_BACK, false);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
}
void SetLabel(string name, int x, int y, string text, color clr,
int anchor = ANCHOR_LEFT_UPPER, int fontSize = 0)
{
if(ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize > 0 ? fontSize : InpFontSize);
ObjectSetString(0, name, OBJPROP_FONT, InpFontName);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
ObjectSetInteger(0, name, OBJPROP_BACK, false);
}
void SetButton(string name, int x, int y, int w, int h, string text,
color bg, color textClr, bool pressed)
{
if(ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg);
ObjectSetInteger(0, name, OBJPROP_COLOR, textClr);
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, InpBorderColor);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, InpFontSize);
ObjectSetString(0, name, OBJPROP_FONT, InpFontName);
ObjectSetInteger(0, name, OBJPROP_STATE, pressed);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
}
//==================================================================
// 左面板:汇总
//==================================================================
void DrawLeftPanel()
{
int x = InpLeftPanelX;
int y = InpLeftPanelY;
int w = InpLeftPanelW;
//--- 背景
SetRect(OBJ_PREFIX + "left_bg", x, y, w, 400, InpBgColor, InpBorderColor);
int cy = y + 10;
int lx = x + 15;
int rx = x + w - 15;
//--- 标题
SetLabel(OBJ_PREFIX + "left_title", x + w / 2, cy,
"◈ 账户概览 ◈", InpTitleColor, ANCHOR_UPPER);
cy += 24;
//--- 账户信息
double balance = AccountBalance();
double equity = AccountEquity();
double floating = equity - balance;
double margin = AccountMargin();
double freeM = AccountFreeMargin();
string currency = AccountCurrency();
SetLabel(OBJ_PREFIX + "l1a", lx, cy, "账户货币", InpLabelColor);
SetLabel(OBJ_PREFIX + "l1b", rx, cy, currency, InpTextColor, ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "l2a", lx, cy, "账户余额", InpLabelColor);
SetLabel(OBJ_PREFIX + "l2b", rx, cy, FmtMoney(balance), InpTextColor, ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "l3a", lx, cy, "账户净值", InpLabelColor);
SetLabel(OBJ_PREFIX + "l3b", rx, cy, FmtMoney(equity), ColorForProfit(equity - balance), ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "l4a", lx, cy, "浮动盈亏", InpLabelColor);
SetLabel(OBJ_PREFIX + "l4b", rx, cy, FmtMoney(floating, true), ColorForProfit(floating), ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "l5a", lx, cy, "已用保证金", InpLabelColor);
SetLabel(OBJ_PREFIX + "l5b", rx, cy, FmtMoney(margin), InpTextColor, ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "l6a", lx, cy, "可用保证金", InpLabelColor);
SetLabel(OBJ_PREFIX + "l6b", rx, cy, FmtMoney(freeM), InpTextColor, ANCHOR_RIGHT_UPPER);
cy += 16;
if(margin > 0.0001)
{
double level = equity / margin * 100.0;
SetLabel(OBJ_PREFIX + "l7a", lx, cy, "保证金水平", InpLabelColor);
SetLabel(OBJ_PREFIX + "l7b", rx, cy,
DoubleToString(level, 1) + "%",
(level > 200 ? InpProfitColor : InpLossColor),
ANCHOR_RIGHT_UPPER);
cy += 16;
}
cy += 8;
//--- 分节:统计概览
SetLabel(OBJ_PREFIX + "left_sec1", lx, cy, "【 历史统计汇总 】", InpSectionColor);
cy += 20;
SetLabel(OBJ_PREFIX + "s1a", lx, cy, "统计交易数", InpLabelColor);
SetLabel(OBJ_PREFIX + "s1b", rx, cy, (string)g_totalTrades + " 笔", InpTextColor, ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "s2a", lx, cy, "统计总手数", InpLabelColor);
SetLabel(OBJ_PREFIX + "s2b", rx, cy, FmtLots(g_totalLots) + " 手", InpTextColor, ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "s3a", lx, cy, "盈利笔数", InpLabelColor);
double winRate = (g_totalTrades > 0) ? (double)g_totalWins / g_totalTrades * 100.0 : 0;
SetLabel(OBJ_PREFIX + "s3b", rx, cy,
(string)g_totalWins + " (" + DoubleToString(winRate, 1) + "%)",
InpProfitColor, ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "s4a", lx, cy, "亏损笔数", InpLabelColor);
SetLabel(OBJ_PREFIX + "s4b", rx, cy,
(string)(g_totalTrades - g_totalWins) + " (" +
DoubleToString(100.0 - winRate, 1) + "%)",
InpLossColor, ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "s5a", lx, cy, "总盈利", InpLabelColor);
SetLabel(OBJ_PREFIX + "s5b", rx, cy, FmtMoney(g_grossWin), InpProfitColor, ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "s6a", lx, cy, "总亏损", InpLabelColor);
SetLabel(OBJ_PREFIX + "s6b", rx, cy, FmtMoney(g_grossLoss), InpLossColor, ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "s7a", lx, cy, "净利润", InpLabelColor);
SetLabel(OBJ_PREFIX + "s7b", rx, cy, FmtMoney(g_totalNet, true),
ColorForProfit(g_totalNet), ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "s8a", lx, cy, "盈利因子", InpLabelColor);
double pf = (g_grossLoss != 0) ? MathAbs(g_grossWin / g_grossLoss) : 0;
SetLabel(OBJ_PREFIX + "s8b", rx, cy, DoubleToString(pf, 2),
(pf >= 1.5 ? InpProfitColor : (pf >= 1.0 ? InpTextColor : InpLossColor)),
ANCHOR_RIGHT_UPPER);
cy += 16;
SetLabel(OBJ_PREFIX + "s9a", lx, cy, "平均每笔", InpLabelColor);
double avg = (g_totalTrades > 0) ? g_totalNet / g_totalTrades : 0;
SetLabel(OBJ_PREFIX + "s9b", rx, cy, FmtMoney(avg, true),
ColorForProfit(avg), ANCHOR_RIGHT_UPPER);
cy += 20;
//--- 更新左面板高度
SetRect(OBJ_PREFIX + "left_bg", x, y, w, cy - y + 8, InpBgColor, InpBorderColor);
}
//==================================================================
// 右面板:表格
//==================================================================
void DrawRightPanel()
{
int x = InpRightPanelX;
int y = InpLeftPanelY;
int w = InpRightPanelW;
//--- 列布局
int colDate = x + 12;
int colTrades = x + 130;
int colLots = x + 200;
int colWin = x + 270;
int colLoss = x + 350;
int colNet = x + 430;
int colRate = x + 505;
//--- 选择数据源
SPeriodStat arr[];
int count = 0;
string title = "";
string dateHeader = "";
if(g_viewMode == 0)
{
ArrayResize(arr, g_dayCount);
for(int i = 0; i < g_dayCount; i++) arr[i] = g_days[i];
count = g_dayCount;
title = " |