438 lines
13 KiB
Plaintext
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
|