Files
profitable-expert-advisor/frontline/MQL5/_united_dynamic/UnitedProfitPanel.mqh
T
2026-04-15 23:38:45 +02:00

438 lines
13 KiB
Plaintext

//+------------------------------------------------------------------+
//| UnitedProfitPanel.mqh — chart object P&L by strategy / magic |
//| One OBJ_LABEL per line (MT5 often ignores \n in a single label). |
//| Include only after all United EA `input` declarations. |
//+------------------------------------------------------------------+
#ifndef UNITED_PROFIT_PANEL_MQH
#define UNITED_PROFIT_PANEL_MQH
#define UNITED_PNL_MAX_LINES 48
struct UnitedPnLRow
{
string name;
long magic;
};
UnitedPnLRow g_unitedPnLRows[];
int g_unitedPnLRowCount = 0;
int g_unitedPnL_visibleLineCount = 0;
string UnitedPnL_Obj(const string suffix) { return "UnitedPnL_" + suffix; }
long UnitedPnL_ChartId() { return ChartID(); }
string UnitedPnL_LineObjName(const int idx) { return UnitedPnL_Obj("L") + IntegerToString(idx); }
datetime UnitedPnL_StartOfYear(const datetime now)
{
MqlDateTime dt;
TimeToStruct(now, dt);
dt.mon = 1;
dt.day = 1;
dt.hour = 0;
dt.min = 0;
dt.sec = 0;
return StructToTime(dt);
}
datetime UnitedPnL_StartOfMonth(const datetime now)
{
MqlDateTime dt;
TimeToStruct(now, dt);
dt.day = 1;
dt.hour = 0;
dt.min = 0;
dt.sec = 0;
return StructToTime(dt);
}
int UnitedPnL_FindRowIndex(const long magic)
{
for(int r = 0; r < g_unitedPnLRowCount; r++)
{
if(g_unitedPnLRows[r].magic == magic)
return r;
}
return -1;
}
bool UnitedPnL_ScanDealsOnce(double &yearPl[], double &monthPl[], const datetime y0, const datetime m0, const datetime now)
{
const int R = g_unitedPnLRowCount;
ArrayResize(yearPl, R);
ArrayResize(monthPl, R);
for(int j = 0; j < R; j++)
{
yearPl[j] = 0.0;
monthPl[j] = 0.0;
}
if(R <= 0 || y0 >= now)
return true;
datetime to = now;
if(to <= y0)
to = y0 + 1;
if(!HistorySelect(y0, to))
return false;
const int n = HistoryDealsTotal();
for(int i = 0; i < n; i++)
{
const ulong dealTicket = HistoryDealGetTicket(i);
if(dealTicket == 0)
continue;
const long mg = (long)HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
const int idx = UnitedPnL_FindRowIndex(mg);
if(idx < 0)
continue;
const datetime dt = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
const double p = HistoryDealGetDouble(dealTicket, DEAL_PROFIT)
+ HistoryDealGetDouble(dealTicket, DEAL_SWAP)
+ HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
yearPl[idx] += p;
if(dt >= m0)
monthPl[idx] += p;
}
return true;
}
double UnitedPnL_SumFloatingForMagic(const long magic)
{
double sum = 0.0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
const ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(!PositionSelectByTicket(ticket))
continue;
if((long)PositionGetInteger(POSITION_MAGIC) != magic)
continue;
sum += PositionGetDouble(POSITION_PROFIT);
sum += PositionGetDouble(POSITION_SWAP);
}
return sum;
}
void UnitedPnL_CollectRows()
{
g_unitedPnLRowCount = 0;
ArrayResize(g_unitedPnLRows, 24);
if(EnableDarvasBox)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "DarvasBox";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)DB_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableEMASlopeDistance)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "EMASlope";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)ES_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableRSICrossOverReversal)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RSICrossOver";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RC_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableRSIMidPointHijack)
{
if(RM_InpEnableRSIFollow)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RM_RSIFollow";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RM_InpMagicNumberRSIFollow;
g_unitedPnLRowCount++;
}
if(RM_InpEnableRSIReverse)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RM_RSIRev";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RM_InpMagicNumberRSIReverse;
g_unitedPnLRowCount++;
}
if(RM_InpEnableEMACross)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RM_EMACross";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RM_InpMagicNumberEMACross;
g_unitedPnLRowCount++;
}
}
if(EnableRSIScalpingAPPL)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RS_Scalp_AAPL";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RS_APPL_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableRSIScalpingBTCUSD)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RS_Scalp_BTC";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RS_BTCUSD_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableRSIScalpingNVDA)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RS_Scalp_NVDA";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RS_NVDA_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableRSIScalpingTSLA)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RS_Scalp_TSLA";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RS_TSLA_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableRSIScalpingXAUUSD)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RS_Scalp_XAU";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RS_XAUUSD_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableRSIReversalEURUSD)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RRA_EURUSD";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RRA_EURUSD_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableRSIReversalAUDUSD)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "RRA_AUDUSD";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RRA_AUDUSD_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableRSISecretSauceXAUUSD)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "SecretSauce";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)RSS_XAUUSD_MagicNumber;
g_unitedPnLRowCount++;
}
if(EnableSuperEMA)
{
g_unitedPnLRows[g_unitedPnLRowCount].name = "SuperEMA";
g_unitedPnLRows[g_unitedPnLRowCount].magic = (long)SE_MagicNumber;
g_unitedPnLRowCount++;
}
ArrayResize(g_unitedPnLRows, MathMax(g_unitedPnLRowCount, 1));
}
void UnitedPnL_EnsureObjects()
{
const long ch = UnitedPnL_ChartId();
const string bg = UnitedPnL_Obj("BG");
if(ObjectFind(ch, bg) < 0)
{
if(!ObjectCreate(ch, bg, OBJ_RECTANGLE_LABEL, 0, 0, 0))
{
Print("UnitedPnL: BG ObjectCreate failed, err=", GetLastError());
return;
}
ObjectSetInteger(ch, bg, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(ch, bg, OBJPROP_COLOR, C'90,90,90');
ObjectSetInteger(ch, bg, OBJPROP_BGCOLOR, C'25,25,30');
ObjectSetInteger(ch, bg, OBJPROP_WIDTH, 1);
ObjectSetInteger(ch, bg, OBJPROP_BACK, false);
ObjectSetInteger(ch, bg, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, bg, OBJPROP_HIDDEN, false);
ObjectSetInteger(ch, bg, OBJPROP_ZORDER, 0);
}
for(int i = 0; i < UNITED_PNL_MAX_LINES; i++)
{
const string nm = UnitedPnL_LineObjName(i);
if(ObjectFind(ch, nm) >= 0)
continue;
if(!ObjectCreate(ch, nm, OBJ_LABEL, 0, 0, 0))
{
Print("UnitedPnL: line ", i, " ObjectCreate failed, err=", GetLastError());
continue;
}
ObjectSetString(ch, nm, OBJPROP_FONT, "Arial");
ObjectSetInteger(ch, nm, OBJPROP_FONTSIZE, UnitedPanel_FontSize);
ObjectSetInteger(ch, nm, OBJPROP_BACK, false);
ObjectSetInteger(ch, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, nm, OBJPROP_HIDDEN, false);
ObjectSetInteger(ch, nm, OBJPROP_ZORDER, 2);
}
}
void UnitedPnL_ApplyGeometry()
{
if(!UnitedPanel_Enable)
return;
const long ch = UnitedPnL_ChartId();
const string bg = UnitedPnL_Obj("BG");
if(ObjectFind(ch, bg) < 0)
return;
const int lineH = UnitedPanel_FontSize + 5;
const int vis = MathMax(g_unitedPnL_visibleLineCount, 6);
const int h = UnitedPanel_YMargin * 2 + lineH * vis;
const ENUM_BASE_CORNER corner = (ENUM_BASE_CORNER)UnitedPanel_Corner;
ObjectSetInteger(ch, bg, OBJPROP_CORNER, corner);
ObjectSetInteger(ch, bg, OBJPROP_XDISTANCE, UnitedPanel_X);
ObjectSetInteger(ch, bg, OBJPROP_YDISTANCE, UnitedPanel_Y);
ObjectSetInteger(ch, bg, OBJPROP_XSIZE, UnitedPanel_Width);
ObjectSetInteger(ch, bg, OBJPROP_YSIZE, MathMax(h, 80));
const int baseX = UnitedPanel_X + UnitedPanel_XMargin;
const int baseY = UnitedPanel_Y + UnitedPanel_YMargin;
for(int i = 0; i < UNITED_PNL_MAX_LINES; i++)
{
const string nm = UnitedPnL_LineObjName(i);
if(ObjectFind(ch, nm) < 0)
continue;
ObjectSetInteger(ch, nm, OBJPROP_CORNER, corner);
ObjectSetInteger(ch, nm, OBJPROP_XDISTANCE, baseX);
ObjectSetInteger(ch, nm, OBJPROP_YDISTANCE, baseY + i * lineH);
ObjectSetInteger(ch, nm, OBJPROP_FONTSIZE, UnitedPanel_FontSize);
}
}
void UnitedPnL_RefreshText()
{
const long ch = UnitedPnL_ChartId();
const string bg = UnitedPnL_Obj("BG");
if(ObjectFind(ch, bg) < 0)
return;
UnitedPnL_CollectRows();
const datetime now = TimeCurrent();
const datetime y0 = UnitedPnL_StartOfYear(now);
const datetime m0 = UnitedPnL_StartOfMonth(now);
const string cur = AccountInfoString(ACCOUNT_CURRENCY);
double yearPl[];
double monthPl[];
const bool histOk = UnitedPnL_ScanDealsOnce(yearPl, monthPl, y0, m0, now);
const string histNote = histOk ? "" : " [hist fail: Toolbox>History]";
double sumY = 0.0, sumM = 0.0, sumF = 0.0;
string lines[];
int n = 0;
ArrayResize(lines, UNITED_PNL_MAX_LINES);
lines[n++] = "United EA " + cur + histNote;
lines[n++] = UnitedPanel_ShowFloating
? "Y/M = closed deals | F = open P/L+swap"
: "Y/M = closed deal P/L+swap+comm";
lines[n++] = "----------------------------------";
for(int r = 0; r < g_unitedPnLRowCount; r++)
{
const long mg = g_unitedPnLRows[r].magic;
double yv = 0.0, mv = 0.0;
if(histOk && r < ArraySize(yearPl))
yv = yearPl[r];
if(histOk && r < ArraySize(monthPl))
mv = monthPl[r];
const double fv = UnitedPanel_ShowFloating ? UnitedPnL_SumFloatingForMagic(mg) : 0.0;
sumY += yv;
sumM += mv;
sumF += fv;
}
string tot = "TOTAL Y:" + DoubleToString(sumY, 2) + " M:" + DoubleToString(sumM, 2);
if(UnitedPanel_ShowFloating)
tot += " F:" + DoubleToString(sumF, 2);
lines[n++] = tot;
lines[n++] = "----------------------------------";
if(g_unitedPnLRowCount == 0)
lines[n++] = "(no strategies enabled)";
else
{
for(int r = 0; r < g_unitedPnLRowCount; r++)
{
if(n >= UNITED_PNL_MAX_LINES - 1)
break;
const long mg = g_unitedPnLRows[r].magic;
double yv = 0.0, mv = 0.0;
if(histOk && r < ArraySize(yearPl))
yv = yearPl[r];
if(histOk && r < ArraySize(monthPl))
mv = monthPl[r];
const double fv = UnitedPanel_ShowFloating ? UnitedPnL_SumFloatingForMagic(mg) : 0.0;
lines[n++] = g_unitedPnLRows[r].name + " [" + IntegerToString((int)mg) + "]";
string row2 = " Y:" + DoubleToString(yv, 2) + " M:" + DoubleToString(mv, 2);
if(UnitedPanel_ShowFloating)
row2 += " F:" + DoubleToString(fv, 2);
lines[n++] = row2;
}
}
g_unitedPnL_visibleLineCount = n;
color c = clrSilver;
if(sumM > 0.0001)
c = clrPaleGreen;
else if(sumM < -0.0001)
c = clrTomato;
for(int i = 0; i < UNITED_PNL_MAX_LINES; i++)
{
const string nm = UnitedPnL_LineObjName(i);
if(ObjectFind(ch, nm) < 0)
continue;
if(i < n)
{
ObjectSetString(ch, nm, OBJPROP_TEXT, lines[i]);
ObjectSetInteger(ch, nm, OBJPROP_COLOR, c);
}
else
{
ObjectSetString(ch, nm, OBJPROP_TEXT, "");
}
}
UnitedPnL_ApplyGeometry();
ChartRedraw(ch);
}
void UnitedProfitPanelInit()
{
if(!UnitedPanel_Enable)
return;
UnitedPnL_EnsureObjects();
UnitedPnL_CollectRows();
g_unitedPnL_visibleLineCount = 6;
UnitedPnL_ApplyGeometry();
UnitedPnL_RefreshText();
}
void UnitedProfitPanelDeinit()
{
ObjectsDeleteAll(UnitedPnL_ChartId(), "UnitedPnL_");
}
void UnitedProfitPanelRefresh()
{
if(!UnitedPanel_Enable)
return;
UnitedPnL_RefreshText();
}
void UnitedProfitPanelOnChartEvent(const int id)
{
if(!UnitedPanel_Enable)
return;
if(id == CHARTEVENT_CHART_CHANGE)
{
UnitedPnL_CollectRows();
UnitedPnL_ApplyGeometry();
ChartRedraw(UnitedPnL_ChartId());
}
}
#endif