Vigiar
1
0
Derivar
Você já tinha feito uma derivação do repositório Warrior_EA, anteriormente
1
Warrior_EA/System/StatusLabel.mqh
AnimateDread b2784b5a4d Enhance Feature and Topology Interfaces with Bulk Operations and Cache Management
- Added bulk read/write methods for feature caches in IFeaturesView and its implementations to optimize performance.
- Introduced LabelCacheInvalidateAll method to manage label cache invalidation alongside feature cache.
- Implemented PooledIndependentBars method in topology interfaces to account for additional independent observations.
- Enhanced risk budget management with throttling for peak-equity updates to reduce unnecessary file operations.
- Improved error handling and logging for ATR trailing stops to ensure better visibility of issues.
- Updated alt-data handling to prevent unnecessary operations during testing and optimization phases.
2026-08-25 22:51:50 -04:00

296 linhas
14 KiB
MQL5

//+------------------------------------------------------------------+
//| StatusLabel.mqh |
//| AnimateDread |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "AnimateDread"
#property link "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Replaces Comment() for on-chart status text. Comment() draws |
//| directly onto the chart surface with no background, so status |
//| text becomes unreadable over candles. |
//+------------------------------------------------------------------+
#define STATUS_LABEL_PREFIX "WarriorStatusLine"
#define STATUS_LABEL_FONTSIZE 9
#define STATUS_LABEL_FONT "Consolas"
#define STATUS_LABEL_PAD_X 3
#define STATUS_LABEL_PAD_Y 1
#define STATUS_LABEL_LINE_GAP 2 // extra pixels between successive lines' backgrounds
#define STATUS_LABEL_RIGHT_MARGIN 20
#define STATUS_LABEL_MIN_WRAP_WIDTH 150
#define STATUS_LABEL_MAX_CHARS 55 // safe margin under OBJPROP_TEXT's observed ~63-char truncation
#define STATUS_LABEL_MAX_LINES 64
int g_statusLabelLineCount = 0;
//--- Y (chart pixels, CORNER_LEFT_UPPER) just below the last line SetStatusLabel() drew - i.e. where
//--- something else can be placed to sit UNDER the label instead of overlapping it. Defaults to the
//--- label's own default top (see SetStatusLabel()'s y=20 default) before the label has ever drawn.
int g_statusLabelBottomY = 20;
int StatusLabelBottomY(void) { return g_statusLabelBottomY; }
void EnsureStatusLine(int idx)
{
string bg = STATUS_LABEL_PREFIX + (string)idx + "BG";
string txt = STATUS_LABEL_PREFIX + (string)idx + "Text";
if(ObjectFind(0, bg) < 0)
{
ObjectCreate(0, bg, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, bg, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, bg, OBJPROP_BGCOLOR, (color)ColorToARGB(clrBlack, 150));
ObjectSetInteger(0, bg, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, bg, OBJPROP_COLOR, clrNONE);
ObjectSetInteger(0, bg, OBJPROP_BACK, false);
ObjectSetInteger(0, bg, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, bg, OBJPROP_SELECTED, false);
ObjectSetInteger(0, bg, OBJPROP_HIDDEN, true);
}
if(ObjectFind(0, txt) < 0)
{
ObjectCreate(0, txt, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, txt, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, txt, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
ObjectSetInteger(0, txt, OBJPROP_FONTSIZE, STATUS_LABEL_FONTSIZE);
ObjectSetString(0, txt, OBJPROP_FONT, STATUS_LABEL_FONT);
ObjectSetInteger(0, txt, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, txt, OBJPROP_BACK, false);
ObjectSetInteger(0, txt, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, txt, OBJPROP_SELECTED, false);
ObjectSetInteger(0, txt, OBJPROP_HIDDEN, true);
}
}
//+------------------------------------------------------------------+
//| Appends one already-short-enough chunk to outLines/outCount, |
//| force-splitting it further if it still exceeds |
//| STATUS_LABEL_MAX_CHARS (a single word longer than the cap, with |
//| no spaces to break on). |
//+------------------------------------------------------------------+
void AppendStatusChunk(string chunk, string &outLines[], int &outCount)
{
int pos = 0;
int len = StringLen(chunk);
while(len - pos > STATUS_LABEL_MAX_CHARS)
{
ArrayResize(outLines, outCount + 1);
outLines[outCount++] = StringSubstr(chunk, pos, STATUS_LABEL_MAX_CHARS);
pos += STATUS_LABEL_MAX_CHARS;
}
ArrayResize(outLines, outCount + 1);
outLines[outCount++] = StringSubstr(chunk, pos);
}
//+------------------------------------------------------------------+
//| Greedily breaks one logical line into as many visual lines as |
//| needed to keep each one's measured TextGetSize() width within |
//| maxWidth AND its character count within STATUS_LABEL_MAX_CHARS - |
//| the latter is what actually matters (see this file's header |
//| comment on ObjectSetString's ~63-char truncation), the pixel width |
//| check just wraps sooner on a narrow chart. Requires TextSetFont() |
//| to already have been called by the caller so TextGetSize() |
//| measures against the same font actually rendered. |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Deletes status line objects (both bg and text) for indices |
//| [fromIdx, toIdx) - the shared cleanup loop body used by both |
//| SetStatusLabel()'s trailing-line trim and ClearStatusLabel()'s |
//| full wipe. |
//+------------------------------------------------------------------+
void DeleteStatusLines(int fromIdx, int toIdx)
{
for(int i = fromIdx; i < toIdx; i++)
{
string bg = STATUS_LABEL_PREFIX + (string)i + "BG";
string txt = STATUS_LABEL_PREFIX + (string)i + "Text";
if(ObjectFind(0, bg) >= 0)
ObjectDelete(0, bg);
if(ObjectFind(0, txt) >= 0)
ObjectDelete(0, txt);
}
}
void WrapLineInto(string line, int maxWidth, string &outLines[], int &outCount)
{
uint dummyW = 0, dummyH = 0;
TextGetSize(line, dummyW, dummyH);
if(StringLen(line) == 0 || ((int)dummyW <= maxWidth && StringLen(line) <= STATUS_LABEL_MAX_CHARS))
{
ArrayResize(outLines, outCount + 1);
outLines[outCount++] = line;
return;
}
string words[];
int wordCount = StringSplit(line, ' ', words);
string current = "";
for(int w = 0; w < wordCount; w++)
{
string candidate = (StringLen(current) == 0) ? words[w] : current + " " + words[w];
uint cw = 0, ch = 0;
TextGetSize(candidate, cw, ch);
bool tooWide = ((int)cw > maxWidth || StringLen(candidate) > STATUS_LABEL_MAX_CHARS);
if(tooWide && StringLen(current) > 0)
{
AppendStatusChunk(current, outLines, outCount);
current = words[w];
}
else
current = candidate;
}
if(StringLen(current) > 0)
AppendStatusChunk(current, outLines, outCount);
}
void SetStatusLabel(string text, int x = 10, int y = 20)
{
//--- TextSetFont's size argument is tenths of a point when negative (matches OBJPROP_FONTSIZE's
//--- point-based units above) - must be set before any TextGetSize() call since it's global state
TextSetFont(STATUS_LABEL_FONT, -STATUS_LABEL_FONTSIZE * 10, 0);
long chartWidthPx = 0;
ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0, chartWidthPx);
int maxWidth = (int)chartWidthPx - x - STATUS_LABEL_RIGHT_MARGIN;
if(maxWidth < STATUS_LABEL_MIN_WRAP_WIDTH)
maxWidth = STATUS_LABEL_MIN_WRAP_WIDTH;
string rawLines[];
int rawCount = StringSplit(text, '\n', rawLines);
string lines[];
int lineCount = 0;
for(int r = 0; r < rawCount && lineCount < STATUS_LABEL_MAX_LINES; r++)
WrapLineInto(rawLines[r], maxWidth, lines, lineCount);
if(lineCount > STATUS_LABEL_MAX_LINES)
lineCount = STATUS_LABEL_MAX_LINES;
int lineY = y;
for(int i = 0; i < lineCount; i++)
{
EnsureStatusLine(i);
string bg = STATUS_LABEL_PREFIX + (string)i + "BG";
string txt = STATUS_LABEL_PREFIX + (string)i + "Text";
uint textW = 0, textH = 0;
//--- an empty line measures 0x0, which would collapse its background - fall back to a single
//--- space so blank separator lines still reserve a row of height
TextGetSize(StringLen(lines[i]) > 0 ? lines[i] : " ", textW, textH);
ObjectSetInteger(0, bg, OBJPROP_XDISTANCE, x - STATUS_LABEL_PAD_X);
ObjectSetInteger(0, bg, OBJPROP_YDISTANCE, lineY - STATUS_LABEL_PAD_Y);
ObjectSetInteger(0, bg, OBJPROP_XSIZE, (int)textW + STATUS_LABEL_PAD_X * 2);
ObjectSetInteger(0, bg, OBJPROP_YSIZE, (int)textH + STATUS_LABEL_PAD_Y * 2);
ObjectSetInteger(0, txt, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, txt, OBJPROP_YDISTANCE, lineY);
ObjectSetString(0, txt, OBJPROP_TEXT, lines[i]);
lineY += (int)textH + STATUS_LABEL_LINE_GAP;
}
g_statusLabelBottomY = lineY;
//--- fewer lines than last call - remove the now-unused trailing line objects rather than
//--- leaving stale text (e.g. a previous longer status block) hanging below the new one
DeleteStatusLines(lineCount, g_statusLabelLineCount);
g_statusLabelLineCount = lineCount;
//--- ObjectSet*() alone does not trigger a repaint - without this the new text/background only
//--- appears once something else forces a redraw (dragging the chart, clicking an object, a new
//--- bar), which made updates look stalled between those events
ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| Removes every status line object. Mirrors the old Comment("") |
//| clear on shutdown - ObjectsDeleteAll(0) elsewhere clears chart |
//| objects, but callers still need an explicit clear at points where |
//| only the status text (not the whole chart) should be reset. |
//+------------------------------------------------------------------+
void ClearStatusLabel(void)
{
DeleteStatusLines(0, g_statusLabelLineCount);
g_statusLabelLineCount = 0;
ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| ENSEMBLE PANEL (two or more NNs enabled). |
//+------------------------------------------------------------------+
#define ENSEMBLE_PANEL_MAX_MEMBERS 6
string g_ensPanelTag[ENSEMBLE_PANEL_MAX_MEMBERS];
string g_ensPanelLine[ENSEMBLE_PANEL_MAX_MEMBERS];
//--- ROW OWNERSHIP, so the panel is ordered by the ENSEMBLE's own member order and not by which
//--- member happened to publish first.
bool g_ensPanelSlotUsed[ENSEMBLE_PANEL_MAX_MEMBERS];
int g_ensPanelUsed = 0;
uint g_ensPanelLastRender = 0;
string g_ensPanelLastText = "";
//--- The ensemble's COMBINED-vote OOS score line, written once per era by the last member to finish
//--- its pass-3 scan (CExpertSignalAIBase::EnsembleScoreCombinedVote) and rendered under the member
//--- lines below.
string g_ensembleVoteLine = "";
//--- index = the caller's ensemble registration index. Idempotent: safe to call on every publish, which
//--- is what keeps the tag current for a member whose ID was not final when it first claimed its row.
int ClaimEnsemblePanelSlot(string tag, int index)
{
int slot = index;
//--- Unregistered or out of range (should not happen for a real member): fall back to the first free
//--- row rather than dropping the model off the panel entirely.
if(slot < 0 || slot >= ENSEMBLE_PANEL_MAX_MEMBERS)
{
slot = -1;
for(int i = 0; i < ENSEMBLE_PANEL_MAX_MEMBERS; i++)
if(!g_ensPanelSlotUsed[i])
{
slot = i;
break;
}
if(slot < 0)
slot = ENSEMBLE_PANEL_MAX_MEMBERS - 1;
}
if(!g_ensPanelSlotUsed[slot])
{
g_ensPanelSlotUsed[slot] = true;
g_ensPanelLine[slot] = "";
}
if(tag != "")
g_ensPanelTag[slot] = tag;
//--- High-water mark of CLAIMED rows, not a running count - rows are addressed by index now, so a
//--- member that has not published yet leaves a gap the render skips rather than shifting everyone up.
if(slot + 1 > g_ensPanelUsed)
g_ensPanelUsed = slot + 1;
return slot;
}
void PublishEnsembleStatus(int slot, string headline, bool force = false)
{
if(slot < 0 || slot >= g_ensPanelUsed || !g_ensPanelSlotUsed[slot])
return;
g_ensPanelLine[slot] = headline;
int shown = 0;
for(int i = 0; i < g_ensPanelUsed; i++)
if(g_ensPanelSlotUsed[i])
shown++;
//--- TWO PANELS, ONE FOR EACH JOB (user request 2026-08-25: "once converged I don't need to see
//--- every nn's name and percentage, only the global aggregated win rate and result and current
//--- vote").
//---
//--- WHILE TRAINING the per-member rows ARE the information: they are how a member that has
//--- collapsed, stalled or is lagging the era barrier becomes visible at all, and a collapsed
//--- member is invisible in the aggregate by construction (it abstains, so it only dilutes).
//---
//--- ONCE DEPLOYED they are noise, and worse than noise - they invite reading a member's own
//--- precision as the thing being traded. It is not: the EA trades the COMBINED VOTE against
//--- Signal_ThresholdOpen, so a member's raw call rate is not a number anyone can act on. What
//--- ships to a live panel is what the vote did, on the bars the vote actually fired.
//--- THROTTLE FIRST, build never. This function runs per timer tick per chart; the string build
//--- below (a per-member loop, several concatenations) used to run unconditionally and get
//--- discarded by the checks that are now here instead - on a live/tester distinction that never
//--- changes mid-run, checking it first costs nothing and skips all of it during the throttle
//--- window. Building has no side effects (pure read of ensemble panel globals), so reordering
//--- past it changes no output - only which branch pays for the string.
uint now = GetTickCount();
if(!force && g_ensPanelLastRender != 0 && now - g_ensPanelLastRender < 300)
return;
bool deployed = WarriorChartModelsDeployed();
string s;
if(deployed)
s = "AI ensemble - " + (string)shown + " models, deployed";
else
{
s = "AI ensemble - " + (string)shown + " independent models, one line each";
for(int i = 0; i < g_ensPanelUsed; i++)
if(g_ensPanelSlotUsed[i])
s += "\n" + g_ensPanelTag[i] + ": " + (g_ensPanelLine[i] == "" ? "starting..." : g_ensPanelLine[i]);
}
if(g_ensembleVoteLine != "")
s += "\n" + g_ensembleVoteLine;
//--- THE LIVE AGGREGATED VOTE (g_liveVoteLine, written by CExpertSignalCustom::UpdateVoteReadout),
//--- one line, in place of the old separate top-right per-member HUD (user request 2026-08-24).
if(g_liveVoteLine != "")
s += "\n" + g_liveVoteLine;
//--- unchanged text needs no relayout
if(s == g_ensPanelLastText)
return;
g_ensPanelLastText = s;
g_ensPanelLastRender = now;
SetStatusLabel(s);
}